ASNObject.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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. namespace FG\ASN1;
  11. use FG\ASN1\Exception\ParserException;
  12. use FG\ASN1\Universal\BitString;
  13. use FG\ASN1\Universal\Boolean;
  14. use FG\ASN1\Universal\Enumerated;
  15. use FG\ASN1\Universal\GeneralizedTime;
  16. use FG\ASN1\Universal\Integer;
  17. use FG\ASN1\Universal\NullObject;
  18. use FG\ASN1\Universal\ObjectIdentifier;
  19. use FG\ASN1\Universal\RelativeObjectIdentifier;
  20. use FG\ASN1\Universal\OctetString;
  21. use FG\ASN1\Universal\Sequence;
  22. use FG\ASN1\Universal\Set;
  23. use FG\ASN1\Universal\UTCTime;
  24. use FG\ASN1\Universal\IA5String;
  25. use FG\ASN1\Universal\PrintableString;
  26. use FG\ASN1\Universal\NumericString;
  27. use FG\ASN1\Universal\UTF8String;
  28. use FG\ASN1\Universal\UniversalString;
  29. use FG\ASN1\Universal\CharacterString;
  30. use FG\ASN1\Universal\GeneralString;
  31. use FG\ASN1\Universal\VisibleString;
  32. use FG\ASN1\Universal\GraphicString;
  33. use FG\ASN1\Universal\BMPString;
  34. use FG\ASN1\Universal\T61String;
  35. use FG\ASN1\Universal\ObjectDescriptor;
  36. use FG\Utility\BigInteger;
  37. use LogicException;
  38. /**
  39. * Class ASNObject is the base class for all concrete ASN.1 objects.
  40. */
  41. abstract class ASNObject implements Parsable
  42. {
  43. private $contentLength;
  44. private $nrOfLengthOctets;
  45. /**
  46. * Must return the number of octets of the content part.
  47. *
  48. * @return int
  49. */
  50. abstract protected function calculateContentLength();
  51. /**
  52. * Encode the object using DER encoding.
  53. *
  54. * @see http://en.wikipedia.org/wiki/X.690#DER_encoding
  55. *
  56. * @return string the binary representation of an objects value
  57. */
  58. abstract protected function getEncodedValue();
  59. /**
  60. * Return the content of this object in a non encoded form.
  61. * This can be used to print the value in human readable form.
  62. *
  63. * @return mixed
  64. */
  65. abstract public function getContent();
  66. /**
  67. * Return the object type octet.
  68. * This should use the class constants of Identifier.
  69. *
  70. * @see Identifier
  71. *
  72. * @return int
  73. */
  74. abstract public function getType();
  75. /**
  76. * Returns all identifier octets. If an inheriting class models a tag with
  77. * the long form identifier format, it MUST reimplement this method to
  78. * return all octets of the identifier.
  79. *
  80. * @throws LogicException If the identifier format is long form
  81. *
  82. * @return string Identifier as a set of octets
  83. */
  84. public function getIdentifier()
  85. {
  86. $firstOctet = $this->getType();
  87. if (Identifier::isLongForm($firstOctet)) {
  88. throw new LogicException(sprintf('Identifier of %s uses the long form and must therefor override "ASNObject::getIdentifier()".', get_class($this)));
  89. }
  90. return chr($firstOctet);
  91. }
  92. /**
  93. * Encode this object using DER encoding.
  94. *
  95. * @return string the full binary representation of the complete object
  96. */
  97. public function getBinary()
  98. {
  99. $result = $this->getIdentifier();
  100. $result .= $this->createLengthPart();
  101. $result .= $this->getEncodedValue();
  102. return $result;
  103. }
  104. private function createLengthPart()
  105. {
  106. $contentLength = $this->getContentLength();
  107. $nrOfLengthOctets = $this->getNumberOfLengthOctets($contentLength);
  108. if ($nrOfLengthOctets == 1) {
  109. return chr($contentLength);
  110. } else {
  111. // the first length octet determines the number subsequent length octets
  112. $lengthOctets = chr(0x80 | ($nrOfLengthOctets - 1));
  113. for ($shiftLength = 8 * ($nrOfLengthOctets - 2); $shiftLength >= 0; $shiftLength -= 8) {
  114. $lengthOctets .= chr($contentLength >> $shiftLength);
  115. }
  116. return $lengthOctets;
  117. }
  118. }
  119. protected function getNumberOfLengthOctets($contentLength = null)
  120. {
  121. if (!isset($this->nrOfLengthOctets)) {
  122. if ($contentLength == null) {
  123. $contentLength = $this->getContentLength();
  124. }
  125. $this->nrOfLengthOctets = 1;
  126. if ($contentLength > 127) {
  127. do { // long form
  128. $this->nrOfLengthOctets++;
  129. $contentLength = $contentLength >> 8;
  130. } while ($contentLength > 0);
  131. }
  132. }
  133. return $this->nrOfLengthOctets;
  134. }
  135. protected function getContentLength()
  136. {
  137. if (!isset($this->contentLength)) {
  138. $this->contentLength = $this->calculateContentLength();
  139. }
  140. return $this->contentLength;
  141. }
  142. protected function setContentLength($newContentLength)
  143. {
  144. $this->contentLength = $newContentLength;
  145. $this->getNumberOfLengthOctets($newContentLength);
  146. }
  147. /**
  148. * Returns the length of the whole object (including the identifier and length octets).
  149. */
  150. public function getObjectLength()
  151. {
  152. $nrOfIdentifierOctets = strlen($this->getIdentifier());
  153. $contentLength = $this->getContentLength();
  154. $nrOfLengthOctets = $this->getNumberOfLengthOctets($contentLength);
  155. return $nrOfIdentifierOctets + $nrOfLengthOctets + $contentLength;
  156. }
  157. public function __toString()
  158. {
  159. return $this->getContent();
  160. }
  161. /**
  162. * Returns the name of the ASN.1 Type of this object.
  163. *
  164. * @see Identifier::getName()
  165. */
  166. public function getTypeName()
  167. {
  168. return Identifier::getName($this->getType());
  169. }
  170. /**
  171. * @param string $binaryData
  172. * @param int $offsetIndex
  173. *
  174. * @throws ParserException
  175. *
  176. * @return \FG\ASN1\ASNObject
  177. */
  178. public static function fromBinary(&$binaryData, &$offsetIndex = 0)
  179. {
  180. if (strlen($binaryData) <= $offsetIndex) {
  181. throw new ParserException('Can not parse binary from data: Offset index larger than input size', $offsetIndex);
  182. }
  183. $identifierOctet = ord($binaryData[$offsetIndex]);
  184. if (Identifier::isContextSpecificClass($identifierOctet) && Identifier::isConstructed($identifierOctet)) {
  185. return ExplicitlyTaggedObject::fromBinary($binaryData, $offsetIndex);
  186. }
  187. switch ($identifierOctet) {
  188. case Identifier::BITSTRING:
  189. return BitString::fromBinary($binaryData, $offsetIndex);
  190. case Identifier::BOOLEAN:
  191. return Boolean::fromBinary($binaryData, $offsetIndex);
  192. case Identifier::ENUMERATED:
  193. return Enumerated::fromBinary($binaryData, $offsetIndex);
  194. case Identifier::INTEGER:
  195. return Integer::fromBinary($binaryData, $offsetIndex);
  196. case Identifier::NULL:
  197. return NullObject::fromBinary($binaryData, $offsetIndex);
  198. case Identifier::OBJECT_IDENTIFIER:
  199. return ObjectIdentifier::fromBinary($binaryData, $offsetIndex);
  200. case Identifier::RELATIVE_OID:
  201. return RelativeObjectIdentifier::fromBinary($binaryData, $offsetIndex);
  202. case Identifier::OCTETSTRING:
  203. return OctetString::fromBinary($binaryData, $offsetIndex);
  204. case Identifier::SEQUENCE:
  205. return Sequence::fromBinary($binaryData, $offsetIndex);
  206. case Identifier::SET:
  207. return Set::fromBinary($binaryData, $offsetIndex);
  208. case Identifier::UTC_TIME:
  209. return UTCTime::fromBinary($binaryData, $offsetIndex);
  210. case Identifier::GENERALIZED_TIME:
  211. return GeneralizedTime::fromBinary($binaryData, $offsetIndex);
  212. case Identifier::IA5_STRING:
  213. return IA5String::fromBinary($binaryData, $offsetIndex);
  214. case Identifier::PRINTABLE_STRING:
  215. return PrintableString::fromBinary($binaryData, $offsetIndex);
  216. case Identifier::NUMERIC_STRING:
  217. return NumericString::fromBinary($binaryData, $offsetIndex);
  218. case Identifier::UTF8_STRING:
  219. return UTF8String::fromBinary($binaryData, $offsetIndex);
  220. case Identifier::UNIVERSAL_STRING:
  221. return UniversalString::fromBinary($binaryData, $offsetIndex);
  222. case Identifier::CHARACTER_STRING:
  223. return CharacterString::fromBinary($binaryData, $offsetIndex);
  224. case Identifier::GENERAL_STRING:
  225. return GeneralString::fromBinary($binaryData, $offsetIndex);
  226. case Identifier::VISIBLE_STRING:
  227. return VisibleString::fromBinary($binaryData, $offsetIndex);
  228. case Identifier::GRAPHIC_STRING:
  229. return GraphicString::fromBinary($binaryData, $offsetIndex);
  230. case Identifier::BMP_STRING:
  231. return BMPString::fromBinary($binaryData, $offsetIndex);
  232. case Identifier::T61_STRING:
  233. return T61String::fromBinary($binaryData, $offsetIndex);
  234. case Identifier::OBJECT_DESCRIPTOR:
  235. return ObjectDescriptor::fromBinary($binaryData, $offsetIndex);
  236. default:
  237. // At this point the identifier may be >1 byte.
  238. if (Identifier::isConstructed($identifierOctet)) {
  239. return new UnknownConstructedObject($binaryData, $offsetIndex);
  240. } else {
  241. $identifier = self::parseBinaryIdentifier($binaryData, $offsetIndex);
  242. $lengthOfUnknownObject = self::parseContentLength($binaryData, $offsetIndex);
  243. $offsetIndex += $lengthOfUnknownObject;
  244. return new UnknownObject($identifier, $lengthOfUnknownObject);
  245. }
  246. }
  247. }
  248. protected static function parseIdentifier($identifierOctet, $expectedIdentifier, $offsetForExceptionHandling)
  249. {
  250. if (is_string($identifierOctet) || is_numeric($identifierOctet) == false) {
  251. $identifierOctet = ord($identifierOctet);
  252. }
  253. if ($identifierOctet != $expectedIdentifier) {
  254. $message = 'Can not create an '.Identifier::getName($expectedIdentifier).' from an '.Identifier::getName($identifierOctet);
  255. throw new ParserException($message, $offsetForExceptionHandling);
  256. }
  257. }
  258. protected static function parseBinaryIdentifier($binaryData, &$offsetIndex)
  259. {
  260. if (strlen($binaryData) <= $offsetIndex) {
  261. throw new ParserException('Can not parse identifier from data: Offset index larger than input size', $offsetIndex);
  262. }
  263. $identifier = $binaryData[$offsetIndex++];
  264. if (Identifier::isLongForm(ord($identifier)) == false) {
  265. return $identifier;
  266. }
  267. while (true) {
  268. if (strlen($binaryData) <= $offsetIndex) {
  269. throw new ParserException('Can not parse identifier (long form) from data: Offset index larger than input size', $offsetIndex);
  270. }
  271. $nextOctet = $binaryData[$offsetIndex++];
  272. $identifier .= $nextOctet;
  273. if ((ord($nextOctet) & 0x80) === 0) {
  274. // the most significant bit is 0 to we have reached the end of the identifier
  275. break;
  276. }
  277. }
  278. return $identifier;
  279. }
  280. protected static function parseContentLength(&$binaryData, &$offsetIndex, $minimumLength = 0)
  281. {
  282. if (strlen($binaryData) <= $offsetIndex) {
  283. throw new ParserException('Can not parse content length from data: Offset index larger than input size', $offsetIndex);
  284. }
  285. $contentLength = ord($binaryData[$offsetIndex++]);
  286. if (($contentLength & 0x80) != 0) {
  287. // bit 8 is set -> this is the long form
  288. $nrOfLengthOctets = $contentLength & 0x7F;
  289. $contentLength = BigInteger::create(0x00);
  290. for ($i = 0; $i < $nrOfLengthOctets; $i++) {
  291. if (strlen($binaryData) <= $offsetIndex) {
  292. throw new ParserException('Can not parse content length (long form) from data: Offset index larger than input size', $offsetIndex);
  293. }
  294. $contentLength = $contentLength->shiftLeft(8)->add(ord($binaryData[$offsetIndex++]));
  295. }
  296. if ($contentLength->compare(PHP_INT_MAX) > 0) {
  297. throw new ParserException("Can not parse content length from data: length > maximum integer", $offsetIndex);
  298. }
  299. $contentLength = $contentLength->toInteger();
  300. }
  301. if ($contentLength < $minimumLength) {
  302. throw new ParserException('A '.get_called_class()." should have a content length of at least {$minimumLength}. Extracted length was {$contentLength}", $offsetIndex);
  303. }
  304. $lenDataRemaining = strlen($binaryData) - $offsetIndex;
  305. if ($lenDataRemaining < $contentLength) {
  306. throw new ParserException("Content length {$contentLength} exceeds remaining data length {$lenDataRemaining}", $offsetIndex);
  307. }
  308. return $contentLength;
  309. }
  310. }