passbolt/passbolt_api · error · CustomValidationException

A valid OpenPGP key must be provided.

Error message

A valid OpenPGP key must be provided.

What it means

CustomValidationException with message 'A valid OpenPGP key must be provided.' thrown when isParsableArmoredPublicKey fails — the armored string cannot be parsed as an OpenPGP key at all. This mandatory check always runs first in parseAndValidatePublicKey; other rules are skipped when it fails.

Solutions

  1. Provide a complete, valid OpenPGP public key ASCII armor including the BEGIN/END PGP PUBLIC KEY BLOCK lines.
  2. Pre-validate with PublicKeyValidationService::isParsableArmoredPublicKey before submitting to give a friendlier error.
  3. Ensure the client sends the key as a plain string without HTML-escaping or base64 wrapping.
  4. Catch CustomValidationException to read the structured validation errors (armored_key.IS_PARSABLE_ARMORED_KEY_RULE).

Example fix

// before
$this->assertMetadataKey($armoredKey); // throws on bad paste

// after
if (!PublicKeyValidationService::isParsableArmoredPublicKey($armoredKey)) {
    throw new BadRequestException(__('Please paste a complete OpenPGP public key block starting with -----BEGIN PGP PUBLIC KEY BLOCK-----'));
}
$this->assertMetadataKey($armoredKey);
Defensive patterns

Strategy: validation

Validate before calling

// PHP
if (!PublicKeyValidationService::isParsableArmoredPublicKey($armoredKey)) {
    throw new BadRequestException(__('Provide a complete OpenPGP public key armored block.'));
}

Type guard

function looksLikeArmoredPublicKey(?string $key): bool {
    return is_string($key)
        && str_contains($key, '-----BEGIN PGP PUBLIC KEY BLOCK-----')
        && str_contains($key, '-----END PGP PUBLIC KEY BLOCK-----');
}

Try / catch

try {
    PublicKeyValidationService::parseAndValidatePublicKey($armoredKey);
} catch (\App\Error\Exception\CustomValidationException $e) {
    return $this->respondWithError(400, __('The provided key is not a parsable OpenPGP key.'));
}

Prevention

When it happens

Trigger: Any call to parseAndValidatePublicKey (from assertMetadataKey, buildPublicKeyEntityFromDataOrFail, buildRevokedKeyEntityFromDataOrFail, create, rule, canValidate) with input missing the PGP armored block headers, malformed ASCII armor, or binary garbage.

Common situations: Users pasting a private key, a plain text file, or a truncated key; missing BEGIN PGP PUBLIC KEY BLOCK header due to copy/paste; client sending an empty string.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at src/Service/OpenPGP/PublicKeyValidationService.php:154

     * @param string $armoredKey user provided data
     * @param array|null $rules to override default rules
     * @throws \App\Error\Exception\CustomValidationException If parsing public key fails
     * @throws \App\Error\Exception\CustomValidationException Validation rules fails
     * @throws \Cake\Http\Exception\InternalErrorException No public key validation rules
     * @throws \Cake\Http\Exception\InternalErrorException Unknown key validation rule
     * @return array key information (see OpenPGPBackendInterface::getKeyInfo)
     */
    public static function parseAndValidatePublicKey(string $armoredKey, ?array $rules = null): array
    {
        $rules = $rules ?? self::getDefaultRules();
        if (!count($rules)) {
            throw new InternalErrorException('Invalid public key validation rules are missing.');
        }

        // Parsing check is mandatory and always done first
        // We don't even try the other rules if this one fails
        if (!self::isParsableArmoredPublicKey($armoredKey)) {
            throw new CustomValidationException(__('A valid OpenPGP key must be provided.'), [
                'armored_key' => [
                    self::IS_PARSABLE_ARMORED_KEY_RULE => __('The public key could not be parsed.'),
                ],
            ]);
        }

        // Other rules are recommended but not mandatory
        // As one may want to see what's inside the key info for debugging purpose
        $keyInfo = self::getPublicKeyInfo($armoredKey);
        $validationErrors = [];
        foreach ($rules as $ruleName) {
            switch ($ruleName) {
                case self::IS_VALID_ALGORITHM_RULE:
                    if (!self::isValidAlgorithm($keyInfo['type'], false)) {
                        $validationErrors[$ruleName] = __('The algorithm is invalid.');
                    }
                    break;
                case self::IS_VALID_ALGORITHM_STRICT_RULE:

View on GitHub (pinned to 31c1bbc10f)