* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FG\ASN1; use FG\ASN1\Exception\ParserException; use FG\ASN1\Universal\BitString; use FG\ASN1\Universal\Boolean; use FG\ASN1\Universal\Enumerated; use FG\ASN1\Universal\GeneralizedTime; use FG\ASN1\Universal\Integer; use FG\ASN1\Universal\NullObject; use FG\ASN1\Universal\ObjectIdentifier; use FG\ASN1\Universal\RelativeObjectIdentifier; use FG\ASN1\Universal\OctetString; use FG\ASN1\Universal\Sequence; use FG\ASN1\Universal\Set; use FG\ASN1\Universal\UTCTime; use FG\ASN1\Universal\IA5String; use FG\ASN1\Universal\PrintableString; use FG\ASN1\Universal\NumericString; use FG\ASN1\Universal\UTF8String; use FG\ASN1\Universal\UniversalString; use FG\ASN1\Universal\CharacterString; use FG\ASN1\Universal\GeneralString; use FG\ASN1\Universal\VisibleString; use FG\ASN1\Universal\GraphicString; use FG\ASN1\Universal\BMPString; use FG\ASN1\Universal\T61String; use FG\ASN1\Universal\ObjectDescriptor; use FG\Utility\BigInteger; use LogicException; /** * Class ASNObject is the base class for all concrete ASN.1 objects. */ abstract class ASNObject implements Parsable { private $contentLength; private $nrOfLengthOctets; /** * Must return the number of octets of the content part. * * @return int */ abstract protected function calculateContentLength(); /** * Encode the object using DER encoding. * * @see http://en.wikipedia.org/wiki/X.690#DER_encoding * * @return string the binary representation of an objects value */ abstract protected function getEncodedValue(); /** * Return the content of this object in a non encoded form. * This can be used to print the value in human readable form. * * @return mixed */ abstract public function getContent(); /** * Return the object type octet. * This should use the class constants of Identifier. * * @see Identifier * * @return int */ abstract public function getType(); /** * Returns all identifier octets. If an inheriting class models a tag with * the long form identifier format, it MUST reimplement this method to * return all octets of the identifier. * * @throws LogicException If the identifier format is long form * * @return string Identifier as a set of octets */ public function getIdentifier() { $firstOctet = $this->getType(); if (Identifier::isLongForm($firstOctet)) { throw new LogicException(sprintf('Identifier of %s uses the long form and must therefor override "ASNObject::getIdentifier()".', get_class($this))); } return chr($firstOctet); } /** * Encode this object using DER encoding. * * @return string the full binary representation of the complete object */ public function getBinary() { $result = $this->getIdentifier(); $result .= $this->createLengthPart(); $result .= $this->getEncodedValue(); return $result; } private function createLengthPart() { $contentLength = $this->getContentLength(); $nrOfLengthOctets = $this->getNumberOfLengthOctets($contentLength); if ($nrOfLengthOctets == 1) { return chr($contentLength); } else { // the first length octet determines the number subsequent length octets $lengthOctets = chr(0x80 | ($nrOfLengthOctets - 1)); for ($shiftLength = 8 * ($nrOfLengthOctets - 2); $shiftLength >= 0; $shiftLength -= 8) { $lengthOctets .= chr($contentLength >> $shiftLength); } return $lengthOctets; } } protected function getNumberOfLengthOctets($contentLength = null) { if (!isset($this->nrOfLengthOctets)) { if ($contentLength == null) { $contentLength = $this->getContentLength(); } $this->nrOfLengthOctets = 1; if ($contentLength > 127) { do { // long form $this->nrOfLengthOctets++; $contentLength = $contentLength >> 8; } while ($contentLength > 0); } } return $this->nrOfLengthOctets; } protected function getContentLength() { if (!isset($this->contentLength)) { $this->contentLength = $this->calculateContentLength(); } return $this->contentLength; } protected function setContentLength($newContentLength) { $this->contentLength = $newContentLength; $this->getNumberOfLengthOctets($newContentLength); } /** * Returns the length of the whole object (including the identifier and length octets). */ public function getObjectLength() { $nrOfIdentifierOctets = strlen($this->getIdentifier()); $contentLength = $this->getContentLength(); $nrOfLengthOctets = $this->getNumberOfLengthOctets($contentLength); return $nrOfIdentifierOctets + $nrOfLengthOctets + $contentLength; } public function __toString() { return $this->getContent(); } /** * Returns the name of the ASN.1 Type of this object. * * @see Identifier::getName() */ public function getTypeName() { return Identifier::getName($this->getType()); } /** * @param string $binaryData * @param int $offsetIndex * * @throws ParserException * * @return \FG\ASN1\ASNObject */ public static function fromBinary(&$binaryData, &$offsetIndex = 0) { if (strlen($binaryData) <= $offsetIndex) { throw new ParserException('Can not parse binary from data: Offset index larger than input size', $offsetIndex); } $identifierOctet = ord($binaryData[$offsetIndex]); if (Identifier::isContextSpecificClass($identifierOctet) && Identifier::isConstructed($identifierOctet)) { return ExplicitlyTaggedObject::fromBinary($binaryData, $offsetIndex); } switch ($identifierOctet) { case Identifier::BITSTRING: return BitString::fromBinary($binaryData, $offsetIndex); case Identifier::BOOLEAN: return Boolean::fromBinary($binaryData, $offsetIndex); case Identifier::ENUMERATED: return Enumerated::fromBinary($binaryData, $offsetIndex); case Identifier::INTEGER: return Integer::fromBinary($binaryData, $offsetIndex); case Identifier::NULL: return NullObject::fromBinary($binaryData, $offsetIndex); case Identifier::OBJECT_IDENTIFIER: return ObjectIdentifier::fromBinary($binaryData, $offsetIndex); case Identifier::RELATIVE_OID: return RelativeObjectIdentifier::fromBinary($binaryData, $offsetIndex); case Identifier::OCTETSTRING: return OctetString::fromBinary($binaryData, $offsetIndex); case Identifier::SEQUENCE: return Sequence::fromBinary($binaryData, $offsetIndex); case Identifier::SET: return Set::fromBinary($binaryData, $offsetIndex); case Identifier::UTC_TIME: return UTCTime::fromBinary($binaryData, $offsetIndex); case Identifier::GENERALIZED_TIME: return GeneralizedTime::fromBinary($binaryData, $offsetIndex); case Identifier::IA5_STRING: return IA5String::fromBinary($binaryData, $offsetIndex); case Identifier::PRINTABLE_STRING: return PrintableString::fromBinary($binaryData, $offsetIndex); case Identifier::NUMERIC_STRING: return NumericString::fromBinary($binaryData, $offsetIndex); case Identifier::UTF8_STRING: return UTF8String::fromBinary($binaryData, $offsetIndex); case Identifier::UNIVERSAL_STRING: return UniversalString::fromBinary($binaryData, $offsetIndex); case Identifier::CHARACTER_STRING: return CharacterString::fromBinary($binaryData, $offsetIndex); case Identifier::GENERAL_STRING: return GeneralString::fromBinary($binaryData, $offsetIndex); case Identifier::VISIBLE_STRING: return VisibleString::fromBinary($binaryData, $offsetIndex); case Identifier::GRAPHIC_STRING: return GraphicString::fromBinary($binaryData, $offsetIndex); case Identifier::BMP_STRING: return BMPString::fromBinary($binaryData, $offsetIndex); case Identifier::T61_STRING: return T61String::fromBinary($binaryData, $offsetIndex); case Identifier::OBJECT_DESCRIPTOR: return ObjectDescriptor::fromBinary($binaryData, $offsetIndex); default: // At this point the identifier may be >1 byte. if (Identifier::isConstructed($identifierOctet)) { return new UnknownConstructedObject($binaryData, $offsetIndex); } else { $identifier = self::parseBinaryIdentifier($binaryData, $offsetIndex); $lengthOfUnknownObject = self::parseContentLength($binaryData, $offsetIndex); $offsetIndex += $lengthOfUnknownObject; return new UnknownObject($identifier, $lengthOfUnknownObject); } } } protected static function parseIdentifier($identifierOctet, $expectedIdentifier, $offsetForExceptionHandling) { if (is_string($identifierOctet) || is_numeric($identifierOctet) == false) { $identifierOctet = ord($identifierOctet); } if ($identifierOctet != $expectedIdentifier) { $message = 'Can not create an '.Identifier::getName($expectedIdentifier).' from an '.Identifier::getName($identifierOctet); throw new ParserException($message, $offsetForExceptionHandling); } } protected static function parseBinaryIdentifier($binaryData, &$offsetIndex) { if (strlen($binaryData) <= $offsetIndex) { throw new ParserException('Can not parse identifier from data: Offset index larger than input size', $offsetIndex); } $identifier = $binaryData[$offsetIndex++]; if (Identifier::isLongForm(ord($identifier)) == false) { return $identifier; } while (true) { if (strlen($binaryData) <= $offsetIndex) { throw new ParserException('Can not parse identifier (long form) from data: Offset index larger than input size', $offsetIndex); } $nextOctet = $binaryData[$offsetIndex++]; $identifier .= $nextOctet; if ((ord($nextOctet) & 0x80) === 0) { // the most significant bit is 0 to we have reached the end of the identifier break; } } return $identifier; } protected static function parseContentLength(&$binaryData, &$offsetIndex, $minimumLength = 0) { if (strlen($binaryData) <= $offsetIndex) { throw new ParserException('Can not parse content length from data: Offset index larger than input size', $offsetIndex); } $contentLength = ord($binaryData[$offsetIndex++]); if (($contentLength & 0x80) != 0) { // bit 8 is set -> this is the long form $nrOfLengthOctets = $contentLength & 0x7F; $contentLength = BigInteger::create(0x00); for ($i = 0; $i < $nrOfLengthOctets; $i++) { if (strlen($binaryData) <= $offsetIndex) { throw new ParserException('Can not parse content length (long form) from data: Offset index larger than input size', $offsetIndex); } $contentLength = $contentLength->shiftLeft(8)->add(ord($binaryData[$offsetIndex++])); } if ($contentLength->compare(PHP_INT_MAX) > 0) { throw new ParserException("Can not parse content length from data: length > maximum integer", $offsetIndex); } $contentLength = $contentLength->toInteger(); } if ($contentLength < $minimumLength) { throw new ParserException('A '.get_called_class()." should have a content length of at least {$minimumLength}. Extracted length was {$contentLength}", $offsetIndex); } $lenDataRemaining = strlen($binaryData) - $offsetIndex; if ($lenDataRemaining < $contentLength) { throw new ParserException("Content length {$contentLength} exceeds remaining data length {$lenDataRemaining}", $offsetIndex); } return $contentLength; } }