From 514b907ab04975557becb35623adf9db5b1b4d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9on=20Melis?= Date: Tue, 21 May 2024 17:03:21 +0200 Subject: [PATCH 1/2] Fix support for Ed448 private keys in PKCS#8 format --- phpseclib/Crypt/EC/Formats/Keys/PKCS8.php | 46 ++++++++++++++++------- tests/Unit/Crypt/EC/KeyTest.php | 27 +++++++++++++ 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php b/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php index 0ec7742f..7dab2193 100644 --- a/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php +++ b/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php @@ -129,12 +129,21 @@ abstract class PKCS8 extends Progenitor $components = []; if (isset($key['privateKey'])) { - $components['curve'] = $key['privateKeyAlgorithm']['algorithm'] == 'id-Ed25519' ? new Ed25519() : new Ed448(); - - // 0x04 == octet string - // 0x20 == length (32 bytes) - if (substr($key['privateKey'], 0, 2) != "\x04\x20") { - throw new \RuntimeException('The first two bytes of the private key field should be 0x0420'); + if ($key['privateKeyAlgorithm']['algorithm'] == 'id-Ed25519') { + $components['curve'] = new Ed25519(); + // 0x04 == octet string + // 0x20 == length (32 bytes) + if (substr($key['privateKey'], 0, 2) != "\x04\x20") { + throw new \RuntimeException('The first two bytes of the Ed25519 private key field should be 0x0420'); + } + } else { + // Assume Ed448 + $components['curve'] = new Ed448(); + // 0x04 == octet string + // 0x39 == length (57 bytes) + if (substr($key['privateKey'], 0, 2) != "\x04\x39") { + throw new \RuntimeException('The first two bytes of the Ed448 private key field should be 0x0439'); + } } $arr = $components['curve']->extractSecret(substr($key['privateKey'], 2)); $components['dA'] = $arr['dA']; @@ -207,13 +216,24 @@ abstract class PKCS8 extends Progenitor } if ($curve instanceof TwistedEdwardsCurve) { - return self::wrapPrivateKey( - "\x04\x20" . $secret, - [], - null, - $password, - $curve instanceof Ed25519 ? 'id-Ed25519' : 'id-Ed448' - ); + if ($curve instanceof Ed25519) { + return self::wrapPrivateKey( + "\x04\x20" . $secret, + [], + null, + $password, + 'id-Ed25519' + ); + } else { + // Assume Ed448 + return self::wrapPrivateKey( + "\x04\x39" . $secret, + [], + null, + $password, + 'id-Ed448' + ); + } } $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes(); diff --git a/tests/Unit/Crypt/EC/KeyTest.php b/tests/Unit/Crypt/EC/KeyTest.php index adbb8f42..83ea2e8d 100644 --- a/tests/Unit/Crypt/EC/KeyTest.php +++ b/tests/Unit/Crypt/EC/KeyTest.php @@ -297,6 +297,33 @@ WEKBIQAZv0QJaYTN/oVBusFn3DuWyFCGqjC2tssMXDitcDFm4Q== $this->assertSameNL('Ed25519', $key->getPublicKey()->getCurve()); } + // Generate with: + // openssl genpkey -algorithm ed448 | openssl ec -pubout + public function testEd448PublicKey() + { + $expected = '-----BEGIN PUBLIC KEY----- +MEMwBQYDK2VxAzoAsA7zbld48IfDhm7Qd6FYrvnljtjhPRRqZi04NWyj8VXrWe1x +BMLQFJEE0JDmKayUWpUWsRXwmb6A +-----END PUBLIC KEY-----'; + $key = PublicKeyLoader::load($expected); + $this->assertSameNL('Ed448', $key->getCurve()); + $this->assertSameNL($expected, $key->toString('PKCS8')); + } + + // Generate with: + // openssl genpkey -algorithm ed448 + public function testEd448PrivateKey() + { + $expected = '-----BEGIN PRIVATE KEY----- +MEcCAQAwBQYDK2VxBDsEOettXaJYob4hJNKJNOD+FfMvdesLKNp0KwochI6AKmAb +tWhtkn99WOjd1PsGMh9zz2Vhdg3MwasOMQ== +-----END PRIVATE KEY-----'; + $key = PublicKeyLoader::load($expected); + $this->assertSameNL($expected, $key->toString('PKCS8')); + $this->assertSameNL('Ed448', $key->getCurve()); + $this->assertSameNL('Ed448', $key->getPublicKey()->getCurve()); + } + public function testPuTTYnistp256() { $key = PublicKeyLoader::load($expected = 'PuTTY-User-Key-File-2: ecdsa-sha2-nistp256 From b718a63aae519f630589769b31f4b9e164e72613 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sat, 25 May 2024 14:43:40 -0500 Subject: [PATCH 2/2] EC/Keys/PKCS8: code reduction --- phpseclib/Crypt/EC/Formats/Keys/PKCS8.php | 48 +++++++---------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php b/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php index 7dab2193..e5012e5d 100644 --- a/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php +++ b/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php @@ -129,21 +129,14 @@ abstract class PKCS8 extends Progenitor $components = []; if (isset($key['privateKey'])) { - if ($key['privateKeyAlgorithm']['algorithm'] == 'id-Ed25519') { - $components['curve'] = new Ed25519(); - // 0x04 == octet string - // 0x20 == length (32 bytes) - if (substr($key['privateKey'], 0, 2) != "\x04\x20") { - throw new \RuntimeException('The first two bytes of the Ed25519 private key field should be 0x0420'); - } - } else { - // Assume Ed448 - $components['curve'] = new Ed448(); - // 0x04 == octet string - // 0x39 == length (57 bytes) - if (substr($key['privateKey'], 0, 2) != "\x04\x39") { - throw new \RuntimeException('The first two bytes of the Ed448 private key field should be 0x0439'); - } + $components['curve'] = $key['privateKeyAlgorithm']['algorithm'] == 'id-Ed25519' ? new Ed25519() : new Ed448(); + $expected = chr(ASN1::TYPE_OCTET_STRING) . ASN1::encodeLength($components['curve']::SIZE); + if (substr($key['privateKey'], 0, 2) != $expected) { + throw new \RuntimeException( + 'The first two bytes of the ' . + $key['privateKeyAlgorithm']['algorithm'] . + ' private key field should be 0x' . bin2hex($expected) + ); } $arr = $components['curve']->extractSecret(substr($key['privateKey'], 2)); $components['dA'] = $arr['dA']; @@ -216,24 +209,13 @@ abstract class PKCS8 extends Progenitor } if ($curve instanceof TwistedEdwardsCurve) { - if ($curve instanceof Ed25519) { - return self::wrapPrivateKey( - "\x04\x20" . $secret, - [], - null, - $password, - 'id-Ed25519' - ); - } else { - // Assume Ed448 - return self::wrapPrivateKey( - "\x04\x39" . $secret, - [], - null, - $password, - 'id-Ed448' - ); - } + return self::wrapPrivateKey( + chr(ASN1::TYPE_OCTET_STRING) . ASN1::encodeLength($curve::SIZE) . $secret, + [], + null, + $password, + $curve instanceof Ed25519 ? 'id-Ed25519' : 'id-Ed448' + ); } $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();