bitwarden/server · error · BadRequestException

Rotated encryption keys must be provided for all devices tha

Error message

Rotated encryption keys must be provided for all devices that are trusted.

What it means

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.

Source

Thrown at src/Api/KeyManagement/Validators/DeviceRotationValidator.cs:45

        var result = new List<Device>();

        var existingTrustedDevices = (await _deviceRepository.GetManyByUserIdAsync(user.Id)).Where(d => d.IsTrusted()).ToList();
        if (existingTrustedDevices.Count == 0)
        {
            return result;
        }

        foreach (var existing in existingTrustedDevices)
        {
            var device = devices.FirstOrDefault(c => c.DeviceId == existing.Id);
            if (device == null)
            {
                throw new BadRequestException("All existing trusted devices must be included in the rotation.");
            }

            if (device.EncryptedUserKey == null || device.EncryptedPublicKey == null)
            {
                throw new BadRequestException("Rotated encryption keys must be provided for all devices that are trusted.");
            }

            result.Add(device.ToDevice(existing));
        }

        return result;
    }
}

View on GitHub (pinned to e93b962371)

Solutions

  1. Re-encrypt both the user key and public key for every trusted device using the new user key before submitting.
  2. Add a pre-send assertion that every device entry has non-null EncryptedUserKey and EncryptedPublicKey.
  3. If a trusted device's old keys cannot be read, un-trust the device first so it is excluded from rotation.
  4. Inspect the failing entry's DeviceId in the client logs to find which device was under-populated.

Example fix

// before: entry missing keys
{ deviceId: d.id }

// after: both keys re-encrypted with new user key
{ deviceId: d.id, encryptedUserKey: reencrypt(d.userKey), encryptedPublicKey: reencrypt(d.publicKey) }
Defensive patterns

Strategy: validation

Validate before calling

// Ensure every submitted trusted device has both keys
const incomplete = payload.devices.filter(d =>
  trustedIds.has(d.deviceId) && (!d.encryptedUserKey || !d.encryptedPublicKey));
if (incomplete.length) {
  throw new Error(`Missing re-encrypted keys for: ${incomplete.map(d => d.deviceId).join(', ')}`);
}

Type guard

function hasDeviceKeys(d: { encryptedUserKey?: string; encryptedPublicKey?: string }): boolean {
  return !!d.encryptedUserKey && !!d.encryptedPublicKey;
}

Try / catch

try {
  await api.rotateKey(payload);
} catch (e) {
  if (e.status === 400 && /keys must be provided for all devices/i.test(e.message)) {
    payload.devices = payload.devices.map(d => reencryptDevice(d));
    return api.rotateKey(payload);
  }
  throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/cb7b3e6c849d9882. Report an issue: GitHub.