From e2fdc6172ed6104e586176ade76ecdbbcbca7de0 Mon Sep 17 00:00:00 2001 From: Kelvin Mo Date: Sat, 21 Sep 2024 08:28:35 +1000 Subject: [PATCH] Add PKCS#8 parsing code --- src/SimpleJWT/Keys/RSAKey.php | 28 +++++++++++++++++++ tests/KeyFactoryTest.php | 13 ++++++++- tests/rsa_private_pkcs8.pem | 16 +++++++++++ .../{rsa_public_pkcs1.pem => rsa_public.pem} | 0 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/rsa_private_pkcs8.pem rename tests/{rsa_public_pkcs1.pem => rsa_public.pem} (100%) diff --git a/src/SimpleJWT/Keys/RSAKey.php b/src/SimpleJWT/Keys/RSAKey.php index 1e28250..15035f6 100644 --- a/src/SimpleJWT/Keys/RSAKey.php +++ b/src/SimpleJWT/Keys/RSAKey.php @@ -119,6 +119,34 @@ public function __construct($data, string $format, ?string $password = null, ?st $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()); + } elseif (preg_match(Key::PEM_PKCS8_PRIVATE, $data, $matches)) { + /** @var string $binary */ + $binary = base64_decode($matches[1]); + if ($binary == FALSE) throw new KeyException('Cannot read PEM key'); + + $seq = $der->decode($binary); + + $version = $seq->getChildAt(0)->getValue(); + if ($version != 0) throw new KeyException('Invalid private key version: ' . $version); + + $key_oid = $seq->getChildAt(1)->getChildAt(0)->getValue(); + if ($key_oid != self::OID) throw new KeyException('Invalid key type: ' . $key_oid); + + $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()); } else { throw new KeyException('Unrecognised key format'); } diff --git a/tests/KeyFactoryTest.php b/tests/KeyFactoryTest.php index 69d262b..7262dae 100644 --- a/tests/KeyFactoryTest.php +++ b/tests/KeyFactoryTest.php @@ -18,7 +18,7 @@ public function testPEMRSA_PKCS1() { $this->assertEquals("2sUA6n24v51XMCk_H3WKTELYfEyWzdr6yI3a8xNol8RlLNcjr9NxthPWkOGY6uT1JAbDgBNsiPQXpAiOdRD5FQ", $key_data['p']); $this->assertEquals("xFpV_GjvAlkzeElY_fb5AWgV9_APIfyuo3NxqmvaRTIUyatsHdrUNE0jsOTJF-2uZZ818sbHltDOb-x3_3y0lw", $key_data['q']); - $pem = file_get_contents('rsa_public_pkcs1.pem'); + $pem = file_get_contents('rsa_public.pem'); $key = KeyFactory::create($pem, 'pem'); $this->assertInstanceOf(RSAKey::class, $key); $key_data = $key->getKeyData(); @@ -26,6 +26,17 @@ public function testPEMRSA_PKCS1() { $this->assertEquals("p8wHcPeYsIbQBwFg-mUXIFjZI-b1gQJuNoGboa3ub7KMVjmob9c4mOqc8j2u9cMS6PLnqGMIiM2H1HVDZSwZs6kS7Kq942uNBsut2cHy-PZd5Jq3cWIoQZwnhrjg_OfwbJugYeGe0Orub8J42qyT8HuhLX65Q6iSSf_3bo1Rr2M", $key_data['n']); } + public function testPEMRSA_PKCS8() { + $pem = file_get_contents('rsa_private_pkcs8.pem'); + $key = KeyFactory::create($pem, 'pem'); + $this->assertInstanceOf(RSAKey::class, $key); + $key_data = $key->getKeyData(); + $this->assertFalse($key->isPublic()); + $this->assertEquals("p8wHcPeYsIbQBwFg-mUXIFjZI-b1gQJuNoGboa3ub7KMVjmob9c4mOqc8j2u9cMS6PLnqGMIiM2H1HVDZSwZs6kS7Kq942uNBsut2cHy-PZd5Jq3cWIoQZwnhrjg_OfwbJugYeGe0Orub8J42qyT8HuhLX65Q6iSSf_3bo1Rr2M", $key_data['n']); + $this->assertEquals("Wxts7umA_lg0m5kkDtDUvbuAKv48TtADB5VX63GFBSDtEeQ8kH1LPbwle2ICnW5N1i4NmmArQhxWpAUHkudfDExa9fZf5wUtsDlw8zNhzoDKqtw50D1BhWCfYO19IobTL-x3RJmPAepK5IH8ZYfGbQD2ZEwN0WF2I8sBg4Pu6Q", $key_data['d']); + $this->assertEquals("2sUA6n24v51XMCk_H3WKTELYfEyWzdr6yI3a8xNol8RlLNcjr9NxthPWkOGY6uT1JAbDgBNsiPQXpAiOdRD5FQ", $key_data['p']); + $this->assertEquals("xFpV_GjvAlkzeElY_fb5AWgV9_APIfyuo3NxqmvaRTIUyatsHdrUNE0jsOTJF-2uZZ818sbHltDOb-x3_3y0lw", $key_data['q']); + } public function testPEMEC() { $pem = file_get_contents('ec_private.pem'); diff --git a/tests/rsa_private_pkcs8.pem b/tests/rsa_private_pkcs8.pem new file mode 100644 index 0000000..dd1575e --- /dev/null +++ b/tests/rsa_private_pkcs8.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdAIBADANBgkqhkiG9w0BAQEFAASCAl4wggJaAgEAAoGBAKfMB3D3mLCG0AcB +YPplFyBY2SPm9YECbjaBm6Gt7m+yjFY5qG/XOJjqnPI9rvXDEujy56hjCIjNh9R1 +Q2UsGbOpEuyqveNrjQbLrdnB8vj2XeSat3FiKEGcJ4a44Pzn8GyboGHhntDq7m/C +eNqsk/B7oS1+uUOokkn/926NUa9jAgMBAAECf1sbbO7pgP5YNJuZJA7Q1L27gCr+ +PE7QAweVV+txhQUg7RHkPJB9Sz28JXtiAp1uTdYuDZpgK0IcVqQFB5LnXwxMWvX2 +X+cFLbA5cPMzYc6AyqrcOdA9QYVgn2DtfSKG0y/sd0SZjwHqSuSB/GWHxm0A9mRM +DdFhdiPLAYOD7ukCQQDaxQDqfbi/nVcwKT8fdYpMQth8TJbN2vrIjdrzE2iXxGUs +1yOv03G2E9aQ4Zjq5PUkBsOAE2yI9BekCI51EPkVAkEAxFpV/GjvAlkzeElY/fb5 +AWgV9/APIfyuo3NxqmvaRTIUyatsHdrUNE0jsOTJF+2uZZ818sbHltDOb+x3/3y0 +lwJAJKcE9ESEA3Z0+Riv/rFOrmA0rP6X9X1OrvM1T6xcxbCd0tlonTirwHmqZTOm +zxP1Dkgj0P1wHbHnlA/q7iod0QJBAKcmd7ht47lOEkC9v+JCkmseHkV4uIkoP8qp +BgQb2C27mnKrWVh45ti3KkD2IjIahbHAvP41Nccvbe1dkjGOCXUCQGbnRafT+O5B +YNjKRUNVDDyTlrdrAfG/C3v6REH3Q+6Z7kCYBi1ZyryUUwH+2iLpO9+6w1geGFPT +siByv/VbT1I= +-----END PRIVATE KEY----- diff --git a/tests/rsa_public_pkcs1.pem b/tests/rsa_public.pem similarity index 100% rename from tests/rsa_public_pkcs1.pem rename to tests/rsa_public.pem