bitwarden/server · error · BadRequestException

AccountKeys are only supported for V2 encryption.

Error message

AccountKeys are only supported for V2 encryption.

What it means

In POST /accounts/keys, when model.AccountKeys is supplied but accountKeysData.IsV2Encryption() is false, the controller throws BadRequestException("AccountKeys are only supported for V2 encryption.") → HTTP 400. The AccountKeys field exclusively carries V2 (account-keys / SSO-style) encryption data; V1 keypair submissions must omit it.

Source

Thrown at src/Api/Auth/Controllers/AccountsController.cs:527

    public async Task<KeysResponseModel> PostKeys([FromBody] KeysRequestModel model)
    {
        var user = await _userService.GetUserByPrincipalAsync(User);
        if (user == null)
        {
            throw new UnauthorizedAccessException();
        }

        if (!string.IsNullOrWhiteSpace(user.PrivateKey) || !string.IsNullOrWhiteSpace(user.PublicKey))
        {
            throw new BadRequestException("User has existing keypair");
        }

        if (model.AccountKeys != null)
        {
            var accountKeysData = model.AccountKeys.ToAccountKeysData();
            if (!accountKeysData.IsV2Encryption())
            {
                throw new BadRequestException("AccountKeys are only supported for V2 encryption.");
            }
            // A client that predates the key id field sends none. The account then picks one up from
            // the backfill endpoint on a later sync rather than here.
            var userKeyId = KeyId.FromHexEncodedString(model.UserKeyId);
            var updateUserDataTasks = userKeyId == null
                ? null
                : new UpdateUserData[] { _userRepository.SetUserKeyId(user.Id, userKeyId) };

            await _userRepository.SetV2AccountCryptographicStateAsync(user.Id, accountKeysData,
                updateUserDataTasks);
            return new KeysResponseModel(accountKeysData, user.Key);
        }
        else
        {
            // Todo: Drop this after a transition period. This will drop no-account-keys requests.
            // The V1 check in the other branch should persist
            // https://bitwarden.atlassian.net/browse/PM-27329
            await _userService.SaveUserAsync(model.ToUser(user));

View on GitHub (pinned to e93b962371)

Solutions

  1. Ensure AccountKeys is only populated with V2-encryption data; for V1 keypair submission omit AccountKeys entirely (the else branch handles it).
  2. Verify ToAccountKeysData().IsV2Encryption() returns true on the client before posting.
  3. Upgrade the client fully to the V2 key model so the payload is internally consistent.

Example fix

// before
model.AccountKeys = legacyV1Keypair; // IsV2Encryption() == false
// after — V1 path: omit AccountKeys
model.AccountKeys = null;
model.PublicKey = pub; model.PrivateKey = priv;
Defensive patterns

Strategy: type-guard

Validate before calling

// Only send AccountKeys when it represents V2 encryption
if (model.AccountKeys != null && !model.AccountKeys.ToAccountKeysData().IsV2Encryption())
    model.AccountKeys = null; // fall back to V1 keypair path

Type guard

static bool IsV2(AccountKeysData? d) => d is not null && d.IsV2Encryption();

Try / catch

try { await client.PostAsync("accounts/keys", content); }
catch (BadRequestException ex) when (ex.Message.Contains("V2 encryption"))
{ /* rebuild payload: omit AccountKeys or supply real V2 data */ }

Prevention

When it happens

Trigger: A client sends an AccountKeys payload whose structure does not represent V2 encryption (e.g. a legacy/V1 keypair wrapped in the AccountKeys field), or passes the V1 public/private keypair through the V2 field.

Common situations: Mixed client version sending both V1 keypair fields and a non-V2 AccountKeys object; serialization bug putting PublicKeyEncryptionKeyPairData in the wrong place; client upgraded partially and sends an inconsistent payload.

Related errors


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