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 }