From 3d35690a0a6c71e2c81fc6f6619755de924fe879 Mon Sep 17 00:00:00 2001 From: Bastien Miclo Date: Wed, 6 Jan 2021 23:25:23 +0100 Subject: [PATCH] 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']); + } +}