2012-03-11 07:54:41 +00:00
< ? php
/**
* Pure - PHP ASN . 1 Parser
*
2015-04-02 10:57:52 +00:00
* PHP version 5
2012-03-11 07:54:41 +00:00
*
* 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
* DER blobs .
*
2014-12-10 00:53:05 +00:00
* \phpseclib\File\ASN1 decodes and encodes DER formatted messages and places them in a semantic context .
2012-03-11 07:54:41 +00:00
*
* Uses the 1988 ASN . 1 syntax .
*
2013-12-10 19:10:37 +00:00
* @ category File
2014-12-10 00:53:05 +00:00
* @ package ASN1
2013-12-10 19:10:37 +00:00
* @ author Jim Wigginton < terrafrost @ php . net >
2014-12-09 23:02:44 +00:00
* @ copyright 2012 Jim Wigginton
2013-12-10 19:10:37 +00:00
* @ license http :// www . opensource . org / licenses / mit - license . html MIT License
* @ link http :// phpseclib . sourceforge . net
2012-03-11 07:54:41 +00:00
*/
2014-12-10 00:53:05 +00:00
namespace phpseclib\File ;
2014-06-02 18:09:47 +00:00
2014-12-10 00:53:05 +00:00
use phpseclib\File\ASN1\Element ;
use phpseclib\Math\BigInteger ;
2017-08-24 17:50:00 +00:00
use DateTime ;
use DateTimeZone ;
2012-03-20 05:25:43 +00:00
2012-03-11 07:54:41 +00:00
/**
* Pure - PHP ASN . 1 Parser
*
2014-12-10 00:53:05 +00:00
* @ package ASN1
2012-03-11 07:54:41 +00:00
* @ author Jim Wigginton < terrafrost @ php . net >
* @ access public
*/
2014-12-10 00:53:05 +00:00
class ASN1
2013-12-03 18:34:41 +00:00
{
2014-12-04 02:08:22 +00:00
/** #@+
* Tag Classes
*
* @ access private
* @ link http :// www . itu . int / ITU - T / studygroups / com17 / languages / X . 690 - 0207. pdf #page=12
*/
const CLASS_UNIVERSAL = 0 ;
const CLASS_APPLICATION = 1 ;
const CLASS_CONTEXT_SPECIFIC = 2 ;
const CLASS_PRIVATE = 3 ;
/**#@-*/
/** #@+
* Tag Classes
*
* @ access private
* @ link http :// www . obj - sys . com / asn1tutorial / node124 . html
*/
const TYPE_BOOLEAN = 1 ;
const TYPE_INTEGER = 2 ;
const TYPE_BIT_STRING = 3 ;
const TYPE_OCTET_STRING = 4 ;
const TYPE_NULL = 5 ;
const TYPE_OBJECT_IDENTIFIER = 6 ;
//const TYPE_OBJECT_DESCRIPTOR = 7;
//const TYPE_INSTANCE_OF = 8; // EXTERNAL
const TYPE_REAL = 9 ;
const TYPE_ENUMERATED = 10 ;
//const TYPE_EMBEDDED = 11;
const TYPE_UTF8_STRING = 12 ;
//const TYPE_RELATIVE_OID = 13;
const TYPE_SEQUENCE = 16 ; // SEQUENCE OF
const TYPE_SET = 17 ; // SET OF
/**#@-*/
/** #@+
* More Tag Classes
*
* @ access private
* @ link http :// www . obj - sys . com / asn1tutorial / node10 . html
*/
const TYPE_NUMERIC_STRING = 18 ;
const TYPE_PRINTABLE_STRING = 19 ;
const TYPE_TELETEX_STRING = 20 ; // T61String
const TYPE_VIDEOTEX_STRING = 21 ;
const TYPE_IA5_STRING = 22 ;
const TYPE_UTC_TIME = 23 ;
const TYPE_GENERALIZED_TIME = 24 ;
const TYPE_GRAPHIC_STRING = 25 ;
const TYPE_VISIBLE_STRING = 26 ; // ISO646String
const TYPE_GENERAL_STRING = 27 ;
const TYPE_UNIVERSAL_STRING = 28 ;
//const TYPE_CHARACTER_STRING = 29;
const TYPE_BMP_STRING = 30 ;
/**#@-*/
/** #@+
* Tag Aliases
*
* These tags are kinda place holders for other tags .
*
* @ access private
*/
const TYPE_CHOICE = - 1 ;
const TYPE_ANY = - 2 ;
/**#@-*/
2012-03-11 07:54:41 +00:00
/**
* ASN . 1 object identifier
*
2016-04-10 16:30:59 +00:00
* @ var array
2012-03-11 07:54:41 +00:00
* @ access private
* @ link http :// en . wikipedia . org / wiki / Object_identifier
*/
var $oids = array ();
/**
* Default date format
*
2016-04-10 16:30:59 +00:00
* @ var string
2012-03-11 07:54:41 +00:00
* @ access private
* @ link http :// php . net / class . datetime
*/
2014-03-19 13:04:55 +00:00
var $format = 'D, d M Y H:i:s O' ;
2012-03-11 07:54:41 +00:00
/**
* Default date format
*
2016-04-10 16:30:59 +00:00
* @ var array
2012-03-11 07:54:41 +00:00
* @ access private
2016-04-10 16:30:59 +00:00
* @ see self :: setTimeFormat ()
* @ see self :: asn1map ()
2012-03-11 07:54:41 +00:00
* @ link http :// php . net / class . datetime
*/
var $encoded ;
/**
* Filters
*
2014-12-04 02:08:22 +00:00
* If the mapping type is self :: TYPE_ANY what do we actually encode it as ?
2012-03-11 07:54:41 +00:00
*
2016-04-10 16:30:59 +00:00
* @ var array
2012-03-11 07:54:41 +00:00
* @ access private
2016-04-10 16:30:59 +00:00
* @ see self :: _encode_der ()
2012-03-11 07:54:41 +00:00
*/
var $filters ;
2022-11-24 11:11:42 +00:00
/**
* Current Location of most recent ASN . 1 encode process
*
* Useful for debug purposes
*
* @ var array
* @ see self :: encode_der ()
*/
var $location ;
2012-10-23 11:37:51 +00:00
/**
* Type mapping table for the ANY type .
*
2014-12-10 00:53:05 +00:00
* Structured or unknown types are mapped to a \phpseclib\File\ASN1\Element .
2012-10-23 11:37:51 +00:00
* Unambiguous types get the direct mapping ( int / real / bool ) .
* Others are mapped as a choice , with an extra indexing level .
*
2016-04-10 16:30:59 +00:00
* @ var array
2012-11-02 15:53:32 +00:00
* @ access public
2012-10-23 11:37:51 +00:00
*/
var $ANYmap = array (
2014-12-04 02:08:22 +00:00
self :: TYPE_BOOLEAN => true ,
self :: TYPE_INTEGER => true ,
self :: TYPE_BIT_STRING => 'bitString' ,
self :: TYPE_OCTET_STRING => 'octetString' ,
self :: TYPE_NULL => 'null' ,
self :: TYPE_OBJECT_IDENTIFIER => 'objectIdentifier' ,
self :: TYPE_REAL => true ,
self :: TYPE_ENUMERATED => 'enumerated' ,
self :: TYPE_UTF8_STRING => 'utf8String' ,
self :: TYPE_NUMERIC_STRING => 'numericString' ,
self :: TYPE_PRINTABLE_STRING => 'printableString' ,
self :: TYPE_TELETEX_STRING => 'teletexString' ,
self :: TYPE_VIDEOTEX_STRING => 'videotexString' ,
self :: TYPE_IA5_STRING => 'ia5String' ,
self :: TYPE_UTC_TIME => 'utcTime' ,
self :: TYPE_GENERALIZED_TIME => 'generalTime' ,
self :: TYPE_GRAPHIC_STRING => 'graphicString' ,
self :: TYPE_VISIBLE_STRING => 'visibleString' ,
self :: TYPE_GENERAL_STRING => 'generalString' ,
self :: TYPE_UNIVERSAL_STRING => 'universalString' ,
//self::TYPE_CHARACTER_STRING => 'characterString',
self :: TYPE_BMP_STRING => 'bmpString'
2012-10-23 11:37:51 +00:00
);
2012-11-02 15:53:32 +00:00
/**
* String type to character size mapping table .
*
* Non - convertable types are absent from this table .
* size == 0 indicates variable length encoding .
*
2016-04-10 16:30:59 +00:00
* @ var array
2012-11-02 15:53:32 +00:00
* @ access public
*/
var $stringTypeSize = array (
2014-12-04 02:08:22 +00:00
self :: TYPE_UTF8_STRING => 0 ,
self :: TYPE_BMP_STRING => 2 ,
self :: TYPE_UNIVERSAL_STRING => 4 ,
self :: TYPE_PRINTABLE_STRING => 1 ,
self :: TYPE_TELETEX_STRING => 1 ,
self :: TYPE_IA5_STRING => 1 ,
self :: TYPE_VISIBLE_STRING => 1 ,
2012-11-02 15:53:32 +00:00
);
2012-03-11 07:54:41 +00:00
/**
* Parse BER - encoding
*
* Serves a similar purpose to openssl ' s asn1parse
*
2016-04-10 16:30:59 +00:00
* @ param string $encoded
* @ return array
2012-03-11 07:54:41 +00:00
* @ access public
*/
function decodeBER ( $encoded )
{
2014-12-24 21:05:56 +00:00
if ( $encoded instanceof Element ) {
2012-10-25 15:54:02 +00:00
$encoded = $encoded -> element ;
}
$this -> encoded = $encoded ;
2014-08-25 15:12:56 +00:00
// encapsulate in an array for BC with the old decodeBER
return array ( $this -> _decode_ber ( $encoded ));
2012-03-11 07:54:41 +00:00
}
/**
* 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 .
2014-12-04 02:08:22 +00:00
* $encoded is passed by reference for the recursive calls done for self :: TYPE_BIT_STRING and
* self :: TYPE_OCTET_STRING . In those cases , the indefinite length is used .
2012-03-11 07:54:41 +00:00
*
2016-04-10 16:30:59 +00:00
* @ param string $encoded
* @ param int $start
2016-06-19 04:34:49 +00:00
* @ param int $encoded_pos
2016-04-10 16:30:59 +00:00
* @ return array
2012-03-11 07:54:41 +00:00
* @ access private
*/
2016-06-19 04:34:49 +00:00
function _decode_ber ( $encoded , $start = 0 , $encoded_pos = 0 )
2012-03-11 07:54:41 +00:00
{
2014-08-25 15:12:56 +00:00
$current = array ( 'start' => $start );
2021-06-25 02:36:03 +00:00
if ( ! isset ( $encoded [ $encoded_pos ])) {
return false ;
}
2016-06-19 04:34:49 +00:00
$type = ord ( $encoded [ $encoded_pos ++ ]);
2021-04-02 18:46:14 +00:00
$startOffset = 1 ;
2014-08-25 15:12:56 +00:00
$constructed = ( $type >> 5 ) & 1 ;
$tag = $type & 0x1F ;
if ( $tag == 0x1F ) {
$tag = 0 ;
// process septets (since the eighth bit is ignored, it's not an octet)
do {
2021-06-25 02:36:03 +00:00
if ( ! isset ( $encoded [ $encoded_pos ])) {
return false ;
}
2019-05-26 08:18:06 +00:00
$temp = ord ( $encoded [ $encoded_pos ++ ]);
2021-04-02 18:46:14 +00:00
$startOffset ++ ;
2019-05-26 08:18:06 +00:00
$loop = $temp >> 7 ;
2014-08-25 15:12:56 +00:00
$tag <<= 7 ;
2021-04-02 18:46:14 +00:00
$temp &= 0x7F ;
// "bits 7 to 1 of the first subsequent octet shall not all be zero"
if ( $startOffset == 2 && $temp == 0 ) {
return false ;
}
$tag |= $temp ;
2015-07-15 01:52:31 +00:00
} while ( $loop );
2014-08-25 15:12:56 +00:00
}
2021-04-02 18:46:14 +00:00
$start += $startOffset ;
2014-08-25 15:12:56 +00:00
// Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
2021-06-25 02:36:03 +00:00
if ( ! isset ( $encoded [ $encoded_pos ])) {
return false ;
}
2016-06-19 04:34:49 +00:00
$length = ord ( $encoded [ $encoded_pos ++ ]);
2014-08-25 15:12:56 +00:00
$start ++ ;
2015-07-15 01:52:31 +00:00
if ( $length == 0x80 ) { // indefinite length
2014-08-31 00:44:12 +00:00
// "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
2014-08-25 15:12:56 +00:00
// immediately available." -- paragraph 8.1.3.2.c
2016-06-19 04:34:49 +00:00
$length = strlen ( $encoded ) - $encoded_pos ;
2015-07-15 01:52:31 +00:00
} elseif ( $length & 0x80 ) { // definite length, long form
2014-08-25 15:12:56 +00:00
// 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.
$length &= 0x7F ;
2016-06-19 04:34:49 +00:00
$temp = substr ( $encoded , $encoded_pos , $length );
$encoded_pos += $length ;
2014-08-25 15:12:56 +00:00
// tags of indefinte length don't really have a header length; this length includes the tag
$current += array ( 'headerlength' => $length + 2 );
$start += $length ;
extract ( unpack ( 'Nlength' , substr ( str_pad ( $temp , 4 , chr ( 0 ), STR_PAD_LEFT ), - 4 )));
} else {
$current += array ( 'headerlength' => 2 );
}
2016-06-19 04:34:49 +00:00
if ( $length > ( strlen ( $encoded ) - $encoded_pos )) {
2015-06-07 14:59:13 +00:00
return false ;
}
2016-06-19 04:34:49 +00:00
$content = substr ( $encoded , $encoded_pos , $length );
$content_pos = 0 ;
2014-08-25 15:12:56 +00:00
// at this point $length can be overwritten. it's only accurate for definite length things as is
/* 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 ;
switch ( $class ) {
2014-12-04 02:08:22 +00:00
case self :: CLASS_APPLICATION :
case self :: CLASS_PRIVATE :
case self :: CLASS_CONTEXT_SPECIFIC :
2015-01-11 05:58:50 +00:00
if ( ! $constructed ) {
return array (
'type' => $class ,
'constant' => $tag ,
'content' => $content ,
'length' => $length + $start - $current [ 'start' ]
);
}
$newcontent = array ();
2015-06-04 04:48:43 +00:00
$remainingLength = $length ;
2015-06-07 14:59:13 +00:00
while ( $remainingLength > 0 ) {
2016-08-28 15:38:01 +00:00
$temp = $this -> _decode_ber ( $content , $start , $content_pos );
2018-09-11 09:16:19 +00:00
if ( $temp === false ) {
break ;
}
2015-06-04 04:48:43 +00:00
$length = $temp [ 'length' ];
// end-of-content octets - see paragraph 8.1.5
2016-06-19 04:34:49 +00:00
if ( substr ( $content , $content_pos + $length , 2 ) == " \0 \0 " ) {
2014-09-22 06:01:34 +00:00
$length += 2 ;
2015-06-04 04:48:43 +00:00
$start += $length ;
$newcontent [] = $temp ;
break ;
2014-09-22 06:01:34 +00:00
}
2015-01-11 05:58:50 +00:00
$start += $length ;
2015-06-04 04:48:43 +00:00
$remainingLength -= $length ;
$newcontent [] = $temp ;
2016-06-19 04:34:49 +00:00
$content_pos += $length ;
2014-08-25 15:12:56 +00:00
}
2012-03-11 07:54:41 +00:00
2014-08-25 15:12:56 +00:00
return array (
'type' => $class ,
'constant' => $tag ,
// the array encapsulation is for BC with the old format
2015-01-11 05:58:50 +00:00
'content' => $newcontent ,
2014-08-25 15:12:56 +00:00
// the only time when $content['headerlength'] isn't defined is when the length is indefinite.
// the absence of $content['headerlength'] is how we know if something is indefinite or not.
// technically, it could be defined to be 2 and then another indicator could be used but whatever.
'length' => $start - $current [ 'start' ]
) + $current ;
}
2012-03-11 07:54:41 +00:00
2014-08-25 15:12:56 +00:00
$current += array ( 'type' => $tag );
2012-03-11 07:54:41 +00:00
2014-08-25 15:12:56 +00:00
// decode UNIVERSAL tags
switch ( $tag ) {
2014-12-04 02:08:22 +00:00
case self :: TYPE_BOOLEAN :
2014-08-25 15:12:56 +00:00
// "The contents octets shall consist of a single octet." -- paragraph 8.2.1
2021-04-03 18:15:58 +00:00
if ( $constructed || strlen ( $content ) != 1 ) {
2021-04-03 16:07:25 +00:00
return false ;
}
2016-06-19 04:34:49 +00:00
$current [ 'content' ] = ( bool ) ord ( $content [ $content_pos ]);
2014-08-25 15:12:56 +00:00
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_INTEGER :
case self :: TYPE_ENUMERATED :
2021-04-03 18:15:58 +00:00
if ( $constructed ) {
return false ;
}
2016-06-27 01:34:15 +00:00
$current [ 'content' ] = new BigInteger ( substr ( $content , $content_pos ), - 256 );
2014-08-25 15:12:56 +00:00
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_REAL : // not currently supported
2014-08-25 15:12:56 +00:00
return false ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_BIT_STRING :
2014-08-25 15:12:56 +00:00
// 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
// seven.
if ( ! $constructed ) {
2016-06-19 04:34:49 +00:00
$current [ 'content' ] = substr ( $content , $content_pos );
2014-08-25 15:12:56 +00:00
} else {
2016-06-19 04:34:49 +00:00
$temp = $this -> _decode_ber ( $content , $start , $content_pos );
2018-09-11 09:16:19 +00:00
if ( $temp === false ) {
return false ;
}
2016-06-19 04:34:49 +00:00
$length -= ( strlen ( $content ) - $content_pos );
2014-08-25 15:12:56 +00:00
$last = count ( $temp ) - 1 ;
for ( $i = 0 ; $i < $last ; $i ++ ) {
2012-03-11 07:54:41 +00:00
// all subtags should be bit strings
2021-04-03 16:10:05 +00:00
if ( $temp [ $i ][ 'type' ] != self :: TYPE_BIT_STRING ) {
2021-04-03 16:07:25 +00:00
return false ;
}
2014-08-25 15:12:56 +00:00
$current [ 'content' ] .= substr ( $temp [ $i ][ 'content' ], 1 );
2012-03-11 07:54:41 +00:00
}
2014-08-25 15:12:56 +00:00
// all subtags should be bit strings
2021-04-03 16:10:05 +00:00
if ( $temp [ $last ][ 'type' ] != self :: TYPE_BIT_STRING ) {
2021-04-03 16:07:25 +00:00
return false ;
}
2014-08-25 15:12:56 +00:00
$current [ 'content' ] = $temp [ $last ][ 'content' ][ 0 ] . $current [ 'content' ] . substr ( $temp [ $i ][ 'content' ], 1 );
}
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_OCTET_STRING :
2014-08-25 15:12:56 +00:00
if ( ! $constructed ) {
2016-06-19 04:34:49 +00:00
$current [ 'content' ] = substr ( $content , $content_pos );
2014-08-25 15:12:56 +00:00
} else {
$current [ 'content' ] = '' ;
$length = 0 ;
2016-06-19 04:34:49 +00:00
while ( substr ( $content , $content_pos , 2 ) != " \0 \0 " ) {
$temp = $this -> _decode_ber ( $content , $length + $start , $content_pos );
2018-09-11 09:16:19 +00:00
if ( $temp === false ) {
return false ;
}
2016-06-19 04:34:49 +00:00
$content_pos += $temp [ 'length' ];
2014-08-25 15:12:56 +00:00
// all subtags should be octet strings
2021-04-03 16:10:05 +00:00
if ( $temp [ 'type' ] != self :: TYPE_OCTET_STRING ) {
2021-04-03 16:07:25 +00:00
return false ;
}
2014-08-25 15:12:56 +00:00
$current [ 'content' ] .= $temp [ 'content' ];
$length += $temp [ 'length' ];
}
2016-06-19 04:34:49 +00:00
if ( substr ( $content , $content_pos , 2 ) == " \0 \0 " ) {
2014-08-25 15:12:56 +00:00
$length += 2 ; // +2 for the EOC
}
}
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_NULL :
2014-08-25 15:12:56 +00:00
// "The contents octets shall not contain any octets." -- paragraph 8.8.2
2021-04-03 18:15:58 +00:00
if ( $constructed || strlen ( $content )) {
2021-04-03 16:07:25 +00:00
return false ;
}
2014-08-25 15:12:56 +00:00
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_SEQUENCE :
case self :: TYPE_SET :
2021-04-03 18:15:58 +00:00
if ( ! $constructed ) {
return false ;
}
2014-08-25 15:12:56 +00:00
$offset = 0 ;
$current [ 'content' ] = array ();
2016-06-19 04:34:49 +00:00
$content_len = strlen ( $content );
while ( $content_pos < $content_len ) {
2014-08-25 15:12:56 +00:00
// if indefinite length construction was used and we have an end-of-content string next
// 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
2016-06-19 04:34:49 +00:00
if ( ! isset ( $current [ 'headerlength' ]) && substr ( $content , $content_pos , 2 ) == " \0 \0 " ) {
2014-08-25 15:12:56 +00:00
$length = $offset + 2 ; // +2 for the EOC
break 2 ;
}
2016-06-19 04:34:49 +00:00
$temp = $this -> _decode_ber ( $content , $start + $offset , $content_pos );
2018-09-11 09:16:19 +00:00
if ( $temp === false ) {
return false ;
}
2016-06-19 04:34:49 +00:00
$content_pos += $temp [ 'length' ];
2014-08-25 15:12:56 +00:00
$current [ 'content' ][] = $temp ;
$offset += $temp [ 'length' ];
}
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_OBJECT_IDENTIFIER :
2021-04-03 18:15:58 +00:00
if ( $constructed ) {
return false ;
}
2019-05-01 03:37:19 +00:00
$current [ 'content' ] = $this -> _decodeOID ( substr ( $content , $content_pos ));
2021-04-02 15:43:15 +00:00
if ( $current [ 'content' ] === false ) {
return false ;
}
2014-08-25 15:12:56 +00:00
break ;
/* Each character string type shall be encoded as if it had been declared :
[ UNIVERSAL x ] IMPLICIT OCTET STRING
2012-03-11 07:54:41 +00:00
2014-08-25 15:12:56 +00:00
-- X . 690 - 0207. pdf #page=23 (paragraph 8.21.3)
2012-03-11 07:54:41 +00:00
2014-08-25 15:12:56 +00:00
Per that , we ' re not going to do any validation . If there are any illegal characters in the string ,
we don ' t really care */
2014-12-04 02:08:22 +00:00
case self :: TYPE_NUMERIC_STRING :
2014-08-25 15:12:56 +00:00
// 0,1,2,3,4,5,6,7,8,9, and space
2014-12-04 02:08:22 +00:00
case self :: TYPE_PRINTABLE_STRING :
2014-08-25 15:12:56 +00:00
// Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
// hyphen, full stop, solidus, colon, equal sign, question mark
2014-12-04 02:08:22 +00:00
case self :: TYPE_TELETEX_STRING :
2014-08-25 15:12:56 +00:00
// The Teletex character set in CCITT's T61, space, and delete
// see http://en.wikipedia.org/wiki/Teletex#Character_sets
2014-12-04 02:08:22 +00:00
case self :: TYPE_VIDEOTEX_STRING :
2014-08-25 15:12:56 +00:00
// The Videotex character set in CCITT's T.100 and T.101, space, and delete
2014-12-04 02:08:22 +00:00
case self :: TYPE_VISIBLE_STRING :
2014-08-25 15:12:56 +00:00
// Printing character sets of international ASCII, and space
2014-12-04 02:08:22 +00:00
case self :: TYPE_IA5_STRING :
2014-08-25 15:12:56 +00:00
// International Alphabet 5 (International ASCII)
2014-12-04 02:08:22 +00:00
case self :: TYPE_GRAPHIC_STRING :
2014-08-25 15:12:56 +00:00
// All registered G sets, and space
2014-12-04 02:08:22 +00:00
case self :: TYPE_GENERAL_STRING :
2014-08-25 15:12:56 +00:00
// All registered C and G sets, space and delete
2014-12-04 02:08:22 +00:00
case self :: TYPE_UTF8_STRING :
2014-08-25 15:12:56 +00:00
// ????
2014-12-04 02:08:22 +00:00
case self :: TYPE_BMP_STRING :
2021-04-03 18:15:58 +00:00
if ( $constructed ) {
return false ;
}
2016-06-19 04:34:49 +00:00
$current [ 'content' ] = substr ( $content , $content_pos );
2014-08-25 15:12:56 +00:00
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_UTC_TIME :
case self :: TYPE_GENERALIZED_TIME :
2021-04-03 18:15:58 +00:00
if ( $constructed ) {
return false ;
}
2016-06-19 04:34:49 +00:00
$current [ 'content' ] = $this -> _decodeTime ( substr ( $content , $content_pos ), $tag );
2021-04-03 20:54:27 +00:00
break ;
2014-08-25 15:12:56 +00:00
default :
2021-04-03 20:54:27 +00:00
return false ;
2012-03-11 07:54:41 +00:00
}
2014-08-25 15:12:56 +00:00
$start += $length ;
// ie. length is the length of the full TLV encoding - it's not just the length of the value
return $current + array ( 'length' => $start - $current [ 'start' ]);
2012-03-11 07:54:41 +00:00
}
/**
2014-08-25 15:12:56 +00:00
* ASN . 1 Map
2012-03-11 07:54:41 +00:00
*
* Provides an ASN . 1 semantic mapping ( $mapping ) from a parsed BER - encoding to a human readable format .
*
2013-10-09 19:25:52 +00:00
* " Special " mappings may be applied on a per tag - name basis via $special .
*
2016-04-10 16:30:59 +00:00
* @ param array $decoded
* @ param array $mapping
* @ param array $special
* @ return array
2012-03-11 07:54:41 +00:00
* @ access public
*/
2013-10-09 19:25:52 +00:00
function asn1map ( $decoded , $mapping , $special = array ())
2012-03-11 07:54:41 +00:00
{
2020-03-02 16:18:30 +00:00
if ( ! is_array ( $decoded )) {
return false ;
}
2014-04-10 18:49:28 +00:00
if ( isset ( $mapping [ 'explicit' ]) && is_array ( $decoded [ 'content' ])) {
2012-05-13 17:52:12 +00:00
$decoded = $decoded [ 'content' ][ 0 ];
}
2012-03-11 07:54:41 +00:00
switch ( true ) {
2014-12-04 02:08:22 +00:00
case $mapping [ 'type' ] == self :: TYPE_ANY :
2012-10-23 11:37:51 +00:00
$intype = $decoded [ 'type' ];
2016-07-23 16:01:43 +00:00
if ( isset ( $decoded [ 'constant' ]) || ! isset ( $this -> ANYmap [ $intype ]) || ( ord ( $this -> encoded [ $decoded [ 'start' ]]) & 0x20 )) {
2014-12-10 00:53:05 +00:00
return new Element ( substr ( $this -> encoded , $decoded [ 'start' ], $decoded [ 'length' ]));
2012-10-23 11:37:51 +00:00
}
$inmap = $this -> ANYmap [ $intype ];
if ( is_string ( $inmap )) {
2013-10-09 19:25:52 +00:00
return array ( $inmap => $this -> asn1map ( $decoded , array ( 'type' => $intype ) + $mapping , $special ));
2012-10-23 11:37:51 +00:00
}
break ;
2014-12-04 02:08:22 +00:00
case $mapping [ 'type' ] == self :: TYPE_CHOICE :
2012-03-11 07:54:41 +00:00
foreach ( $mapping [ 'children' ] as $key => $option ) {
switch ( true ) {
case isset ( $option [ 'constant' ]) && $option [ 'constant' ] == $decoded [ 'constant' ] :
case ! isset ( $option [ 'constant' ]) && $option [ 'type' ] == $decoded [ 'type' ] :
2013-10-09 19:25:52 +00:00
$value = $this -> asn1map ( $decoded , $option , $special );
2012-11-07 14:23:54 +00:00
break ;
2014-12-04 02:08:22 +00:00
case ! isset ( $option [ 'constant' ]) && $option [ 'type' ] == self :: TYPE_CHOICE :
2013-10-09 19:25:52 +00:00
$v = $this -> asn1map ( $decoded , $option , $special );
2012-11-07 14:23:54 +00:00
if ( isset ( $v )) {
$value = $v ;
}
2012-03-11 07:54:41 +00:00
}
if ( isset ( $value )) {
2013-10-09 19:25:52 +00:00
if ( isset ( $special [ $key ])) {
2013-10-12 17:19:54 +00:00
$value = call_user_func ( $special [ $key ], $value );
2013-10-09 19:25:52 +00:00
}
2012-03-11 07:54:41 +00:00
return array ( $key => $value );
}
}
2013-12-03 17:54:43 +00:00
return null ;
2012-03-11 07:54:41 +00:00
case isset ( $mapping [ 'implicit' ]) :
case isset ( $mapping [ 'explicit' ]) :
case $decoded [ 'type' ] == $mapping [ 'type' ] :
break ;
default :
2014-04-10 18:49:28 +00:00
// if $decoded['type'] and $mapping['type'] are both strings, but different types of strings,
// let it through
switch ( true ) {
2014-12-04 02:08:22 +00:00
case $decoded [ 'type' ] < 18 : // self::TYPE_NUMERIC_STRING == 18
case $decoded [ 'type' ] > 30 : // self::TYPE_BMP_STRING == 30
2014-04-10 18:49:28 +00:00
case $mapping [ 'type' ] < 18 :
case $mapping [ 'type' ] > 30 :
return null ;
}
2012-03-11 07:54:41 +00:00
}
if ( isset ( $mapping [ 'implicit' ])) {
$decoded [ 'type' ] = $mapping [ 'type' ];
}
switch ( $decoded [ 'type' ]) {
2014-12-04 02:08:22 +00:00
case self :: TYPE_SEQUENCE :
2012-03-11 07:54:41 +00:00
$map = array ();
// ignore the min and max
if ( isset ( $mapping [ 'min' ]) && isset ( $mapping [ 'max' ])) {
$child = $mapping [ 'children' ];
foreach ( $decoded [ 'content' ] as $content ) {
2013-12-03 17:54:43 +00:00
if (( $map [] = $this -> asn1map ( $content , $child , $special )) === null ) {
return null ;
2012-11-07 14:23:54 +00:00
}
2012-03-11 07:54:41 +00:00
}
2012-11-07 14:23:54 +00:00
2012-03-11 07:54:41 +00:00
return $map ;
}
2012-11-07 14:23:54 +00:00
$n = count ( $decoded [ 'content' ]);
$i = 0 ;
2012-03-11 07:54:41 +00:00
foreach ( $mapping [ 'children' ] as $key => $child ) {
2012-11-07 14:23:54 +00:00
$maymatch = $i < $n ; // Match only existing input.
if ( $maymatch ) {
2012-03-20 05:25:43 +00:00
$temp = $decoded [ 'content' ][ $i ];
2014-12-04 02:08:22 +00:00
if ( $child [ 'type' ] != self :: TYPE_CHOICE ) {
2012-11-07 14:23:54 +00:00
// Get the mapping and input class & constant.
2014-12-04 02:08:22 +00:00
$childClass = $tempClass = self :: CLASS_UNIVERSAL ;
2013-12-03 17:54:43 +00:00
$constant = null ;
2012-11-07 14:23:54 +00:00
if ( isset ( $temp [ 'constant' ])) {
2018-04-15 04:39:05 +00:00
$tempClass = $temp [ 'type' ];
2012-03-11 07:54:41 +00:00
}
2012-11-07 14:23:54 +00:00
if ( isset ( $child [ 'class' ])) {
$childClass = $child [ 'class' ];
$constant = $child [ 'cast' ];
2013-12-03 20:02:00 +00:00
} elseif ( isset ( $child [ 'constant' ])) {
2014-12-04 02:08:22 +00:00
$childClass = self :: CLASS_CONTEXT_SPECIFIC ;
2012-11-07 14:23:54 +00:00
$constant = $child [ 'constant' ];
2012-03-11 07:54:41 +00:00
}
2012-11-07 14:23:54 +00:00
if ( isset ( $constant ) && isset ( $temp [ 'constant' ])) {
// Can only match if constants and class match.
$maymatch = $constant == $temp [ 'constant' ] && $childClass == $tempClass ;
} else {
// Can only match if no constant expected and type matches or is generic.
2014-12-04 02:08:22 +00:00
$maymatch = ! isset ( $child [ 'constant' ]) && array_search ( $child [ 'type' ], array ( $temp [ 'type' ], self :: TYPE_ANY , self :: TYPE_CHOICE )) !== false ;
2012-11-07 14:23:54 +00:00
}
2012-03-11 07:54:41 +00:00
}
2012-11-07 14:23:54 +00:00
}
if ( $maymatch ) {
// Attempt submapping.
2013-10-09 19:25:52 +00:00
$candidate = $this -> asn1map ( $temp , $child , $special );
2013-12-03 17:54:43 +00:00
$maymatch = $candidate !== null ;
2012-11-07 14:23:54 +00:00
}
if ( $maymatch ) {
// Got the match: use it.
2013-10-09 19:25:52 +00:00
if ( isset ( $special [ $key ])) {
2013-10-12 17:19:54 +00:00
$candidate = call_user_func ( $special [ $key ], $candidate );
2013-10-09 19:25:52 +00:00
}
2012-11-07 14:23:54 +00:00
$map [ $key ] = $candidate ;
2012-03-12 03:23:04 +00:00
$i ++ ;
2012-11-07 14:23:54 +00:00
} elseif ( isset ( $child [ 'default' ])) {
$map [ $key ] = $child [ 'default' ]; // Use default.
} elseif ( ! isset ( $child [ 'optional' ])) {
2013-12-03 17:54:43 +00:00
return null ; // Syntax error.
2012-03-11 07:54:41 +00:00
}
}
2012-11-07 14:23:54 +00:00
// Fail mapping if all input items have not been consumed.
2016-04-10 16:30:59 +00:00
return $i < $n ? null : $map ;
2012-11-07 14:23:54 +00:00
2012-03-11 07:54:41 +00:00
// the main diff between sets and sequences is the encapsulation of the foreach in another for loop
2014-12-04 02:08:22 +00:00
case self :: TYPE_SET :
2012-03-11 07:54:41 +00:00
$map = array ();
// ignore the min and max
if ( isset ( $mapping [ 'min' ]) && isset ( $mapping [ 'max' ])) {
$child = $mapping [ 'children' ];
foreach ( $decoded [ 'content' ] as $content ) {
2013-12-03 17:54:43 +00:00
if (( $map [] = $this -> asn1map ( $content , $child , $special )) === null ) {
return null ;
2012-11-07 14:23:54 +00:00
}
2012-03-11 07:54:41 +00:00
}
return $map ;
}
for ( $i = 0 ; $i < count ( $decoded [ 'content' ]); $i ++ ) {
2012-11-07 14:23:54 +00:00
$temp = $decoded [ 'content' ][ $i ];
2014-12-04 02:08:22 +00:00
$tempClass = self :: CLASS_UNIVERSAL ;
2012-11-07 14:23:54 +00:00
if ( isset ( $temp [ 'constant' ])) {
2018-04-15 04:39:05 +00:00
$tempClass = $temp [ 'type' ];
2012-11-07 14:23:54 +00:00
}
2012-03-20 05:25:43 +00:00
2012-11-07 14:23:54 +00:00
foreach ( $mapping [ 'children' ] as $key => $child ) {
if ( isset ( $map [ $key ])) {
2012-03-20 05:25:43 +00:00
continue ;
}
2012-11-07 14:23:54 +00:00
$maymatch = true ;
2014-12-04 02:08:22 +00:00
if ( $child [ 'type' ] != self :: TYPE_CHOICE ) {
$childClass = self :: CLASS_UNIVERSAL ;
2013-12-03 17:54:43 +00:00
$constant = null ;
2012-11-07 14:23:54 +00:00
if ( isset ( $child [ 'class' ])) {
$childClass = $child [ 'class' ];
$constant = $child [ 'cast' ];
2013-12-03 20:02:00 +00:00
} elseif ( isset ( $child [ 'constant' ])) {
2014-12-04 02:08:22 +00:00
$childClass = self :: CLASS_CONTEXT_SPECIFIC ;
2012-11-07 14:23:54 +00:00
$constant = $child [ 'constant' ];
}
2012-03-20 05:25:43 +00:00
2012-11-07 14:23:54 +00:00
if ( isset ( $constant ) && isset ( $temp [ 'constant' ])) {
// Can only match if constants and class match.
$maymatch = $constant == $temp [ 'constant' ] && $childClass == $tempClass ;
} else {
// Can only match if no constant expected and type matches or is generic.
2014-12-04 02:08:22 +00:00
$maymatch = ! isset ( $child [ 'constant' ]) && array_search ( $child [ 'type' ], array ( $temp [ 'type' ], self :: TYPE_ANY , self :: TYPE_CHOICE )) !== false ;
2012-11-07 14:23:54 +00:00
}
2012-10-11 23:58:36 +00:00
}
2012-11-07 14:23:54 +00:00
if ( $maymatch ) {
// Attempt submapping.
2013-10-09 19:25:52 +00:00
$candidate = $this -> asn1map ( $temp , $child , $special );
2013-12-03 17:54:43 +00:00
$maymatch = $candidate !== null ;
2012-10-11 23:58:36 +00:00
}
2012-11-07 14:23:54 +00:00
if ( ! $maymatch ) {
break ;
2012-03-11 07:54:41 +00:00
}
2012-11-07 14:23:54 +00:00
// Got the match: use it.
2013-10-09 19:25:52 +00:00
if ( isset ( $special [ $key ])) {
2013-10-12 17:19:54 +00:00
$candidate = call_user_func ( $special [ $key ], $candidate );
2013-10-09 19:25:52 +00:00
}
2012-11-07 14:23:54 +00:00
$map [ $key ] = $candidate ;
break ;
2012-03-11 07:54:41 +00:00
}
}
foreach ( $mapping [ 'children' ] as $key => $child ) {
2012-11-07 14:23:54 +00:00
if ( ! isset ( $map [ $key ])) {
if ( isset ( $child [ 'default' ])) {
$map [ $key ] = $child [ 'default' ];
} elseif ( ! isset ( $child [ 'optional' ])) {
2013-12-03 17:54:43 +00:00
return null ;
2012-11-07 14:23:54 +00:00
}
2012-03-11 07:54:41 +00:00
}
}
return $map ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_OBJECT_IDENTIFIER :
2012-03-11 07:54:41 +00:00
return isset ( $this -> oids [ $decoded [ 'content' ]]) ? $this -> oids [ $decoded [ 'content' ]] : $decoded [ 'content' ];
2014-12-04 02:08:22 +00:00
case self :: TYPE_UTC_TIME :
case self :: TYPE_GENERALIZED_TIME :
2019-08-03 03:55:22 +00:00
// for explicitly tagged optional stuff
if ( is_array ( $decoded [ 'content' ])) {
$decoded [ 'content' ] = $decoded [ 'content' ][ 0 ][ 'content' ];
2012-03-11 07:54:41 +00:00
}
2019-08-03 03:55:22 +00:00
// for implicitly tagged optional stuff
// in theory, doing isset($mapping['implicit']) would work but malformed certs do exist
// in the wild that OpenSSL decodes without issue so we'll support them as well
if ( ! is_object ( $decoded [ 'content' ])) {
2012-03-11 07:54:41 +00:00
$decoded [ 'content' ] = $this -> _decodeTime ( $decoded [ 'content' ], $decoded [ 'type' ]);
}
2017-08-24 17:50:00 +00:00
return $decoded [ 'content' ] ? $decoded [ 'content' ] -> format ( $this -> format ) : false ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_BIT_STRING :
2012-03-11 07:54:41 +00:00
if ( isset ( $mapping [ 'mapping' ])) {
$offset = ord ( $decoded [ 'content' ][ 0 ]);
$size = ( strlen ( $decoded [ 'content' ]) - 1 ) * 8 - $offset ;
/*
From X . 680 - 0207. pdf #page=46 (21.7):
" When a " NamedBitList " is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
arbitrarily any trailing 0 bits to ( or from ) values that are being encoded or decoded . Application designers should
therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
0 bits . "
*/
$bits = count ( $mapping [ 'mapping' ]) == $size ? array () : array_fill ( 0 , count ( $mapping [ 'mapping' ]) - $size , false );
for ( $i = strlen ( $decoded [ 'content' ]) - 1 ; $i > 0 ; $i -- ) {
$current = ord ( $decoded [ 'content' ][ $i ]);
for ( $j = $offset ; $j < 8 ; $j ++ ) {
$bits [] = ( bool ) ( $current & ( 1 << $j ));
}
$offset = 0 ;
}
$values = array ();
$map = array_reverse ( $mapping [ 'mapping' ]);
foreach ( $map as $i => $value ) {
if ( $bits [ $i ]) {
$values [] = $value ;
}
}
return $values ;
}
2014-12-04 02:08:22 +00:00
case self :: TYPE_OCTET_STRING :
2012-03-11 07:54:41 +00:00
return base64_encode ( $decoded [ 'content' ]);
2014-12-04 02:08:22 +00:00
case self :: TYPE_NULL :
2012-10-12 00:01:20 +00:00
return '' ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_BOOLEAN :
2012-03-11 07:54:41 +00:00
return $decoded [ 'content' ];
2014-12-04 02:08:22 +00:00
case self :: TYPE_NUMERIC_STRING :
case self :: TYPE_PRINTABLE_STRING :
case self :: TYPE_TELETEX_STRING :
case self :: TYPE_VIDEOTEX_STRING :
case self :: TYPE_IA5_STRING :
case self :: TYPE_GRAPHIC_STRING :
case self :: TYPE_VISIBLE_STRING :
case self :: TYPE_GENERAL_STRING :
case self :: TYPE_UNIVERSAL_STRING :
case self :: TYPE_UTF8_STRING :
case self :: TYPE_BMP_STRING :
2012-03-11 07:54:41 +00:00
return $decoded [ 'content' ];
2014-12-04 02:08:22 +00:00
case self :: TYPE_INTEGER :
case self :: TYPE_ENUMERATED :
2012-03-11 07:54:41 +00:00
$temp = $decoded [ 'content' ];
if ( isset ( $mapping [ 'implicit' ])) {
2014-06-02 18:09:47 +00:00
$temp = new BigInteger ( $decoded [ 'content' ], - 256 );
2012-03-11 07:54:41 +00:00
}
if ( isset ( $mapping [ 'mapping' ])) {
$temp = ( int ) $temp -> toString ();
return isset ( $mapping [ 'mapping' ][ $temp ]) ?
$mapping [ 'mapping' ][ $temp ] :
false ;
}
return $temp ;
}
}
/**
* ASN . 1 Encode
*
* DER - encodes an ASN . 1 semantic mapping ( $mapping ) . Some libraries would probably call this function
* an ASN . 1 compiler .
*
2013-10-09 19:25:52 +00:00
* " Special " mappings can be applied via $special .
*
2016-04-10 16:30:59 +00:00
* @ param string $source
* @ param string $mapping
2020-08-21 16:18:18 +00:00
* @ param array $special
2016-04-10 16:30:59 +00:00
* @ return string
2012-03-11 07:54:41 +00:00
* @ access public
*/
2013-10-09 19:25:52 +00:00
function encodeDER ( $source , $mapping , $special = array ())
2012-03-11 07:54:41 +00:00
{
$this -> location = array ();
2013-12-03 17:54:43 +00:00
return $this -> _encode_der ( $source , $mapping , null , $special );
2012-03-11 07:54:41 +00:00
}
/**
* ASN . 1 Encode ( Helper function )
*
2016-04-10 16:30:59 +00:00
* @ param string $source
* @ param string $mapping
* @ param int $idx
2020-08-21 16:18:18 +00:00
* @ param array $special
2016-04-10 16:30:59 +00:00
* @ return string
2012-03-11 07:54:41 +00:00
* @ access private
*/
2013-12-03 17:54:43 +00:00
function _encode_der ( $source , $mapping , $idx = null , $special = array ())
2012-03-11 07:54:41 +00:00
{
2014-12-24 21:05:56 +00:00
if ( $source instanceof Element ) {
2012-03-20 05:25:43 +00:00
return $source -> element ;
}
2012-10-18 10:38:43 +00:00
// do not encode (implicitly optional) fields with value set to default
if ( isset ( $mapping [ 'default' ]) && $source === $mapping [ 'default' ]) {
2012-10-12 00:07:01 +00:00
return '' ;
2012-10-12 14:17:34 +00:00
}
2012-10-12 00:07:01 +00:00
2012-03-11 07:54:41 +00:00
if ( isset ( $idx )) {
2013-10-09 19:25:52 +00:00
if ( isset ( $special [ $idx ])) {
2013-10-12 17:19:54 +00:00
$source = call_user_func ( $special [ $idx ], $source );
2013-10-09 19:25:52 +00:00
}
2012-03-11 07:54:41 +00:00
$this -> location [] = $idx ;
}
$tag = $mapping [ 'type' ];
switch ( $tag ) {
2014-12-04 02:08:22 +00:00
case self :: TYPE_SET : // Children order is not important, thus process in sequence.
case self :: TYPE_SEQUENCE :
2012-03-11 07:54:41 +00:00
$tag |= 0x20 ; // set the constructed bit
// ignore the min and max
if ( isset ( $mapping [ 'min' ]) && isset ( $mapping [ 'max' ])) {
2016-07-19 21:27:09 +00:00
$value = array ();
2012-03-11 07:54:41 +00:00
$child = $mapping [ 'children' ];
2012-03-25 00:24:03 +00:00
2012-03-11 07:54:41 +00:00
foreach ( $source as $content ) {
2013-12-03 17:54:43 +00:00
$temp = $this -> _encode_der ( $content , $child , null , $special );
2012-03-11 07:54:41 +00:00
if ( $temp === false ) {
return false ;
}
2016-07-19 21:27:09 +00:00
$value [] = $temp ;
}
/* " The encodings of the component values of a set-of value shall appear in ascending order, the encodings being compared
as octet strings with the shorter components being padded at their trailing end with 0 - octets .
NOTE - The padding octets are for comparison purposes only and do not appear in the encodings . "
-- sec 11.6 of http :// www . itu . int / ITU - T / studygroups / com17 / languages / X . 690 - 0207. pdf */
2016-07-20 05:17:24 +00:00
if ( $mapping [ 'type' ] == self :: TYPE_SET ) {
2016-07-19 21:27:09 +00:00
sort ( $value );
2012-03-11 07:54:41 +00:00
}
2019-08-03 12:27:26 +00:00
$value = implode ( '' , $value );
2012-03-11 07:54:41 +00:00
break ;
}
2016-07-19 21:27:09 +00:00
$value = '' ;
2012-03-11 07:54:41 +00:00
foreach ( $mapping [ 'children' ] as $key => $child ) {
2015-06-28 16:32:42 +00:00
if ( ! array_key_exists ( $key , $source )) {
2012-03-11 07:54:41 +00:00
if ( ! isset ( $child [ 'optional' ])) {
return false ;
}
continue ;
}
2013-10-09 19:25:52 +00:00
$temp = $this -> _encode_der ( $source [ $key ], $child , $key , $special );
2012-10-18 10:38:43 +00:00
if ( $temp === false ) {
return false ;
}
// An empty child encoding means it has been optimized out.
// Else we should have at least one tag byte.
if ( $temp === '' ) {
continue ;
}
2012-03-11 07:54:41 +00:00
// if isset($child['constant']) is true then isset($child['optional']) should be true as well
if ( isset ( $child [ 'constant' ])) {
/*
From X . 680 - 0207. pdf #page=58 (30.6):
" The tagging construction specifies explicit tagging if any of the following holds:
...
c ) the " Tag Type " alternative is used and the value of " TagDefault " for the module is IMPLICIT TAGS or
AUTOMATIC TAGS , but the type defined by " Type " is an untagged choice type , an untagged open type , or
an untagged " DummyReference " ( see ITU - T Rec . X . 683 | ISO / IEC 8824 - 4 , 8.3 ) . "
*/
2014-12-04 02:08:22 +00:00
if ( isset ( $child [ 'explicit' ]) || $child [ 'type' ] == self :: TYPE_CHOICE ) {
$subtag = chr (( self :: CLASS_CONTEXT_SPECIFIC << 6 ) | 0x20 | $child [ 'constant' ]);
2012-03-11 07:54:41 +00:00
$temp = $subtag . $this -> _encodeLength ( strlen ( $temp )) . $temp ;
} else {
2014-12-04 02:08:22 +00:00
$subtag = chr (( self :: CLASS_CONTEXT_SPECIFIC << 6 ) | ( ord ( $temp [ 0 ]) & 0x20 ) | $child [ 'constant' ]);
2012-03-11 07:54:41 +00:00
$temp = $subtag . substr ( $temp , 1 );
}
}
$value .= $temp ;
}
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_CHOICE :
2012-03-11 07:54:41 +00:00
$temp = false ;
foreach ( $mapping [ 'children' ] as $key => $child ) {
if ( ! isset ( $source [ $key ])) {
continue ;
}
2013-10-09 19:25:52 +00:00
$temp = $this -> _encode_der ( $source [ $key ], $child , $key , $special );
2012-10-18 10:38:43 +00:00
if ( $temp === false ) {
return false ;
}
// An empty child encoding means it has been optimized out.
// Else we should have at least one tag byte.
if ( $temp === '' ) {
continue ;
}
2012-03-11 07:54:41 +00:00
$tag = ord ( $temp [ 0 ]);
// if isset($child['constant']) is true then isset($child['optional']) should be true as well
if ( isset ( $child [ 'constant' ])) {
2014-12-04 02:08:22 +00:00
if ( isset ( $child [ 'explicit' ]) || $child [ 'type' ] == self :: TYPE_CHOICE ) {
$subtag = chr (( self :: CLASS_CONTEXT_SPECIFIC << 6 ) | 0x20 | $child [ 'constant' ]);
2012-03-11 07:54:41 +00:00
$temp = $subtag . $this -> _encodeLength ( strlen ( $temp )) . $temp ;
} else {
2014-12-04 02:08:22 +00:00
$subtag = chr (( self :: CLASS_CONTEXT_SPECIFIC << 6 ) | ( ord ( $temp [ 0 ]) & 0x20 ) | $child [ 'constant' ]);
2012-03-11 07:54:41 +00:00
$temp = $subtag . substr ( $temp , 1 );
}
}
}
if ( isset ( $idx )) {
array_pop ( $this -> location );
}
2012-10-18 10:38:43 +00:00
if ( $temp && isset ( $mapping [ 'cast' ])) {
2012-03-11 07:54:41 +00:00
$temp [ 0 ] = chr (( $mapping [ 'class' ] << 6 ) | ( $tag & 0x20 ) | $mapping [ 'cast' ]);
}
return $temp ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_INTEGER :
case self :: TYPE_ENUMERATED :
2012-03-11 07:54:41 +00:00
if ( ! isset ( $mapping [ 'mapping' ])) {
2014-05-05 22:38:33 +00:00
if ( is_numeric ( $source )) {
2014-06-02 18:09:47 +00:00
$source = new BigInteger ( $source );
2014-05-05 21:39:35 +00:00
}
2012-03-11 07:54:41 +00:00
$value = $source -> toBytes ( true );
} else {
$value = array_search ( $source , $mapping [ 'mapping' ]);
if ( $value === false ) {
return false ;
}
2014-06-02 18:09:47 +00:00
$value = new BigInteger ( $value );
2012-03-11 07:54:41 +00:00
$value = $value -> toBytes ( true );
2013-07-01 12:38:35 +00:00
}
if ( ! strlen ( $value )) {
$value = chr ( 0 );
2012-03-11 07:54:41 +00:00
}
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_UTC_TIME :
case self :: TYPE_GENERALIZED_TIME :
$format = $mapping [ 'type' ] == self :: TYPE_UTC_TIME ? 'y' : 'Y' ;
2012-03-11 07:54:41 +00:00
$format .= 'mdHis' ;
2021-05-09 12:54:40 +00:00
// if $source does _not_ include timezone information within it then assume that the timezone is GMT
2017-08-24 17:50:00 +00:00
$date = new DateTime ( $source , new DateTimeZone ( 'GMT' ));
2021-05-09 12:54:40 +00:00
// if $source _does_ include timezone information within it then convert the time to GMT
$date -> setTimezone ( new DateTimeZone ( 'GMT' ));
2017-08-24 17:50:00 +00:00
$value = $date -> format ( $format ) . 'Z' ;
2012-03-11 07:54:41 +00:00
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_BIT_STRING :
2012-03-11 07:54:41 +00:00
if ( isset ( $mapping [ 'mapping' ])) {
$bits = array_fill ( 0 , count ( $mapping [ 'mapping' ]), 0 );
$size = 0 ;
for ( $i = 0 ; $i < count ( $mapping [ 'mapping' ]); $i ++ ) {
if ( in_array ( $mapping [ 'mapping' ][ $i ], $source )) {
$bits [ $i ] = 1 ;
$size = $i ;
}
}
2014-04-05 23:07:35 +00:00
2014-04-12 17:01:04 +00:00
if ( isset ( $mapping [ 'min' ]) && $mapping [ 'min' ] >= 1 && $size < $mapping [ 'min' ]) {
2014-04-05 23:07:35 +00:00
$size = $mapping [ 'min' ] - 1 ;
}
2012-03-11 07:54:41 +00:00
$offset = 8 - (( $size + 1 ) & 7 );
$offset = $offset !== 8 ? $offset : 0 ;
$value = chr ( $offset );
for ( $i = $size + 1 ; $i < count ( $mapping [ 'mapping' ]); $i ++ ) {
unset ( $bits [ $i ]);
}
$bits = implode ( '' , array_pad ( $bits , $size + $offset + 1 , 0 ));
$bytes = explode ( ' ' , rtrim ( chunk_split ( $bits , 8 , ' ' )));
foreach ( $bytes as $byte ) {
$value .= chr ( bindec ( $byte ));
}
break ;
}
2014-12-04 02:08:22 +00:00
case self :: TYPE_OCTET_STRING :
2012-03-11 07:54:41 +00:00
/* 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 seven .
-- http :// www . itu . int / ITU - T / studygroups / com17 / languages / X . 690 - 0207. pdf #page=16 */
$value = base64_decode ( $source );
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_OBJECT_IDENTIFIER :
2019-05-01 03:37:19 +00:00
$value = $this -> _encodeOID ( $source );
2012-03-11 07:54:41 +00:00
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_ANY :
2012-10-23 11:37:51 +00:00
$loc = $this -> location ;
if ( isset ( $idx )) {
array_pop ( $this -> location );
2012-03-11 07:54:41 +00:00
}
2012-10-23 11:37:51 +00:00
switch ( true ) {
case ! isset ( $source ) :
2014-12-04 02:08:22 +00:00
return $this -> _encode_der ( null , array ( 'type' => self :: TYPE_NULL ) + $mapping , null , $special );
2012-10-23 11:37:51 +00:00
case is_int ( $source ) :
2014-12-24 21:05:56 +00:00
case $source instanceof BigInteger :
2014-12-04 02:08:22 +00:00
return $this -> _encode_der ( $source , array ( 'type' => self :: TYPE_INTEGER ) + $mapping , null , $special );
2012-10-23 11:37:51 +00:00
case is_float ( $source ) :
2014-12-04 02:08:22 +00:00
return $this -> _encode_der ( $source , array ( 'type' => self :: TYPE_REAL ) + $mapping , null , $special );
2012-10-23 11:37:51 +00:00
case is_bool ( $source ) :
2014-12-04 02:08:22 +00:00
return $this -> _encode_der ( $source , array ( 'type' => self :: TYPE_BOOLEAN ) + $mapping , null , $special );
2012-10-23 11:37:51 +00:00
case is_array ( $source ) && count ( $source ) == 1 :
$typename = implode ( '' , array_keys ( $source ));
$outtype = array_search ( $typename , $this -> ANYmap , true );
if ( $outtype !== false ) {
2013-12-03 17:54:43 +00:00
return $this -> _encode_der ( $source [ $typename ], array ( 'type' => $outtype ) + $mapping , null , $special );
2012-10-23 11:37:51 +00:00
}
2015-07-15 01:52:31 +00:00
}
2012-10-23 11:37:51 +00:00
2012-03-11 07:54:41 +00:00
$filters = $this -> filters ;
2012-10-23 11:37:51 +00:00
foreach ( $loc as $part ) {
2012-03-11 07:54:41 +00:00
if ( ! isset ( $filters [ $part ])) {
$filters = false ;
break ;
}
$filters = $filters [ $part ];
}
if ( $filters === false ) {
2016-04-28 20:34:10 +00:00
user_error ( 'No filters defined for ' . implode ( '/' , $loc ));
2012-03-11 07:54:41 +00:00
return false ;
}
2013-12-03 17:54:43 +00:00
return $this -> _encode_der ( $source , $filters + $mapping , null , $special );
2014-12-04 02:08:22 +00:00
case self :: TYPE_NULL :
2012-03-11 07:54:41 +00:00
$value = '' ;
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_NUMERIC_STRING :
case self :: TYPE_TELETEX_STRING :
case self :: TYPE_PRINTABLE_STRING :
case self :: TYPE_UNIVERSAL_STRING :
case self :: TYPE_UTF8_STRING :
case self :: TYPE_BMP_STRING :
case self :: TYPE_IA5_STRING :
case self :: TYPE_VISIBLE_STRING :
case self :: TYPE_VIDEOTEX_STRING :
case self :: TYPE_GRAPHIC_STRING :
case self :: TYPE_GENERAL_STRING :
2012-03-11 07:54:41 +00:00
$value = $source ;
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_BOOLEAN :
2012-03-11 07:54:41 +00:00
$value = $source ? " \xFF " : " \x00 " ;
break ;
default :
2016-04-28 20:34:10 +00:00
user_error ( 'Mapping provides no type definition for ' . implode ( '/' , $this -> location ));
2012-03-11 07:54:41 +00:00
return false ;
}
if ( isset ( $idx )) {
array_pop ( $this -> location );
}
if ( isset ( $mapping [ 'cast' ])) {
2014-12-04 02:08:22 +00:00
if ( isset ( $mapping [ 'explicit' ]) || $mapping [ 'type' ] == self :: TYPE_CHOICE ) {
2014-03-30 06:11:47 +00:00
$value = chr ( $tag ) . $this -> _encodeLength ( strlen ( $value )) . $value ;
$tag = ( $mapping [ 'class' ] << 6 ) | 0x20 | $mapping [ 'cast' ];
} else {
$tag = ( $mapping [ 'class' ] << 6 ) | ( ord ( $temp [ 0 ]) & 0x20 ) | $mapping [ 'cast' ];
}
2012-03-11 07:54:41 +00:00
}
return chr ( $tag ) . $this -> _encodeLength ( strlen ( $value )) . $value ;
}
/**
* DER - encode the length
*
* DER supports lengths up to ( 2 ** 8 ) ** 127 , however , we ' ll only support lengths up to ( 2 ** 8 ) ** 4. See
2013-02-20 18:25:47 +00:00
* { @ link http :// itu . int / ITU - T / studygroups / com17 / languages / X . 690 - 0207. pdf #p=13 X.690 paragraph 8.1.3} for more information.
2012-03-11 07:54:41 +00:00
*
* @ access private
2016-04-10 16:30:59 +00:00
* @ param int $length
* @ return string
2012-03-11 07:54:41 +00:00
*/
function _encodeLength ( $length )
{
if ( $length <= 0x7F ) {
return chr ( $length );
}
$temp = ltrim ( pack ( 'N' , $length ), chr ( 0 ));
return pack ( 'Ca*' , 0x80 | strlen ( $temp ), $temp );
}
2019-05-01 03:37:19 +00:00
/**
* BER - decode the OID
*
* Called by _decode_ber ()
*
* @ access private
* @ param string $content
* @ return string
*/
function _decodeOID ( $content )
{
static $eighty ;
if ( ! $eighty ) {
2019-05-02 12:18:37 +00:00
$eighty = new BigInteger ( 80 );
2019-05-01 03:37:19 +00:00
}
$oid = array ();
$pos = 0 ;
$len = strlen ( $content );
2024-02-24 19:07:01 +00:00
// see https://github.com/openjdk/jdk/blob/2deb318c9f047ec5a4b160d66a4b52f93688ec42/src/java.base/share/classes/sun/security/util/ObjectIdentifier.java#L55
if ( $len > 4096 ) {
//user_error('Object Identifier size is limited to 4096 bytes');
return false ;
}
2021-04-02 15:43:15 +00:00
if ( ord ( $content [ $len - 1 ]) & 0x80 ) {
return false ;
}
2019-05-02 12:18:37 +00:00
$n = new BigInteger ();
2019-05-01 03:37:19 +00:00
while ( $pos < $len ) {
$temp = ord ( $content [ $pos ++ ]);
$n = $n -> bitwise_leftShift ( 7 );
2019-05-02 12:18:37 +00:00
$n = $n -> bitwise_or ( new BigInteger ( $temp & 0x7F ));
2019-05-01 03:37:19 +00:00
if ( ~ $temp & 0x80 ) {
$oid [] = $n ;
2019-05-02 12:18:37 +00:00
$n = new BigInteger ();
2019-05-01 03:37:19 +00:00
}
}
$part1 = array_shift ( $oid );
$first = floor ( ord ( $content [ 0 ]) / 40 );
/*
" This packing of the first two object identifier components recognizes that only three values are allocated from the root
node , and at most 39 subsequent values from nodes reached by X = 0 and X = 1. "
-- https :// www . itu . int / ITU - T / studygroups / com17 / languages / X . 690 - 0207. pdf #page=22
*/
if ( $first <= 2 ) { // ie. 0 <= ord($content[0]) < 120 (0x78)
array_unshift ( $oid , ord ( $content [ 0 ]) % 40 );
array_unshift ( $oid , $first );
} else {
array_unshift ( $oid , $part1 -> subtract ( $eighty ));
array_unshift ( $oid , 2 );
}
return implode ( '.' , $oid );
}
/**
* DER - encode the OID
*
* Called by _encode_der ()
*
* @ access private
2020-08-21 16:18:18 +00:00
* @ param string $source
2019-05-01 03:37:19 +00:00
* @ return string
*/
function _encodeOID ( $source )
{
static $mask , $zero , $forty ;
if ( ! $mask ) {
2019-05-02 12:18:37 +00:00
$mask = new BigInteger ( 0x7F );
$zero = new BigInteger ();
$forty = new BigInteger ( 40 );
2019-05-01 03:37:19 +00:00
}
$oid = preg_match ( '#(?:\d+\.)+#' , $source ) ? $source : array_search ( $source , $this -> oids );
if ( $oid === false ) {
user_error ( 'Invalid OID' );
return false ;
}
$parts = explode ( '.' , $oid );
$part1 = array_shift ( $parts );
$part2 = array_shift ( $parts );
2019-05-02 12:18:37 +00:00
$first = new BigInteger ( $part1 );
2019-05-01 03:37:19 +00:00
$first = $first -> multiply ( $forty );
2019-05-02 12:18:37 +00:00
$first = $first -> add ( new BigInteger ( $part2 ));
2019-05-01 03:37:19 +00:00
array_unshift ( $parts , $first -> toString ());
$value = '' ;
foreach ( $parts as $part ) {
if ( ! $part ) {
$temp = " \0 " ;
} else {
$temp = '' ;
2019-05-02 12:18:37 +00:00
$part = new BigInteger ( $part );
2019-05-01 03:37:19 +00:00
while ( ! $part -> equals ( $zero )) {
$submask = $part -> bitwise_and ( $mask );
$submask -> setPrecision ( 8 );
$temp = ( chr ( 0x80 ) | $submask -> toBytes ()) . $temp ;
$part = $part -> bitwise_rightShift ( 7 );
}
$temp [ strlen ( $temp ) - 1 ] = $temp [ strlen ( $temp ) - 1 ] & chr ( 0x7F );
}
$value .= $temp ;
}
return $value ;
}
2012-03-11 07:54:41 +00:00
/**
* BER - decode the time
*
* Called by _decode_ber () and in the case of implicit tags asn1map () .
*
* @ access private
2016-04-10 16:30:59 +00:00
* @ param string $content
* @ param int $tag
* @ return string
2012-03-11 07:54:41 +00:00
*/
function _decodeTime ( $content , $tag )
{
/* UTCTime :
http :// tools . ietf . org / html / rfc5280 #section-4.1.2.5.1
http :// www . obj - sys . com / asn1tutorial / node15 . html
GeneralizedTime :
http :// tools . ietf . org / html / rfc5280 #section-4.1.2.5.2
http :// www . obj - sys . com / asn1tutorial / node14 . html */
2017-08-19 05:42:36 +00:00
$format = 'YmdHis' ;
2012-03-11 07:54:41 +00:00
2014-12-04 02:08:22 +00:00
if ( $tag == self :: TYPE_UTC_TIME ) {
2017-08-19 05:42:36 +00:00
// https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=28 says "the seconds
// element shall always be present" but none-the-less I've seen X509 certs where it isn't and if the
// browsers parse it phpseclib ought to too
if ( preg_match ( '#^(\d{10})(Z|[+-]\d{4})$#' , $content , $matches )) {
$content = $matches [ 1 ] . '00' . $matches [ 2 ];
}
$prefix = substr ( $content , 0 , 2 ) >= 50 ? '19' : '20' ;
$content = $prefix . $content ;
} elseif ( strpos ( $content , '.' ) !== false ) {
$format .= '.u' ;
2012-03-11 07:54:41 +00:00
}
2017-08-19 05:42:36 +00:00
if ( $content [ strlen ( $content ) - 1 ] == 'Z' ) {
$content = substr ( $content , 0 , - 1 ) . '+0000' ;
}
if ( strpos ( $content , '-' ) !== false || strpos ( $content , '+' ) !== false ) {
$format .= 'O' ;
2012-03-11 07:54:41 +00:00
}
2017-08-19 05:42:36 +00:00
// error supression isn't necessary as of PHP 7.0:
// http://php.net/manual/en/migration70.other-changes.php
return @ DateTime :: createFromFormat ( $format , $content );
2012-03-11 07:54:41 +00:00
}
/**
* Set the time format
*
* Sets the time / date format for asn1map () .
*
* @ access public
2016-04-10 16:30:59 +00:00
* @ param string $format
2012-03-11 07:54:41 +00:00
*/
function setTimeFormat ( $format )
{
$this -> format = $format ;
}
/**
* Load OIDs
*
* Load the relevant OIDs for a particular ASN . 1 semantic mapping .
*
* @ access public
2016-04-10 16:30:59 +00:00
* @ param array $oids
2012-03-11 07:54:41 +00:00
*/
function loadOIDs ( $oids )
{
$this -> oids = $oids ;
}
/**
* Load filters
*
2014-12-10 00:53:05 +00:00
* See \phpseclib\File\X509 , etc , for an example .
2012-03-11 07:54:41 +00:00
*
* @ access public
2016-04-10 16:30:59 +00:00
* @ param array $filters
2012-03-11 07:54:41 +00:00
*/
function loadFilters ( $filters )
{
$this -> filters = $filters ;
}
/**
* String Shift
*
* Inspired by array_shift
*
2016-04-10 16:30:59 +00:00
* @ param string $string
* @ param int $index
* @ return string
2012-03-11 07:54:41 +00:00
* @ access private
*/
function _string_shift ( & $string , $index = 1 )
{
$substr = substr ( $string , 0 , $index );
$string = substr ( $string , $index );
return $substr ;
}
2012-11-02 15:53:32 +00:00
/**
* String type conversion
*
* This is a lazy conversion , dealing only with character size .
* No real conversion table is used .
*
2016-04-10 16:30:59 +00:00
* @ param string $in
* @ param int $from
* @ param int $to
* @ return string
2012-11-02 15:53:32 +00:00
* @ access public
*/
2014-12-04 02:08:22 +00:00
function convert ( $in , $from = self :: TYPE_UTF8_STRING , $to = self :: TYPE_UTF8_STRING )
2012-11-02 15:53:32 +00:00
{
if ( ! isset ( $this -> stringTypeSize [ $from ]) || ! isset ( $this -> stringTypeSize [ $to ])) {
return false ;
}
$insize = $this -> stringTypeSize [ $from ];
$outsize = $this -> stringTypeSize [ $to ];
$inlength = strlen ( $in );
$out = '' ;
for ( $i = 0 ; $i < $inlength ;) {
if ( $inlength - $i < $insize ) {
return false ;
}
// Get an input character as a 32-bit value.
$c = ord ( $in [ $i ++ ]);
switch ( true ) {
case $insize == 4 :
$c = ( $c << 8 ) | ord ( $in [ $i ++ ]);
$c = ( $c << 8 ) | ord ( $in [ $i ++ ]);
case $insize == 2 :
$c = ( $c << 8 ) | ord ( $in [ $i ++ ]);
case $insize == 1 :
break ;
case ( $c & 0x80 ) == 0x00 :
break ;
case ( $c & 0x40 ) == 0x00 :
return false ;
default :
$bit = 6 ;
do {
if ( $bit > 25 || $i >= $inlength || ( ord ( $in [ $i ]) & 0xC0 ) != 0x80 ) {
return false ;
}
$c = ( $c << 6 ) | ( ord ( $in [ $i ++ ]) & 0x3F );
$bit += 5 ;
$mask = 1 << $bit ;
} while ( $c & $bit );
$c &= $mask - 1 ;
break ;
}
// Convert and append the character to output string.
$v = '' ;
switch ( true ) {
case $outsize == 4 :
$v .= chr ( $c & 0xFF );
$c >>= 8 ;
$v .= chr ( $c & 0xFF );
$c >>= 8 ;
case $outsize == 2 :
$v .= chr ( $c & 0xFF );
$c >>= 8 ;
case $outsize == 1 :
$v .= chr ( $c & 0xFF );
$c >>= 8 ;
if ( $c ) {
return false ;
}
break ;
2023-08-25 01:20:11 +00:00
case ( $c & ( PHP_INT_SIZE == 8 ? 0x80000000 : ( 1 << 31 ))) != 0 :
2012-11-02 15:53:32 +00:00
return false ;
case $c >= 0x04000000 :
$v .= chr ( 0x80 | ( $c & 0x3F ));
$c = ( $c >> 6 ) | 0x04000000 ;
case $c >= 0x00200000 :
$v .= chr ( 0x80 | ( $c & 0x3F ));
$c = ( $c >> 6 ) | 0x00200000 ;
case $c >= 0x00010000 :
$v .= chr ( 0x80 | ( $c & 0x3F ));
$c = ( $c >> 6 ) | 0x00010000 ;
case $c >= 0x00000800 :
$v .= chr ( 0x80 | ( $c & 0x3F ));
$c = ( $c >> 6 ) | 0x00000800 ;
case $c >= 0x00000080 :
$v .= chr ( 0x80 | ( $c & 0x3F ));
$c = ( $c >> 6 ) | 0x000000C0 ;
default :
$v .= chr ( $c );
break ;
}
$out .= strrev ( $v );
}
return $out ;
}
2012-03-11 07:54:41 +00:00
}