1<?php 2/* 3 * This file is part of the PHPASN1 library. 4 * 5 * Copyright © Friedrich Große <friedrich.grosse@gmail.com> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11namespace FG\ASN1; 12 13use Exception; 14 15/** 16 * The Identifier encodes the ASN.1 tag (class and number) of the type of a data value. 17 * 18 * Every identifier whose number is in the range 0 to 30 has the following structure: 19 * 20 * Bits: 8 7 6 5 4 3 2 1 21 * | Class | P/C | Tag number | 22 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 23 * 24 * Bits 8 and 7 define the class of this type ( Universal, Application, Context-specific or Private). 25 * Bit 6 encoded whether this type is primitive or constructed 26 * The remaining bits 5 - 1 encode the tag number 27 */ 28class Identifier 29{ 30 const CLASS_UNIVERSAL = 0x00; 31 const CLASS_APPLICATION = 0x01; 32 const CLASS_CONTEXT_SPECIFIC = 0x02; 33 const CLASS_PRIVATE = 0x03; 34 35 const EOC = 0x00; // unsupported for now 36 const BOOLEAN = 0x01; 37 const INTEGER = 0x02; 38 const BITSTRING = 0x03; 39 const OCTETSTRING = 0x04; 40 const NULL = 0x05; 41 const OBJECT_IDENTIFIER = 0x06; 42 const OBJECT_DESCRIPTOR = 0x07; 43 const EXTERNAL = 0x08; // unsupported for now 44 const REAL = 0x09; // unsupported for now 45 const ENUMERATED = 0x0A; 46 const EMBEDDED_PDV = 0x0B; // unsupported for now 47 const UTF8_STRING = 0x0C; 48 const RELATIVE_OID = 0x0D; 49 // value 0x0E and 0x0F are reserved for future use 50 51 const SEQUENCE = 0x30; 52 const SET = 0x31; 53 const NUMERIC_STRING = 0x12; 54 const PRINTABLE_STRING = 0x13; 55 const T61_STRING = 0x14; // sometimes referred to as TeletextString 56 const VIDEOTEXT_STRING = 0x15; 57 const IA5_STRING = 0x16; 58 const UTC_TIME = 0x17; 59 const GENERALIZED_TIME = 0x18; 60 const GRAPHIC_STRING = 0x19; 61 const VISIBLE_STRING = 0x1A; 62 const GENERAL_STRING = 0x1B; 63 const UNIVERSAL_STRING = 0x1C; 64 const CHARACTER_STRING = 0x1D; // Unrestricted character type 65 const BMP_STRING = 0x1E; 66 67 const LONG_FORM = 0x1F; 68 const IS_CONSTRUCTED = 0x20; 69 70 /** 71 * Creates an identifier. Short form identifiers are returned as integers 72 * for BC, long form identifiers will be returned as a string of octets. 73 * 74 * @param int $class 75 * @param bool $isConstructed 76 * @param int $tagNumber 77 * 78 * @throws Exception if the given arguments are invalid 79 * 80 * @return int|string 81 */ 82 public static function create($class, $isConstructed, $tagNumber) 83 { 84 if (!is_numeric($class) || $class < self::CLASS_UNIVERSAL || $class > self::CLASS_PRIVATE) { 85 throw new Exception(sprintf('Invalid class %d given', $class)); 86 } 87 88 if (!is_bool($isConstructed)) { 89 throw new Exception("\$isConstructed must be a boolean value ($isConstructed given)"); 90 } 91 92 $tagNumber = self::makeNumeric($tagNumber); 93 if ($tagNumber < 0) { 94 throw new Exception(sprintf('Invalid $tagNumber %d given. You can only use positive integers.', $tagNumber)); 95 } 96 97 if ($tagNumber < self::LONG_FORM) { 98 return ($class << 6) | ($isConstructed << 5) | $tagNumber; 99 } 100 101 $firstOctet = ($class << 6) | ($isConstructed << 5) | self::LONG_FORM; 102 103 // Tag numbers formatted in long form are base-128 encoded. See X.609#8.1.2.4 104 return chr($firstOctet).Base128::encode($tagNumber); 105 } 106 107 public static function isConstructed($identifierOctet) 108 { 109 return ($identifierOctet & self::IS_CONSTRUCTED) === self::IS_CONSTRUCTED; 110 } 111 112 public static function isLongForm($identifierOctet) 113 { 114 return ($identifierOctet & self::LONG_FORM) === self::LONG_FORM; 115 } 116 117 /** 118 * Return the name of the mapped ASN.1 type with a preceding "ASN.1 ". 119 * 120 * Example: ASN.1 Octet String 121 * 122 * @see Identifier::getShortName() 123 * 124 * @param int|string $identifier 125 * 126 * @return string 127 */ 128 public static function getName($identifier) 129 { 130 $identifierOctet = self::makeNumeric($identifier); 131 132 $typeName = static::getShortName($identifier); 133 134 if (($identifierOctet & self::LONG_FORM) < self::LONG_FORM) { 135 $typeName = "ASN.1 {$typeName}"; 136 } 137 138 return $typeName; 139 } 140 141 /** 142 * Return the short version of the type name. 143 * 144 * If the given identifier octet can be mapped to a known universal type this will 145 * return its name. Else Identifier::getClassDescription() is used to retrieve 146 * information about the identifier. 147 * 148 * @see Identifier::getName() 149 * @see Identifier::getClassDescription() 150 * 151 * @param int|string $identifier 152 * 153 * @return string 154 */ 155 public static function getShortName($identifier) 156 { 157 $identifierOctet = self::makeNumeric($identifier); 158 159 switch ($identifierOctet) { 160 case self::EOC: 161 return 'End-of-contents octet'; 162 case self::BOOLEAN: 163 return 'Boolean'; 164 case self::INTEGER: 165 return 'Integer'; 166 case self::BITSTRING: 167 return 'Bit String'; 168 case self::OCTETSTRING: 169 return 'Octet String'; 170 case self::NULL: 171 return 'NULL'; 172 case self::OBJECT_IDENTIFIER: 173 return 'Object Identifier'; 174 case self::OBJECT_DESCRIPTOR: 175 return 'Object Descriptor'; 176 case self::EXTERNAL: 177 return 'External Type'; 178 case self::REAL: 179 return 'Real'; 180 case self::ENUMERATED: 181 return 'Enumerated'; 182 case self::EMBEDDED_PDV: 183 return 'Embedded PDV'; 184 case self::UTF8_STRING: 185 return 'UTF8 String'; 186 case self::RELATIVE_OID: 187 return 'Relative OID'; 188 case self::SEQUENCE: 189 return 'Sequence'; 190 case self::SET: 191 return 'Set'; 192 case self::NUMERIC_STRING: 193 return 'Numeric String'; 194 case self::PRINTABLE_STRING: 195 return 'Printable String'; 196 case self::T61_STRING: 197 return 'T61 String'; 198 case self::VIDEOTEXT_STRING: 199 return 'Videotext String'; 200 case self::IA5_STRING: 201 return 'IA5 String'; 202 case self::UTC_TIME: 203 return 'UTC Time'; 204 case self::GENERALIZED_TIME: 205 return 'Generalized Time'; 206 case self::GRAPHIC_STRING: 207 return 'Graphic String'; 208 case self::VISIBLE_STRING: 209 return 'Visible String'; 210 case self::GENERAL_STRING: 211 return 'General String'; 212 case self::UNIVERSAL_STRING: 213 return 'Universal String'; 214 case self::CHARACTER_STRING: 215 return 'Character String'; 216 case self::BMP_STRING: 217 return 'BMP String'; 218 219 case 0x0E: 220 return 'RESERVED (0x0E)'; 221 case 0x0F: 222 return 'RESERVED (0x0F)'; 223 224 case self::LONG_FORM: 225 default: 226 $classDescription = self::getClassDescription($identifier); 227 228 if (is_int($identifier)) { 229 $identifier = chr($identifier); 230 } 231 232 return "$classDescription (0x".strtoupper(bin2hex($identifier)).')'; 233 } 234 } 235 236 /** 237 * Returns a textual description of the information encoded in a given identifier octet. 238 * 239 * The first three (most significant) bytes are evaluated to determine if this is a 240 * constructed or primitive type and if it is either universal, application, context-specific or 241 * private. 242 * 243 * Example: 244 * Constructed context-specific 245 * Primitive universal 246 * 247 * @param int|string $identifier 248 * 249 * @return string 250 */ 251 public static function getClassDescription($identifier) 252 { 253 $identifierOctet = self::makeNumeric($identifier); 254 255 if (self::isConstructed($identifierOctet)) { 256 $classDescription = 'Constructed '; 257 } else { 258 $classDescription = 'Primitive '; 259 } 260 $classBits = $identifierOctet >> 6; 261 switch ($classBits) { 262 case self::CLASS_UNIVERSAL: 263 $classDescription .= 'universal'; 264 break; 265 case self::CLASS_APPLICATION: 266 $classDescription .= 'application'; 267 break; 268 case self::CLASS_CONTEXT_SPECIFIC: 269 $tagNumber = self::getTagNumber($identifier); 270 $classDescription = "[$tagNumber] Context-specific"; 271 break; 272 case self::CLASS_PRIVATE: 273 $classDescription .= 'private'; 274 break; 275 276 default: 277 return "INVALID IDENTIFIER OCTET: {$identifierOctet}"; 278 } 279 280 return $classDescription; 281 } 282 283 /** 284 * @param int|string $identifier 285 * 286 * @return int 287 */ 288 public static function getTagNumber($identifier) 289 { 290 $firstOctet = self::makeNumeric($identifier); 291 $tagNumber = $firstOctet & self::LONG_FORM; 292 293 if ($tagNumber < self::LONG_FORM) { 294 return $tagNumber; 295 } 296 297 if (is_numeric($identifier)) { 298 $identifier = chr($identifier); 299 } 300 return Base128::decode(substr($identifier, 1)); 301 } 302 303 public static function isUniversalClass($identifier) 304 { 305 $identifier = self::makeNumeric($identifier); 306 307 return $identifier >> 6 == self::CLASS_UNIVERSAL; 308 } 309 310 public static function isApplicationClass($identifier) 311 { 312 $identifier = self::makeNumeric($identifier); 313 314 return $identifier >> 6 == self::CLASS_APPLICATION; 315 } 316 317 public static function isContextSpecificClass($identifier) 318 { 319 $identifier = self::makeNumeric($identifier); 320 321 return $identifier >> 6 == self::CLASS_CONTEXT_SPECIFIC; 322 } 323 324 public static function isPrivateClass($identifier) 325 { 326 $identifier = self::makeNumeric($identifier); 327 328 return $identifier >> 6 == self::CLASS_PRIVATE; 329 } 330 331 private static function makeNumeric($identifierOctet) 332 { 333 if (!is_numeric($identifierOctet)) { 334 return ord($identifierOctet); 335 } else { 336 return $identifierOctet; 337 } 338 } 339} 340