3 namespace BookStack\Auth\Access\Oidc;
5 use phpseclib3\Crypt\Common\PublicKey;
6 use phpseclib3\Crypt\PublicKeyLoader;
7 use phpseclib3\Crypt\RSA;
8 use phpseclib3\Math\BigInteger;
10 class OidcJwtSigningKey
18 * Can be created either from a JWK parameter array or local file path to load a certificate from.
20 * 'file:///var/www/cert.pem'
21 * ['kty' => 'RSA', 'alg' => 'RS256', 'n' => 'abc123...']
22 * @param array|string $jwkOrKeyPath
23 * @throws OidcInvalidKeyException
25 public function __construct($jwkOrKeyPath)
27 if (is_array($jwkOrKeyPath)) {
28 $this->loadFromJwkArray($jwkOrKeyPath);
29 } else if (is_string($jwkOrKeyPath) && strpos($jwkOrKeyPath, 'file://') === 0) {
30 $this->loadFromPath($jwkOrKeyPath);
32 throw new OidcInvalidKeyException('Unexpected type of key value provided');
37 * @throws OidcInvalidKeyException
39 protected function loadFromPath(string $path)
42 $this->key = PublicKeyLoader::load(
43 file_get_contents($path)
44 )->withPadding(RSA::SIGNATURE_PKCS1);
45 } catch (\Exception $exception) {
46 throw new OidcInvalidKeyException("Failed to load key from file path with error: {$exception->getMessage()}");
49 if (!($this->key instanceof RSA)) {
50 throw new OidcInvalidKeyException("Key loaded from file path is not an RSA key as expected");
55 * @throws OidcInvalidKeyException
57 protected function loadFromJwkArray(array $jwk)
59 if ($jwk['alg'] !== 'RS256') {
60 throw new OidcInvalidKeyException("Only RS256 keys are currently supported. Found key using {$jwk['alg']}");
63 if (empty($jwk['use'])) {
64 throw new OidcInvalidKeyException('A "use" parameter on the provided key is expected');
67 if ($jwk['use'] !== 'sig') {
68 throw new OidcInvalidKeyException("Only signature keys are currently supported. Found key for use {$jwk['use']}");
71 if (empty($jwk['e'])) {
72 throw new OidcInvalidKeyException('An "e" parameter on the provided key is expected');
75 if (empty($jwk['n'])) {
76 throw new OidcInvalidKeyException('A "n" parameter on the provided key is expected');
79 $n = strtr($jwk['n'] ?? '', '-_', '+/');
83 $this->key = PublicKeyLoader::load([
84 'e' => new BigInteger(base64_decode($jwk['e']), 256),
85 'n' => new BigInteger(base64_decode($n), 256),
86 ])->withPadding(RSA::SIGNATURE_PKCS1);
87 } catch (\Exception $exception) {
88 throw new OidcInvalidKeyException("Failed to load key from JWK parameters with error: {$exception->getMessage()}");
93 * Use this key to sign the given content and return the signature.
95 public function verify(string $content, string $signature): bool
97 return $this->key->verify($content, $signature);
101 * Convert the key to a PEM encoded key string.
103 public function toPem(): string
105 return $this->key->toString('PKCS8');