github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/saltpackkeys/saltpack_pseudonym_resolvers.go (about)

     1  package saltpackkeys
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/keybase/client/go/libkb"
     8  	"github.com/keybase/client/go/protocol/keybase1"
     9  	"github.com/keybase/client/go/teams"
    10  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    11  	"github.com/keybase/saltpack"
    12  )
    13  
    14  // KeyPseudonymResolver resolves new (team based) Key Pseudonyms, but falls back to old Kbfs Pseudonyms when it cannot find any match.
    15  // A mock implementation (which does not communicate with the sever and avoids circular dependencies) is available in the saltpackkeysmocks package.
    16  type KeyPseudonymResolver struct {
    17  	m libkb.MetaContext
    18  }
    19  
    20  var _ saltpack.SymmetricKeyResolver = (*KeyPseudonymResolver)(nil)
    21  
    22  func NewKeyPseudonymResolver(m libkb.MetaContext) saltpack.SymmetricKeyResolver {
    23  	return &KeyPseudonymResolver{m: m}
    24  }
    25  
    26  func (r *KeyPseudonymResolver) ResolveKeys(identifiers [][]byte) ([]*saltpack.SymmetricKey, error) {
    27  	keyPseudonyms := []libkb.KeyPseudonym{}
    28  	for _, identifier := range identifiers {
    29  		pseudonym := libkb.KeyPseudonym{}
    30  		if len(pseudonym) != len(identifier) {
    31  			return nil, fmt.Errorf("identifier is the wrong length for a key pseudonym (%d != %d)", len(pseudonym), len(identifier))
    32  		}
    33  		copy(pseudonym[:], identifier)
    34  		keyPseudonyms = append(keyPseudonyms, pseudonym)
    35  	}
    36  
    37  	results, err := libkb.GetKeyPseudonyms(r.m, keyPseudonyms)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	success := false
    43  
    44  	symmetricKeys := []*saltpack.SymmetricKey{}
    45  	for _, result := range results {
    46  		if result.Err != nil || result.Info.Application != keybase1.TeamApplication_SALTPACK {
    47  			r.m.Debug("skipping unresolved pseudonym: %s", result.Err)
    48  			symmetricKeys = append(symmetricKeys, nil)
    49  			continue
    50  		}
    51  		r.m.Debug("resolved pseudonym for %s, fetching key", result.Info.ID)
    52  		symmetricKey, err := r.getSymmetricKey(result.Info.ID, result.Info.KeyGen)
    53  		if err != nil {
    54  			return nil, err
    55  		}
    56  		success = true
    57  		symmetricKeys = append(symmetricKeys, symmetricKey)
    58  	}
    59  
    60  	if success {
    61  		return symmetricKeys, nil
    62  	}
    63  	r.m.Debug("No pseudonyms resolved, fallback to old kbfs pseudonyms")
    64  	// Fallback to old kbfs pseudonyms
    65  	return r.kbfsResolveKeys(identifiers)
    66  }
    67  
    68  func (r *KeyPseudonymResolver) getSymmetricKey(id keybase1.UserOrTeamID, gen libkb.KeyGen) (*saltpack.SymmetricKey, error) {
    69  	// For now resolving key pseudonyms for users is not necessary, as keybase encrypt does not
    70  	// use symmetric per user encryption keys.
    71  
    72  	team, err := teams.Load(r.m.Ctx(), r.m.G(), keybase1.LoadTeamArg{
    73  		ID: keybase1.TeamID(id),
    74  	})
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	var key keybase1.TeamApplicationKey
    80  	key, err = team.SaltpackEncryptionKeyAtGeneration(r.m.Ctx(), keybase1.PerTeamKeyGeneration(gen))
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	ssk := saltpack.SymmetricKey(key.Key)
    86  	return &ssk, nil
    87  }
    88  
    89  func (r *KeyPseudonymResolver) kbfsResolveKeys(identifiers [][]byte) ([]*saltpack.SymmetricKey, error) {
    90  	tlfPseudonyms := []libkb.TlfPseudonym{}
    91  	for _, identifier := range identifiers {
    92  		pseudonym := libkb.TlfPseudonym{}
    93  		if len(pseudonym) != len(identifier) {
    94  			return nil, fmt.Errorf("identifier is the wrong length for a TLF pseudonym (%d != %d)", len(pseudonym), len(identifier))
    95  		}
    96  		copy(pseudonym[:], identifier)
    97  		tlfPseudonyms = append(tlfPseudonyms, pseudonym)
    98  	}
    99  
   100  	results, err := libkb.GetTlfPseudonyms(r.m.Ctx(), r.m.G(), tlfPseudonyms)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	symmetricKeys := []*saltpack.SymmetricKey{}
   106  	for _, result := range results {
   107  		if result.Err != nil {
   108  			r.m.Debug("skipping unresolved pseudonym: %s", result.Err)
   109  			symmetricKeys = append(symmetricKeys, nil)
   110  			continue
   111  		}
   112  		r.m.Debug("resolved pseudonym for %s, fetching key", result.Info.Name)
   113  		symmetricKey, err := r.kbfsGetSymmetricKey(r.m, *result.Info)
   114  		if err != nil {
   115  			return nil, err
   116  		}
   117  		symmetricKeys = append(symmetricKeys, symmetricKey)
   118  	}
   119  	return symmetricKeys, nil
   120  }
   121  
   122  func (r *KeyPseudonymResolver) getCryptKeys(m libkb.MetaContext, name string) (keybase1.GetTLFCryptKeysRes, error) {
   123  	xp := m.G().ConnectionManager.LookupByClientType(keybase1.ClientType_KBFS)
   124  	if xp == nil {
   125  		return keybase1.GetTLFCryptKeysRes{}, libkb.KBFSNotRunningError{}
   126  	}
   127  	cli := &keybase1.TlfKeysClient{
   128  		Cli: rpc.NewClient(xp, libkb.NewContextifiedErrorUnwrapper(r.m.G()), libkb.LogTagsFromContext),
   129  	}
   130  	return cli.GetTLFCryptKeys(m.Ctx(), keybase1.TLFQuery{
   131  		TlfName:          name,
   132  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI,
   133  	})
   134  }
   135  
   136  func (r *KeyPseudonymResolver) kbfsGetSymmetricKey(m libkb.MetaContext, info libkb.TlfPseudonymServerInfo) (*saltpack.SymmetricKey, error) {
   137  	// NOTE: In order to handle finalized TLFs (which is one of the main
   138  	// benefits of using TLF keys to begin with, for forward readability), we
   139  	// need the server to tell us what the current, potentially-finalized name
   140  	// of the TLF is. If that's not the same as what the name was when the
   141  	// message was sent, we can't necessarily check that the server is being
   142  	// honest. That's ok insofar as we're not relying on these keys for
   143  	// authenticity, but it's a drag to not be able to use the pseudonym
   144  	// machinery.
   145  
   146  	// TODO: Check as much as we can, if the original TLF was fully resolved.
   147  
   148  	// Strip "/keybase/private/" from the name.
   149  	basename := strings.TrimPrefix(info.UntrustedCurrentName, "/keybase/private/")
   150  	if len(basename) >= len(info.UntrustedCurrentName) {
   151  		return nil, fmt.Errorf("unexpected prefix, expected '/keybase/private', found %q", info.UntrustedCurrentName)
   152  	}
   153  	res, err := r.getCryptKeys(m, basename)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	for _, key := range res.CryptKeys {
   158  		if libkb.KeyGen(key.KeyGeneration) == info.KeyGen {
   159  			// Success!
   160  			return (*saltpack.SymmetricKey)(&key.Key), nil
   161  		}
   162  	}
   163  	return nil, fmt.Errorf("no keys in TLF %q matched generation %d", basename, info.KeyGen)
   164  }