From 1351500247ff73e60006f4120304e382b0168925 Mon Sep 17 00:00:00 2001 From: fernando Date: Wed, 17 Jul 2024 16:15:01 +1000 Subject: [PATCH 1/4] phpseclib: v2 or 3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a687e7b9..d2e06c5f 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "php": ">=5.6.0", "ext-json": "*", "guzzlehttp/guzzle": "^6.2.1 || ^7.0.1", - "phpseclib/phpseclib": "^2.0.11", + "phpseclib/phpseclib": "^2.0.11 || ^3.0.0", "gree/jose": "^2.2.1" }, "autoload": { From 80579b12462f1909207155ccb71983f6cf5dccca Mon Sep 17 00:00:00 2001 From: fernando Date: Thu, 18 Jul 2024 09:16:10 +1000 Subject: [PATCH 2/4] feat: only v3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d2e06c5f..26cea204 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "php": ">=5.6.0", "ext-json": "*", "guzzlehttp/guzzle": "^6.2.1 || ^7.0.1", - "phpseclib/phpseclib": "^2.0.11 || ^3.0.0", + "phpseclib/phpseclib": "^3.0.0", "gree/jose": "^2.2.1" }, "autoload": { From 7c5a492bd6bc0480ca138f186ef9fe165380ebac Mon Sep 17 00:00:00 2001 From: fernando Date: Thu, 18 Jul 2024 12:00:29 +1000 Subject: [PATCH 3/4] feat: only v3 --- composer.json | 12 +- .../Util/HyperwalletEncryption.php | 32 ++-- src/Services/Jose/Exception.php | 8 + .../Jose/Exception/DecryptionFailed.php | 10 ++ .../Jose/Exception/EncryptionFailed.php | 10 ++ src/Services/Jose/Exception/InvalidFormat.php | 10 ++ .../Jose/Exception/UnexpectedAlgorithm.php | 10 ++ .../Jose/Exception/VerificationFailed.php | 10 ++ src/{JWE => Services/Jose/JOSE_JWE.php} | 59 ++++--- src/Services/Jose/JOSE_JWK.php | 93 +++++++++++ src/Services/Jose/JOSE_JWS.php | 158 ++++++++++++++++++ src/Services/Jose/JOSE_JWT.php | 108 ++++++++++++ src/Services/Jose/URLSafeBase64.php | 158 ++++++++++++++++++ 13 files changed, 631 insertions(+), 47 deletions(-) create mode 100644 src/Services/Jose/Exception.php create mode 100644 src/Services/Jose/Exception/DecryptionFailed.php create mode 100644 src/Services/Jose/Exception/EncryptionFailed.php create mode 100644 src/Services/Jose/Exception/InvalidFormat.php create mode 100644 src/Services/Jose/Exception/UnexpectedAlgorithm.php create mode 100644 src/Services/Jose/Exception/VerificationFailed.php rename src/{JWE => Services/Jose/JOSE_JWE.php} (85%) create mode 100644 src/Services/Jose/JOSE_JWK.php create mode 100644 src/Services/Jose/JOSE_JWS.php create mode 100644 src/Services/Jose/JOSE_JWT.php create mode 100644 src/Services/Jose/URLSafeBase64.php diff --git a/composer.json b/composer.json index 26cea204..02779f54 100644 --- a/composer.json +++ b/composer.json @@ -23,11 +23,14 @@ "php": ">=5.6.0", "ext-json": "*", "guzzlehttp/guzzle": "^6.2.1 || ^7.0.1", - "phpseclib/phpseclib": "^3.0.0", - "gree/jose": "^2.2.1" + "phpseclib/phpseclib": "^3.0.0" }, "autoload": { - "psr-4": { "Hyperwallet\\": "src/Hyperwallet", "ComposerScript\\" : "src/ComposerScript" } + "psr-4": { + "Hyperwallet\\": "src/Hyperwallet", + "ComposerScript\\": "src/ComposerScript", + "Services\\": "src/Services" + } }, "autoload-dev" : { "psr-4": { "Hyperwallet\\Tests\\" : "tests/Hyperwallet/Tests", "ComposerScript\\" : "src/ComposerScript" } @@ -36,8 +39,5 @@ "phpunit/phpunit": "^5.7 || ^7.0.0 || ^9.0", "phake/phake": "^2.3 || ^4.2", "php-coveralls/php-coveralls": "^2.5" - }, - "scripts": { - "post-install-cmd": "ComposerScript\\RsaOaep256AlgorithmInstaller::install" } } diff --git a/src/Hyperwallet/Util/HyperwalletEncryption.php b/src/Hyperwallet/Util/HyperwalletEncryption.php index 3cc5cbe6..c8f658ae 100644 --- a/src/Hyperwallet/Util/HyperwalletEncryption.php +++ b/src/Hyperwallet/Util/HyperwalletEncryption.php @@ -1,22 +1,15 @@ signAlgorithm = $signAlgorithm; $this->encryptionMethod = $encryptionMethod; $this->jwsExpirationMinutes = $jwsExpirationMinutes; - file_put_contents($this->getVendorPath() . "/gree/jose/src/JOSE/JWE.php", file_get_contents(__DIR__ . "/../../JWE")); } /** @@ -113,6 +105,8 @@ public function __construct($clientPrivateKeySetLocation, $hyperwalletKeySetLoca */ public function encrypt($body) { $privateJwsKey = $this->getPrivateJwsKey(); + var_dump($privateJwsKey); + exit; $jws = new JOSE_JWS(new JOSE_JWT($body)); $jws->header['exp'] = $this->getSignatureExpirationTime(); $jws->header['kid'] = $this->jwsKid; @@ -202,6 +196,7 @@ private function getPublicJwsKey() { * @return RSA */ private function getPrivateKey($privateKeyData) { + var_dump($privateKeyData); $n = $this->keyParamToBigInteger($privateKeyData['n']); $e = $this->keyParamToBigInteger($privateKeyData['e']); $d = $this->keyParamToBigInteger($privateKeyData['d']); @@ -237,7 +232,10 @@ private function getPrivateKey($privateKeyData) { * @return BigInteger */ private function keyParamToBigInteger($param) { - return new BigInteger('0x' . bin2hex(JOSE_URLSafeBase64::decode($param)), 16); + var_dump(URLSafeBase64::decode($param)); + exit; + + return new BigInteger('0x' . bin2hex(URLSafeBase64::decode($param)), 16); } /** diff --git a/src/Services/Jose/Exception.php b/src/Services/Jose/Exception.php new file mode 100644 index 00000000..90271d28 --- /dev/null +++ b/src/Services/Jose/Exception.php @@ -0,0 +1,8 @@ +header['typ']); } + /** + * @throws EncryptionFailed + * @throws UnexpectedAlgorithm + * @throws DecryptionFailed + */ function encrypt($public_key_or_secret, $algorithm = 'RSA1_5', $encryption_method = 'A128CBC-HS256') { $this->header['alg'] = $algorithm; $this->header['enc'] = $encryption_method; @@ -44,6 +54,10 @@ function encrypt($public_key_or_secret, $algorithm = 'RSA1_5', $encryption_metho return $this; } + /** + * @throws UnexpectedAlgorithm + * @throws DecryptionFailed + */ function decrypt($private_key_or_secret) { $this->decryptContentEncryptionKey($private_key_or_secret); $this->deriveEncryptionAndMacKeys(); @@ -79,13 +93,13 @@ private function cipher() { switch ($this->header['enc']) { case 'A128GCM': case 'A256GCM': - throw new JOSE_Exception_UnexpectedAlgorithm('Algorithm not supported'); + throw new UnexpectedAlgorithm('Algorithm not supported'); case 'A128CBC-HS256': case 'A256CBC-HS512': $cipher = new AES(AES::MODE_CBC); break; default: - throw new JOSE_Exception_UnexpectedAlgorithm('Unknown algorithm'); + throw new UnexpectedAlgorithm('Unknown algorithm'); } switch ($this->header['enc']) { case 'A128GCM': @@ -97,7 +111,7 @@ private function cipher() { $cipher->setBlockLength(256); break; default: - throw new JOSE_Exception_UnexpectedAlgorithm('Unknown algorithm'); + throw new UnexpectedAlgorithm('Unknown algorithm'); } return $cipher; } @@ -110,15 +124,14 @@ private function generateIv() { switch ($this->header['enc']) { case 'A128GCM': case 'A128CBC-HS256': - case 'A256CBC-HS512': $this->iv = $this->generateRandomBytes(128 / 8); break; case 'A256GCM': - //case 'A256CBC-HS512': + case 'A256CBC-HS512': $this->iv = $this->generateRandomBytes(256 / 8); break; default: - throw new JOSE_Exception_UnexpectedAlgorithm('Unknown algorithm'); + throw new UnexpectedAlgorithm('Unknown algorithm'); } } @@ -136,7 +149,7 @@ private function generateContentEncryptionKey($public_key_or_secret) { $this->content_encryption_key = $this->generateRandomBytes(512 / 8); break; default: - throw new JOSE_Exception_UnexpectedAlgorithm('Unknown algorithm'); + throw new UnexpectedAlgorithm('Unknown algorithm'); } } } @@ -147,7 +160,6 @@ private function encryptContentEncryptionKey($public_key_or_secret) { $rsa = $this->rsa($public_key_or_secret, RSA::ENCRYPTION_PKCS1); $this->jwe_encrypted_key = $rsa->encrypt($this->content_encryption_key); break; - case 'RSA-OAEP-256': case 'RSA-OAEP': $rsa = $this->rsa($public_key_or_secret, RSA::ENCRYPTION_OAEP); $this->jwe_encrypted_key = $rsa->encrypt($this->content_encryption_key); @@ -160,12 +172,12 @@ private function encryptContentEncryptionKey($public_key_or_secret) { case 'ECDH-ES': case 'ECDH-ES+A128KW': case 'ECDH-ES+A256KW': - throw new JOSE_Exception_UnexpectedAlgorithm('Algorithm not supported'); + throw new UnexpectedAlgorithm('Algorithm not supported'); default: - throw new JOSE_Exception_UnexpectedAlgorithm('Unknown algorithm'); + throw new UnexpectedAlgorithm('Unknown algorithm'); } if (!$this->jwe_encrypted_key) { - throw new JOSE_Exception_EncryptionFailed('Master key encryption failed'); + throw new EncryptionFailed('Master key encryption failed'); } } @@ -177,7 +189,6 @@ private function decryptContentEncryptionKey($private_key_or_secret) { $rsa = $this->rsa($private_key_or_secret, RSA::ENCRYPTION_PKCS1); $this->content_encryption_key = $rsa->decrypt($this->jwe_encrypted_key); break; - case 'RSA-OAEP-256': case 'RSA-OAEP': $rsa = $this->rsa($private_key_or_secret, RSA::ENCRYPTION_OAEP); $this->content_encryption_key = $rsa->decrypt($this->jwe_encrypted_key); @@ -190,9 +201,9 @@ private function decryptContentEncryptionKey($private_key_or_secret) { case 'ECDH-ES': case 'ECDH-ES+A128KW': case 'ECDH-ES+A256KW': - throw new JOSE_Exception_UnexpectedAlgorithm('Algorithm not supported'); + throw new UnexpectedAlgorithm('Algorithm not supported'); default: - throw new JOSE_Exception_UnexpectedAlgorithm('Unknown algorithm'); + throw new UnexpectedAlgorithm('Unknown algorithm'); } if (!$this->content_encryption_key) { # NOTE: @@ -217,10 +228,10 @@ private function deriveEncryptionAndMacKeys() { $this->deriveEncryptionAndMacKeysCBC(512); break; default: - throw new JOSE_Exception_UnexpectedAlgorithm('Unknown algorithm'); + throw new UnexpectedAlgorithm('Unknown algorithm'); } if (!$this->encryption_key || !$this->mac_key) { - throw new JOSE_Exception_DecryptionFailed('Encryption/Mac key derivation failed'); + throw new DecryptionFailed('Encryption/Mac key derivation failed'); } } @@ -235,7 +246,7 @@ private function encryptCipherText() { $cipher->setIV($this->iv); $this->cipher_text = $cipher->encrypt($this->plain_text); if (!$this->cipher_text) { - throw new JOSE_Exception_DecryptionFailed('Payload encryption failed'); + throw new DecryptionFailed('Payload encryption failed'); } } @@ -245,7 +256,7 @@ private function decryptCipherText() { $cipher->setIV($this->iv); $this->plain_text = $cipher->decrypt($this->cipher_text); if (!$this->plain_text) { - throw new JOSE_Exception_DecryptionFailed('Payload decryption failed'); + throw new DecryptionFailed('Payload decryption failed'); } } @@ -257,13 +268,13 @@ private function calculateAuthenticationTag($use_raw = false) { switch ($this->header['enc']) { case 'A128GCM': case 'A256GCM': - throw new JOSE_Exception_UnexpectedAlgorithm('Algorithm not supported'); + throw new UnexpectedAlgorithm('Algorithm not supported'); case 'A128CBC-HS256': return $this->calculateAuthenticationTagCBC(256); case 'A256CBC-HS512': return $this->calculateAuthenticationTagCBC(512); default: - throw new JOSE_Exception_UnexpectedAlgorithm('Unknown algorithm'); + throw new UnexpectedAlgorithm('Unknown algorithm'); } } @@ -290,7 +301,7 @@ private function checkAuthenticationTag() { if (hash_equals($this->authentication_tag, $this->calculateAuthenticationTag())) { return true; } else { - throw new JOSE_Exception_UnexpectedAlgorithm('Invalid authentication tag'); + throw new UnexpectedAlgorithm('Invalid authentication tag'); } } } diff --git a/src/Services/Jose/JOSE_JWK.php b/src/Services/Jose/JOSE_JWK.php new file mode 100644 index 00000000..e9914d0c --- /dev/null +++ b/src/Services/Jose/JOSE_JWK.php @@ -0,0 +1,93 @@ +components = $components; + if (!array_key_exists('kid', $this->components)) { + $this->components['kid'] = $this->thumbprint(); + } + } + + function toKey() { + switch ($this->components['kty']) { + case 'RSA': + $rsa = new RSA(); + $n = new BigInteger('0x' . bin2hex(JOSE_URLSafeBase64::decode($this->components['n'])), 16); + $e = new BigInteger('0x' . bin2hex(JOSE_URLSafeBase64::decode($this->components['e'])), 16); + if (array_key_exists('d', $this->components)) { + throw new UnexpectedAlgorithm('RSA private key isn\'t supported'); + } else { + $pem_string = $rsa->_convertPublicKey($n, $e); + } + $rsa->loadKey($pem_string); + return $rsa; + default: + throw new UnexpectedAlgorithm('Unknown key type'); + } + } + + function thumbprint($hash_algorithm = 'sha256') { + $hash = new Hash($hash_algorithm); + return JOSE_URLSafeBase64::encode( + $hash->hash( + json_encode($this->normalized()) + ) + ); + } + + private function normalized() { + switch ($this->components['kty']) { + case 'RSA': + return array( + 'e' => $this->components['e'], + 'kty' => $this->components['kty'], + 'n' => $this->components['n'] + ); + default: + throw new UnexpectedAlgorithm('Unknown key type'); + } + } + + function toString() { + return json_encode($this->components); + } + function __toString() { + return $this->toString(); + } + + static function encode($key, $extra_components = array()) { + switch(get_class($key)) { + case 'phpseclib\Crypt\RSA': + $components = array( + 'kty' => 'RSA', + 'e' => JOSE_URLSafeBase64::encode($key->publicExponent->toBytes()), + 'n' => JOSE_URLSafeBase64::encode($key->modulus->toBytes()) + ); + if ($key->exponent != $key->publicExponent) { + $components = array_merge($components, array( + 'd' => JOSE_URLSafeBase64::encode($key->exponent->toBytes()) + )); + } + return new self(array_merge($components, $extra_components)); + default: + throw new UnexpectedAlgorithm('Unknown key type'); + } + } + + static function decode($components) { + $jwk = new self($components); + return $jwk->toKey(); + } +} diff --git a/src/Services/Jose/JOSE_JWS.php b/src/Services/Jose/JOSE_JWS.php new file mode 100644 index 00000000..c77c2fce --- /dev/null +++ b/src/Services/Jose/JOSE_JWS.php @@ -0,0 +1,158 @@ +header = $jwt->header; + $this->claims = $jwt->claims; + $this->signature = $jwt->signature; + $this->raw = $jwt->raw; + } + + function toJson($syntax = 'flattened') { + if ($syntax == 'flattened') { + $components = array( + 'protected' => $this->compact((object) $this->header), + 'payload' => $this->compact((object) $this->claims), + 'signature' => $this->compact($this->signature) + ); + } else { + $components = array( + 'payload' => $this->compact((object) $this->claims), + 'signatures' => array( + 'protected' => $this->compact((object) $this->header), + 'signature' => $this->compact($this->signature) + ) + ); + } + return json_encode($components); + } + + function sign($private_key_or_secret, $algorithm = 'HS256') { + $this->header['alg'] = $algorithm; + if ( + $private_key_or_secret instanceof JOSE_JWK && + !array_key_exists('kid', $this->header) && + array_key_exists('kid', $private_key_or_secret->components) + ) { + $this->header['kid'] = $private_key_or_secret->components['kid']; + } + $this->signature = $this->_sign($private_key_or_secret); + if (!$this->signature) { + throw new Exception('Signing failed because of unknown reason'); + } + return $this; + } + + function verify($public_key_or_secret, $alg = null) { + if ($this->_verify($public_key_or_secret, $alg)) { + return $this; + } else { + throw new VerificationFailed('Signature verification failed'); + } + } + + private function rsa($public_or_private_key, $padding_mode) { + if ($public_or_private_key instanceof JOSE_JWK) { + $rsa = $public_or_private_key->toKey(); + } else if ($public_or_private_key instanceof RSA) { + $rsa = $public_or_private_key; + } else { + $rsa = new RSA(); + $rsa->loadKey($public_or_private_key); + } + $rsa->setHash($this->digest()); + $rsa->setMGFHash($this->digest()); + $rsa->setSignatureMode($padding_mode); + return $rsa; + } + + private function digest() { + switch ($this->header['alg']) { + case 'HS256': + case 'RS256': + case 'ES256': + case 'PS256': + return 'sha256'; + case 'HS384': + case 'RS384': + case 'ES384': + case 'PS384': + return 'sha384'; + case 'HS512': + case 'RS512': + case 'ES512': + case 'PS512': + return 'sha512'; + default: + throw new UnexpectedAlgorithm('Unknown algorithm'); + } + } + + private function _sign($private_key_or_secret) { + $signature_base_string = implode('.', array( + $this->compact((object) $this->header), + $this->compact((object) $this->claims) + )); + switch ($this->header['alg']) { + case 'HS256': + case 'HS384': + case 'HS512': + return hash_hmac($this->digest(), $signature_base_string, $private_key_or_secret, true); + case 'RS256': + case 'RS384': + case 'RS512': + return $this->rsa($private_key_or_secret, RSA::SIGNATURE_PKCS1)->sign($signature_base_string); + case 'ES256': + case 'ES384': + case 'ES512': + throw new UnexpectedAlgorithm('Algorithm not supported'); + case 'PS256': + case 'PS384': + case 'PS512': + return $this->rsa($private_key_or_secret, RSA::SIGNATURE_PSS)->sign($signature_base_string); + default: + throw new UnexpectedAlgorithm('Unknown algorithm'); + } + } + + private function _verify($public_key_or_secret, $expected_alg = null) { + $segments = explode('.', $this->raw); + $signature_base_string = implode('.', array($segments[0], $segments[1])); + if (!$expected_alg) { + $expected_alg = $this->header['alg']; + $using_autodetected_alg = true; + } + switch ($expected_alg) { + case 'HS256': + case 'HS384': + case 'HS512': + if (isset($using_autodetected_alg)) { + throw new UnexpectedAlgorithm( + 'HMAC algs MUST be explicitly specified as $expected_alg' + ); + } + $hmac_hash = hash_hmac($this->digest(), $signature_base_string, $public_key_or_secret, true); + return hash_equals($this->signature, $hmac_hash); + case 'RS256': + case 'RS384': + case 'RS512': + return $this->rsa($public_key_or_secret, RSA::SIGNATURE_PKCS1)->verify($signature_base_string, $this->signature); + case 'ES256': + case 'ES384': + case 'ES512': + throw new UnexpectedAlgorithm('Algorithm not supported'); + case 'PS256': + case 'PS384': + case 'PS512': + return $this->rsa($public_key_or_secret, RSA::SIGNATURE_PSS)->verify($signature_base_string, $this->signature); + default: + throw new UnexpectedAlgorithm('Unknown algorithm'); + } + } +} diff --git a/src/Services/Jose/JOSE_JWT.php b/src/Services/Jose/JOSE_JWT.php new file mode 100644 index 00000000..9a3acb69 --- /dev/null +++ b/src/Services/Jose/JOSE_JWT.php @@ -0,0 +1,108 @@ + 'JWT', + 'alg' => 'none' + ); + var $claims = array(); + var $signature = ''; + var $raw; + + function __construct($claims = array()) { + $this->claims = $claims; + } + + function toString() { + return implode('.', array( + $this->compact((object) $this->header), + $this->compact((object) $this->claims), + $this->compact($this->signature) + )); + } + + function __toString() { + return $this->toString(); + } + + function sign($private_key_or_secret, $algorithm = 'HS256') { + $jws = $this->toJWS(); + $jws->sign($private_key_or_secret, $algorithm); + return $jws; + } + + function verify($public_key_or_secret, $alg = null) { + $jws = $this->toJWS(); + $jws->verify($public_key_or_secret, $alg); + return $jws; + } + + function encrypt($public_key_or_secret, $algorithm = 'RSA1_5', $encryption_method = 'A128CBC-HS256') { + $jwe = new JOSE_JWE($this); + $jwe->encrypt($public_key_or_secret, $algorithm, $encryption_method); + return $jwe; + } + + static function encode($claims) { + return new self($claims); + } + + static function decode($jwt_string) { + $segments = explode('.', $jwt_string); + var_dump($jwt_string); + exit; + switch (count($segments)) { + case 3: + $jwt = new self(); + $jwt->raw = $jwt_string; + $jwt->header = (array) $jwt->extract($segments[0]); + $jwt->claims = (array) $jwt->extract($segments[1]); + $jwt->signature = $jwt->extract($segments[2], 'as_binary'); + return $jwt; + case 5: + $jwe = new JOSE_JWE($jwt_string); + $jwe->auth_data = $segments[0]; + $jwe->header = (array) $jwe->extract($segments[0]); + $jwe->jwe_encrypted_key = $jwe->extract($segments[1], 'as_binary'); + $jwe->iv = $jwe->extract($segments[2], 'as_binary'); + $jwe->cipher_text = $jwe->extract($segments[3], 'as_binary'); + $jwe->authentication_tag = $jwe->extract($segments[4], 'as_binary'); + return $jwe; + default: + throw new InvalidFormat('JWT should have exact 3 or 5 segments'); + } + } + + protected function compact($segment) { + if (is_object($segment)) { + $stringified = str_replace("\/", "/", json_encode($segment)); + } else { + $stringified = $segment; + } + if ($stringified === 'null' && $segment !== null) { // shouldn't happen, just for safe + throw new InvalidFormat('Compact seriarization failed'); + } + return URLSafeBase64::encode($stringified); + } + + protected function extract($segment, $as_binary = false) { + $stringified = URLSafeBase64::decode($segment); + if ($as_binary) { + $extracted = $stringified; + } else { + $extracted = json_decode($stringified); + if ($stringified !== 'null' && $extracted === null) { + throw new InvalidFormat('Compact de-serialization failed'); + } + } + return $extracted; + } + + private function toJWS() { + return new JOSE_JWS($this); + } +} diff --git a/src/Services/Jose/URLSafeBase64.php b/src/Services/Jose/URLSafeBase64.php new file mode 100644 index 00000000..aef6237c --- /dev/null +++ b/src/Services/Jose/URLSafeBase64.php @@ -0,0 +1,158 @@ +header = $jwt->header; + $this->claims = $jwt->claims; + $this->signature = $jwt->signature; + $this->raw = $jwt->raw; + } + + function toJson($syntax = 'flattened') { + if ($syntax == 'flattened') { + $components = array( + 'protected' => $this->compact((object) $this->header), + 'payload' => $this->compact((object) $this->claims), + 'signature' => $this->compact($this->signature) + ); + } else { + $components = array( + 'payload' => $this->compact((object) $this->claims), + 'signatures' => array( + 'protected' => $this->compact((object) $this->header), + 'signature' => $this->compact($this->signature) + ) + ); + } + return json_encode($components); + } + + function sign($private_key_or_secret, $algorithm = 'HS256') { + $this->header['alg'] = $algorithm; + if ( + $private_key_or_secret instanceof JOSE_JWK && + !array_key_exists('kid', $this->header) && + array_key_exists('kid', $private_key_or_secret->components) + ) { + $this->header['kid'] = $private_key_or_secret->components['kid']; + } + $this->signature = $this->_sign($private_key_or_secret); + if (!$this->signature) { + throw new Exception('Signing failed because of unknown reason'); + } + return $this; + } + + function verify($public_key_or_secret, $alg = null) { + if ($this->_verify($public_key_or_secret, $alg)) { + return $this; + } else { + throw new VerificationFailed('Signature verification failed'); + } + } + + private function rsa($public_or_private_key, $padding_mode) { + if ($public_or_private_key instanceof JOSE_JWK) { + $rsa = $public_or_private_key->toKey(); + } else if ($public_or_private_key instanceof RSA) { + $rsa = $public_or_private_key; + } else { + $rsa = new RSA(); + $rsa->loadKey($public_or_private_key); + } + $rsa->setHash($this->digest()); + $rsa->setMGFHash($this->digest()); + $rsa->setSignatureMode($padding_mode); + return $rsa; + } + + private function digest() { + switch ($this->header['alg']) { + case 'HS256': + case 'RS256': + case 'ES256': + case 'PS256': + return 'sha256'; + case 'HS384': + case 'RS384': + case 'ES384': + case 'PS384': + return 'sha384'; + case 'HS512': + case 'RS512': + case 'ES512': + case 'PS512': + return 'sha512'; + default: + throw new UnexpectedAlgorithm('Unknown algorithm'); + } + } + + private function _sign($private_key_or_secret) { + $signature_base_string = implode('.', array( + $this->compact((object) $this->header), + $this->compact((object) $this->claims) + )); + switch ($this->header['alg']) { + case 'HS256': + case 'HS384': + case 'HS512': + return hash_hmac($this->digest(), $signature_base_string, $private_key_or_secret, true); + case 'RS256': + case 'RS384': + case 'RS512': + return $this->rsa($private_key_or_secret, RSA::SIGNATURE_PKCS1)->sign($signature_base_string); + case 'ES256': + case 'ES384': + case 'ES512': + throw new UnexpectedAlgorithm('Algorithm not supported'); + case 'PS256': + case 'PS384': + case 'PS512': + return $this->rsa($private_key_or_secret, RSA::SIGNATURE_PSS)->sign($signature_base_string); + default: + throw new UnexpectedAlgorithm('Unknown algorithm'); + } + } + + private function _verify($public_key_or_secret, $expected_alg = null) { + $segments = explode('.', $this->raw); + $signature_base_string = implode('.', array($segments[0], $segments[1])); + if (!$expected_alg) { + $expected_alg = $this->header['alg']; + $using_autodetected_alg = true; + } + switch ($expected_alg) { + case 'HS256': + case 'HS384': + case 'HS512': + if (isset($using_autodetected_alg)) { + throw new UnexpectedAlgorithm( + 'HMAC algs MUST be explicitly specified as $expected_alg' + ); + } + $hmac_hash = hash_hmac($this->digest(), $signature_base_string, $public_key_or_secret, true); + return hash_equals($this->signature, $hmac_hash); + case 'RS256': + case 'RS384': + case 'RS512': + return $this->rsa($public_key_or_secret, RSA::SIGNATURE_PKCS1)->verify($signature_base_string, $this->signature); + case 'ES256': + case 'ES384': + case 'ES512': + throw new UnexpectedAlgorithm('Algorithm not supported'); + case 'PS256': + case 'PS384': + case 'PS512': + return $this->rsa($public_key_or_secret, RSA::SIGNATURE_PSS)->verify($signature_base_string, $this->signature); + default: + throw new UnexpectedAlgorithm('Unknown algorithm'); + } + } +} From acf57c6cc37d4965e1e983a288bc25b44a38fff9 Mon Sep 17 00:00:00 2001 From: fernando Date: Thu, 18 Jul 2024 14:52:11 +1000 Subject: [PATCH 4/4] feat: phpcbf only v3 bringing gree/jose package within HW sdk --- .../Util/HyperwalletEncryption.php | 120 ++++++------- src/Services/Jose/JOSE_JWE.php | 12 +- src/Services/Jose/JOSE_JWK.php | 63 ++++--- src/Services/Jose/JOSE_JWS.php | 6 +- src/Services/Jose/JOSE_JWT.php | 2 - src/Services/Jose/URLSafeBase64.php | 158 +----------------- 6 files changed, 120 insertions(+), 241 deletions(-) diff --git a/src/Hyperwallet/Util/HyperwalletEncryption.php b/src/Hyperwallet/Util/HyperwalletEncryption.php index c8f658ae..f0ed2ee5 100644 --- a/src/Hyperwallet/Util/HyperwalletEncryption.php +++ b/src/Hyperwallet/Util/HyperwalletEncryption.php @@ -1,4 +1,5 @@ clientPrivateKeySetLocation = $clientPrivateKeySetLocation; $this->hyperwalletKeySetLocation = $hyperwalletKeySetLocation; $this->encryptionAlgorithm = $encryptionAlgorithm; @@ -103,10 +110,9 @@ public function __construct($clientPrivateKeySetLocation, $hyperwalletKeySetLoca * * @throws HyperwalletException */ - public function encrypt($body) { + public function encrypt($body) + { $privateJwsKey = $this->getPrivateJwsKey(); - var_dump($privateJwsKey); - exit; $jws = new JOSE_JWS(new JOSE_JWT($body)); $jws->header['exp'] = $this->getSignatureExpirationTime(); $jws->header['kid'] = $this->jwsKid; @@ -127,7 +133,8 @@ public function encrypt($body) { * * @throws HyperwalletException */ - public function decrypt($body) { + public function decrypt($body) + { $privateJweKey = $this->getPrivateJweKey(); $jwe = JOSE_JWT::decode($body); $decryptedBody = $jwe->decrypt($privateJweKey); @@ -146,7 +153,8 @@ public function decrypt($body) { * * @throws HyperwalletException */ - private function getPrivateJwsKey() { + private function getPrivateJwsKey() + { $privateKeyData = $this->getJwk($this->clientPrivateKeySetLocation, $this->signAlgorithm); $this->jwsKid = $privateKeyData['kid']; return $this->getPrivateKey($privateKeyData); @@ -159,7 +167,8 @@ private function getPrivateJwsKey() { * * @throws HyperwalletException */ - private function getPublicJweKey() { + private function getPublicJweKey() + { $publicKeyData = $this->getJwk($this->hyperwalletKeySetLocation, $this->encryptionAlgorithm); $this->jweKid = $publicKeyData['kid']; return $this->getPublicKey($this->convertPrivateKeyToPublic($publicKeyData)); @@ -172,7 +181,8 @@ private function getPublicJweKey() { * * @throws HyperwalletException */ - private function getPrivateJweKey() { + private function getPrivateJweKey() + { $privateKeyData = $this->getJwk($this->clientPrivateKeySetLocation, $this->encryptionAlgorithm); return $this->getPrivateKey($privateKeyData); } @@ -184,7 +194,8 @@ private function getPrivateJweKey() { * * @throws HyperwalletException */ - private function getPublicJwsKey() { + private function getPublicJwsKey() + { $publicKeyData = $this->getJwk($this->hyperwalletKeySetLocation, $this->signAlgorithm); return $this->getPublicKey($this->convertPrivateKeyToPublic($publicKeyData)); } @@ -195,32 +206,24 @@ private function getPublicJwsKey() { * @param array $privateKeyData The JWK key data * @return RSA */ - private function getPrivateKey($privateKeyData) { - var_dump($privateKeyData); - $n = $this->keyParamToBigInteger($privateKeyData['n']); - $e = $this->keyParamToBigInteger($privateKeyData['e']); - $d = $this->keyParamToBigInteger($privateKeyData['d']); - $p = $this->keyParamToBigInteger($privateKeyData['p']); - $q = $this->keyParamToBigInteger($privateKeyData['q']); - $qi = $this->keyParamToBigInteger($privateKeyData['qi']); - $dp = $this->keyParamToBigInteger($privateKeyData['dp']); - $dq = $this->keyParamToBigInteger($privateKeyData['dq']); - $primes = array($p, $q); - $exponents = array($dp, $dq); - $coefficients = array($qi, $qi); - array_unshift($primes, "phoney"); - unset($primes[0]); - array_unshift($exponents, "phoney"); - unset($exponents[0]); - array_unshift($coefficients, "phoney"); - unset($coefficients[0]); + private function getPrivateKey($privateKeyData) + { + $pemData = RSA::load([ + 'e' => $this->keyParamToBigInteger($privateKeyData['e']), + 'n' => $this->keyParamToBigInteger($privateKeyData['n']), + 'd' => $this->keyParamToBigInteger($privateKeyData['d']), + 'p' => $this->keyParamToBigInteger($privateKeyData['p']), + 'q' => $this->keyParamToBigInteger($privateKeyData['q']), + 'dp' => $this->keyParamToBigInteger($privateKeyData['dp']), + 'dq' => $this->keyParamToBigInteger($privateKeyData['dq']), + 'qi' => $this->keyParamToBigInteger($privateKeyData['qi']), + ]); + + $privateKey = RSA::loadPrivateKey($pemData->toString('PKCS1')); - $pemData = (new RSA())->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients); - $privateKey = new RSA(); - $privateKey->loadKey($pemData); if ($privateKeyData['alg'] == 'RSA-OAEP-256') { - $privateKey->setHash('sha256'); - $privateKey->setMGFHash('sha256'); +// $privateKey->setHash('sha256'); +// $privateKey->setMGFHash('sha256'); } return $privateKey; } @@ -231,10 +234,8 @@ private function getPrivateKey($privateKeyData) { * @param string $param base 64 encoded string * @return BigInteger */ - private function keyParamToBigInteger($param) { - var_dump(URLSafeBase64::decode($param)); - exit; - + private function keyParamToBigInteger($param) + { return new BigInteger('0x' . bin2hex(URLSafeBase64::decode($param)), 16); } @@ -244,12 +245,13 @@ private function keyParamToBigInteger($param) { * @param array $publicKeyData The JWK key data * @return RSA */ - private function getPublicKey($publicKeyData) { + private function getPublicKey($publicKeyData) + { $publicKeyRaw = new JOSE_JWK($publicKeyData); $publicKey = $publicKeyRaw->toKey(); if ($publicKeyData['alg'] == 'RSA-OAEP-256') { - $publicKey->setHash('sha256'); - $publicKey->setMGFHash('sha256'); +// $publicKey->setHash('sha256'); +// $publicKey->setMGFHash('sha256'); } return $publicKey; } @@ -263,8 +265,9 @@ private function getPublicKey($publicKeyData) { * * @throws HyperwalletException */ - private function getJwk($keySetLocation, $alg) { - if (filter_var($keySetLocation, FILTER_VALIDATE_URL) === FALSE) { + private function getJwk($keySetLocation, $alg) + { + if (filter_var($keySetLocation, FILTER_VALIDATE_URL) === false) { if (!file_exists($keySetLocation)) { throw new HyperwalletException("Wrong JWK key set location path = " . $keySetLocation); } @@ -281,8 +284,9 @@ private function getJwk($keySetLocation, $alg) { * * @throws HyperwalletException */ - private function findJwkByAlgorithm($jwkSetArray, $alg) { - foreach($jwkSetArray['keys'] as $jwk) { + private function findJwkByAlgorithm($jwkSetArray, $alg) + { + foreach ($jwkSetArray['keys'] as $jwk) { if ($alg == $jwk['alg']) { return $jwk; } @@ -296,7 +300,8 @@ private function findJwkByAlgorithm($jwkSetArray, $alg) { * @param string $jwk JWK key * @return array */ - private function convertPrivateKeyToPublic($jwk) { + private function convertPrivateKeyToPublic($jwk) + { if (isset($jwk['d'])) { unset($jwk['d']); } @@ -323,7 +328,8 @@ private function convertPrivateKeyToPublic($jwk) { * * @return integer */ - private function getSignatureExpirationTime() { + private function getSignatureExpirationTime() + { date_default_timezone_set("UTC"); $secondsInMinute = 60; return time() + $this->jwsExpirationMinutes * $secondsInMinute; @@ -336,15 +342,16 @@ private function getSignatureExpirationTime() { * * @throws HyperwalletException */ - public function checkJwsExpiration($header) { - if(!isset($header['exp'])) { + public function checkJwsExpiration($header) + { + if (!isset($header['exp'])) { throw new HyperwalletException('While trying to verify JWS signature no [exp] header is found'); } $exp = $header['exp']; - if(!is_numeric($exp)) { + if (!is_numeric($exp)) { throw new HyperwalletException('Wrong value in [exp] header of JWS signature, must be integer'); } - if((int)time() > (int)$exp) { + if ((int)time() > (int)$exp) { throw new HyperwalletException('JWS signature has expired, checked by [exp] JWS header'); } } @@ -356,10 +363,11 @@ public function checkJwsExpiration($header) { * * @throws HyperwalletException */ - public function getVendorPath() { + public function getVendorPath() + { $reflector = new \ReflectionClass(ClassLoader::class); - $vendorPath = preg_replace('/^(.*)\/composer\/ClassLoader\.php$/', '$1', $reflector->getFileName() ); - if($vendorPath && is_dir($vendorPath)) { + $vendorPath = preg_replace('/^(.*)\/composer\/ClassLoader\.php$/', '$1', $reflector->getFileName()); + if ($vendorPath && is_dir($vendorPath)) { return $vendorPath . '/'; } throw new HyperwalletException('Failed to find a vendor path'); diff --git a/src/Services/Jose/JOSE_JWE.php b/src/Services/Jose/JOSE_JWE.php index 24475018..11b3f03d 100644 --- a/src/Services/Jose/JOSE_JWE.php +++ b/src/Services/Jose/JOSE_JWE.php @@ -82,10 +82,8 @@ private function rsa($public_or_private_key, $padding_mode) { } else if ($public_or_private_key instanceof RSA) { $rsa = $public_or_private_key; } else { - $rsa = new RSA(); - $rsa->loadKey($public_or_private_key); + } - $rsa->setEncryptionMode($padding_mode); return $rsa; } @@ -96,7 +94,7 @@ private function cipher() { throw new UnexpectedAlgorithm('Algorithm not supported'); case 'A128CBC-HS256': case 'A256CBC-HS512': - $cipher = new AES(AES::MODE_CBC); + $cipher = new AES('cbc'); break; default: throw new UnexpectedAlgorithm('Unknown algorithm'); @@ -108,7 +106,7 @@ private function cipher() { break; case 'A256GCM': case 'A256CBC-HS512': - $cipher->setBlockLength(256); +// $cipher->setBlockLength(256); break; default: throw new UnexpectedAlgorithm('Unknown algorithm'); @@ -128,7 +126,7 @@ private function generateIv() { break; case 'A256GCM': case 'A256CBC-HS512': - $this->iv = $this->generateRandomBytes(256 / 8); + $this->iv = $this->generateRandomBytes(128 / 8); break; default: throw new UnexpectedAlgorithm('Unknown algorithm'); @@ -161,6 +159,7 @@ private function encryptContentEncryptionKey($public_key_or_secret) { $this->jwe_encrypted_key = $rsa->encrypt($this->content_encryption_key); break; case 'RSA-OAEP': + case 'RSA-OAEP-256': $rsa = $this->rsa($public_key_or_secret, RSA::ENCRYPTION_OAEP); $this->jwe_encrypted_key = $rsa->encrypt($this->content_encryption_key); break; @@ -189,6 +188,7 @@ private function decryptContentEncryptionKey($private_key_or_secret) { $rsa = $this->rsa($private_key_or_secret, RSA::ENCRYPTION_PKCS1); $this->content_encryption_key = $rsa->decrypt($this->jwe_encrypted_key); break; + case 'RSA-OAEP-256': case 'RSA-OAEP': $rsa = $this->rsa($private_key_or_secret, RSA::ENCRYPTION_OAEP); $this->content_encryption_key = $rsa->decrypt($this->jwe_encrypted_key); diff --git a/src/Services/Jose/JOSE_JWK.php b/src/Services/Jose/JOSE_JWK.php index e9914d0c..0bd3d997 100644 --- a/src/Services/Jose/JOSE_JWK.php +++ b/src/Services/Jose/JOSE_JWK.php @@ -5,14 +5,17 @@ use phpseclib3\Crypt\RSA; use phpseclib3\Math\BigInteger; use phpseclib3\Crypt\Hash; +use Services\Jose\Exception\InvalidFormat; use Services\Jose\Exception\UnexpectedAlgorithm; -class JOSE_JWK { +class JOSE_JWK +{ var $components = array(); - function __construct($components = array()) { + function __construct($components = array()) + { if (!array_key_exists('kty', $components)) { - throw new JOSE_Exception_InvalidFormat('"kty" is required'); + throw new InvalidFormat('"kty" is required'); } $this->components = $components; if (!array_key_exists('kid', $this->components)) { @@ -20,64 +23,77 @@ function __construct($components = array()) { } } - function toKey() { + private function keyParamToBigInteger($param) + { + return new BigInteger('0x' . bin2hex(URLSafeBase64::decode($param)), 16); + } + + function toKey() + { switch ($this->components['kty']) { case 'RSA': - $rsa = new RSA(); - $n = new BigInteger('0x' . bin2hex(JOSE_URLSafeBase64::decode($this->components['n'])), 16); - $e = new BigInteger('0x' . bin2hex(JOSE_URLSafeBase64::decode($this->components['e'])), 16); + $pemData = RSA::load([ + 'e' => $this->keyParamToBigInteger($this->components['e']), + 'n' => $this->keyParamToBigInteger($this->components['n']), + ]); + if (array_key_exists('d', $this->components)) { throw new UnexpectedAlgorithm('RSA private key isn\'t supported'); } else { - $pem_string = $rsa->_convertPublicKey($n, $e); + $pem_string = RSA::loadPublicKey($pemData->toString('PKCS1')); } - $rsa->loadKey($pem_string); - return $rsa; + return RSA::load($pem_string); default: throw new UnexpectedAlgorithm('Unknown key type'); } } - function thumbprint($hash_algorithm = 'sha256') { + function thumbprint($hash_algorithm = 'sha256') + { $hash = new Hash($hash_algorithm); - return JOSE_URLSafeBase64::encode( + return URLSafeBase64::encode( $hash->hash( json_encode($this->normalized()) ) ); } - private function normalized() { + private function normalized() + { switch ($this->components['kty']) { case 'RSA': return array( - 'e' => $this->components['e'], + 'e' => $this->components['e'], 'kty' => $this->components['kty'], - 'n' => $this->components['n'] + 'n' => $this->components['n'] ); default: throw new UnexpectedAlgorithm('Unknown key type'); } } - function toString() { + function toString() + { return json_encode($this->components); } - function __toString() { + + function __toString() + { return $this->toString(); } - static function encode($key, $extra_components = array()) { - switch(get_class($key)) { + static function encode($key, $extra_components = array()) + { + switch (get_class($key)) { case 'phpseclib\Crypt\RSA': $components = array( 'kty' => 'RSA', - 'e' => JOSE_URLSafeBase64::encode($key->publicExponent->toBytes()), - 'n' => JOSE_URLSafeBase64::encode($key->modulus->toBytes()) + 'e' => URLSafeBase64::encode($key->publicExponent->toBytes()), + 'n' => URLSafeBase64::encode($key->modulus->toBytes()) ); if ($key->exponent != $key->publicExponent) { $components = array_merge($components, array( - 'd' => JOSE_URLSafeBase64::encode($key->exponent->toBytes()) + 'd' => URLSafeBase64::encode($key->exponent->toBytes()) )); } return new self(array_merge($components, $extra_components)); @@ -86,7 +102,8 @@ static function encode($key, $extra_components = array()) { } } - static function decode($components) { + static function decode($components) + { $jwk = new self($components); return $jwk->toKey(); } diff --git a/src/Services/Jose/JOSE_JWS.php b/src/Services/Jose/JOSE_JWS.php index c77c2fce..401265fe 100644 --- a/src/Services/Jose/JOSE_JWS.php +++ b/src/Services/Jose/JOSE_JWS.php @@ -63,12 +63,8 @@ private function rsa($public_or_private_key, $padding_mode) { } else if ($public_or_private_key instanceof RSA) { $rsa = $public_or_private_key; } else { - $rsa = new RSA(); - $rsa->loadKey($public_or_private_key); + $rsa = RSA::loadPrivateKey($public_or_private_key); } - $rsa->setHash($this->digest()); - $rsa->setMGFHash($this->digest()); - $rsa->setSignatureMode($padding_mode); return $rsa; } diff --git a/src/Services/Jose/JOSE_JWT.php b/src/Services/Jose/JOSE_JWT.php index 9a3acb69..3a21eef6 100644 --- a/src/Services/Jose/JOSE_JWT.php +++ b/src/Services/Jose/JOSE_JWT.php @@ -53,8 +53,6 @@ static function encode($claims) { static function decode($jwt_string) { $segments = explode('.', $jwt_string); - var_dump($jwt_string); - exit; switch (count($segments)) { case 3: $jwt = new self(); diff --git a/src/Services/Jose/URLSafeBase64.php b/src/Services/Jose/URLSafeBase64.php index aef6237c..8c365a68 100644 --- a/src/Services/Jose/URLSafeBase64.php +++ b/src/Services/Jose/URLSafeBase64.php @@ -2,157 +2,17 @@ namespace Services\Jose; -use phpseclib3\Crypt\RSA; -use Services\Jose\Exception\UnexpectedAlgorithm; -use Services\Jose\Exception\VerificationFailed; - -class URLSafeBase64 extends JOSE_JWT { - function __construct($jwt) { - $this->header = $jwt->header; - $this->claims = $jwt->claims; - $this->signature = $jwt->signature; - $this->raw = $jwt->raw; - } - - function toJson($syntax = 'flattened') { - if ($syntax == 'flattened') { - $components = array( - 'protected' => $this->compact((object) $this->header), - 'payload' => $this->compact((object) $this->claims), - 'signature' => $this->compact($this->signature) - ); - } else { - $components = array( - 'payload' => $this->compact((object) $this->claims), - 'signatures' => array( - 'protected' => $this->compact((object) $this->header), - 'signature' => $this->compact($this->signature) - ) - ); - } - return json_encode($components); - } - - function sign($private_key_or_secret, $algorithm = 'HS256') { - $this->header['alg'] = $algorithm; - if ( - $private_key_or_secret instanceof JOSE_JWK && - !array_key_exists('kid', $this->header) && - array_key_exists('kid', $private_key_or_secret->components) - ) { - $this->header['kid'] = $private_key_or_secret->components['kid']; - } - $this->signature = $this->_sign($private_key_or_secret); - if (!$this->signature) { - throw new Exception('Signing failed because of unknown reason'); - } - return $this; - } - - function verify($public_key_or_secret, $alg = null) { - if ($this->_verify($public_key_or_secret, $alg)) { - return $this; - } else { - throw new VerificationFailed('Signature verification failed'); - } +class URLSafeBase64 { + static function encode($input) { + return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); } - private function rsa($public_or_private_key, $padding_mode) { - if ($public_or_private_key instanceof JOSE_JWK) { - $rsa = $public_or_private_key->toKey(); - } else if ($public_or_private_key instanceof RSA) { - $rsa = $public_or_private_key; - } else { - $rsa = new RSA(); - $rsa->loadKey($public_or_private_key); - } - $rsa->setHash($this->digest()); - $rsa->setMGFHash($this->digest()); - $rsa->setSignatureMode($padding_mode); - return $rsa; - } - - private function digest() { - switch ($this->header['alg']) { - case 'HS256': - case 'RS256': - case 'ES256': - case 'PS256': - return 'sha256'; - case 'HS384': - case 'RS384': - case 'ES384': - case 'PS384': - return 'sha384'; - case 'HS512': - case 'RS512': - case 'ES512': - case 'PS512': - return 'sha512'; - default: - throw new UnexpectedAlgorithm('Unknown algorithm'); - } - } - - private function _sign($private_key_or_secret) { - $signature_base_string = implode('.', array( - $this->compact((object) $this->header), - $this->compact((object) $this->claims) - )); - switch ($this->header['alg']) { - case 'HS256': - case 'HS384': - case 'HS512': - return hash_hmac($this->digest(), $signature_base_string, $private_key_or_secret, true); - case 'RS256': - case 'RS384': - case 'RS512': - return $this->rsa($private_key_or_secret, RSA::SIGNATURE_PKCS1)->sign($signature_base_string); - case 'ES256': - case 'ES384': - case 'ES512': - throw new UnexpectedAlgorithm('Algorithm not supported'); - case 'PS256': - case 'PS384': - case 'PS512': - return $this->rsa($private_key_or_secret, RSA::SIGNATURE_PSS)->sign($signature_base_string); - default: - throw new UnexpectedAlgorithm('Unknown algorithm'); - } - } - - private function _verify($public_key_or_secret, $expected_alg = null) { - $segments = explode('.', $this->raw); - $signature_base_string = implode('.', array($segments[0], $segments[1])); - if (!$expected_alg) { - $expected_alg = $this->header['alg']; - $using_autodetected_alg = true; - } - switch ($expected_alg) { - case 'HS256': - case 'HS384': - case 'HS512': - if (isset($using_autodetected_alg)) { - throw new UnexpectedAlgorithm( - 'HMAC algs MUST be explicitly specified as $expected_alg' - ); - } - $hmac_hash = hash_hmac($this->digest(), $signature_base_string, $public_key_or_secret, true); - return hash_equals($this->signature, $hmac_hash); - case 'RS256': - case 'RS384': - case 'RS512': - return $this->rsa($public_key_or_secret, RSA::SIGNATURE_PKCS1)->verify($signature_base_string, $this->signature); - case 'ES256': - case 'ES384': - case 'ES512': - throw new UnexpectedAlgorithm('Algorithm not supported'); - case 'PS256': - case 'PS384': - case 'PS512': - return $this->rsa($public_key_or_secret, RSA::SIGNATURE_PSS)->verify($signature_base_string, $this->signature); - default: - throw new UnexpectedAlgorithm('Unknown algorithm'); + static function decode($input) { + $remainder = strlen($input) % 4; + if ($remainder) { + $padlen = 4 - $remainder; + $input .= str_repeat('=', $padlen); } + return base64_decode(strtr($input, '-_', '+/')); } }