diff --git a/src/SimpleJWT/Keys/ECKey.php b/src/SimpleJWT/Keys/ECKey.php index ba4401c..419b5ec 100644 --- a/src/SimpleJWT/Keys/ECKey.php +++ b/src/SimpleJWT/Keys/ECKey.php @@ -163,30 +163,7 @@ public function __construct($data, string $format, ?string $password = null, ?st $seq = $der->decode($binary); - $version = $seq->getChildAt(0)->getValue(); - if ($version != 1) throw new KeyException('Invalid private key version: ' . $version); - - $d = $seq->getChildAt(1)->getValue(); - - $curve_oid = $seq->getChildWithTag(0)->getValue(); - $curve = self::getCurveNameFromOID($curve_oid); - if ($curve == null) throw new KeyException('Unrecognised EC parameter: ' . $curve_oid); - - $len = self::$curves[$curve]['len']; - - $point = $seq->getChildWithTag(1)->getValue(); - if (strlen($point) != $len + 1) throw new KeyException('Incorrect private key length: ' . strlen($point)); - - if (ord($point[0]) != 0x04) throw new KeyException('Invalid private key'); // W - - $x = substr($point, 1, $len / 2); - $y = substr($point, 1 + $len / 2); - - $jwk['kty'] = self::KTY; - $jwk['crv'] = $curve; - $jwk['d'] = Util::base64url_encode($d); - $jwk['x'] = Util::base64url_encode($x); - $jwk['y'] = Util::base64url_encode($y); + $jwk = self::parseASN1PrivateKey($seq); } elseif (preg_match(Key::PEM_PKCS8_PRIVATE, $data, $matches)) { /** @var string $binary */ $binary = base64_decode($matches[1]); @@ -204,29 +181,10 @@ public function __construct($data, string $format, ?string $password = null, ?st $curve = self::getCurveNameFromOID($curve_oid); if ($curve == null) throw new KeyException('Unrecognised EC parameter: ' . $curve_oid); - $len = self::$curves[$curve]['len']; - $private_octet_string = $seq->getChildAt(2)->getValue(); $private_seq = $der->decode($private_octet_string); - $version = $private_seq->getChildAt(0)->getValue(); - if ($version != 1) throw new KeyException('Invalid private key version: ' . $version); - - $d = $private_seq->getChildAt(1)->getValue(); - - $point = $private_seq->getChildWithTag(1)->getValue(); - if (strlen($point) != $len + 1) throw new KeyException('Incorrect private key length: ' . strlen($point)); - - if (ord($point[0]) != 0x04) throw new KeyException('Invalid private key'); // W - - $x = substr($point, 1, $len / 2); - $y = substr($point, 1 + $len / 2); - - $jwk['kty'] = self::KTY; - $jwk['crv'] = $curve; - $jwk['d'] = Util::base64url_encode($d); - $jwk['x'] = Util::base64url_encode($x); - $jwk['y'] = Util::base64url_encode($y); + $jwk = self::parseASN1PrivateKey($private_seq, $curve); } else { throw new KeyException('Unrecognised key format'); } @@ -395,6 +353,51 @@ protected function getThumbnailMembers(): array { return ['crv', 'kty', 'x', 'y']; } + /** + * Parses an EC private key in DER form. + * + * An EC private key is encoded using the ECPrivateKey type as per SEC 1. + * + * @param ASN1Value $seq the ASN.1 sequence to parse + * @param string $curve the name of the elliptic curve. If null, this will be + * read from the sequence + * @return array the parsed private key data + * @throws KeyException if an error occurs in parsing the key + */ + protected static function parseASN1PrivateKey(ASN1Value $seq, $curve = null): array { + $version = $seq->getChildAt(0)->getValue(); + if ($version != 1) throw new KeyException('Invalid private key version: ' . $version); + + $d = $seq->getChildAt(1)->getValue(); + + if ($curve == null) { + $curve_oid_param = $seq->getChildWithTag(0); + if ($curve_oid_param == null) throw new KeyException('Missing EC curve parameter'); + $curve_oid = $curve_oid_param->getValue(); + $curve = self::getCurveNameFromOID($curve_oid); + if ($curve == null) throw new KeyException('Unrecognised EC parameter: ' . $curve_oid); + } + + if (!isset(self::$curves[$curve])) throw new KeyException('Curve not found'); + $len = self::$curves[$curve]['len']; + + $point = $seq->getChildWithTag(1)->getValue(); + if (strlen($point) != $len + 1) throw new KeyException('Incorrect private key length: ' . strlen($point)); + + if (ord($point[0]) != 0x04) throw new KeyException('Invalid private key'); // W + + $x = substr($point, 1, $len / 2); + $y = substr($point, 1 + $len / 2); + + return [ + 'kty' => self::KTY, + 'crv' => $curve, + 'd' => Util::base64url_encode($d), + 'x' => Util::base64url_encode($x), + 'y' => Util::base64url_encode($y) + ]; + } + private static function getCurveNameFromOID(string $curve_oid): ?string { foreach (self::$curves as $crv => $params) { if ($params['oid'] == $curve_oid) return $crv; diff --git a/src/SimpleJWT/Keys/RSAKey.php b/src/SimpleJWT/Keys/RSAKey.php index 15035f6..16db8fc 100644 --- a/src/SimpleJWT/Keys/RSAKey.php +++ b/src/SimpleJWT/Keys/RSAKey.php @@ -107,18 +107,7 @@ public function __construct($data, string $format, ?string $password = null, ?st $seq = $der->decode($binary); - $version = $seq->getChildAt(0)->getValue(); - if ($version != 0) throw new KeyException('Unsupported RSA private key version'); - - $jwk['kty'] = self::KTY; - $jwk['n'] = Util::base64url_encode($seq->getChildAt(1)->getValueAsUIntOctets()); - $jwk['e'] = Util::base64url_encode($seq->getChildAt(2)->getValueAsUIntOctets()); - $jwk['d'] = Util::base64url_encode($seq->getChildAt(3)->getValueAsUIntOctets()); - $jwk['p'] = Util::base64url_encode($seq->getChildAt(4)->getValueAsUIntOctets()); - $jwk['q'] = Util::base64url_encode($seq->getChildAt(5)->getValueAsUIntOctets()); - $jwk['dp'] = Util::base64url_encode($seq->getChildAt(6)->getValueAsUIntOctets()); - $jwk['dq'] = Util::base64url_encode($seq->getChildAt(7)->getValueAsUIntOctets()); - $jwk['qi'] = Util::base64url_encode($seq->getChildAt(8)->getValueAsUIntOctets()); + $jwk = self::parseASN1PrivateKey($seq); } elseif (preg_match(Key::PEM_PKCS8_PRIVATE, $data, $matches)) { /** @var string $binary */ $binary = base64_decode($matches[1]); @@ -135,18 +124,7 @@ public function __construct($data, string $format, ?string $password = null, ?st $private_octet_string = $seq->getChildAt(2)->getValue(); $private_seq = $der->decode($private_octet_string); - $version = $private_seq->getChildAt(0)->getValue(); - if ($version != 0) throw new KeyException('Unsupported RSA private key version'); - - $jwk['kty'] = self::KTY; - $jwk['n'] = Util::base64url_encode($private_seq->getChildAt(1)->getValueAsUIntOctets()); - $jwk['e'] = Util::base64url_encode($private_seq->getChildAt(2)->getValueAsUIntOctets()); - $jwk['d'] = Util::base64url_encode($private_seq->getChildAt(3)->getValueAsUIntOctets()); - $jwk['p'] = Util::base64url_encode($private_seq->getChildAt(4)->getValueAsUIntOctets()); - $jwk['q'] = Util::base64url_encode($private_seq->getChildAt(5)->getValueAsUIntOctets()); - $jwk['dp'] = Util::base64url_encode($private_seq->getChildAt(6)->getValueAsUIntOctets()); - $jwk['dq'] = Util::base64url_encode($private_seq->getChildAt(7)->getValueAsUIntOctets()); - $jwk['qi'] = Util::base64url_encode($private_seq->getChildAt(8)->getValueAsUIntOctets()); + $jwk = self::parseASN1PrivateKey($private_seq); } else { throw new KeyException('Unrecognised key format'); } @@ -221,6 +199,32 @@ protected function getThumbnailMembers(): array { // https://tools.ietf.org/html/rfc7638#section-3.2 return ['e', 'kty', 'n']; } + + /** + * Parses an RSA private key in DER form. + * + * An RSA private key is encoded using the RSAPrivateKey type as per PKCS#1 + * + * @param ASN1Value $seq the ASN.1 sequence to parse + * @return array the parsed private key data + * @throws KeyException if an error occurs in parsing the key + */ + protected static function parseASN1PrivateKey(ASN1Value $seq): array { + $version = $seq->getChildAt(0)->getValue(); + if ($version != 0) throw new KeyException('Unsupported RSA private key version'); + + return [ + 'kty' => self::KTY, + 'n' => Util::base64url_encode($seq->getChildAt(1)->getValueAsUIntOctets()), + 'e' => Util::base64url_encode($seq->getChildAt(2)->getValueAsUIntOctets()), + 'd' => Util::base64url_encode($seq->getChildAt(3)->getValueAsUIntOctets()), + 'p' => Util::base64url_encode($seq->getChildAt(4)->getValueAsUIntOctets()), + 'q' => Util::base64url_encode($seq->getChildAt(5)->getValueAsUIntOctets()), + 'dp' => Util::base64url_encode($seq->getChildAt(6)->getValueAsUIntOctets()), + 'dq' => Util::base64url_encode($seq->getChildAt(7)->getValueAsUIntOctets()), + 'qi' => Util::base64url_encode($seq->getChildAt(8)->getValueAsUIntOctets()) + ]; + } } ?>