<?php
/*
* This file is part of the PHPASN1 library.
*
* Copyright © Friedrich Große <friedrich.grosse@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FG\ASN1;
use Exception;
/**
* The Identifier encodes the ASN.1 tag (class and number) of the type of a data value.
*
* Every identifier whose number is in the range 0 to 30 has the following structure:
*
* Bits: 8 7 6 5 4 3 2 1
* | Class | P/C | Tag number |
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Bits 8 and 7 define the class of this type ( Universal, Application, Context-specific or Private).
* Bit 6 encoded whether this type is primitive or constructed
* The remaining bits 5 - 1 encode the tag number
*/
class Identifier
{
const CLASS_UNIVERSAL = 0x00;
const CLASS_APPLICATION = 0x01;
const CLASS_CONTEXT_SPECIFIC = 0x02;
const CLASS_PRIVATE = 0x03;
const EOC = 0x00; // unsupported for now
const BOOLEAN = 0x01;
const INTEGER = 0x02;
const BITSTRING = 0x03;
const OCTETSTRING = 0x04;
const NULL = 0x05;
const OBJECT_IDENTIFIER = 0x06;
const OBJECT_DESCRIPTOR = 0x07;
const EXTERNAL = 0x08; // unsupported for now
const REAL = 0x09; // unsupported for now
const ENUMERATED = 0x0A;
const EMBEDDED_PDV = 0x0B; // unsupported for now
const UTF8_STRING = 0x0C;
const RELATIVE_OID = 0x0D;
// value 0x0E and 0x0F are reserved for future use
const SEQUENCE = 0x30;
const SET = 0x31;
const NUMERIC_STRING = 0x12;
const PRINTABLE_STRING = 0x13;
const T61_STRING = 0x14; // sometimes referred to as TeletextString
const VIDEOTEXT_STRING = 0x15;
const IA5_STRING = 0x16;
const UTC_TIME = 0x17;
const GENERALIZED_TIME = 0x18;
const GRAPHIC_STRING = 0x19;
const VISIBLE_STRING = 0x1A;
const GENERAL_STRING = 0x1B;
const UNIVERSAL_STRING = 0x1C;
const CHARACTER_STRING = 0x1D; // Unrestricted character type
const BMP_STRING = 0x1E;
const LONG_FORM = 0x1F;
const IS_CONSTRUCTED = 0x20;
/**
* Creates an identifier. Short form identifiers are returned as integers
* for BC, long form identifiers will be returned as a string of octets.
*
* @param int $class
* @param bool $isConstructed
* @param int $tagNumber
*
* @throws Exception if the given arguments are invalid
*
* @return int|string
*/
public static function create($class, $isConstructed, $tagNumber)
{
if (!is_numeric($class) || $class < self::CLASS_UNIVERSAL || $class > self::CLASS_PRIVATE) {
throw new Exception(sprintf('Invalid class %d given', $class));
}
if (!is_bool($isConstructed)) {
throw new Exception("\$isConstructed must be a boolean value ($isConstructed given)");
}
$tagNumber = self::makeNumeric($tagNumber);
if ($tagNumber < 0) {
throw new Exception(sprintf('Invalid $tagNumber %d given. You can only use positive integers.', $tagNumber));
}
if ($tagNumber < self::LONG_FORM) {
return ($class << 6) | ($isConstructed << 5) | $tagNumber;
}
$firstOctet = ($class << 6) | ($isConstructed << 5) | self::LONG_FORM;
// Tag numbers formatted in long form are base-128 encoded. See X.609#8.1.2.4
return chr($firstOctet).Base128::encode($tagNumber);
}
public static function isConstructed($identifierOctet)
{
return ($identifierOctet & self::IS_CONSTRUCTED) === self::IS_CONSTRUCTED;
}
public static function isLongForm($identifierOctet)
{
return ($identifierOctet & self::LONG_FORM) === self::LONG_FORM;
}
/**
* Return the name of the mapped ASN.1 type with a preceding "ASN.1 ".
*
* Example: ASN.1 Octet String
*
* @see Identifier::getShortName()
*
* @param int|string $identifier
*
* @return string
*/
public static function getName($identifier)
{
$identifierOctet = self::makeNumeric($identifier);
$typeName = static::getShortName($identifier);
if (($identifierOctet & self::LONG_FORM) < self::LONG_FORM) {
$typeName = "ASN.1 {$typeName}";
}
return $typeName;
}
/**
* Return the short version of the type name.
*
* If the given identifier octet can be mapped to a known universal type this will
* return its name. Else Identifier::getClassDescription() is used to retrieve
* information about the identifier.
*
* @see Identifier::getName()
* @see Identifier::getClassDescription()
*
* @param int|string $identifier
*
* @return string
*/
public static function getShortName($identifier)
{
$identifierOctet = self::makeNumeric($identifier);
switch ($identifierOctet) {
case self::EOC:
return 'End-of-contents octet';
case self::BOOLEAN:
return 'Boolean';
case self::INTEGER:
return 'Integer';
case self::BITSTRING:
return 'Bit String';
case self::OCTETSTRING:
return 'Octet String';
case self::NULL:
return 'NULL';
case self::OBJECT_IDENTIFIER:
return 'Object Identifier';
case self::OBJECT_DESCRIPTOR:
return 'Object Descriptor';
case self::EXTERNAL:
return 'External Type';
case self::REAL:
return 'Real';
case self::ENUMERATED:
return 'Enumerated';
case self::EMBEDDED_PDV:
return 'Embedded PDV';
case self::UTF8_STRING:
return 'UTF8 String';
case self::RELATIVE_OID:
return 'Relative OID';
case self::SEQUENCE:
return 'Sequence';
case self::SET:
return 'Set';
case self::NUMERIC_STRING:
return 'Numeric String';
case self::PRINTABLE_STRING:
return 'Printable String';
case self::T61_STRING:
return 'T61 String';
case self::VIDEOTEXT_STRING:
return 'Videotext String';
case self::IA5_STRING:
return 'IA5 String';
case self::UTC_TIME:
return 'UTC Time';
case self::GENERALIZED_TIME:
return 'Generalized Time';
case self::GRAPHIC_STRING:
return 'Graphic String';
case self::VISIBLE_STRING:
return 'Visible String';
case self::GENERAL_STRING:
return 'General String';
case self::UNIVERSAL_STRING:
return 'Universal String';
case self::CHARACTER_STRING:
return 'Character String';
case self::BMP_STRING:
return 'BMP String';
case 0x0E:
return 'RESERVED (0x0E)';
case 0x0F:
return 'RESERVED (0x0F)';
case self::LONG_FORM:
default:
$classDescription = self::getClassDescription($identifier);
if (is_int($identifier)) {
$identifier = chr($identifier);
}
return "$classDescription (0x".strtoupper(bin2hex($identifier)).')';
}
}
/**
* Returns a textual description of the information encoded in a given identifier octet.
*
* The first three (most significant) bytes are evaluated to determine if this is a
* constructed or primitive type and if it is either universal, application, context-specific or
* private.
*
* Example:
* Constructed context-specific
* Primitive universal
*
* @param int|string $identifier
*
* @return string
*/
public static function getClassDescription($identifier)
{
$identifierOctet = self::makeNumeric($identifier);
if (self::isConstructed($identifierOctet)) {
$classDescription = 'Constructed ';
} else {
$classDescription = 'Primitive ';
}
$classBits = $identifierOctet >> 6;
switch ($classBits) {
case self::CLASS_UNIVERSAL:
$classDescription .= 'universal';
break;
case self::CLASS_APPLICATION:
$classDescription .= 'application';
break;
case self::CLASS_CONTEXT_SPECIFIC:
$tagNumber = self::getTagNumber($identifier);
$classDescription = "[$tagNumber] Context-specific";
break;
case self::CLASS_PRIVATE:
$classDescription .= 'private';
break;
default:
return "INVALID IDENTIFIER OCTET: {$identifierOctet}";
}
return $classDescription;
}
/**
* @param int|string $identifier
*
* @return int
*/
public static function getTagNumber($identifier)
{
$firstOctet = self::makeNumeric($identifier);
$tagNumber = $firstOctet & self::LONG_FORM;
if ($tagNumber < self::LONG_FORM) {
return $tagNumber;
}
if (is_numeric($identifier)) {
$identifier = chr($identifier);
}
return Base128::decode(substr($identifier, 1));
}
public static function isUniversalClass($identifier)
{
$identifier = self::makeNumeric($identifier);
return $identifier >> 6 == self::CLASS_UNIVERSAL;
}
public static function isApplicationClass($identifier)
{
$identifier = self::makeNumeric($identifier);
return $identifier >> 6 == self::CLASS_APPLICATION;
}
public static function isContextSpecificClass($identifier)
{
$identifier = self::makeNumeric($identifier);
return $identifier >> 6 == self::CLASS_CONTEXT_SPECIFIC;
}
public static function isPrivateClass($identifier)
{
$identifier = self::makeNumeric($identifier);
return $identifier >> 6 == self::CLASS_PRIVATE;
}
private static function makeNumeric($identifierOctet)
{
if (!is_numeric($identifierOctet)) {
return ord($identifierOctet);
} else {
return $identifierOctet;
}
}
}