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

     1  package teams
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"crypto/rand"
     6  	"crypto/sha512"
     7  	"encoding/base64"
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/keybase/client/go/libkb"
    12  	"github.com/keybase/client/go/msgpack"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  	"golang.org/x/crypto/nacl/box"
    15  	"golang.org/x/crypto/nacl/secretbox"
    16  	"golang.org/x/net/context"
    17  )
    18  
    19  // Get a PTK seed and verify against the sigchain that is the correct key.
    20  func GetAndVerifyPerTeamKey(mctx libkb.MetaContext, team Teamer, gen keybase1.PerTeamKeyGeneration) (ret keybase1.PerTeamKeySeedItem, err error) {
    21  
    22  	if team.MainChain() == nil {
    23  		return ret, libkb.NotFoundError{Msg: fmt.Sprintf("no team secret found at generation %v, since inner team was nil", gen)}
    24  	}
    25  
    26  	var ok bool
    27  	ret, ok = team.MainChain().PerTeamKeySeedsUnverified[gen]
    28  	if !ok {
    29  		return ret, libkb.NotFoundError{
    30  			Msg: fmt.Sprintf("no team secret found at generation %v", gen)}
    31  	}
    32  	km, err := NewTeamKeyManagerWithSeedItem(team.MainChain().ID(), ret)
    33  	if err != nil {
    34  		return ret, err
    35  	}
    36  
    37  	chainKey, err := newTeamSigChainState(team).GetPerTeamKeyAtGeneration(gen)
    38  	if err != nil {
    39  		return ret, err
    40  	}
    41  
    42  	// Takes roughly 280us on android (Honor 6X)
    43  	localSigKey, err := km.SigningKey()
    44  	if err != nil {
    45  		return ret, err
    46  	}
    47  
    48  	// Takes roughly 900us on android (Honor 6X)
    49  	localEncKey, err := km.EncryptionKey()
    50  	if err != nil {
    51  		return ret, err
    52  	}
    53  
    54  	if !chainKey.SigKID.SecureEqual(localSigKey.GetKID()) {
    55  		mctx.Debug("sig KID gen:%v (local) %v != %v (chain)", gen, localSigKey.GetKID(), chainKey.SigKID)
    56  		return ret, fmt.Errorf("wrong team key found at generation %v", gen)
    57  	}
    58  
    59  	if !chainKey.EncKID.SecureEqual(localEncKey.GetKID()) {
    60  		mctx.Debug("enc KID gen:%v (local) %v != %v (chain)", gen, localEncKey.GetKID(), chainKey.EncKID)
    61  		return ret, fmt.Errorf("wrong team key (enc) found at generation %v", gen)
    62  	}
    63  
    64  	return ret, nil
    65  }
    66  
    67  type PerTeamSharedSecretBoxes struct {
    68  	Generation       keybase1.PerTeamKeyGeneration `json:"generation"`
    69  	EncryptingKid    keybase1.KID                  `json:"encrypting_kid"`
    70  	Nonce            string                        `json:"nonce"`
    71  	PrevKey          *prevKeySealedEncoded         `json:"prev"`
    72  	Boxes            map[keybase1.UID]string       `json:"boxes"`
    73  	BoxSummaryHash   string                        `json:"public_summary"` // encoded hash of the packed box public summary
    74  	boxPublicSummary *boxPublicSummary             // not exported, therefore, won't be JSON'ed
    75  }
    76  
    77  type PerTeamSharedSecretBox struct {
    78  	_struct         bool `codec:",toarray"` //nolint
    79  	Version         uint
    80  	PerUserKeySeqno keybase1.Seqno
    81  	NonceCounter    uint32
    82  	Ctext           []byte
    83  }
    84  
    85  type TeamKeyManager struct {
    86  	sharedSecret keybase1.PerTeamKeySeed
    87  	generation   keybase1.PerTeamKeyGeneration
    88  	check        keybase1.PerTeamSeedCheck
    89  	id           keybase1.TeamID
    90  
    91  	encryptionKey *libkb.NaclDHKeyPair
    92  	signingKey    *libkb.NaclSigningKeyPair
    93  }
    94  
    95  func NewTeamKeyManager(g *libkb.GlobalContext, id keybase1.TeamID) (*TeamKeyManager, error) {
    96  	sharedSecret, err := newSharedSecret()
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	check, err := computeSeedCheck(id, sharedSecret, nil)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	return NewTeamKeyManagerWithSecret(id, sharedSecret, 1, check)
   105  }
   106  
   107  func NewTeamKeyManagerWithSeedItem(id keybase1.TeamID, si keybase1.PerTeamKeySeedItem) (*TeamKeyManager, error) {
   108  	return NewTeamKeyManagerWithSecret(id, si.Seed, si.Generation, si.Check)
   109  }
   110  
   111  func NewTeamKeyManagerWithSecret(id keybase1.TeamID, secret keybase1.PerTeamKeySeed, generation keybase1.PerTeamKeyGeneration, check *keybase1.PerTeamSeedCheck) (*TeamKeyManager, error) {
   112  	if check == nil {
   113  		return nil, fmt.Errorf("unexpected nil check item")
   114  	}
   115  	return &TeamKeyManager{
   116  		sharedSecret: secret,
   117  		generation:   generation,
   118  		check:        *check,
   119  		id:           id,
   120  	}, nil
   121  }
   122  
   123  // SharedSecret returns the team's shared secret.
   124  func (t *TeamKeyManager) SharedSecret() keybase1.PerTeamKeySeed {
   125  	return t.sharedSecret
   126  }
   127  
   128  // EncryptionKey returns the derived NaclSigningKeyPair from the team's shared secret.
   129  func (t *TeamKeyManager) SigningKey() (libkb.NaclSigningKeyPair, error) {
   130  	if t.signingKey == nil {
   131  		key, err := libkb.MakeNaclSigningKeyPairFromSecretBytes(derivedSecret(t.sharedSecret, libkb.TeamEdDSADerivationString))
   132  		if err != nil {
   133  			return libkb.NaclSigningKeyPair{}, err
   134  		}
   135  		t.signingKey = &key
   136  	}
   137  	return *t.signingKey, nil
   138  }
   139  
   140  func (t *TeamKeyManager) Check() keybase1.PerTeamSeedCheck {
   141  	return t.check
   142  }
   143  
   144  func (t *TeamKeyManager) Generation() keybase1.PerTeamKeyGeneration {
   145  	return t.generation
   146  }
   147  
   148  // EncryptionKey returns the derived NaclDHKeyPair from the team's shared secret.
   149  func (t *TeamKeyManager) EncryptionKey() (libkb.NaclDHKeyPair, error) {
   150  	if t.encryptionKey == nil {
   151  		key, err := libkb.MakeNaclDHKeyPairFromSecretBytes(derivedSecret(t.sharedSecret, libkb.TeamDHDerivationString))
   152  		if err != nil {
   153  			return libkb.NaclDHKeyPair{}, err
   154  		}
   155  		t.encryptionKey = &key
   156  	}
   157  	return *t.encryptionKey, nil
   158  }
   159  
   160  // SharedSecretBoxes creates the PerTeamSharedSecretBoxes for recipients with the
   161  // existing team shared secret.
   162  func (t *TeamKeyManager) SharedSecretBoxes(mctx libkb.MetaContext, senderKey libkb.GenericKey, recipients map[keybase1.UserVersion]keybase1.PerUserKey) (boxes *PerTeamSharedSecretBoxes, err error) {
   163  	defer mctx.Trace("SharedSecretBoxes", &err)()
   164  
   165  	// make the nonce prefix, skipping the zero counter
   166  	// (0 used for previous key encryption nonce)
   167  	n, err := newNonce24SkipZero()
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	// make the recipient boxes with the new secret and the nonce prefix
   173  	return t.sharedBoxes(t.sharedSecret, t.generation, n, senderKey, recipients)
   174  }
   175  
   176  // RotateSharedSecretBoxes creates a new shared secret for the team and the
   177  // required PerTeamKey section.
   178  func (t *TeamKeyManager) RotateSharedSecretBoxes(mctx libkb.MetaContext, senderKey libkb.GenericKey, recipients map[keybase1.UserVersion]keybase1.PerUserKey) (boxes *PerTeamSharedSecretBoxes, keySection *SCPerTeamKey, err error) {
   179  	defer mctx.Trace("RotateSharedSecretBoxes", &err)()
   180  
   181  	// make a new secret
   182  	nextSecret, err := newSharedSecret()
   183  	if err != nil {
   184  		return nil, nil, err
   185  	}
   186  
   187  	// derive new key from new secret for PrevKey
   188  	key := derivedSecret(nextSecret, libkb.TeamPrevKeySecretBoxDerivationString)
   189  	var keyb [32]byte
   190  	copy(keyb[:], key)
   191  
   192  	// encrypt existing secret with derived key and nonce counter 0
   193  	nonce, err := newNonce24()
   194  	if err != nil {
   195  		return nil, nil, err
   196  	}
   197  	nonceBytes, counter := nonce.Nonce()
   198  	if counter != 0 {
   199  		// this should never happen, but might as well make sure it is zero
   200  		return nil, nil, errors.New("nonce counter not 0 for first use")
   201  	}
   202  	sealed := secretbox.Seal(nil, t.sharedSecret.ToBytes(), &nonceBytes, &keyb)
   203  
   204  	// encode encrypted prev key
   205  	prevKeyEncoded, err := encodeSealedPrevKey(nonceBytes, sealed)
   206  	if err != nil {
   207  		return nil, nil, err
   208  	}
   209  
   210  	// make the recipient boxes with the new secret and the incrementing nonce24
   211  	err = t.setNextSharedSecret(mctx, nextSecret)
   212  	if err != nil {
   213  		return nil, nil, err
   214  	}
   215  	boxes, err = t.sharedBoxes(t.sharedSecret, t.generation, nonce, senderKey, recipients)
   216  	if err != nil {
   217  		return nil, nil, err
   218  	}
   219  
   220  	// insert encoded encrypted PrevKey
   221  	boxes.PrevKey = &prevKeyEncoded
   222  
   223  	// need a new PerTeamKey section since the key was rotated
   224  	keySection, err = t.perTeamKeySection()
   225  	if err != nil {
   226  		return nil, nil, err
   227  	}
   228  
   229  	return boxes, keySection, nil
   230  }
   231  
   232  func (t *TeamKeyManager) sharedBoxes(secret keybase1.PerTeamKeySeed, generation keybase1.PerTeamKeyGeneration, nonce *nonce24, senderKey libkb.GenericKey, recipients map[keybase1.UserVersion]keybase1.PerUserKey) (*PerTeamSharedSecretBoxes, error) {
   233  	senderNaclDHKey, ok := senderKey.(libkb.NaclDHKeyPair)
   234  	if !ok {
   235  		return nil, fmt.Errorf("got an unexpected key type for device encryption key: %T", senderKey)
   236  	}
   237  
   238  	boxes, err := t.recipientBoxes(secret, nonce, senderNaclDHKey, recipients)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  	boxPublicSummary, err := newBoxPublicSummary(recipients)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	return &PerTeamSharedSecretBoxes{
   248  		Generation:       generation,
   249  		EncryptingKid:    senderNaclDHKey.GetKID(),
   250  		Nonce:            nonce.PrefixEncoded(),
   251  		Boxes:            boxes,
   252  		BoxSummaryHash:   boxPublicSummary.EncodeToString(),
   253  		boxPublicSummary: boxPublicSummary,
   254  	}, nil
   255  }
   256  
   257  func (t *TeamKeyManager) recipientBoxes(secret keybase1.PerTeamKeySeed, nonce *nonce24, senderKey libkb.NaclDHKeyPair, recipients map[keybase1.UserVersion]keybase1.PerUserKey) (map[keybase1.UID]string, error) {
   258  	boxes := make(map[keybase1.UID]string)
   259  	for uv, recipientPerUserKey := range recipients {
   260  		boxStruct, err := t.recipientBox(secret, nonce, senderKey, recipientPerUserKey)
   261  		if err != nil {
   262  			return nil, err
   263  		}
   264  
   265  		encodedArray, err := msgpack.Encode(boxStruct)
   266  		if err != nil {
   267  			return nil, err
   268  		}
   269  
   270  		boxes[uv.Uid] = base64.StdEncoding.EncodeToString(encodedArray)
   271  	}
   272  
   273  	return boxes, nil
   274  }
   275  
   276  func (t *TeamKeyManager) recipientBox(secret keybase1.PerTeamKeySeed, nonce *nonce24, senderKey libkb.NaclDHKeyPair, recipient keybase1.PerUserKey) (*PerTeamSharedSecretBox, error) {
   277  	recipientPerUserGenericKeypair, err := libkb.ImportKeypairFromKID(recipient.EncKID)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  	recipientPerUserNaclKeypair, ok := recipientPerUserGenericKeypair.(libkb.NaclDHKeyPair)
   282  	if !ok {
   283  		return nil, fmt.Errorf("got an unexpected key type for recipient KID in sharedTeamKeyBox: %T", recipientPerUserGenericKeypair)
   284  	}
   285  
   286  	nonceBytes, nonceCounter := nonce.Nonce()
   287  	ctext := box.Seal(nil, secret[:], &nonceBytes, ((*[32]byte)(&recipientPerUserNaclKeypair.Public)), ((*[32]byte)(senderKey.Private)))
   288  
   289  	boxStruct := PerTeamSharedSecretBox{
   290  		Version:         libkb.SharedTeamKeyBoxVersion1,
   291  		PerUserKeySeqno: recipient.Seqno,
   292  		NonceCounter:    nonceCounter,
   293  		Ctext:           ctext,
   294  	}
   295  
   296  	return &boxStruct, nil
   297  }
   298  
   299  func (t *TeamKeyManager) perTeamKeySection() (*SCPerTeamKey, error) {
   300  	sigKey, err := t.SigningKey()
   301  	if err != nil {
   302  		return nil, err
   303  	}
   304  	encKey, err := t.EncryptionKey()
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  	return &SCPerTeamKey{
   309  		Generation: t.generation,
   310  		SigKID:     sigKey.GetKID(),
   311  		EncKID:     encKey.GetKID(),
   312  	}, nil
   313  }
   314  
   315  func (t *TeamKeyManager) setNextSharedSecret(mctx libkb.MetaContext, secret keybase1.PerTeamKeySeed) (err error) {
   316  
   317  	check, err := computeSeedCheck(t.id, secret, &t.check)
   318  	if err != nil {
   319  		return err
   320  	}
   321  
   322  	t.sharedSecret = secret
   323  	t.check = *check
   324  
   325  	// bump generation number
   326  	t.generation++
   327  
   328  	// clear out derived keys
   329  	t.signingKey = nil
   330  	t.encryptionKey = nil
   331  
   332  	mctx.Debug("TeamKeyManager: set next shared secret, generation %d", t.generation)
   333  
   334  	return nil
   335  }
   336  
   337  type prevKeySealedDecoded struct {
   338  	_struct bool `codec:",toarray"` //nolint
   339  	Version int
   340  	Nonce   [24]byte
   341  	Key     []byte
   342  }
   343  
   344  type prevKeySealedEncoded string
   345  
   346  func encodeSealedPrevKey(nonceBytes [24]byte, key []byte) (prevKeySealedEncoded, error) {
   347  	prevKey := prevKeySealedDecoded{
   348  		Version: 1,
   349  		Nonce:   nonceBytes,
   350  		Key:     key,
   351  	}
   352  	packed, err := msgpack.Encode(prevKey)
   353  	if err != nil {
   354  		return "", err
   355  	}
   356  	encoded := base64.StdEncoding.EncodeToString(packed)
   357  	return prevKeySealedEncoded(encoded), nil
   358  }
   359  
   360  func decodeSealedPrevKey(e prevKeySealedEncoded) (nonce [24]byte, ctext []byte, err error) {
   361  	decoded, err := base64.StdEncoding.DecodeString(string(e))
   362  	if err != nil {
   363  		return nonce, nil, err
   364  	}
   365  	var tmp prevKeySealedDecoded
   366  	err = msgpack.Decode(&tmp, decoded)
   367  	if err != nil {
   368  		return nonce, nil, err
   369  	}
   370  	if tmp.Version != 1 {
   371  		return nonce, nil, fmt.Errorf("can only handle V1 encrypted prevs")
   372  	}
   373  	return tmp.Nonce, tmp.Key, nil
   374  }
   375  
   376  func newSharedSecret() (ret keybase1.PerTeamKeySeed, err error) {
   377  	n, err := rand.Read(ret[:])
   378  	if err != nil {
   379  		return ret, err
   380  	}
   381  	if n != len(ret) {
   382  		return ret, errors.New("short random read in newSharedSecret")
   383  	}
   384  	return ret, nil
   385  }
   386  
   387  func derivedSecret(secret keybase1.PerTeamKeySeed, context string) []byte {
   388  	if secret.IsZero() {
   389  		panic("Should never be using a zero key in derivedSecret; something went terribly wrong")
   390  	}
   391  	digest := hmac.New(sha512.New, secret[:])
   392  	_, _ = digest.Write([]byte(context))
   393  	return digest.Sum(nil)[:32]
   394  }
   395  
   396  // Decrypt a single prev secretbox.
   397  // Takes a prev to decrypt and the seed of the successor generation.
   398  // For example (prev[3], seed[4]) -> seed[3]
   399  func decryptPrevSingle(ctx context.Context,
   400  	prevToDecrypt prevKeySealedEncoded, successor keybase1.PerTeamKeySeed) (*keybase1.PerTeamKeySeed, error) {
   401  	if successor.IsZero() {
   402  		return nil, fmt.Errorf("Got 0 key, which can't be right")
   403  	}
   404  	if len(prevToDecrypt) == 0 {
   405  		return nil, fmt.Errorf("zero-length encoded prev")
   406  	}
   407  	nonce, ctext, err := decodeSealedPrevKey(prevToDecrypt)
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  	var keyFixed [32]byte
   412  	// prev key to decrypt with
   413  	key := derivedSecret(successor, libkb.TeamPrevKeySecretBoxDerivationString)
   414  	copy(keyFixed[:], key)
   415  	opened, ok := secretbox.Open(nil, ctext, &nonce, &keyFixed)
   416  	if !ok {
   417  		return nil, fmt.Errorf("prev decryption failed")
   418  	}
   419  	ret, err := keybase1.PerTeamKeySeedFromBytes(opened)
   420  	return &ret, err
   421  }
   422  
   423  func computeSeedCheck(id keybase1.TeamID, seed keybase1.PerTeamKeySeed, prev *keybase1.PerTeamSeedCheck) (*keybase1.PerTeamSeedCheck, error) {
   424  
   425  	var prevValue keybase1.PerTeamSeedCheckValue
   426  	switch {
   427  	case prev == nil:
   428  		tmp := []byte(libkb.TeamKeySeedCheckDerivationString)
   429  		tmp = append(tmp, byte(0))
   430  		tmp = append(tmp, id.ToBytes()...)
   431  		prevValue = keybase1.PerTeamSeedCheckValue(tmp)
   432  	case prev.Version != keybase1.PerTeamSeedCheckVersion_V1:
   433  		return nil, fmt.Errorf("cannot handle PerTeamSeedCheck version > 1")
   434  	case prev.Version == keybase1.PerTeamSeedCheckVersion_V1:
   435  		prevValue = prev.Value
   436  	}
   437  
   438  	g := func(seed keybase1.PerTeamKeySeed, prev keybase1.PerTeamSeedCheckValue) keybase1.PerTeamSeedCheckValue {
   439  		digest := hmac.New(sha512.New, seed[:])
   440  		_, _ = digest.Write([]byte(prev))
   441  		sum := digest.Sum(nil)[:32]
   442  		return keybase1.PerTeamSeedCheckValue(sum)
   443  	}
   444  
   445  	return &keybase1.PerTeamSeedCheck{
   446  		Version: keybase1.PerTeamSeedCheckVersion_V1,
   447  		Value:   g(seed, prevValue),
   448  	}, nil
   449  }
   450  
   451  // computeSeedChecks looks at a sequence of checks, newest to oldest, trying to find the first nil. Once it finds
   452  // such a nil, it goes forward and fills in the checks, based on the current seeds. There's an unfortunately level
   453  // of indirection since we need the same code to work over slow and fast team loading; thus we have setters and getters
   454  // as functions. But the idea is the same in both cases. Not that we're assuming there aren't "holes". That the nils
   455  // are at the back of the sequence and not interspersed throughout. This is valid so long as we always compute
   456  // these checks on load of new links.
   457  func computeSeedChecks(ctx context.Context, teamID keybase1.TeamID, latestChainGen keybase1.PerTeamKeyGeneration, getter func(g keybase1.PerTeamKeyGeneration) (*keybase1.PerTeamSeedCheck, keybase1.PerTeamKeySeed, error), setter func(g keybase1.PerTeamKeyGeneration, c keybase1.PerTeamSeedCheck)) error {
   458  
   459  	var firstNonNilCheck keybase1.PerTeamKeyGeneration
   460  	var foundLinkToUpdate bool
   461  
   462  	for i := latestChainGen; i >= 1; i-- {
   463  		check, _, err := getter(i)
   464  		if err != nil {
   465  			return err
   466  		}
   467  		if check != nil {
   468  			firstNonNilCheck = i
   469  			break
   470  		}
   471  		foundLinkToUpdate = true
   472  	}
   473  
   474  	if !foundLinkToUpdate {
   475  		// NoOp, we're all up-to-date
   476  		return nil
   477  	}
   478  
   479  	var prev *keybase1.PerTeamSeedCheck
   480  
   481  	if firstNonNilCheck > keybase1.PerTeamKeyGeneration(0) {
   482  		var err error
   483  		prev, _, err = getter(firstNonNilCheck)
   484  		if err != nil {
   485  			return err
   486  		}
   487  		if prev == nil {
   488  			return fmt.Errorf("unexpected nil PerTeamKeySeedsUnverified.Check at %d", firstNonNilCheck)
   489  		}
   490  	}
   491  
   492  	start := firstNonNilCheck + keybase1.PerTeamKeyGeneration(1)
   493  
   494  	for i := start; i <= latestChainGen; i++ {
   495  		_, seed, err := getter(i)
   496  		if err != nil {
   497  			return err
   498  		}
   499  		check, err := computeSeedCheck(teamID, seed, prev)
   500  		if err != nil {
   501  			return err
   502  		}
   503  		setter(i, *check)
   504  		prev = check
   505  	}
   506  	return nil
   507  }