%PDF- %PDF-
| Direktori : /www/varak.net/nextcloud.varak.net/3rdparty/spomky-labs/pki-framework/src/ASN1/ |
| Current File : //www/varak.net/nextcloud.varak.net/3rdparty/spomky-labs/pki-framework/src/ASN1/Element.php |
<?php
declare(strict_types=1);
namespace SpomkyLabs\Pki\ASN1;
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;
use function array_key_exists;
use function mb_strlen;
/**
* Base class for all ASN.1 type elements.
* @see \SpomkyLabs\Pki\Test\ASN1\ElementTest
*/
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,
};
}
}