passbolt/passbolt_api · error · InvalidJwtKeyPairException

The permission of $secretKeyPath could not be set to…

Error message

The permission of $secretKeyPath could not be set to $permission.

What it means

After writing both key files, createKeyPair() chmods the private key to 0640. If chmod() fails, this interpolated error names the failing secret key path and the intended permission, then is rethrown as InvalidJwtKeyPairException.

Solutions

  1. Run the key generation as the same user that owns config/jwt (e.g. su -s /bin/bash -c '...' www-data) or chown the files afterwards: chown www-data:www-data config/jwt/*
  2. Set the permissions manually: chmod 640 config/jwt/jwt.private.key config/jwt/jwt.public.key, then re-run validation only
  3. Check whether the filesystem supports chmod; on unsupported mounts pre-set permissions at mount/build time
  4. Verify open_basedir/safe-mode settings in php.ini are not restricting the path

Example fix

// before
sudo bin/cake passbolt create jwt_keys   # files owned by root
// after
sudo -u www-data bin/cake passbolt create jwt_keys
sudo chmod 640 config/jwt/jwt.*.key
Defensive patterns

Strategy: try-catch

Validate before calling

clearstatcache();
if (fileowner($secretKeyPath) !== posix_getuid()) { /* regenerate as owning user or pre-chmod */ }

Try / catch

try { $service->createKeyPair(); } catch (InvalidJwtKeyPairException $e) { exec('chmod 640 ' . escapeshellarg($secretKeyPath)); }

Prevention

When it happens

Trigger: chmod($secretKeyPath, 0640) returns false — the file is owned by another user (root created it, web server cannot chmod), the filesystem does not support chmod (some NFS/Samba mounts, Windows), or safe-mode/open_basedir restrictions apply.

Common situations: Running the JWT command with sudo then serving passbolt as www-data; NFS mounts with root_squash; containers running as a different UID than the key file owner.

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/d816d13672c70012. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/JwtAuthentication/src/Service/AccessToken/JwtKeyPairService.php:92

                throw new Exception('The JWT private key could not be created.');
            }
            $export = openssl_pkey_export_to_file($pk, $secretKeyPath);
            if ($export === false) {
                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

View on GitHub (pinned to 31c1bbc10f)