passbolt/passbolt_api · error · InvalidJwtKeyPairException
The permission of $publicKeyPath could not be set to…
Error message
The permission of $publicKeyPath could not be set to $permission.
What it means
Same as the private-key variant: createKeyPair() chmods the public key file to 0640 and throws this error naming the public key path when chmod() fails. It is rethrown as InvalidJwtKeyPairException, aborting key pair creation.
Solutions
- Normalise ownership of the whole directory: chown -R www-data:www-data config/jwt then re-run the command
- Manually chmod 640 config/jwt/jwt.public.key and re-run validation
- Delete partial key files and regenerate as the runtime user: rm config/jwt/* && sudo -u www-data bin/cake passbolt create jwt_keys
- Confirm the filesystem honours chmod (touch f; chmod 600 f; stat f)
Example fix
// before # partial run left root-owned files // after sudo chown -R www-data:www-data config/jwt sudo rm config/jwt/* sudo -u www-data bin/cake passbolt create jwt_keys
Defensive patterns
Strategy: try-catch
Validate before calling
clearstatcache();
$owner = fileowner($publicKeyPath);
if ($owner !== posix_getuid() && !posix_getpwuid($owner)) { /* ownership anomaly */ } Try / catch
try { $service->createKeyPair(); } catch (InvalidJwtKeyPairException $e) { // chown -R www-data:www-data config/jwt and regenerate cleanly } Prevention
- Delete partial key files before regenerating (rm config/jwt/*)
- Keep single ownership across config/jwt
- Pre-set permissions at build time on filesystems without chmod support
When it happens
Trigger: chmod($publicKeyPath, 0640) returns false after the private key chmod succeeded — ownership mismatch between the writing process and the file, unsupported filesystem, or PHP security restrictions on the path.
Common situations: Mixed ownership in config/jwt (some files root-owned, some www-data-owned) from previous partial runs; root_squash on NFS; OpenCart-style shared hosting where the web user cannot chmod CLI-created files.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- The permission of $secretKeyPath could not be set to…
- The JWT private key could not be written.
- The JWT public key could not be read or is not valid.
- The JWT public key could not be written.
- The key pair for JWT Authentication is not complete.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/c335c931ab5d238c.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/JwtAuthentication/src/Service/AccessToken/JwtKeyPairService.php:96
throw new Exception('The JWT private key could not be written.');
}
$publicKey = openssl_pkey_get_details($pk)['key'] ?? false;
if ($publicKey === false) {
throw new Exception('The JWT public key could not be extracted.');
}
$export = file_put_contents($publicKeyPath, $publicKey);
if ($export === false) {
throw new Exception('The JWT public key could not be written.');
}
$permission = 0640;
$res = chmod($secretKeyPath, $permission);
if (!$res) {
throw new Exception("The permission of $secretKeyPath could not be set to $permission.");
}
$res = chmod($publicKeyPath, $permission);
if (!$res) {
throw new Exception("The permission of $publicKeyPath could not be set to $permission.");
}
} catch (Throwable $e) {
throw new InvalidJwtKeyPairException($e->getMessage());
}
}
/**
* Validate the key pair validity as defined by the public and secret services.
*
* @param string|null $uuid Uuid for testing aim
* @return object
* @throws \Passbolt\JwtAuthentication\Error\Exception\AccessToken\InvalidJwtKeyPairException
*/
public function validateKeyPair(?string $uuid = null): object
{
// Minimal size of the private key
$minSecretKeySize = JwtTokenCreateService::JWT_KEY_LENGTH;
$uuid = $uuid ?? UuidFactory::uuid();View on GitHub (pinned to 31c1bbc10f)