]> BookStack Code Mirror - bookstack/blob - app/Auth/Access/Oidc/OidcJwtSigningKey.php
Fixes padding issues of the sidebar's items
[bookstack] / app / Auth / Access / Oidc / OidcJwtSigningKey.php
1 <?php
2
3 namespace BookStack\Auth\Access\Oidc;
4
5 use phpseclib3\Crypt\Common\PublicKey;
6 use phpseclib3\Crypt\PublicKeyLoader;
7 use phpseclib3\Crypt\RSA;
8 use phpseclib3\Math\BigInteger;
9
10 class OidcJwtSigningKey
11 {
12     /**
13      * @var PublicKey
14      */
15     protected $key;
16
17     /**
18      * Can be created either from a JWK parameter array or local file path to load a certificate from.
19      * Examples:
20      * 'file:///var/www/cert.pem'
21      * ['kty' => 'RSA', 'alg' => 'RS256', 'n' => 'abc123...'].
22      *
23      * @param array|string $jwkOrKeyPath
24      *
25      * @throws OidcInvalidKeyException
26      */
27     public function __construct($jwkOrKeyPath)
28     {
29         if (is_array($jwkOrKeyPath)) {
30             $this->loadFromJwkArray($jwkOrKeyPath);
31         } elseif (is_string($jwkOrKeyPath) && strpos($jwkOrKeyPath, 'file://') === 0) {
32             $this->loadFromPath($jwkOrKeyPath);
33         } else {
34             throw new OidcInvalidKeyException('Unexpected type of key value provided');
35         }
36     }
37
38     /**
39      * @throws OidcInvalidKeyException
40      */
41     protected function loadFromPath(string $path)
42     {
43         try {
44             $this->key = PublicKeyLoader::load(
45                 file_get_contents($path)
46             )->withPadding(RSA::SIGNATURE_PKCS1);
47         } catch (\Exception $exception) {
48             throw new OidcInvalidKeyException("Failed to load key from file path with error: {$exception->getMessage()}");
49         }
50
51         if (!($this->key instanceof RSA)) {
52             throw new OidcInvalidKeyException('Key loaded from file path is not an RSA key as expected');
53         }
54     }
55
56     /**
57      * @throws OidcInvalidKeyException
58      */
59     protected function loadFromJwkArray(array $jwk)
60     {
61         if ($jwk['alg'] !== 'RS256') {
62             throw new OidcInvalidKeyException("Only RS256 keys are currently supported. Found key using {$jwk['alg']}");
63         }
64
65         if (empty($jwk['use'])) {
66             throw new OidcInvalidKeyException('A "use" parameter on the provided key is expected');
67         }
68
69         if ($jwk['use'] !== 'sig') {
70             throw new OidcInvalidKeyException("Only signature keys are currently supported. Found key for use {$jwk['use']}");
71         }
72
73         if (empty($jwk['e'])) {
74             throw new OidcInvalidKeyException('An "e" parameter on the provided key is expected');
75         }
76
77         if (empty($jwk['n'])) {
78             throw new OidcInvalidKeyException('A "n" parameter on the provided key is expected');
79         }
80
81         $n = strtr($jwk['n'] ?? '', '-_', '+/');
82
83         try {
84             /** @var RSA $key */
85             $this->key = PublicKeyLoader::load([
86                 'e' => new BigInteger(base64_decode($jwk['e']), 256),
87                 'n' => new BigInteger(base64_decode($n), 256),
88             ])->withPadding(RSA::SIGNATURE_PKCS1);
89         } catch (\Exception $exception) {
90             throw new OidcInvalidKeyException("Failed to load key from JWK parameters with error: {$exception->getMessage()}");
91         }
92     }
93
94     /**
95      * Use this key to sign the given content and return the signature.
96      */
97     public function verify(string $content, string $signature): bool
98     {
99         return $this->key->verify($content, $signature);
100     }
101
102     /**
103      * Convert the key to a PEM encoded key string.
104      */
105     public function toPem(): string
106     {
107         return $this->key->toString('PKCS8');
108     }
109 }