![]() 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/cartforge.co/vendor/spomky-labs/pki-framework/src/ASN1/Component/ |
<?php declare(strict_types=1); namespace SpomkyLabs\Pki\ASN1\Component; use Brick\Math\BigInteger; use SpomkyLabs\Pki\ASN1\Exception\DecodeException; use SpomkyLabs\Pki\ASN1\Feature\Encodable; use SpomkyLabs\Pki\ASN1\Util\BigInt; use function array_key_exists; use function mb_strlen; use function ord; /** * Class to represent BER/DER identifier octets. * @see \SpomkyLabs\Pki\Test\ASN1\Component\IdentifierTest */ final class Identifier implements Encodable { // Type class enumerations final public const CLASS_UNIVERSAL = 0b00; final public const CLASS_APPLICATION = 0b01; final public const CLASS_CONTEXT_SPECIFIC = 0b10; final public const CLASS_PRIVATE = 0b11; // P/C enumerations final public const PRIMITIVE = 0b0; final public const CONSTRUCTED = 0b1; /** * Mapping from type class to human readable name. * * @internal * * @var array<int, string> */ private const MAP_CLASS_TO_NAME = [ self::CLASS_UNIVERSAL => 'UNIVERSAL', self::CLASS_APPLICATION => 'APPLICATION', self::CLASS_CONTEXT_SPECIFIC => 'CONTEXT SPECIFIC', self::CLASS_PRIVATE => 'PRIVATE', ]; /** * Type class. */ private int $_class; /** * Primitive or Constructed. */ private readonly int $_pc; /** * Content type tag. */ private BigInt $_tag; /** * @param int $class Type class * @param int $pc Primitive / Constructed * @param BigInteger|int $tag Type tag number */ private function __construct(int $class, int $pc, BigInteger|int $tag) { $this->_class = 0b11 & $class; $this->_pc = 0b1 & $pc; $this->_tag = BigInt::create($tag); } public static function create(int $class, int $pc, BigInteger|int $tag): self { return new self($class, $pc, $tag); } /** * Decode identifier component 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 identifier. If null, start from offset 0. */ public static function fromDER(string $data, int &$offset = null): self { $idx = $offset ?? 0; $datalen = mb_strlen($data, '8bit'); if ($idx >= $datalen) { throw new DecodeException('Invalid offset.'); } $byte = ord($data[$idx++]); // bits 8 and 7 (class) // 0 = universal, 1 = application, 2 = context-specific, 3 = private $class = (0b11000000 & $byte) >> 6; // bit 6 (0 = primitive / 1 = constructed) $pc = (0b00100000 & $byte) >> 5; // bits 5 to 1 (tag number) $tag = (0b00011111 & $byte); // long-form identifier if ($tag === 0x1f) { $tag = self::decodeLongFormTag($data, $idx); } if (isset($offset)) { $offset = $idx; } return self::create($class, $pc, $tag); } public function toDER(): string { $bytes = []; $byte = $this->_class << 6 | $this->_pc << 5; $tag = $this->_tag->getValue(); if ($tag->isLessThan(0x1f)) { $bytes[] = $byte | $tag->toInt(); } // long-form identifier else { $bytes[] = $byte | 0x1f; $octets = []; for (; $tag->isGreaterThan(0); $tag = $tag->shiftedRight(7)) { $octets[] = 0x80 | $tag->and(0x7f)->toInt(); } // last octet has bit 8 set to zero $octets[0] &= 0x7f; foreach (array_reverse($octets) as $octet) { $bytes[] = $octet; } } return pack('C*', ...$bytes); } /** * Get class of the type. */ public function typeClass(): int { return $this->_class; } public function pc(): int { return $this->_pc; } /** * Get the tag number. * * @return string Base 10 integer string */ public function tag(): string { return $this->_tag->base10(); } /** * Get the tag as an integer. */ public function intTag(): int { return $this->_tag->toInt(); } /** * Check whether type is of an universal class. */ public function isUniversal(): bool { return $this->_class === self::CLASS_UNIVERSAL; } /** * Check whether type is of an application class. */ public function isApplication(): bool { return $this->_class === self::CLASS_APPLICATION; } /** * Check whether type is of a context specific class. */ public function isContextSpecific(): bool { return $this->_class === self::CLASS_CONTEXT_SPECIFIC; } /** * Check whether type is of a private class. */ public function isPrivate(): bool { return $this->_class === self::CLASS_PRIVATE; } /** * Check whether content is primitive type. */ public function isPrimitive(): bool { return $this->_pc === self::PRIMITIVE; } /** * Check hether content is constructed type. */ public function isConstructed(): bool { return $this->_pc === self::CONSTRUCTED; } /** * Get self with given type class. * * @param int $class One of `CLASS_*` enumerations */ public function withClass(int $class): self { $obj = clone $this; $obj->_class = 0b11 & $class; return $obj; } /** * Get self with given type tag. * * @param int $tag Tag number */ public function withTag(int $tag): self { $obj = clone $this; $obj->_tag = BigInt::create($tag); return $obj; } /** * Get human readable name of the type class. */ public static function classToName(int $class): string { if (! array_key_exists($class, self::MAP_CLASS_TO_NAME)) { return "CLASS {$class}"; } return self::MAP_CLASS_TO_NAME[$class]; } /** * Parse long form tag. * * @param string $data DER data * @param int $offset Reference to the variable containing offset to data * * @return BigInteger Tag number */ private static function decodeLongFormTag(string $data, int &$offset): BigInteger { $datalen = mb_strlen($data, '8bit'); $tag = BigInteger::of(0); while (true) { if ($offset >= $datalen) { throw new DecodeException('Unexpected end of data while decoding long form identifier.'); } $byte = ord($data[$offset++]); $tag = $tag->shiftedLeft(7); $tag = $tag->or(0x7f & $byte); // last byte has bit 8 set to zero if ((0x80 & $byte) === 0) { break; } } return $tag; } }