{"record":{"id":"cb7b3e6c849d9882","repo":"bitwarden/server","slug":"rotated-encryption-keys-must-be-provided-for-all-d","errorCode":null,"errorMessage":"Rotated encryption keys must be provided for all devices that are trusted.","messagePattern":"Rotated encryption keys must be provided for all devices that are trusted\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/KeyManagement/Validators/DeviceRotationValidator.cs","lineNumber":45,"sourceCode":"        var result = new List<Device>();\n\n        var existingTrustedDevices = (await _deviceRepository.GetManyByUserIdAsync(user.Id)).Where(d => d.IsTrusted()).ToList();\n        if (existingTrustedDevices.Count == 0)\n        {\n            return result;\n        }\n\n        foreach (var existing in existingTrustedDevices)\n        {\n            var device = devices.FirstOrDefault(c => c.DeviceId == existing.Id);\n            if (device == null)\n            {\n                throw new BadRequestException(\"All existing trusted devices must be included in the rotation.\");\n            }\n\n            if (device.EncryptedUserKey == null || device.EncryptedPublicKey == null)\n            {\n                throw new BadRequestException(\"Rotated encryption keys must be provided for all devices that are trusted.\");\n            }\n\n            result.Add(device.ToDevice(existing));\n        }\n\n        return result;\n    }\n}\n","sourceCodeStart":27,"sourceCodeEnd":54,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/KeyManagement/Validators/DeviceRotationValidator.cs#L27-L54","documentation":"Thrown by DeviceRotationValidator when a device in the rotation request matched an existing trusted device, but its EncryptedUserKey or EncryptedPublicKey is null. Trusted devices must be re-encrypted with the new user key, so submitting a matched entry without the freshly-encrypted keys is treated as an incomplete rotation and rejected.","triggerScenarios":"The rotation payload includes the DeviceId of a trusted device but the entry was constructed without populating EncryptedUserKey and/or EncryptedPublicKey (e.g. a shallow copy, a placeholder object, or the re-encryption step was skipped for that device).","commonSituations":"Client reused a request template and forgot to set the key fields on a newly-trusted device; the device's old keys failed to decrypt so the client skipped re-encryption; a serialization bug dropped null-suppressed fields.","solutions":["Re-encrypt both the user key and public key for every trusted device using the new user key before submitting.","Add a pre-send assertion that every device entry has non-null EncryptedUserKey and EncryptedPublicKey.","If a trusted device's old keys cannot be read, un-trust the device first so it is excluded from rotation.","Inspect the failing entry's DeviceId in the client logs to find which device was under-populated."],"exampleFix":"// before: entry missing keys\n{ deviceId: d.id }\n\n// after: both keys re-encrypted with new user key\n{ deviceId: d.id, encryptedUserKey: reencrypt(d.userKey), encryptedPublicKey: reencrypt(d.publicKey) }","handlingStrategy":"validation","validationCode":"// Ensure every submitted trusted device has both keys\nconst incomplete = payload.devices.filter(d =>\n  trustedIds.has(d.deviceId) && (!d.encryptedUserKey || !d.encryptedPublicKey));\nif (incomplete.length) {\n  throw new Error(`Missing re-encrypted keys for: ${incomplete.map(d => d.deviceId).join(', ')}`);\n}","typeGuard":"function hasDeviceKeys(d: { encryptedUserKey?: string; encryptedPublicKey?: string }): boolean {\n  return !!d.encryptedUserKey && !!d.encryptedPublicKey;\n}","tryCatchPattern":"try {\n  await api.rotateKey(payload);\n} catch (e) {\n  if (e.status === 400 && /keys must be provided for all devices/i.test(e.message)) {\n    payload.devices = payload.devices.map(d => reencryptDevice(d));\n    return api.rotateKey(payload);\n  }\n  throw e;\n}","preventionTips":["Run a pre-submit assertion that each trusted-device entry has non-null EncryptedUserKey and EncryptedPublicKey.","Centralize re-encryption in a single mapper so no field is forgotten.","Unit-test the request builder against a device fixture to catch missing keys."],"tags":["key-rotation","device","encryption","null-check","bad-request"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}