* ASN.1 provides the semantics for data encoded using various schemes. The most commonly
* utilized scheme is DER or the "Distinguished Encoding Rules". PEM's are base64 encoded
* File_ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
* Uses the 1988 ASN.1 syntax.
* LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMXII Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
* @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
define('FILE_ASN1_CLASS_UNIVERSAL', 0);
define('FILE_ASN1_CLASS_APPLICATION', 1);
define('FILE_ASN1_CLASS_CONTEXT_SPECIFIC', 2);
define('FILE_ASN1_CLASS_PRIVATE', 3);
* @link http://www.obj-sys.com/asn1tutorial/node124.html
define('FILE_ASN1_TYPE_BOOLEAN', 1);
define('FILE_ASN1_TYPE_INTEGER', 2);
define('FILE_ASN1_TYPE_BIT_STRING', 3);
define('FILE_ASN1_TYPE_OCTET_STRING', 4);
define('FILE_ASN1_TYPE_NULL', 5);
define('FILE_ASN1_TYPE_OBJECT_IDENTIFIER', 6);
//define('FILE_ASN1_TYPE_OBJECT_DESCRIPTOR', 7);
//define('FILE_ASN1_TYPE_INSTANCE_OF', 8); // EXTERNAL
define('FILE_ASN1_TYPE_REAL', 9);
define('FILE_ASN1_TYPE_ENUMERATED', 10);
//define('FILE_ASN1_TYPE_EMBEDDED', 11);
define('FILE_ASN1_TYPE_UTF8_STRING', 12);
//define('FILE_ASN1_TYPE_RELATIVE_OID', 13);
define('FILE_ASN1_TYPE_SEQUENCE', 16); // SEQUENCE OF
define('FILE_ASN1_TYPE_SET', 17); // SET OF
* @link http://www.obj-sys.com/asn1tutorial/node10.html
define('FILE_ASN1_TYPE_NUMERIC_STRING', 18);
define('FILE_ASN1_TYPE_PRINTABLE_STRING', 19);
define('FILE_ASN1_TYPE_TELETEX_STRING', 20); // T61String
define('FILE_ASN1_TYPE_VIDEOTEX_STRING', 21);
define('FILE_ASN1_TYPE_IA5_STRING', 22);
define('FILE_ASN1_TYPE_UTC_TIME', 23);
define('FILE_ASN1_TYPE_GENERALIZED_TIME', 24);
define('FILE_ASN1_TYPE_GRAPHIC_STRING', 25);
define('FILE_ASN1_TYPE_VISIBLE_STRING', 26); // ISO646String
define('FILE_ASN1_TYPE_GENERAL_STRING', 27);
define('FILE_ASN1_TYPE_UNIVERSAL_STRING', 28);
//define('FILE_ASN1_TYPE_CHARACTER_STRING', 29);
define('FILE_ASN1_TYPE_BMP_STRING', 30);
* These tags are kinda place holders for other tags.
define('FILE_ASN1_TYPE_CHOICE', -1);
define('FILE_ASN1_TYPE_ANY', -2);
* Bypass normal encoding rules in File_ASN1::encodeDER()
* @author Jim Wigginton <terrafrost@php.net>
* @return File_ASN1_Element
public function __construct($encoded)
$this->element = $encoded;
* @author Jim Wigginton <terrafrost@php.net>
* ASN.1 object identifier
* @link http://en.wikipedia.org/wiki/Object_identifier
* @link http://php.net/class.datetime
public $format = 'D, d M Y H:i:s O';
* @see File_ASN1::setTimeFormat()
* @see File_ASN1::asn1map()
* @link http://php.net/class.datetime
* If the mapping type is FILE_ASN1_TYPE_ANY what do we actually encode it as?
* @see File_ASN1::_encode_der()
* Type mapping table for the ANY type.
* Structured or unknown types are mapped to a FILE_ASN1_Element.
* Unambiguous types get the direct mapping (int/real/bool).
* Others are mapped as a choice, with an extra indexing level.
FILE_ASN1_TYPE_BOOLEAN => true,
FILE_ASN1_TYPE_INTEGER => true,
FILE_ASN1_TYPE_BIT_STRING => 'bitString',
FILE_ASN1_TYPE_OCTET_STRING => 'octetString',
FILE_ASN1_TYPE_NULL => 'null',
FILE_ASN1_TYPE_OBJECT_IDENTIFIER => 'objectIdentifier',
FILE_ASN1_TYPE_REAL => true,
FILE_ASN1_TYPE_ENUMERATED => 'enumerated',
FILE_ASN1_TYPE_UTF8_STRING => 'utf8String',
FILE_ASN1_TYPE_NUMERIC_STRING => 'numericString',
FILE_ASN1_TYPE_PRINTABLE_STRING => 'printableString',
FILE_ASN1_TYPE_TELETEX_STRING => 'teletexString',
FILE_ASN1_TYPE_VIDEOTEX_STRING => 'videotexString',
FILE_ASN1_TYPE_IA5_STRING => 'ia5String',
FILE_ASN1_TYPE_UTC_TIME => 'utcTime',
FILE_ASN1_TYPE_GENERALIZED_TIME => 'generalTime',
FILE_ASN1_TYPE_GRAPHIC_STRING => 'graphicString',
FILE_ASN1_TYPE_VISIBLE_STRING => 'visibleString',
FILE_ASN1_TYPE_GENERAL_STRING => 'generalString',
FILE_ASN1_TYPE_UNIVERSAL_STRING => 'universalString',
//FILE_ASN1_TYPE_CHARACTER_STRING => 'characterString',
FILE_ASN1_TYPE_BMP_STRING => 'bmpString',
* String type to character size mapping table.
* Non-convertable types are absent from this table.
* size == 0 indicates variable length encoding.
var $stringTypeSize = array(
FILE_ASN1_TYPE_UTF8_STRING => 0,
FILE_ASN1_TYPE_BMP_STRING => 2,
FILE_ASN1_TYPE_UNIVERSAL_STRING => 4,
FILE_ASN1_TYPE_PRINTABLE_STRING => 1,
FILE_ASN1_TYPE_TELETEX_STRING => 1,
FILE_ASN1_TYPE_IA5_STRING => 1,
FILE_ASN1_TYPE_VISIBLE_STRING => 1,
public function __construct()
static $static_init = null;
if (!class_exists('Math_BigInteger')) {
require_once dirname(__FILE__).'/../Math/BigInteger.php';
* Serves a similar purpose to openssl's asn1parse
public function decodeBER($encoded)
if (is_object($encoded) && strtolower(get_class($encoded)) == 'file_asn1_element') {
$encoded = $encoded->element;
$this->encoded = $encoded;
return $this->_decode_ber($encoded);
* Parse BER-encoding (Helper function)
* Sometimes we want to get the BER encoding of a particular tag. $start lets us do that without having to reencode.
* $encoded is passed by reference for the recursive calls done for FILE_ASN1_TYPE_BIT_STRING and
* FILE_ASN1_TYPE_OCTET_STRING. In those cases, the indefinite length is used.
public function _decode_ber(&$encoded, $start = 0)
while (strlen($encoded)) {
$current = array('start' => $start);
$type = ord($this->_string_shift($encoded));
$constructed = ($type >> 5) & 1;
// process septets (since the eighth bit is ignored, it's not an octet)
$loop = ord($encoded[0]) >> 7;
$tag |= ord($this->_string_shift($encoded)) & 0x7F;
// Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
$length = ord($this->_string_shift($encoded));
if ($length == 0x80) { // indefinite length
// "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
// immediately available." -- paragraph 8.1.3.2.c
$length = strlen($encoded);
} elseif ($length & 0x80) { // definite length, long form
// technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
// support it up to four.
$temp = $this->_string_shift($encoded, $length);
// tags of indefinite length don't really have a header length; this length includes the tag
$current += array('headerlength' => $length + 2);
extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
$current += array('headerlength' => 2);
// End-of-content, see paragraphs 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
if (!$type && !$length) {
$content = $this->_string_shift($encoded, $length);
/* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
built-in types. It defines an application-independent data type that must be distinguishable from all other
data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within
a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the
alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this
data type; the term CONTEXT-SPECIFIC does not appear.
-- http://www.obj-sys.com/asn1tutorial/node12.html */
$class = ($type >> 6) & 3;
case FILE_ASN1_CLASS_APPLICATION:
case FILE_ASN1_CLASS_PRIVATE:
case FILE_ASN1_CLASS_CONTEXT_SPECIFIC:
'content' => $constructed ? $this->_decode_ber($content, $start) : $content,
'length' => $length + $start - $current['start'],
$current += array('type' => $tag);
case FILE_ASN1_TYPE_BOOLEAN:
// "The contents octets shall consist of a single octet." -- paragraph 8.2.1
//if (strlen($content) != 1) {
$current['content'] = (bool) ord($content[0]);
case FILE_ASN1_TYPE_INTEGER:
case FILE_ASN1_TYPE_ENUMERATED:
$current['content'] = new Math_BigInteger($content, -256);
case FILE_ASN1_TYPE_REAL: // not currently supported
case FILE_ASN1_TYPE_BIT_STRING:
// The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
// the number of unused bits in the final subsequent octet. The number shall be in the range zero to
$current['content'] = $content;
$temp = $this->_decode_ber($content, $start);
$length -= strlen($content);
$last = count($temp) - 1;
for ($i = 0; $i < $last; $i++) {
// all subtags should be bit strings
//if ($temp[$i]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
$current['content'] .= substr($temp[$i]['content'], 1);
// all subtags should be bit strings
//if ($temp[$last]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
$current['content'] = $temp[$last]['content'][0].$current['content'].substr($temp[$i]['content'], 1);
case FILE_ASN1_TYPE_OCTET_STRING:
$current['content'] = $content;
$temp = $this->_decode_ber($content, $start);
$length -= strlen($content);
for ($i = 0, $size = count($temp); $i < $size; $i++) {
// all subtags should be octet strings
//if ($temp[$i]['type'] != FILE_ASN1_TYPE_OCTET_STRING) {
$current['content'] .= $temp[$i]['content'];
case FILE_ASN1_TYPE_NULL:
// "The contents octets shall not contain any octets." -- paragraph 8.8.2
//if (strlen($content)) {
case FILE_ASN1_TYPE_SEQUENCE:
$current['content'] = $this->_decode_ber($content, $start);
case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
$temp = ord($this->_string_shift($content));
$current['content'] = sprintf('%d.%d', floor($temp / 40), $temp % 40);
while (strlen($content)) {
$temp = ord($this->_string_shift($content));
$current['content'] .= ".$valuen";
// the eighth bit of the last byte should not be 1
/* Each character string type shall be encoded as if it had been declared:
[UNIVERSAL x] IMPLICIT OCTET STRING
-- X.690-0207.pdf#page=23 (paragraph 8.21.3)
Per that, we're not going to do any validation. If there are any illegal characters in the string,
case FILE_ASN1_TYPE_NUMERIC_STRING:
// 0,1,2,3,4,5,6,7,8,9, and space
case FILE_ASN1_TYPE_PRINTABLE_STRING:
// Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
// hyphen, full stop, solidus, colon, equal sign, question mark
case FILE_ASN1_TYPE_TELETEX_STRING:
// The Teletex character set in CCITT's T61, space, and delete
// see http://en.wikipedia.org/wiki/Teletex#Character_sets
case FILE_ASN1_TYPE_VIDEOTEX_STRING:
// The Videotex character set in CCITT's T.100 and T.101, space, and delete
case FILE_ASN1_TYPE_VISIBLE_STRING:
// Printing character sets of international ASCII, and space
case FILE_ASN1_TYPE_IA5_STRING:
// International Alphabet 5 (International ASCII)
case FILE_ASN1_TYPE_GRAPHIC_STRING:
// All registered G sets, and space
case FILE_ASN1_TYPE_GENERAL_STRING:
// All registered C and G sets, space and delete
case FILE_ASN1_TYPE_UTF8_STRING:
case FILE_ASN1_TYPE_BMP_STRING:
$current['content'] = $content;
case FILE_ASN1_TYPE_UTC_TIME:
case FILE_ASN1_TYPE_GENERALIZED_TIME:
$current['content'] = $this->_decodeTime($content, $tag);
$decoded[] = $current + array('length' => $start - $current['start']);