3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
12 namespace Civi\Crypto
;
14 use Civi\Crypto\Exception\CryptoException
;
18 * The "Crypto JWT" service supports a token format suitable for
19 * exchanging/transmitting with external consumers (e.g. web-browsers).
20 * It integrates with the CryptoRegistry (which is a source of valid signing keys).
22 * By default, tokens are signed and validated using any 'SIGN'ing keys
23 * (ie 'CIVICRM_SIGN_KEYS').
25 * @package Civi\Crypto
26 * @see https://jwt.io/
31 * @var \Civi\Crypto\CryptoRegistry
36 * @param array $payload
37 * List of JWT claims. See IANA link below.
38 * @param string $keyIdOrTag
39 * Choose a valid key from the CryptoRegistry using $keyIdOrTag.
41 * @throws \Civi\Crypto\Exception\CryptoException
43 * @see https://www.iana.org/assignments/jwt/jwt.xhtml
45 public function encode($payload, $keyIdOrTag = 'SIGN') {
46 $key = $this->getRegistry()->findKey($keyIdOrTag);
47 $alg = $this->suiteToAlg($key['suite']);
48 // Currently, registry only has symmetric keys in $key['key']. For public key-pairs, might need to change.
49 return JWT
::encode($payload, $key['key'], $alg, $key['id']);
53 * @param string $token
55 * @param string $keyTag
56 * Lookup valid keys from the CryptoRegistry using $keyTag.
58 * List of validated JWT claims.
59 * @throws CryptoException
61 public function decode($token, $keyTag = 'SIGN') {
62 $keyRows = $this->getRegistry()->findKeysByTag($keyTag);
64 // We want to call JWT::decode(), but there's a slight mismatch -- the
65 // registry contains whitelisted permutations of ($key,$alg), but
66 // JWT::decode() accepts all permutations ($keys x $algs).
68 // Grouping by alg will give proper granularity and also produces one
69 // call to JWT::decode() in typical usage.
71 // Defn: $keysByAlg[$alg][$keyId] === $keyData
73 foreach ($keyRows as $key) {
74 if ($alg = $this->suiteToAlg($key['suite'])) {
75 // Currently, registry only has symmetric keys in $key['key']. For public key-pairs, might need to change.
76 $keysByAlg[$alg][$key['id']] = $key['key'];
80 foreach ($keysByAlg as $alg => $keys) {
82 return (array) JWT
::decode($token, $keys, [$alg]);
84 catch (\UnexpectedValueException
$e) {
85 // Depending on the error, we might able to try other algos
87 !preg_match(';unable to lookup correct key;', $e->getMessage())
89 !preg_match(';Signature verification failed;', $e->getMessage())
91 // Keep our signature independent of the implementation.
92 throw new CryptoException(get_class($e) . ': ' . $e->getMessage());
97 throw new CryptoException('Signature verification failed');
101 * @param string $suite
102 * Ex: 'jwt-hs256', 'jwt-hs384'
104 * Ex: 'HS256', 'HS384'
106 protected static function suiteToAlg($suite) {
107 if (substr($suite, 0, 4) === 'jwt-') {
108 return strtoupper(substr($suite, 4));
116 * @return CryptoRegistry
118 protected function getRegistry(): CryptoRegistry
{
119 if ($this->registry
=== NULL) {
120 $this->registry
= \Civi
::service('crypto.registry');
122 return $this->registry
;