Skip to content

Commit

Permalink
Fix key selection in ECDH_AESKeyWrap (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
kelvinmo authored Aug 13, 2023
2 parents d6ecce0 + 3a32b58 commit 7430266
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 15 deletions.
39 changes: 35 additions & 4 deletions src/SimpleJWT/Crypt/KeyManagement/ECDH_AESKeyWrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@

namespace SimpleJWT\Crypt\KeyManagement;

use SimpleJWT\Crypt\CryptException;
use SimpleJWT\Keys\KeySet;
use SimpleJWT\Keys\SymmetricKey;

/**
* Implementation of the Elliptic Curve Diffie-Hellman
Expand Down Expand Up @@ -71,14 +73,43 @@ public function getSupportedAlgs() {
return array_map(function ($alg) { return 'ECDH-ES+' . $alg; }, $aeskw_algs);
}

/**
* Returns the criteria for selecting the symmetric wrapping key.
* Note that this is different from the criteria for the key used
* to derive the wrapping key.
*
* @return array<string, mixed> the key selection criteria
*/
protected function getWrappingKeyCriteria() {
return [
'kty' => 'oct',
'~alg' => $this->getAlg(),
'@use' => 'enc'
];
}

public function encryptKey($cek, $keys, &$headers, $kid = null) {
$wrapping_key = $this->deriveKey($keys, $headers, $kid);
return $this->wrapKey($cek, $wrapping_key, $headers);
$criteria = $this->getWrappingKeyCriteria();
if ($kid != null) $criteria['kid'] = $kid;

$wrapping_key = $this->selectKey($keys, $criteria);
if (($wrapping_key == null) || !($wrapping_key instanceof SymmetricKey)) {
throw new CryptException('Wrapping key not found');
}

return $this->wrapKey($cek, $wrapping_key->toBinary(), $headers);
}

public function decryptKey($encrypted_key, $keys, $headers, $kid = null) {
$wrapping_key = $this->deriveKey($keys, $headers, $kid);
return $this->unwrapKey($encrypted_key, $wrapping_key, $headers);
$criteria = $this->getWrappingKeyCriteria();
if ($kid != null) $criteria['kid'] = $kid;

$wrapping_key = $this->selectKey($keys, $criteria);
if (($wrapping_key == null) || !($wrapping_key instanceof SymmetricKey)) {
throw new CryptException('Wrapping key not found');
}

return $this->unwrapKey($encrypted_key, $wrapping_key->toBinary(), $headers);
}
}

Expand Down
17 changes: 13 additions & 4 deletions src/SimpleJWT/JWE.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use SimpleJWT\Crypt\CryptException;
use SimpleJWT\Crypt\KeyManagement\KeyDerivationAlgorithm;
use SimpleJWT\Crypt\KeyManagement\KeyEncryptionAlgorithm;
use SimpleJWT\Keys\KeySet;
use SimpleJWT\Keys\SymmetricKey;
use SimpleJWT\Keys\KeyException;
use SimpleJWT\Util\Helper;
Expand Down Expand Up @@ -136,6 +137,8 @@ public static function decrypt($token, $keys, $expected_alg) {
/** @var \SimpleJWT\Crypt\Encryption\EncryptionAlgorithm $content_enc */
$content_enc = AlgorithmFactory::create($headers['enc']);

$key_decryption_keys = $keys;

if ($key_enc instanceof KeyDerivationAlgorithm) {
try {
$kid = (isset($headers['kid'])) ? $headers['kid'] : null;
Expand All @@ -155,7 +158,9 @@ public static function decrypt($token, $keys, $expected_alg) {
], 'php');
$kid = $agreed_symmetric_key->getThumbnail();
$agreed_symmetric_key->setKeyId($kid);
$keys->add($agreed_symmetric_key);

$key_decryption_keys = new KeySet();
$key_decryption_keys->add($agreed_symmetric_key);
} else {
// Direct key agreement or direct encryption
$cek = $agreed_key;
Expand All @@ -169,7 +174,7 @@ public static function decrypt($token, $keys, $expected_alg) {
if (!isset($cek) && ($key_enc instanceof KeyEncryptionAlgorithm)) {
try {
if (!isset($kid)) $kid = (isset($headers['kid'])) ? $headers['kid'] : null;
$cek = $key_enc->decryptKey($encrypted_key, $keys, $headers, $kid);
$cek = $key_enc->decryptKey($encrypted_key, $key_decryption_keys, $headers, $kid);
} catch (KeyException $e) {
throw new InvalidTokenException($e->getMessage(), InvalidTokenException::DECRYPTION_ERROR, $e);
} catch (CryptException $e) {
Expand Down Expand Up @@ -230,6 +235,8 @@ public function encrypt($keys, $kid = null, $format = self::COMPACT_FORMAT) {
/** @var \SimpleJWT\Crypt\Encryption\EncryptionAlgorithm $content_enc */
$content_enc = AlgorithmFactory::create($this->headers['enc']);

$key_encryption_keys = $keys;

if ($kid != null) $this->headers['kid'] = $kid;

if ($key_enc instanceof KeyDerivationAlgorithm) {
Expand All @@ -244,7 +251,9 @@ public function encrypt($keys, $kid = null, $format = self::COMPACT_FORMAT) {
], 'php');
$kid = $agreed_symmetric_key->getThumbnail();
$agreed_symmetric_key->setKeyId($kid);
$keys->add($agreed_symmetric_key);

$key_encryption_keys = new KeySet();
$key_encryption_keys->add($agreed_symmetric_key);
} else {
// Direct key agreement or direct encryption
$cek = $agreed_key;
Expand All @@ -258,7 +267,7 @@ public function encrypt($keys, $kid = null, $format = self::COMPACT_FORMAT) {
}

if ($key_enc instanceof KeyEncryptionAlgorithm) {
$encrypted_key = $key_enc->encryptKey($cek, $keys, $this->headers, $kid);
$encrypted_key = $key_enc->encryptKey($cek, $key_encryption_keys, $this->headers, $kid);
} else {
$encrypted_key = '';
}
Expand Down
33 changes: 32 additions & 1 deletion tests/JWETest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
namespace SimpleJWT;

use SimpleJWT\Keys\KeySet;
use SimpleJWT\Keys\ECKey;
use SimpleJWT\Keys\RSAKey;
use SimpleJWT\Keys\SymmetricKey;
use SimpleJWT\Util\Util;
use PHPUnit\Framework\TestCase;


class JWETest extends TestCase {
protected $multi_token = ' {
protected $multi_token = '{
"protected":
"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0",
"unprotected":
Expand Down Expand Up @@ -46,6 +47,16 @@ protected function getPrivateKeySet() {
"qi" => "eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlCtUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZB9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo"
], 'php'));

$set->add(new ECKey([
"kty"=> "EC",
"kid" => "issue-159",
"crv" => "P-384",
"use" => "enc",
"d" => "3DCgwJeF_IRdhF1B8JYRZOm4Frt_XrknFotgE_RcVj_z053yhHF4zhM6W-z7dd2X",
"x" => "q4yHCxdvXDA6PODaM9IkpjCUh9gRgpkIN_gV1i5HzJUOHCkC4HMrFiIduZZsVdQf",
"y" => "fFrsS5ZIlf0CKAnxRXhnbSHcGTByVxULEPyN_9jKOlb85wZv4VoIEtIBxeHYkLCe"
], 'php'));

$set->add(new SymmetricKey([
"kty" => "oct",
"kid" => "7",
Expand Down Expand Up @@ -204,6 +215,26 @@ public function testDecryptDirectJSON() {
$this->assertEquals($plaintext, $test_jwe->getPlaintext());
}


public function testDecryptECDHAESKW() {
// https://github.com/kelvinmo/simplejwt/issues/159
$algs = Crypt\AlgorithmFactory::getSupportedAlgs('key');
if (!in_array('ECDH-ES+A256KW', $algs)) {
$this->markTestSkipped('ECDH-ES+A256KW algorithm not available');
return;
}

$token = 'eyJhbGciOiJFQ0RILUVTK0EyNTZLVyIsImVuYyI6IkEyNTZHQ00iLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiVDFIazlQell6SUY5NW9ESDJENTFZXzJGVUZuZ3RKZWxpbW11UTZJbHlyVWhuVGlfYlk1ZFplY0lPNExQRmp1byIsInkiOiJlLVBQbTNEQjB0N2F1RUNCV0Q0MkZxMlVDeXNuQ0NjQUxDUy1NWHMwclV3U0pLQmFMWTcwb1lzcWprMnJQVjROIiwiY3J2IjoiUC0zODQifX0.6vW-S_7om9iHMYc2JzkwijQV4msn55YRrDYQ2EMs3-bg3Y7I0dBrDA.CQ45omsfTgrZlrJd.58LMMeqXOogn6i6JI5VbrFucwI_hStOGNXgOqXsExNARXlYPSHweSXXGS_nYaa90srl9a5HTbn1YJEtduB0YKekULRXK1la5uOiHnw5tuRJUqXVTA-_l_Nv7PZWzPZOua2quUGMw5c8y55c8qImO02gw_tbopnqwROUHR-eeBMiRwEkpBDl8AlSOQsLd-6MZ3kqaLuGyhw0rQ9DPZlucB1DB0rF2WYEwnz72I1aB2XLmrVuIRkTbVRRxMp9Qt8BLP8Uay-8Qr3HvMfQDftKydtAKiQLXHTMLoo5H8s69i-1baFynJjH4nNpnujJGONkBSQg9RmWf-5CdiZnQC1g4hSvL5p6RM0sGXR4jORlzd-TNSmZeOe1mvEHifCmeyCQ1T0NNBrtsSUeT6lckEFjyvjKau6eZxoa3nyzpzMooNw8u-e-s9uctYmdVmYm75PWqkzencTnccTtmZjuBdehplM0SLbGYrxoxIoBBoozrACeIQITHi73DB1kSQdbfOfb_nuo26PEaIgvsncj-he0v.y3mcOAn4nXDleSobp2eQYg';

$private_set = $this->getPrivateKeySet();
$test_jwe = JWE::decrypt($token, $private_set, 'ECDH-ES+A256KW');
$payload = json_decode($test_jwe->getPlaintext(), true);

$this->assertEquals('0607a317-044c-49dc-83ea-89bbf7766c03', $payload['refreshToken']);
$this->assertEquals('c8945473-6217-4ec7-a543-09371ee156e3', $payload['authToken']);
}


/**
* @expectedException SimpleJWT\InvalidTokenException
*/
Expand Down
34 changes: 28 additions & 6 deletions tests/KeyManagement/ECDH_AESKeyWrapTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ private function getPublicKeySet() {
return $set;
}

protected function getSymmetricKeySet($agreed_key) {
return \SimpleJWT\Keys\KeySet::createFromSecret($agreed_key, 'bin');
}

function testECDHES_A128KW() {
if (!$this->isAlgAvailable('ECDH-ES+A128KW')) return;

Expand All @@ -56,8 +60,14 @@ function testECDHES_A128KW() {
'apv' => 'Qm9i'
];

$encrypted_key = $alg->encryptKey($key, $public_set, $headers);
$decrypted_key = $alg->decryptKey($encrypted_key, $private_set, $headers);
$derived_key_from_public = $alg->deriveKey($public_set, $headers);
$derived_key_set_from_public = $this->getSymmetricKeySet($derived_key_from_public);
$encrypted_key = $alg->encryptKey($key, $derived_key_set_from_public, $headers);

$derived_key_from_private = $alg->deriveKey($private_set, $headers);
$derived_key_set_from_private = $this->getSymmetricKeySet($derived_key_from_private);
$decrypted_key = $alg->decryptKey($encrypted_key, $derived_key_set_from_private, $headers);

$this->assertEquals($key, $decrypted_key);
}

Expand All @@ -75,8 +85,14 @@ function testECDHES_A192KW() {
];


$encrypted_key = $alg->encryptKey($key, $public_set, $headers);
$decrypted_key = $alg->decryptKey($encrypted_key, $private_set, $headers);
$derived_key_from_public = $alg->deriveKey($public_set, $headers);
$derived_key_set_from_public = $this->getSymmetricKeySet($derived_key_from_public);
$encrypted_key = $alg->encryptKey($key, $derived_key_set_from_public, $headers);

$derived_key_from_private = $alg->deriveKey($private_set, $headers);
$derived_key_set_from_private = $this->getSymmetricKeySet($derived_key_from_private);
$decrypted_key = $alg->decryptKey($encrypted_key, $derived_key_set_from_private, $headers);

$this->assertEquals($key, $decrypted_key);
}

Expand All @@ -93,8 +109,14 @@ function testECDHES_A256KW() {
'apv' => 'Qm9i'
];

$encrypted_key = $alg->encryptKey($key, $public_set, $headers);
$decrypted_key = $alg->decryptKey($encrypted_key, $private_set, $headers);
$derived_key_from_public = $alg->deriveKey($public_set, $headers);
$derived_key_set_from_public = $this->getSymmetricKeySet($derived_key_from_public);
$encrypted_key = $alg->encryptKey($key, $derived_key_set_from_public, $headers);

$derived_key_from_private = $alg->deriveKey($private_set, $headers);
$derived_key_set_from_private = $this->getSymmetricKeySet($derived_key_from_private);
$decrypted_key = $alg->decryptKey($encrypted_key, $derived_key_set_from_private, $headers);

$this->assertEquals($key, $decrypted_key);
}
}
Expand Down

0 comments on commit 7430266

Please sign in to comment.