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

  1. Normalise ownership of the whole directory: chown -R www-data:www-data config/jwt then re-run the command
  2. Manually chmod 640 config/jwt/jwt.public.key and re-run validation
  3. Delete partial key files and regenerate as the runtime user: rm config/jwt/* && sudo -u www-data bin/cake passbolt create jwt_keys
  4. 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

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


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)