{"record":{"id":"0b96364642f7c59d","repo":"bitwarden/server","slug":"webauthn-prf-keys-must-have-public-key-during-rota","errorCode":null,"errorMessage":"WebAuthn prf keys must have public-key during rotation.","messagePattern":"WebAuthn prf keys must have public-key during rotation\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/KeyManagement/Validators/WebAuthnLoginKeyRotationValidator.cs","lineNumber":51,"sourceCode":"            return result;\n        }\n\n        foreach (var webAuthnCredential in validCredentials)\n        {\n            var keyToRotate = keysToRotate.FirstOrDefault(c => c.Id == webAuthnCredential.Id);\n            if (keyToRotate == null)\n            {\n                throw new BadRequestException(\"All existing webauthn prf keys must be included in the rotation.\");\n            }\n\n            if (keyToRotate.EncryptedUserKey == null)\n            {\n                throw new BadRequestException(\"WebAuthn prf keys must have user-key during rotation.\");\n            }\n\n            if (keyToRotate.EncryptedPublicKey == null)\n            {\n                throw new BadRequestException(\"WebAuthn prf keys must have public-key during rotation.\");\n            }\n\n            result.Add(keyToRotate.ToWebAuthnRotateKeyData());\n        }\n\n        return result;\n    }\n}\n","sourceCodeStart":33,"sourceCodeEnd":60,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/KeyManagement/Validators/WebAuthnLoginKeyRotationValidator.cs#L33-L60","documentation":"Thrown by WebAuthnLoginKeyRotationValidator when a rotation entry matched a PRF-enabled credential (matched by Id) and has a non-null EncryptedUserKey, but its EncryptedPublicKey is null. The credential needs both the re-encrypted user key and public key to remain valid, so a null public key is rejected.","triggerScenarios":"The keys array includes the credential Id and EncryptedUserKey but EncryptedPublicKey is null or was omitted during request construction.","commonSituations":"Client populated the user key but forgot the public key field; a copy/paste or template only carried EncryptedUserKey; serialization dropped the public-key field.","solutions":["Re-encrypt and populate both EncryptedUserKey and EncryptedPublicKey for every PRF-enabled credential.","Add a pre-send assertion that every key entry has non-null EncryptedUserKey and EncryptedPublicKey.","If a credential cannot be fully re-encrypted, disable its PRF or remove the passkey before rotating."],"exampleFix":"// before\n{ id: c.id, encryptedUserKey: reencrypt(c.userKey) }\n\n// after\n{ id: c.id, encryptedUserKey: reencrypt(c.userKey), encryptedPublicKey: reencrypt(c.publicKey) }","handlingStrategy":"validation","validationCode":"const missingPubKey = payload.keys.filter(k => k.encryptedPublicKey == null);\nif (missingPubKey.length) {\n  throw new Error(`WebAuthn keys missing public key: ${missingPubKey.map(k => k.id).join(', ')}`);\n}","typeGuard":"function hasWebAuthnPublicKey(k: { encryptedPublicKey?: string | null }): boolean {\n  return k.encryptedPublicKey != null && k.encryptedPublicKey.length > 0;\n}","tryCatchPattern":"try {\n  await api.rotateKey(payload);\n} catch (e) {\n  if (e.status === 400 && /must have public-key/i.test(e.message)) {\n    payload.keys = payload.keys.map(k => ({ ...k, encryptedPublicKey: k.encryptedPublicKey ?? reencrypt(publicKeyFor(k.id)) }));\n    return api.rotateKey(payload);\n  }\n  throw e;\n}","preventionTips":["Assert every PRF key entry has both non-null EncryptedUserKey and EncryptedPublicKey before submitting.","Use a single builder that always populates both fields.","Disable PRF on a credential if its keys cannot be fully re-encrypted."],"tags":["key-rotation","webauthn","prf","null-check","encryption","bad-request"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}