github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/ephemeral/user_ek.go (about) 1 package ephemeral 2 3 import ( 4 "encoding/json" 5 "fmt" 6 7 "github.com/keybase/client/go/kbcrypto" 8 "github.com/keybase/client/go/libkb" 9 "github.com/keybase/client/go/protocol/keybase1" 10 ) 11 12 type UserEKSeed keybase1.Bytes32 13 14 func newUserEphemeralSeed() (seed UserEKSeed, err error) { 15 randomSeed, err := makeNewRandomSeed() 16 if err != nil { 17 return seed, err 18 } 19 return UserEKSeed(randomSeed), nil 20 } 21 22 func newUserEKSeedFromBytes(b []byte) (s UserEKSeed, err error) { 23 seed, err := newEKSeedFromBytes(b) 24 if err != nil { 25 return s, err 26 } 27 return UserEKSeed(seed), nil 28 } 29 30 func (s *UserEKSeed) DeriveDHKey() *libkb.NaclDHKeyPair { 31 return deriveDHKey(keybase1.Bytes32(*s), libkb.DeriveReasonUserEKEncryption) 32 } 33 34 // Upload a new userEK directly, when we're not adding it to a PUK or device 35 // transaction. 36 func postNewUserEK(mctx libkb.MetaContext, sig string, boxes []keybase1.UserEkBoxMetadata) (err error) { 37 defer mctx.Trace("postNewUserEK", &err)() 38 39 boxesJSON, err := json.Marshal(boxes) 40 if err != nil { 41 return err 42 } 43 apiArg := libkb.APIArg{ 44 Endpoint: "user/user_ek", 45 SessionType: libkb.APISessionTypeREQUIRED, 46 Args: libkb.HTTPArgs{ 47 "sig": libkb.S{Val: sig}, 48 "boxes": libkb.S{Val: string(boxesJSON)}, 49 }, 50 } 51 _, err = mctx.G().GetAPI().Post(mctx, apiArg) 52 return err 53 } 54 55 // There are two cases where we need to generate a new userEK. One is where 56 // we're rolling the userEK by itself, and we need to sign it with the current 57 // PUK. The other is where we're rolling the PUK, and we need to sign a new 58 // userEK with the new PUK to upload both together. This helper covers the 59 // steps common to both cases. 60 func prepareNewUserEK(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot, 61 pukSigning *libkb.NaclSigningKeyPair) (sig string, boxes []keybase1.UserEkBoxMetadata, 62 newMetadata keybase1.UserEkMetadata, myBox *keybase1.UserEkBoxed, err error) { 63 defer mctx.Trace("prepareNewUserEK", &err)() 64 65 seed, err := newUserEphemeralSeed() 66 if err != nil { 67 return "", nil, newMetadata, nil, err 68 } 69 70 prevStatement, latestGeneration, wrongKID, err := fetchUserEKStatement(mctx, mctx.G().Env.GetUID()) 71 if !wrongKID && err != nil { 72 return "", nil, newMetadata, nil, err 73 } 74 var newGeneration keybase1.EkGeneration 75 if prevStatement == nil { 76 // Even if the userEK statement was signed by the wrong key (this can 77 // happen when legacy clients roll the PUK), fetchUserEKStatement will 78 // return the generation number from the last (unverifiable) statement. 79 // If there was never any statement, latestGeneration will be 0, so 80 // adding one is correct in all cases. 81 newGeneration = latestGeneration + 1 82 } else { 83 newGeneration = prevStatement.CurrentUserEkMetadata.Generation + 1 84 } 85 86 dhKeypair := seed.DeriveDHKey() 87 88 metadata := keybase1.UserEkMetadata{ 89 Kid: dhKeypair.GetKID(), 90 Generation: newGeneration, 91 HashMeta: merkleRoot.HashMeta(), 92 // The ctime is derivable from the hash meta, by fetching the hashed 93 // root from the server, but including it saves readers a potential 94 // extra round trip. 95 Ctime: keybase1.TimeFromSeconds(merkleRoot.Ctime()), 96 } 97 98 statement := keybase1.UserEkStatement{ 99 CurrentUserEkMetadata: metadata, 100 } 101 statementJSON, err := json.Marshal(statement) 102 if err != nil { 103 return "", nil, newMetadata, nil, err 104 } 105 sig, _, err = pukSigning.SignToString(statementJSON) 106 if err != nil { 107 return "", nil, newMetadata, nil, err 108 } 109 110 boxes, myUserEKBoxed, err := boxUserEKForDevices(mctx, merkleRoot, seed, metadata) 111 if err != nil { 112 return "", nil, newMetadata, nil, err 113 } 114 115 return sig, boxes, metadata, myUserEKBoxed, nil 116 } 117 118 // Create a new userEK and upload it. Add our box to the local box store. 119 func publishNewUserEK(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot) ( 120 metadata keybase1.UserEkMetadata, err error) { 121 defer mctx.Trace("publishNewUserEK", &err)() 122 123 // Sign the statement blob with the latest PUK. 124 pukKeyring, err := mctx.G().GetPerUserKeyring(mctx.Ctx()) 125 if err != nil { 126 return metadata, err 127 } 128 if err := pukKeyring.Sync(mctx); err != nil { 129 return metadata, err 130 } 131 if !pukKeyring.HasAnyKeys() { 132 return metadata, fmt.Errorf("A PUK is needed to generate ephemeral keys. Aborting.") 133 } 134 pukSigning, err := pukKeyring.GetLatestSigningKey(mctx) 135 if err != nil { 136 return metadata, err 137 } 138 139 sig, boxes, newMetadata, myBox, err := prepareNewUserEK(mctx, merkleRoot, pukSigning) 140 if err != nil { 141 return metadata, err 142 } 143 144 if err = postNewUserEK(mctx, sig, boxes); err != nil { 145 return metadata, err 146 } 147 148 // Cache the new box after we see the post succeeded. 149 if myBox == nil { 150 mctx.Debug("No box made for own deviceEK") 151 } else { 152 storage := mctx.G().GetUserEKBoxStorage() 153 err = storage.Put(mctx, newMetadata.Generation, *myBox) 154 } 155 return newMetadata, err 156 } 157 158 func ForcePublishNewUserEKForTesting(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot) (metadata keybase1.UserEkMetadata, err error) { 159 defer mctx.Trace("ForcePublishNewUserEKForTesting", &err)() 160 return publishNewUserEK(mctx, merkleRoot) 161 } 162 163 func boxUserEKForDevices(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot, 164 seed UserEKSeed, userMetadata keybase1.UserEkMetadata) (boxes []keybase1.UserEkBoxMetadata, 165 myUserEKBoxed *keybase1.UserEkBoxed, err error) { 166 defer mctx.Trace("boxUserEKForDevices", &err)() 167 168 devicesMetadata, err := allActiveDeviceEKMetadata(mctx, merkleRoot) 169 if err != nil { 170 return nil, nil, err 171 } 172 173 myDeviceID := mctx.ActiveDevice().DeviceID() 174 for deviceID, deviceMetadata := range devicesMetadata { 175 recipientKey, err := libkb.ImportKeypairFromKID(deviceMetadata.Kid) 176 if err != nil { 177 return nil, nil, err 178 } 179 // Encrypting with a nil sender means we'll generate a random sender private key. 180 box, err := recipientKey.EncryptToString(seed[:], nil) 181 if err != nil { 182 return nil, nil, err 183 } 184 boxMetadata := keybase1.UserEkBoxMetadata{ 185 RecipientDeviceID: deviceID, 186 RecipientGeneration: deviceMetadata.Generation, 187 Box: box, 188 } 189 boxes = append(boxes, boxMetadata) 190 191 if deviceID == myDeviceID { 192 myUserEKBoxed = &keybase1.UserEkBoxed{ 193 Box: box, 194 DeviceEkGeneration: deviceMetadata.Generation, 195 Metadata: userMetadata, 196 } 197 } 198 } 199 return boxes, myUserEKBoxed, nil 200 } 201 202 type userEKStatementResponse struct { 203 Sigs map[keybase1.UID]*string `json:"sigs"` 204 } 205 206 // Returns nil if the user has never published a userEK. If the user has 207 // published a userEK, but has since rolled their PUK without publishing a new 208 // one, this function will also return nil and log a warning. This is a 209 // transitional thing, and eventually when all "reasonably up to date" clients 210 // in the wild have EK support, we will make that case an error. 211 func fetchUserEKStatements(mctx libkb.MetaContext, uids []keybase1.UID) ( 212 statements map[keybase1.UID]*keybase1.UserEkStatement, err error) { 213 defer mctx.Trace(fmt.Sprintf("fetchUserEKStatements: numUids: %v", len(uids)), &err)() 214 215 apiArg := libkb.APIArg{ 216 Endpoint: "user/get_user_ek_batch", 217 SessionType: libkb.APISessionTypeREQUIRED, 218 Args: libkb.HTTPArgs{ 219 "uids": libkb.S{Val: libkb.UidsToString(uids)}, 220 }, 221 } 222 res, err := mctx.G().GetAPI().Post(mctx, apiArg) 223 if err != nil { 224 return nil, err 225 } 226 227 userEKStatements := userEKStatementResponse{} 228 if err = res.Body.UnmarshalAgain(&userEKStatements); err != nil { 229 return nil, err 230 } 231 232 getArg := func(i int) *libkb.LoadUserArg { 233 if i >= len(uids) { 234 return nil 235 } 236 tmp := libkb.NewLoadUserArgWithMetaContext(mctx).WithUID(uids[i]) 237 return &tmp 238 } 239 240 var upaks []*keybase1.UserPlusKeysV2AllIncarnations 241 statements = make(map[keybase1.UID]*keybase1.UserEkStatement) 242 processResult := func(i int, upak *keybase1.UserPlusKeysV2AllIncarnations) error { 243 mctx.Debug("processing member %d/%d %.2f%% complete", i, len(uids), (float64(i) / float64(len(uids)) * 100)) 244 if upak == nil { 245 mctx.Debug("Unable to load user %v", uids[i]) 246 return nil 247 } 248 upaks = append(upaks, upak) 249 return nil 250 } 251 252 if err = mctx.G().GetUPAKLoader().Batcher(mctx.Ctx(), getArg, processResult, 0); err != nil { 253 return nil, err 254 } 255 256 for _, upak := range upaks { 257 uid := upak.GetUID() 258 sig, ok := userEKStatements.Sigs[uid] 259 if !ok || sig == nil { 260 mctx.Debug("missing memberEK statement for UID %v", uid) 261 continue 262 } 263 264 statement, _, wrongKID, err := verifySigWithLatestPUK(mctx, uid, 265 upak.Current.GetLatestPerUserKey(), *sig) 266 if wrongKID { 267 mctx.Debug("UID %v has a statement signed with the wrongKID, skipping", uid) 268 // Don't box for this member since they have no valid userEK 269 continue 270 } else if err != nil { 271 return nil, err 272 } 273 statements[uid] = statement 274 } 275 276 return statements, nil 277 } 278 279 // Returns nil if the user has never published a userEK. If the user has 280 // published a userEK, but has since rolled their PUK without publishing a new 281 // one, this function will return wrongKID. This allows clients to chose the 282 // correct generation number but not include the statement when generating a 283 // new userEK. 284 func fetchUserEKStatement(mctx libkb.MetaContext, uid keybase1.UID) ( 285 statement *keybase1.UserEkStatement, latestGeneration keybase1.EkGeneration, wrongKID bool, err error) { 286 defer mctx.Trace("fetchUserEKStatement", &err)() 287 288 apiArg := libkb.APIArg{ 289 Endpoint: "user/user_ek", 290 SessionType: libkb.APISessionTypeREQUIRED, 291 Args: libkb.HTTPArgs{ 292 "uids": libkb.S{Val: libkb.UidsToString([]keybase1.UID{uid})}, 293 }, 294 } 295 res, err := mctx.G().GetAPI().Get(mctx, apiArg) 296 if err != nil { 297 return nil, latestGeneration, false, err 298 } 299 300 parsedResponse := userEKStatementResponse{} 301 err = res.Body.UnmarshalAgain(&parsedResponse) 302 if err != nil { 303 return nil, latestGeneration, false, err 304 } 305 // User has no statements 306 if len(parsedResponse.Sigs) == 0 { 307 return nil, latestGeneration, false, nil 308 } 309 if len(parsedResponse.Sigs) != 1 { 310 return nil, latestGeneration, false, fmt.Errorf("Invalid server response, multiple userEK statements returned") 311 } 312 sig, ok := parsedResponse.Sigs[uid] 313 if !ok { 314 return nil, latestGeneration, false, fmt.Errorf("Invalid server response, wrong uid returned") 315 } 316 317 upak, _, err := mctx.G().GetUPAKLoader().LoadV2( 318 libkb.NewLoadUserArgWithMetaContext(mctx).WithUID(uid)) 319 if err != nil { 320 return nil, latestGeneration, false, err 321 } 322 latestPUK := upak.Current.GetLatestPerUserKey() 323 statement, latestGeneration, wrongKID, err = verifySigWithLatestPUK(mctx, uid, latestPUK, *sig) 324 // Check the wrongKID condition before checking the error, since an error 325 // is still returned in this case. TODO: Turn this warning into an error 326 // after EK support is sufficiently widespread. 327 if wrongKID { 328 mctx.Debug("It looks like you revoked a device without generating new ephemeral keys. Are you running an old version?") 329 return nil, latestGeneration, true, nil 330 } else if err != nil { 331 return nil, latestGeneration, false, err 332 } 333 334 return statement, latestGeneration, false, nil 335 } 336 337 func extractUserEKStatementFromSig(sig string) (signerKey *kbcrypto.NaclSigningKeyPublic, statement *keybase1.UserEkStatement, err error) { 338 signerKey, payload, _, err := kbcrypto.NaclVerifyAndExtract(sig) 339 if err != nil { 340 return signerKey, nil, err 341 } 342 343 parsedStatement := keybase1.UserEkStatement{} 344 if err = json.Unmarshal(payload, &parsedStatement); err != nil { 345 return signerKey, nil, err 346 } 347 return signerKey, &parsedStatement, nil 348 } 349 350 // Verify that the blob is validly signed, and that the signing key is the 351 // given user's latest PUK, then parse its contents. If the blob is signed by 352 // the wrong KID, that's still an error, but we'll also return this special 353 // `wrongKID` flag. As a transitional measure while we wait for all clients in 354 // the wild to have EK support, callers will treat that case as "there is no 355 // key" and convert the error to a warning. We set `latestGeneration` so that 356 // callers can use this value to generate a new key even if `wrongKID` is set. 357 func verifySigWithLatestPUK(mctx libkb.MetaContext, uid keybase1.UID, 358 latestPUK *keybase1.PerUserKey, sig string) ( 359 statement *keybase1.UserEkStatement, latestGeneration keybase1.EkGeneration, wrongKID bool, err error) { 360 defer mctx.Trace("verifySigWithLatestPUK", &err)() 361 362 // Parse the statement before we verify the signing key. Even if the 363 // signing key is bad (likely because of a legacy PUK roll that didn't 364 // include a userEK statement), we'll still return the generation number. 365 signerKey, parsedStatement, err := extractUserEKStatementFromSig(sig) 366 if err != nil { 367 return nil, latestGeneration, false, err 368 } 369 latestGeneration = parsedStatement.CurrentUserEkMetadata.Generation 370 371 // Verify the signing key corresponds to the latest PUK. We use the user's 372 // UPAK from cache, but if the KID doesn't match, we try a forced reload to 373 // see if the cache might've been stale. Only if the KID still doesn't 374 // match after the reload do we complain. 375 if latestPUK == nil || !latestPUK.SigKID.Equal(signerKey.GetKID()) { 376 // The latest PUK might be stale. Force a reload, then check this over again. 377 upak, _, err := mctx.G().GetUPAKLoader().LoadV2( 378 libkb.NewLoadUserArgWithMetaContext(mctx).WithUID(uid).WithForceReload()) 379 if err != nil { 380 return nil, latestGeneration, false, err 381 } 382 latestPUK = upak.Current.GetLatestPerUserKey() 383 if latestPUK == nil || !latestPUK.SigKID.Equal(signerKey.GetKID()) { 384 // The latest PUK still doesn't match the signing key after a 385 // forced reload. Bail out, and set the `wrongKID` flag. 386 latestPUKSigningKIDString := "<nil>" 387 if latestPUK != nil { 388 latestPUKSigningKIDString = fmt.Sprint(latestPUK.SigKID) 389 } 390 err = fmt.Errorf("userEK returned for PUK signing KID %s, but latest is %s", 391 signerKey.GetKID(), latestPUKSigningKIDString) 392 return nil, latestGeneration, true, err 393 } 394 } 395 396 return parsedStatement, latestGeneration, false, nil 397 } 398 399 func filterStaleUserEKStatements(mctx libkb.MetaContext, statementMap map[keybase1.UID]*keybase1.UserEkStatement, 400 merkleRoot libkb.MerkleRoot) (activeMap map[keybase1.UID]keybase1.UserEkStatement, err error) { 401 defer mctx.Trace(fmt.Sprintf("filterStaleUserEKStatements: numStatements: %v", len(statementMap)), &err)() 402 403 activeMap = make(map[keybase1.UID]keybase1.UserEkStatement) 404 for uid, statement := range statementMap { 405 if statement == nil { 406 mctx.Debug("found stale userStatement for uid: %s", uid) 407 continue 408 } 409 metadata := statement.CurrentUserEkMetadata 410 if ctimeIsStale(metadata.Ctime.Time(), merkleRoot) { 411 mctx.Debug("found stale userStatement for KID: %s", metadata.Kid) 412 continue 413 } 414 activeMap[uid] = *statement 415 } 416 417 return activeMap, nil 418 } 419 420 func activeUserEKMetadata(mctx libkb.MetaContext, statementMap map[keybase1.UID]*keybase1.UserEkStatement, 421 merkleRoot libkb.MerkleRoot) (activeMetadata map[keybase1.UID]keybase1.UserEkMetadata, err error) { 422 activeMap, err := filterStaleUserEKStatements(mctx, statementMap, merkleRoot) 423 if err != nil { 424 return nil, err 425 } 426 activeMetadata = make(map[keybase1.UID]keybase1.UserEkMetadata) 427 for uid, statement := range activeMap { 428 activeMetadata[uid] = statement.CurrentUserEkMetadata 429 } 430 return activeMetadata, nil 431 }