passbolt/passbolt_api · error · App\Error\Exception\CustomValidationException

Could not validate public key data.

Error message

Could not validate public key data.

What it means

This CustomValidationException is raised during an enabled-to-enabled policy change without key rotation: the request omitted 'public_key_id', so the service cannot determine which existing organization public key to reuse. The message 'Could not validate public key data.' carries a nested validation error (_required) indicating the organization public key identifier is required.

Solutions

  1. Add the current organization public key's id as 'public_key_id' in the request payload so the existing key is reused.
  2. Fetch the current policy via GET /account-recovery/organization-policies.json to obtain the correct public_key_id before submitting.
  3. If a key rotation was intended instead, provide both the new key and the revoked key rather than relying on public_key_id.
  4. Update the client library/UI to always include public_key_id on non-rotation policy updates.

Example fix

// before (policy change without key reference)
await passbolt.setAccountRecoveryOrganizationPolicy({
  policy: 'opt-in'
});

// after (include current public_key_id to reuse existing key)
await passbolt.setAccountRecoveryOrganizationPolicy({
  policy: 'opt-in',
  public_key_id: currentPolicy.account_recovery_organization_public_key.id
});
Defensive patterns

Strategy: validation

Validate before calling

if (!payload.account_recovery_organization_public_key && !payload.account_recovery_organization_revoked_key && !payload.public_key_id) {
  throw new Error('Policy-only change requires public_key_id of the current organization key.');
}

Type guard

function canReuseExistingKey(p) {
  return typeof p.public_key_id === 'string' && p.public_key_id.length > 0;
}

Try / catch

try {
  await passbolt.setAccountRecoveryOrganizationPolicy(payload);
} catch (e) {
  if (e.status === 400 && /Could not validate public key data/.test(e.body ?? e.message) && e.body?.public_key_id?._required) {
    const current = await passbolt.getAccountRecoveryOrganizationPolicy();
    payload.public_key_id = current.account_recovery_organization_public_key.id;
    return retry(payload);
  }
  throw e;
}

Prevention

When it happens

Trigger: Policy-only change (no new key, no revoked key) where the payload lacks both key rotation fields AND 'public_key_id'. Raised in the else branch of AccountRecoveryOrganizationPolicySetService::set() at line 118 when !isset($newPolicy->public_key_id).

Common situations: An admin changes the policy (e.g. mandatory to opt-in) with a minimal payload that omits the current public_key_id; a client library that only sends the policy field on updates; older client versions built before the public_key_id requirement; hand-crafted curl requests copied from enable examples.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/64dd94131aab01e1. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php:118

        if ($isNewKeyProvided && $isRevokedKeyProvided) {
            // assert old and new key$newKey
            $newKey = $this->buildPublicKeyEntityFromDataOrFail($uac);
            $oldKey = $this->buildRevokedKeyEntityFromDataOrFail($uac);

            // If some existing backups are present
            // assert new backups are provided
            if ($this->backupsExists()) {
                if (!$isPrivateKeyPasswordsProvided) {
                    throw new BadRequestException(__('Invalid request. Passwords are required for this change.'));
                }
                // assert passwords backups format and numbers
                $passwords = $this->buildPasswordEntitiesFromDataOrFail($uac, $newKey);
            }
            $newPolicy->account_recovery_organization_public_key = $newKey;
        } else {
            // If key is not changing reuse the old one
            if (!isset($newPolicy->public_key_id)) {
                throw new CustomValidationException(__('Could not validate public key data.'), [
                    'public_key_id' => [
                        '_required' => __('An organization public key is required.'),
                    ],
                ]);
            } else {
                if ($newPolicy->public_key_id !== $this->getCurrentPolicyEntity()->public_key_id) {
                    throw new CustomValidationException(__('Could not validate public key data.'), [
                        'public_key_id' => [
                            'notCurrentPublicKeyId' => __('The public_key_id must match current policy public_key_id.'),
                        ],
                    ]);
                }
            }
        }

        // save new key and disable previous key and backups if any
        return $this->updatePolicy($uac, $newPolicy, $oldKey, $newKey, $passwords);
    }

View on GitHub (pinned to 31c1bbc10f)