![]() 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/ASN1/ |
<?php declare(strict_types=1); namespace SpomkyLabs\Pki\ASN1; use function array_key_exists; use function mb_strlen; use SpomkyLabs\Pki\ASN1\Component\Identifier; use SpomkyLabs\Pki\ASN1\Component\Length; use SpomkyLabs\Pki\ASN1\Feature\ElementBase; use SpomkyLabs\Pki\ASN1\Type\Constructed; use SpomkyLabs\Pki\ASN1\Type\Constructed\ConstructedString; use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence; use SpomkyLabs\Pki\ASN1\Type\Constructed\Set; use SpomkyLabs\Pki\ASN1\Type\Primitive\BitString; use SpomkyLabs\Pki\ASN1\Type\Primitive\BMPString; use SpomkyLabs\Pki\ASN1\Type\Primitive\Boolean; use SpomkyLabs\Pki\ASN1\Type\Primitive\CharacterString; use SpomkyLabs\Pki\ASN1\Type\Primitive\Enumerated; use SpomkyLabs\Pki\ASN1\Type\Primitive\EOC; use SpomkyLabs\Pki\ASN1\Type\Primitive\GeneralizedTime; use SpomkyLabs\Pki\ASN1\Type\Primitive\GeneralString; use SpomkyLabs\Pki\ASN1\Type\Primitive\GraphicString; use SpomkyLabs\Pki\ASN1\Type\Primitive\IA5String; use SpomkyLabs\Pki\ASN1\Type\Primitive\Integer; use SpomkyLabs\Pki\ASN1\Type\Primitive\NullType; use SpomkyLabs\Pki\ASN1\Type\Primitive\NumericString; use SpomkyLabs\Pki\ASN1\Type\Primitive\ObjectDescriptor; use SpomkyLabs\Pki\ASN1\Type\Primitive\ObjectIdentifier; use SpomkyLabs\Pki\ASN1\Type\Primitive\OctetString; use SpomkyLabs\Pki\ASN1\Type\Primitive\PrintableString; use SpomkyLabs\Pki\ASN1\Type\Primitive\Real; use SpomkyLabs\Pki\ASN1\Type\Primitive\RelativeOID; use SpomkyLabs\Pki\ASN1\Type\Primitive\T61String; use SpomkyLabs\Pki\ASN1\Type\Primitive\UniversalString; use SpomkyLabs\Pki\ASN1\Type\Primitive\UTCTime; use SpomkyLabs\Pki\ASN1\Type\Primitive\UTF8String; use SpomkyLabs\Pki\ASN1\Type\Primitive\VideotexString; use SpomkyLabs\Pki\ASN1\Type\Primitive\VisibleString; use SpomkyLabs\Pki\ASN1\Type\StringType; use SpomkyLabs\Pki\ASN1\Type\Tagged\ApplicationType; use SpomkyLabs\Pki\ASN1\Type\Tagged\ContextSpecificType; use SpomkyLabs\Pki\ASN1\Type\Tagged\PrivateType; use SpomkyLabs\Pki\ASN1\Type\TaggedType; use SpomkyLabs\Pki\ASN1\Type\TimeType; use SpomkyLabs\Pki\ASN1\Type\UnspecifiedType; use UnexpectedValueException; /** * Base class for all ASN.1 type elements. */ abstract class Element implements ElementBase { // Universal type tags public const TYPE_EOC = 0x00; public const TYPE_BOOLEAN = 0x01; public const TYPE_INTEGER = 0x02; public const TYPE_BIT_STRING = 0x03; public const TYPE_OCTET_STRING = 0x04; public const TYPE_NULL = 0x05; public const TYPE_OBJECT_IDENTIFIER = 0x06; public const TYPE_OBJECT_DESCRIPTOR = 0x07; public const TYPE_EXTERNAL = 0x08; public const TYPE_REAL = 0x09; public const TYPE_ENUMERATED = 0x0a; public const TYPE_EMBEDDED_PDV = 0x0b; public const TYPE_UTF8_STRING = 0x0c; public const TYPE_RELATIVE_OID = 0x0d; public const TYPE_SEQUENCE = 0x10; public const TYPE_SET = 0x11; public const TYPE_NUMERIC_STRING = 0x12; public const TYPE_PRINTABLE_STRING = 0x13; public const TYPE_T61_STRING = 0x14; public const TYPE_VIDEOTEX_STRING = 0x15; public const TYPE_IA5_STRING = 0x16; public const TYPE_UTC_TIME = 0x17; public const TYPE_GENERALIZED_TIME = 0x18; public const TYPE_GRAPHIC_STRING = 0x19; public const TYPE_VISIBLE_STRING = 0x1a; public const TYPE_GENERAL_STRING = 0x1b; public const TYPE_UNIVERSAL_STRING = 0x1c; public const TYPE_CHARACTER_STRING = 0x1d; public const TYPE_BMP_STRING = 0x1e; /** * Pseudotype for all string types. * * May be used as an expectation parameter. * * @var int */ public const TYPE_STRING = -1; /** * Pseudotype for all time types. * * May be used as an expectation parameter. * * @var int */ public const TYPE_TIME = -2; /** * Pseudotype for constructed strings. * * May be used as an expectation parameter. * * @var int */ public const TYPE_CONSTRUCTED_STRING = -3; /** * Mapping from universal type tag to implementation class name. * * @internal * * @var array<int, string> */ private const MAP_TAG_TO_CLASS = [ self::TYPE_EOC => EOC::class, self::TYPE_BOOLEAN => Boolean::class, self::TYPE_INTEGER => Integer::class, self::TYPE_BIT_STRING => BitString::class, self::TYPE_OCTET_STRING => OctetString::class, self::TYPE_NULL => NullType::class, self::TYPE_OBJECT_IDENTIFIER => ObjectIdentifier::class, self::TYPE_OBJECT_DESCRIPTOR => ObjectDescriptor::class, self::TYPE_REAL => Real::class, self::TYPE_ENUMERATED => Enumerated::class, self::TYPE_UTF8_STRING => UTF8String::class, self::TYPE_RELATIVE_OID => RelativeOID::class, self::TYPE_SEQUENCE => Sequence::class, self::TYPE_SET => Set::class, self::TYPE_NUMERIC_STRING => NumericString::class, self::TYPE_PRINTABLE_STRING => PrintableString::class, self::TYPE_T61_STRING => T61String::class, self::TYPE_VIDEOTEX_STRING => VideotexString::class, self::TYPE_IA5_STRING => IA5String::class, self::TYPE_UTC_TIME => UTCTime::class, self::TYPE_GENERALIZED_TIME => GeneralizedTime::class, self::TYPE_GRAPHIC_STRING => GraphicString::class, self::TYPE_VISIBLE_STRING => VisibleString::class, self::TYPE_GENERAL_STRING => GeneralString::class, self::TYPE_UNIVERSAL_STRING => UniversalString::class, self::TYPE_CHARACTER_STRING => CharacterString::class, self::TYPE_BMP_STRING => BMPString::class, ]; /** * Mapping from universal type tag to human-readable name. * * @internal * * @var array<int, string> */ private const MAP_TYPE_TO_NAME = [ self::TYPE_EOC => 'EOC', self::TYPE_BOOLEAN => 'BOOLEAN', self::TYPE_INTEGER => 'INTEGER', self::TYPE_BIT_STRING => 'BIT STRING', self::TYPE_OCTET_STRING => 'OCTET STRING', self::TYPE_NULL => 'NULL', self::TYPE_OBJECT_IDENTIFIER => 'OBJECT IDENTIFIER', self::TYPE_OBJECT_DESCRIPTOR => 'ObjectDescriptor', self::TYPE_EXTERNAL => 'EXTERNAL', self::TYPE_REAL => 'REAL', self::TYPE_ENUMERATED => 'ENUMERATED', self::TYPE_EMBEDDED_PDV => 'EMBEDDED PDV', self::TYPE_UTF8_STRING => 'UTF8String', self::TYPE_RELATIVE_OID => 'RELATIVE-OID', self::TYPE_SEQUENCE => 'SEQUENCE', self::TYPE_SET => 'SET', self::TYPE_NUMERIC_STRING => 'NumericString', self::TYPE_PRINTABLE_STRING => 'PrintableString', self::TYPE_T61_STRING => 'T61String', self::TYPE_VIDEOTEX_STRING => 'VideotexString', self::TYPE_IA5_STRING => 'IA5String', self::TYPE_UTC_TIME => 'UTCTime', self::TYPE_GENERALIZED_TIME => 'GeneralizedTime', self::TYPE_GRAPHIC_STRING => 'GraphicString', self::TYPE_VISIBLE_STRING => 'VisibleString', self::TYPE_GENERAL_STRING => 'GeneralString', self::TYPE_UNIVERSAL_STRING => 'UniversalString', self::TYPE_CHARACTER_STRING => 'CHARACTER STRING', self::TYPE_BMP_STRING => 'BMPString', self::TYPE_STRING => 'Any String', self::TYPE_TIME => 'Any Time', self::TYPE_CONSTRUCTED_STRING => 'Constructed String', ]; /** * @param bool $indefiniteLength Whether type shall be encoded with indefinite length. */ protected function __construct( protected readonly int $typeTag, protected bool $indefiniteLength = false ) { } abstract public function typeClass(): int; abstract public function isConstructed(): bool; /** * Decode element from DER data. * * @param string $data DER encoded data * @param null|int $offset Reference to the variable that contains offset * into the data where to start parsing. * Variable is updated to the offset next to the * parsed element. If null, start from offset 0. */ public static function fromDER(string $data, int &$offset = null): static { $idx = $offset ?? 0; // decode identifier $identifier = Identifier::fromDER($data, $idx); // determine class that implements type specific decoding $cls = self::determineImplClass($identifier); // decode remaining element $element = $cls::decodeFromDER($identifier, $data, $idx); // if called in the context of a concrete class, check // that decoded type matches the type of calling class $called_class = static::class; if ($called_class !== self::class) { if (! $element instanceof $called_class) { throw new UnexpectedValueException(sprintf('%s expected, got %s.', $called_class, $element::class)); } } // update offset for the caller if (isset($offset)) { $offset = $idx; } return $element; } public function toDER(): string { $identifier = Identifier::create( $this->typeClass(), $this->isConstructed() ? Identifier::CONSTRUCTED : Identifier::PRIMITIVE, $this->typeTag ); $content = $this->encodedAsDER(); if ($this->indefiniteLength) { $length = Length::create(0, true); $eoc = EOC::create(); return $identifier->toDER() . $length->toDER() . $content . $eoc->toDER(); } $length = Length::create(mb_strlen($content, '8bit')); return $identifier->toDER() . $length->toDER() . $content; } public function tag(): int { return $this->typeTag; } public function isType(int $tag): bool { // if element is context specific if ($this->typeClass() === Identifier::CLASS_CONTEXT_SPECIFIC) { return false; } // negative tags identify an abstract pseudotype if ($tag < 0) { return $this->isPseudoType($tag); } return $this->isConcreteType($tag); } public function expectType(int $tag): ElementBase { if (! $this->isType($tag)) { throw new UnexpectedValueException( sprintf('%s expected, got %s.', self::tagToName($tag), $this->typeDescriptorString()) ); } return $this; } public function isTagged(): bool { return $this instanceof TaggedType; } public function expectTagged(?int $tag = null): TaggedType { if (! $this->isTagged()) { throw new UnexpectedValueException( sprintf('Context specific element expected, got %s.', Identifier::classToName($this->typeClass())) ); } if (isset($tag) && $this->tag() !== $tag) { throw new UnexpectedValueException(sprintf('Tag %d expected, got %d.', $tag, $this->tag())); } return $this; } /** * Whether element has indefinite length. */ public function hasIndefiniteLength(): bool { return $this->indefiniteLength; } /** * Get self with indefinite length encoding set. * * @param bool $indefinite True for indefinite length, false for definite length */ public function withIndefiniteLength(bool $indefinite = true): self { $obj = clone $this; $obj->indefiniteLength = $indefinite; return $obj; } final public function asElement(): self { return $this; } /** * Get element decorated with `UnspecifiedType` object. */ public function asUnspecified(): UnspecifiedType { return UnspecifiedType::create($this); } /** * Get human readable name for an universal tag. */ public static function tagToName(int $tag): string { if (! array_key_exists($tag, self::MAP_TYPE_TO_NAME)) { return "TAG {$tag}"; } return self::MAP_TYPE_TO_NAME[$tag]; } /** * Get the content encoded in DER. * * Returns the DER encoded content without identifier and length header octets. */ abstract protected function encodedAsDER(): string; /** * Decode type-specific element from DER. * * @param Identifier $identifier Pre-parsed identifier * @param string $data DER data * @param int $offset Offset in data to the next byte after identifier */ abstract protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): ElementBase; /** * Determine the class that implements the type. * * @return string Class name */ protected static function determineImplClass(Identifier $identifier): string { switch ($identifier->typeClass()) { case Identifier::CLASS_UNIVERSAL: $cls = self::determineUniversalImplClass($identifier->intTag()); // constructed strings may be present in BER if ($identifier->isConstructed() && is_subclass_of($cls, StringType::class)) { $cls = ConstructedString::class; } return $cls; case Identifier::CLASS_CONTEXT_SPECIFIC: return ContextSpecificType::class; case Identifier::CLASS_APPLICATION: return ApplicationType::class; case Identifier::CLASS_PRIVATE: return PrivateType::class; } throw new UnexpectedValueException(sprintf( '%s %d not implemented.', Identifier::classToName($identifier->typeClass()), $identifier->tag() )); } /** * Determine the class that implements an universal type of the given tag. * * @return string Class name */ protected static function determineUniversalImplClass(int $tag): string { if (! array_key_exists($tag, self::MAP_TAG_TO_CLASS)) { throw new UnexpectedValueException("Universal tag {$tag} not implemented."); } return self::MAP_TAG_TO_CLASS[$tag]; } /** * Get textual description of the type for debugging purposes. */ protected function typeDescriptorString(): string { if ($this->typeClass() === Identifier::CLASS_UNIVERSAL) { return self::tagToName($this->typeTag); } return sprintf('%s TAG %d', Identifier::classToName($this->typeClass()), $this->typeTag); } /** * Check whether the element is a concrete type of given tag. */ private function isConcreteType(int $tag): bool { // if tag doesn't match if ($this->tag() !== $tag) { return false; } // if type is universal check that instance is of a correct class if ($this->typeClass() === Identifier::CLASS_UNIVERSAL) { $cls = self::determineUniversalImplClass($tag); if (! $this instanceof $cls) { return false; } } return true; } /** * Check whether the element is a pseudotype. */ private function isPseudoType(int $tag): bool { return match ($tag) { self::TYPE_STRING => $this instanceof StringType, self::TYPE_TIME => $this instanceof TimeType, self::TYPE_CONSTRUCTED_STRING => $this instanceof ConstructedString, default => false, }; } }