<?php
 declare(strict_types=1); namespace SpomkyLabs\Pki\X509\AttributeCertificate; use LogicException; use SpomkyLabs\Pki\ASN1\Element; use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence; use SpomkyLabs\Pki\ASN1\Type\Tagged\ImplicitlyTaggedType; use SpomkyLabs\Pki\X509\Certificate\Certificate; use SpomkyLabs\Pki\X509\GeneralName\DirectoryName; use SpomkyLabs\Pki\X509\GeneralName\GeneralName; use SpomkyLabs\Pki\X509\GeneralName\GeneralNames; final class Holder { private ?ObjectDigestInfo $objectDigestInfo = null; private function __construct( private ?IssuerSerial $baseCertificateID, private ?GeneralNames $entityName ) { } public static function create(?IssuerSerial $baseCertificateID = null, ?GeneralNames $entityName = null): self { return new self($baseCertificateID, $entityName); } public static function fromPKC(Certificate $cert): self { return self::create(IssuerSerial::fromPKC($cert)); } public static function fromASN1(Sequence $seq): self { $cert_id = null; $entity_name = null; $digest_info = null; if ($seq->hasTagged(0)) { $cert_id = IssuerSerial::fromASN1( $seq->getTagged(0) ->asImplicit(Element::TYPE_SEQUENCE) ->asSequence() ); } if ($seq->hasTagged(1)) { $entity_name = GeneralNames::fromASN1( $seq->getTagged(1) ->asImplicit(Element::TYPE_SEQUENCE) ->asSequence() ); } if ($seq->hasTagged(2)) { $digest_info = ObjectDigestInfo::fromASN1( $seq->getTagged(2) ->asImplicit(Element::TYPE_SEQUENCE) ->asSequence() ); } return self::create($cert_id, $entity_name) ->withObjectDigestInfo($digest_info); } public function withBaseCertificateID(IssuerSerial $issuer): self { $obj = clone $this; $obj->baseCertificateID = $issuer; return $obj; } public function withEntityName(GeneralNames $names): self { $obj = clone $this; $obj->entityName = $names; return $obj; } public function withObjectDigestInfo(?ObjectDigestInfo $odi): self { $obj = clone $this; $obj->objectDigestInfo = $odi; return $obj; } public function hasBaseCertificateID(): bool { return isset($this->baseCertificateID); } public function baseCertificateID(): IssuerSerial { if (! $this->hasBaseCertificateID()) { throw new LogicException('baseCertificateID not set.'); } return $this->baseCertificateID; } public function hasEntityName(): bool { return isset($this->entityName); } public function entityName(): GeneralNames { if (! $this->hasEntityName()) { throw new LogicException('entityName not set.'); } return $this->entityName; } public function hasObjectDigestInfo(): bool { return isset($this->objectDigestInfo); } public function objectDigestInfo(): ObjectDigestInfo { if (! $this->hasObjectDigestInfo()) { throw new LogicException('objectDigestInfo not set.'); } return $this->objectDigestInfo; } public function toASN1(): Sequence { $elements = []; if (isset($this->baseCertificateID)) { $elements[] = ImplicitlyTaggedType::create(0, $this->baseCertificateID->toASN1()); } if (isset($this->entityName)) { $elements[] = ImplicitlyTaggedType::create(1, $this->entityName->toASN1()); } if (isset($this->objectDigestInfo)) { $elements[] = ImplicitlyTaggedType::create(2, $this->objectDigestInfo->toASN1()); } return Sequence::create(...$elements); } public function identifiesPKC(Certificate $cert): bool { if ($this->baseCertificateID === null && $this->entityName === null) { return false; } if ($this->baseCertificateID !== null && ! $this->baseCertificateID->identifiesPKC($cert)) { return false; } if ($this->entityName !== null && ! $this->_checkEntityName($cert)) { return false; } return true; } private function _checkEntityName(Certificate $cert): bool { $name = $this->entityName?->firstDN(); if ($name !== null && $cert->tbsCertificate()->subject()->equals($name)) { return true; } $exts = $cert->tbsCertificate() ->extensions(); if ($exts->hasSubjectAlternativeName()) { $ext = $exts->subjectAlternativeName(); if ($this->_checkEntityAlternativeNames($ext->names())) { return true; } } return false; } private function _checkEntityAlternativeNames(GeneralNames $san): bool { $name = $this->entityName?->firstDN(); if ($name === null) { return false; } foreach ($san->allOf(GeneralName::TAG_DIRECTORY_NAME) as $dn) { if ($dn instanceof DirectoryName && $dn->dn()->equals($name)) { return true; } } return false; } } 