From 3d35690a0a6c71e2c81fc6f6619755de924fe879 Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Wed, 6 Jan 2021 23:25:23 +0100 Subject: [PATCH 1/3] Allow to extend X509 extensions --- phpseclib/File/Traits/Extensions.php | 64 +++++++++++++++++ phpseclib/File/X509.php | 18 +++++ tests/Unit/File/X509/X509ExtensionTest.php | 83 ++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 phpseclib/File/Traits/Extensions.php create mode 100644 tests/Unit/File/X509/X509ExtensionTest.php diff --git a/phpseclib/File/Traits/Extensions.php b/phpseclib/File/Traits/Extensions.php new file mode 100644 index 00000000..8705745d --- /dev/null +++ b/phpseclib/File/Traits/Extensions.php @@ -0,0 +1,64 @@ + + * @copyright 2012 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib3\File\Traits; + +trait Extensions +{ + /** + * @var array + * @access private + */ + private static $extensions = []; + + /** + * @var array + * @access private + */ + private $extensionValues = []; + + /** + * Register the mapping for a custom/unsupported extension. + * + * @param string $id + * @param array $mapping + */ + public static function registerExtension($id, array $mapping) + { + self::$extensions[$id] = $mapping; + } + + /** + * Register the mapping for a custom/unsupported extension. + * + * @param string $id + * @param mixed $value + */ + public function setExtensionValue($id, $value) + { + $this->extensionValues[$id] = $value; + } +} diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index 874bcb11..51bf4c81 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -42,6 +42,7 @@ use phpseclib3\Crypt\RSA\Formats\Keys\PSS; use phpseclib3\Exception\UnsupportedAlgorithmException; use phpseclib3\File\ASN1\Element; use phpseclib3\File\ASN1\Maps; +use phpseclib3\File\Traits\Extensions; use phpseclib3\Math\BigInteger; /** @@ -53,6 +54,8 @@ use phpseclib3\Math\BigInteger; */ class X509 { + use Extensions; + /** * Flag to only accept signatures signed by certificate authorities * @@ -558,6 +561,10 @@ class X509 $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string; $filters['directoryName']['rdnSequence']['value'] = $type_utf8_string; + foreach (self::$extensions as $extension) { + $filters['tbsCertificate']['extensions'][] = $extension; + } + /* in the case of policyQualifiers/qualifier, the type has to be \phpseclib3\File\ASN1::TYPE_IA5_STRING. \phpseclib3\File\ASN1::TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random characters. @@ -641,6 +648,13 @@ class X509 */ private function mapOutExtensions(&$root, $path) { + foreach ($this->extensionValues as $id => $value) { + $root['tbsCertificate']['extensions'][] = [ + 'extnId' => $id, + 'extnValue' => $value, + ]; + } + $extensions = &$this->subArray($root, $path); if (is_array($extensions)) { @@ -850,6 +864,10 @@ class X509 return true; } + if (isset(self::$extensions[$extnId])) { + return self::$extensions[$extnId]; + } + switch ($extnId) { case 'id-ce-keyUsage': return Maps\KeyUsage::MAP; diff --git a/tests/Unit/File/X509/X509ExtensionTest.php b/tests/Unit/File/X509/X509ExtensionTest.php new file mode 100644 index 00000000..781a75db --- /dev/null +++ b/tests/Unit/File/X509/X509ExtensionTest.php @@ -0,0 +1,83 @@ + + * @copyright 2014 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +use phpseclib3\Crypt\RSA; +use phpseclib3\File\ASN1; +use phpseclib3\File\X509; + +class Unit_File_X509_X509ExtensionTest extends PhpseclibTestCase +{ + public function testCustomExtension() + { + $customExtensionData = [ + 'toggle' => true, + 'num' => 3, + 'name' => 'Johnny', + 'list' => ['foo', 'bar'], + ]; + $customExtensionName = 'cust'; + $customExtensionNumber = '2.16.840.1.101.3.4.2.99'; + + ASN1::loadOIDs([ + $customExtensionName => $customExtensionNumber, + ]); + + X509::registerExtension($customExtensionName, [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'toggle' => ['type' => ASN1::TYPE_BOOLEAN], + 'num' => ['type' => ASN1::TYPE_INTEGER], + 'name' => ['type' => ASN1::TYPE_OCTET_STRING], + 'list' => [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 0, + 'max' => -1, + 'children' => ['type' => ASN1::TYPE_OCTET_STRING], + ], + ], + ]); + + $privateKey = RSA::createKey(); + + $publicKey = $privateKey->getPublicKey(); + + $subject = new X509(); + $subject->setDNProp('id-at-organizationName', 'phpseclib CA cert'); + $subject->setPublicKey($publicKey); + + $issuer = new X509(); + $issuer->setPrivateKey($privateKey); + $issuer->setDN($subject->getDN()); + + $x509 = new X509(); + $x509->setExtensionValue($customExtensionName, $customExtensionData); + $x509->makeCA(); + + $result = $x509->sign($issuer, $subject); + + $certificate = $x509->saveX509($result); + + $x509 = new X509(); + + $decodedData = $x509->loadX509($certificate); + $customExtensionDecodedData = null; + + foreach ($decodedData['tbsCertificate']['extensions'] as $extension) { + if ($extension['extnId'] === $customExtensionName) { + $customExtensionDecodedData = $extension['extnValue']; + + break; + } + } + + $this->assertTrue($customExtensionDecodedData['toggle']); + $this->assertInstanceOf('phpseclib3\Math\BigInteger', $customExtensionDecodedData['num']); + $this->assertSame('3', (string) $customExtensionDecodedData['num']); + $this->assertSame('Johnny', $customExtensionDecodedData['name']); + $this->assertSame(['foo', 'bar'], $customExtensionDecodedData['list']); + } +} From e69049be5e90e278377015cbd743e7ed20dae62c Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Thu, 14 Jan 2021 16:15:55 +0100 Subject: [PATCH 2/3] Add getRegisteredExtension() --- phpseclib/File/Traits/Extensions.php | 64 ---------------------- phpseclib/File/X509.php | 54 +++++++++++++++++- tests/Unit/File/X509/X509ExtensionTest.php | 46 ++++++++++++++-- 3 files changed, 92 insertions(+), 72 deletions(-) delete mode 100644 phpseclib/File/Traits/Extensions.php diff --git a/phpseclib/File/Traits/Extensions.php b/phpseclib/File/Traits/Extensions.php deleted file mode 100644 index 8705745d..00000000 --- a/phpseclib/File/Traits/Extensions.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @copyright 2012 Jim Wigginton - * @license http://www.opensource.org/licenses/mit-license.html MIT License - * @link http://phpseclib.sourceforge.net - */ - -namespace phpseclib3\File\Traits; - -trait Extensions -{ - /** - * @var array - * @access private - */ - private static $extensions = []; - - /** - * @var array - * @access private - */ - private $extensionValues = []; - - /** - * Register the mapping for a custom/unsupported extension. - * - * @param string $id - * @param array $mapping - */ - public static function registerExtension($id, array $mapping) - { - self::$extensions[$id] = $mapping; - } - - /** - * Register the mapping for a custom/unsupported extension. - * - * @param string $id - * @param mixed $value - */ - public function setExtensionValue($id, $value) - { - $this->extensionValues[$id] = $value; - } -} diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index 51bf4c81..57dbf052 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -54,8 +54,6 @@ use phpseclib3\Math\BigInteger; */ class X509 { - use Extensions; - /** * Flag to only accept signatures signed by certificate authorities * @@ -270,6 +268,12 @@ class X509 */ private $challenge; + /** + * @var array + * @access private + */ + private $extensionValues = []; + /** * OIDs loaded * @@ -294,6 +298,12 @@ class X509 */ private static $disable_url_fetch = false; + /** + * @var array + * @access private + */ + private static $extensions = []; + /** * Default Constructor. * @@ -4014,4 +4024,44 @@ class X509 return false; } + + /** + * Register the mapping for a custom/unsupported extension. + * + * @param string $id + * @param array $mapping + */ + public static function registerExtension($id, array $mapping) + { + if (isset(self::$extensions[$id]) && self::$extensions[$id] !== $mapping) { + throw new \RuntimeException( + 'Extension ' . $id . ' has already been defined with a different mapping.' + ); + } + + self::$extensions[$id] = $mapping; + } + + /** + * Register the mapping for a custom/unsupported extension. + * + * @param string $id + * + * @return array|null + */ + public static function getRegisteredExtension($id) + { + return isset(self::$extensions[$id]) ? self::$extensions[$id] : null; + } + + /** + * Register the mapping for a custom/unsupported extension. + * + * @param string $id + * @param mixed $value + */ + public function setExtensionValue($id, $value) + { + $this->extensionValues[$id] = $value; + } } diff --git a/tests/Unit/File/X509/X509ExtensionTest.php b/tests/Unit/File/X509/X509ExtensionTest.php index 781a75db..4613d9e3 100644 --- a/tests/Unit/File/X509/X509ExtensionTest.php +++ b/tests/Unit/File/X509/X509ExtensionTest.php @@ -21,12 +21,7 @@ class Unit_File_X509_X509ExtensionTest extends PhpseclibTestCase ]; $customExtensionName = 'cust'; $customExtensionNumber = '2.16.840.1.101.3.4.2.99'; - - ASN1::loadOIDs([ - $customExtensionName => $customExtensionNumber, - ]); - - X509::registerExtension($customExtensionName, [ + $customExtensionMapping = [ 'type' => ASN1::TYPE_SEQUENCE, 'children' => [ 'toggle' => ['type' => ASN1::TYPE_BOOLEAN], @@ -39,8 +34,14 @@ class Unit_File_X509_X509ExtensionTest extends PhpseclibTestCase 'children' => ['type' => ASN1::TYPE_OCTET_STRING], ], ], + ]; + + ASN1::loadOIDs([ + $customExtensionName => $customExtensionNumber, ]); + X509::registerExtension($customExtensionName, $customExtensionMapping); + $privateKey = RSA::createKey(); $publicKey = $privateKey->getPublicKey(); @@ -79,5 +80,38 @@ class Unit_File_X509_X509ExtensionTest extends PhpseclibTestCase $this->assertSame('3', (string) $customExtensionDecodedData['num']); $this->assertSame('Johnny', $customExtensionDecodedData['name']); $this->assertSame(['foo', 'bar'], $customExtensionDecodedData['list']); + $this->assertSame($customExtensionMapping, X509::getRegisteredExtension($customExtensionName)); + } + + public function testCustomExtensionRegisterTwiceTheSame() + { + $customExtensionMapping = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'toggle' => ['type' => ASN1::TYPE_BOOLEAN], + 'num' => ['type' => ASN1::TYPE_INTEGER], + 'name' => ['type' => ASN1::TYPE_OCTET_STRING], + 'list' => [ + 'type' => ASN1::TYPE_SEQUENCE, + 'min' => 0, + 'max' => -1, + 'children' => ['type' => ASN1::TYPE_OCTET_STRING], + ], + ], + ]; + + X509::registerExtension('foo', $customExtensionMapping); + X509::registerExtension('foo', $customExtensionMapping); + + $this->assertSame($customExtensionMapping, X509::getRegisteredExtension('foo')); + } + + public function testCustomExtensionRegisterConflict() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Extension bar has already been defined with a different mapping.'); + + X509::registerExtension('bar', ['type' => ASN1::TYPE_OCTET_STRING]); + X509::registerExtension('bar', ['type' => ASN1::TYPE_ANY]); } } From 57476bf304054e7e37f064e873df1fa4e2d02ea7 Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Thu, 14 Jan 2021 16:18:07 +0100 Subject: [PATCH 3/3] Cleanup import --- phpseclib/File/X509.php | 1 - 1 file changed, 1 deletion(-) diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index 57dbf052..45edc25d 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -42,7 +42,6 @@ use phpseclib3\Crypt\RSA\Formats\Keys\PSS; use phpseclib3\Exception\UnsupportedAlgorithmException; use phpseclib3\File\ASN1\Element; use phpseclib3\File\ASN1\Maps; -use phpseclib3\File\Traits\Extensions; use phpseclib3\Math\BigInteger; /**