mirror of
https://github.com/danog/phpseclib.git
synced 2024-12-11 08:39:43 +01:00
Merge pull request #1573 from kylekatarnls/feature/allow-to-use-extensions
Allow to extend X509 extensions
This commit is contained in:
commit
3e32d5a853
@ -267,6 +267,12 @@ class X509
|
|||||||
*/
|
*/
|
||||||
private $challenge;
|
private $challenge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
private $extensionValues = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OIDs loaded
|
* OIDs loaded
|
||||||
*
|
*
|
||||||
@ -291,6 +297,12 @@ class X509
|
|||||||
*/
|
*/
|
||||||
private static $disable_url_fetch = false;
|
private static $disable_url_fetch = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
private static $extensions = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Constructor.
|
* Default Constructor.
|
||||||
*
|
*
|
||||||
@ -558,6 +570,10 @@ class X509
|
|||||||
$filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
|
$filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
|
||||||
$filters['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.
|
/* 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
|
\phpseclib3\File\ASN1::TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random
|
||||||
characters.
|
characters.
|
||||||
@ -641,6 +657,13 @@ class X509
|
|||||||
*/
|
*/
|
||||||
private function mapOutExtensions(&$root, $path)
|
private function mapOutExtensions(&$root, $path)
|
||||||
{
|
{
|
||||||
|
foreach ($this->extensionValues as $id => $value) {
|
||||||
|
$root['tbsCertificate']['extensions'][] = [
|
||||||
|
'extnId' => $id,
|
||||||
|
'extnValue' => $value,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$extensions = &$this->subArray($root, $path);
|
$extensions = &$this->subArray($root, $path);
|
||||||
|
|
||||||
if (is_array($extensions)) {
|
if (is_array($extensions)) {
|
||||||
@ -850,6 +873,10 @@ class X509
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset(self::$extensions[$extnId])) {
|
||||||
|
return self::$extensions[$extnId];
|
||||||
|
}
|
||||||
|
|
||||||
switch ($extnId) {
|
switch ($extnId) {
|
||||||
case 'id-ce-keyUsage':
|
case 'id-ce-keyUsage':
|
||||||
return Maps\KeyUsage::MAP;
|
return Maps\KeyUsage::MAP;
|
||||||
@ -3996,4 +4023,44 @@ class X509
|
|||||||
|
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
117
tests/Unit/File/X509/X509ExtensionTest.php
Normal file
117
tests/Unit/File/X509/X509ExtensionTest.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Jim Wigginton <terrafrost@php.net>
|
||||||
|
* @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';
|
||||||
|
$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],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
ASN1::loadOIDs([
|
||||||
|
$customExtensionName => $customExtensionNumber,
|
||||||
|
]);
|
||||||
|
|
||||||
|
X509::registerExtension($customExtensionName, $customExtensionMapping);
|
||||||
|
|
||||||
|
$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']);
|
||||||
|
$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]);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user