Allow to extend X509 extensions

This commit is contained in:
Bastien Miclo 2021-01-06 23:25:23 +01:00
parent 7b7d254a6c
commit 3d35690a0a
No known key found for this signature in database
GPG Key ID: 06FB4BC7489F07BC
3 changed files with 165 additions and 0 deletions

View File

@ -0,0 +1,64 @@
<?php
/**
* Pure-PHP X.509 Parser
*
* PHP version 5
*
* Encode and decode X.509 certificates.
*
* The extensions are from {@link http://tools.ietf.org/html/rfc5280 RFC5280} and
* {@link http://web.archive.org/web/19961027104704/http://www3.netscape.com/eng/security/cert-exts.html Netscape Certificate Extensions}.
*
* Note that loading an X.509 certificate and resaving it may invalidate the signature. The reason being that the signature is based on a
* portion of the certificate that contains optional parameters with default values. ie. if the parameter isn't there the default value is
* used. Problem is, if the parameter is there and it just so happens to have the default value there are two ways that that parameter can
* be encoded. It can be encoded explicitly or left out all together. This would effect the signature value and thus may invalidate the
* the certificate all together unless the certificate is re-signed.
*
* @category File
* @package X509
* @author Jim Wigginton <terrafrost@php.net>
* @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;
}
}

View File

@ -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;

View File

@ -0,0 +1,83 @@
<?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';
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']);
}
}