![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/vendor/spomky-labs/pki-framework/src/X509/Certificate/ |
<?php declare(strict_types=1); namespace SpomkyLabs\Pki\X509\Certificate; use Brick\Math\BigInteger; use function count; use LogicException; use SpomkyLabs\Pki\ASN1\Element; use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence; use SpomkyLabs\Pki\ASN1\Type\Primitive\Integer; use SpomkyLabs\Pki\ASN1\Type\Tagged\ExplicitlyTaggedType; use SpomkyLabs\Pki\ASN1\Type\Tagged\ImplicitlyTaggedType; use SpomkyLabs\Pki\CryptoBridge\Crypto; use SpomkyLabs\Pki\CryptoTypes\AlgorithmIdentifier\AlgorithmIdentifier; use SpomkyLabs\Pki\CryptoTypes\AlgorithmIdentifier\Feature\SignatureAlgorithmIdentifier; use SpomkyLabs\Pki\CryptoTypes\Asymmetric\PrivateKeyInfo; use SpomkyLabs\Pki\CryptoTypes\Asymmetric\PublicKeyInfo; use SpomkyLabs\Pki\X501\ASN1\Name; use SpomkyLabs\Pki\X509\Certificate\Extension\AuthorityKeyIdentifierExtension; use SpomkyLabs\Pki\X509\Certificate\Extension\Extension; use SpomkyLabs\Pki\X509\Certificate\Extension\SubjectKeyIdentifierExtension; use SpomkyLabs\Pki\X509\CertificationRequest\CertificationRequest; use function strval; use UnexpectedValueException; /** * Implements *TBSCertificate* ASN.1 type. * * @see https://tools.ietf.org/html/rfc5280#section-4.1.2 */ final class TBSCertificate { // Certificate version enumerations final public const VERSION_1 = 0; final public const VERSION_2 = 1; final public const VERSION_3 = 2; /** * Certificate version. */ private ?int $version = null; /** * Serial number. */ private ?string $serialNumber = null; /** * Signature algorithm. */ private ?SignatureAlgorithmIdentifier $signature = null; /** * Issuer unique identifier. */ private ?UniqueIdentifier $issuerUniqueID = null; /** * Subject unique identifier. */ private ?UniqueIdentifier $subjectUniqueID = null; /** * Extensions. */ private Extensions $extensions; /** * @param Name $subject Certificate subject * @param PublicKeyInfo $subjectPublicKeyInfo Subject public key * @param Name $issuer Certificate issuer * @param Validity $validity Validity period */ private function __construct( private Name $subject, private PublicKeyInfo $subjectPublicKeyInfo, private Name $issuer, private Validity $validity ) { $this->extensions = Extensions::create(); } public static function create( Name $subject, PublicKeyInfo $subjectPublicKeyInfo, Name $issuer, Validity $validity ): self { return new self($subject, $subjectPublicKeyInfo, $issuer, $validity); } /** * Initialize from ASN.1. */ public static function fromASN1(Sequence $seq): self { $idx = 0; if ($seq->hasTagged(0)) { ++$idx; $version = $seq->getTagged(0) ->asExplicit() ->asInteger() ->intNumber(); } else { $version = self::VERSION_1; } $serial = $seq->at($idx++) ->asInteger() ->number(); $algo = AlgorithmIdentifier::fromASN1($seq->at($idx++)->asSequence()); if (! $algo instanceof SignatureAlgorithmIdentifier) { throw new UnexpectedValueException('Unsupported signature algorithm ' . $algo->name() . '.'); } $issuer = Name::fromASN1($seq->at($idx++)->asSequence()); $validity = Validity::fromASN1($seq->at($idx++)->asSequence()); $subject = Name::fromASN1($seq->at($idx++)->asSequence()); $pki = PublicKeyInfo::fromASN1($seq->at($idx++)->asSequence()); $tbs_cert = self::create($subject, $pki, $issuer, $validity) ->withVersion($version) ->withSerialNumber($serial) ->withSignature($algo) ; if ($seq->hasTagged(1)) { $tbs_cert = $tbs_cert->withIssuerUniqueID(UniqueIdentifier::fromASN1( $seq->getTagged(1) ->asImplicit(Element::TYPE_BIT_STRING) ->asBitString() )); } if ($seq->hasTagged(2)) { $tbs_cert = $tbs_cert->withSubjectUniqueID(UniqueIdentifier::fromASN1( $seq->getTagged(2) ->asImplicit(Element::TYPE_BIT_STRING) ->asBitString() )); } if ($seq->hasTagged(3)) { $tbs_cert = $tbs_cert->withExtensions(Extensions::fromASN1($seq->getTagged(3)->asExplicit()->asSequence())); } return $tbs_cert; } /** * Initialize from certification request. * * Note that signature is not verified and must be done by the caller. */ public static function fromCSR(CertificationRequest $cr): self { $cri = $cr->certificationRequestInfo(); $tbs_cert = self::create( $cri->subject(), $cri->subjectPKInfo(), Name::create(), Validity::fromStrings(null, null) ); // if CSR has Extension Request attribute if ($cri->hasAttributes()) { $attribs = $cri->attributes(); if ($attribs->hasExtensionRequest()) { $tbs_cert = $tbs_cert->withExtensions($attribs->extensionRequest()->extensions()); } } // add Subject Key Identifier extension return $tbs_cert->withAdditionalExtensions( SubjectKeyIdentifierExtension::create(false, $cri->subjectPKInfo()->keyIdentifier()) ); } /** * Get self with fields set from the issuer's certificate. * * Issuer shall be set to issuing certificate's subject. Authority key identifier extensions shall be added with a * key identifier set to issuing certificate's public key identifier. * * @param Certificate $cert Issuing party's certificate */ public function withIssuerCertificate(Certificate $cert): self { $obj = clone $this; // set issuer DN from cert's subject $obj->issuer = $cert->tbsCertificate() ->subject(); // add authority key identifier extension $key_id = $cert->tbsCertificate() ->subjectPublicKeyInfo() ->keyIdentifier(); $obj->extensions = $obj->extensions->withExtensions(AuthorityKeyIdentifierExtension::create(false, $key_id)); return $obj; } /** * Get self with given version. * * If version is not set, appropriate version is automatically determined during signing. */ public function withVersion(int $version): self { $obj = clone $this; $obj->version = $version; return $obj; } /** * Get self with given serial number. * * @param int|string $serial Base 10 number */ public function withSerialNumber(int|string $serial): self { $obj = clone $this; $obj->serialNumber = strval($serial); return $obj; } /** * Get self with random positive serial number. * * @param int $size Number of random bytes */ public function withRandomSerialNumber(int $size): self { // ensure that first byte is always non-zero and having first bit unset $num = BigInteger::of(random_int(1, 0x7f)); for ($i = 1; $i < $size; ++$i) { $num = $num->shiftedLeft(8); $num = $num->plus(random_int(0, 0xff)); } return $this->withSerialNumber($num->toBase(10)); } /** * Get self with given signature algorithm. */ public function withSignature(SignatureAlgorithmIdentifier $algo): self { $obj = clone $this; $obj->signature = $algo; return $obj; } /** * Get self with given issuer. */ public function withIssuer(Name $issuer): self { $obj = clone $this; $obj->issuer = $issuer; return $obj; } /** * Get self with given validity. */ public function withValidity(Validity $validity): self { $obj = clone $this; $obj->validity = $validity; return $obj; } /** * Get self with given subject. */ public function withSubject(Name $subject): self { $obj = clone $this; $obj->subject = $subject; return $obj; } /** * Get self with given subject public key info. */ public function withSubjectPublicKeyInfo(PublicKeyInfo $pub_key_info): self { $obj = clone $this; $obj->subjectPublicKeyInfo = $pub_key_info; return $obj; } /** * Get self with issuer unique ID. */ public function withIssuerUniqueID(UniqueIdentifier $id): self { $obj = clone $this; $obj->issuerUniqueID = $id; return $obj; } /** * Get self with subject unique ID. */ public function withSubjectUniqueID(UniqueIdentifier $id): self { $obj = clone $this; $obj->subjectUniqueID = $id; return $obj; } /** * Get self with given extensions. */ public function withExtensions(Extensions $extensions): self { $obj = clone $this; $obj->extensions = $extensions; return $obj; } /** * Get self with extensions added. * * @param Extension ...$exts One or more Extension objects */ public function withAdditionalExtensions(Extension ...$exts): self { $obj = clone $this; $obj->extensions = $obj->extensions->withExtensions(...$exts); return $obj; } /** * Check whether version is set. */ public function hasVersion(): bool { return isset($this->version); } /** * Get certificate version. */ public function version(): int { if (! $this->hasVersion()) { throw new LogicException('version not set.'); } return $this->version; } /** * Check whether serial number is set. */ public function hasSerialNumber(): bool { return isset($this->serialNumber); } /** * Get serial number. * * @return string Base 10 integer */ public function serialNumber(): string { if (! $this->hasSerialNumber()) { throw new LogicException('serialNumber not set.'); } return $this->serialNumber; } /** * Check whether signature algorithm is set. */ public function hasSignature(): bool { return isset($this->signature); } /** * Get signature algorithm. */ public function signature(): SignatureAlgorithmIdentifier { if (! $this->hasSignature()) { throw new LogicException('signature not set.'); } return $this->signature; } public function issuer(): Name { return $this->issuer; } /** * Get validity period. */ public function validity(): Validity { return $this->validity; } public function subject(): Name { return $this->subject; } /** * Get subject public key. */ public function subjectPublicKeyInfo(): PublicKeyInfo { return $this->subjectPublicKeyInfo; } /** * Whether issuer unique identifier is present. */ public function hasIssuerUniqueID(): bool { return isset($this->issuerUniqueID); } public function issuerUniqueID(): UniqueIdentifier { if (! $this->hasIssuerUniqueID()) { throw new LogicException('issuerUniqueID not set.'); } return $this->issuerUniqueID; } /** * Whether subject unique identifier is present. */ public function hasSubjectUniqueID(): bool { return isset($this->subjectUniqueID); } public function subjectUniqueID(): UniqueIdentifier { if (! $this->hasSubjectUniqueID()) { throw new LogicException('subjectUniqueID not set.'); } return $this->subjectUniqueID; } public function extensions(): Extensions { return $this->extensions; } /** * Generate ASN.1 structure. */ public function toASN1(): Sequence { $elements = []; $version = $this->version(); // if version is not default if ($version !== self::VERSION_1) { $elements[] = ExplicitlyTaggedType::create(0, Integer::create($version)); } $serial = $this->serialNumber(); $signature = $this->signature(); // add required elements array_push( $elements, Integer::create($serial), $signature->toASN1(), $this->issuer->toASN1(), $this->validity->toASN1(), $this->subject->toASN1(), $this->subjectPublicKeyInfo->toASN1() ); if (isset($this->issuerUniqueID)) { $elements[] = ImplicitlyTaggedType::create(1, $this->issuerUniqueID->toASN1()); } if (isset($this->subjectUniqueID)) { $elements[] = ImplicitlyTaggedType::create(2, $this->subjectUniqueID->toASN1()); } if (count($this->extensions) !== 0) { $elements[] = ExplicitlyTaggedType::create(3, $this->extensions->toASN1()); } return Sequence::create(...$elements); } /** * Create signed certificate. * * @param SignatureAlgorithmIdentifier $algo Algorithm used for signing * @param PrivateKeyInfo $privkey_info Private key used for signing * @param null|Crypto $crypto Crypto engine, use default if not set */ public function sign( SignatureAlgorithmIdentifier $algo, PrivateKeyInfo $privkey_info, ?Crypto $crypto = null ): Certificate { $crypto ??= Crypto::getDefault(); $tbs_cert = clone $this; if (! isset($tbs_cert->version)) { $tbs_cert->version = $tbs_cert->_determineVersion(); } if (! isset($tbs_cert->serialNumber)) { $tbs_cert->serialNumber = '0'; } $tbs_cert->signature = $algo; $data = $tbs_cert->toASN1() ->toDER(); $signature = $crypto->sign($data, $privkey_info, $algo); return Certificate::create($tbs_cert, $algo, $signature); } /** * Determine minimum version for the certificate. */ private function _determineVersion(): int { // if extensions are present if (count($this->extensions) !== 0) { return self::VERSION_3; } // if UniqueIdentifier is present if (isset($this->issuerUniqueID) || isset($this->subjectUniqueID)) { return self::VERSION_2; } return self::VERSION_1; } }