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 .
*
2019-11-07 05:41:40 +00:00
* \phpseclib3\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
* @ 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
*/
2022-06-04 15:31:21 +00:00
declare ( strict_types = 1 );
2019-11-07 05:41:40 +00:00
namespace phpseclib3\File ;
2014-06-02 18:09:47 +00:00
2022-01-30 15:34:42 +00:00
use phpseclib3\Common\Functions\Strings ;
2022-08-18 13:05:57 +00:00
use phpseclib3\Exception\RuntimeException ;
2019-11-07 05:41:40 +00:00
use phpseclib3\File\ASN1\Element ;
use phpseclib3\Math\BigInteger ;
2012-03-20 05:25:43 +00:00
2012-03-11 07:54:41 +00:00
/**
* Pure - PHP ASN . 1 Parser
*
* @ author Jim Wigginton < terrafrost @ php . net >
*/
2016-11-30 15:15:07 +00:00
abstract class ASN1
2013-12-03 18:34:41 +00:00
{
2020-12-30 15:08:05 +00:00
// Tag Classes
// http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
2022-07-07 01:43:09 +00:00
public const CLASS_UNIVERSAL = 0 ;
public const CLASS_APPLICATION = 1 ;
public const CLASS_CONTEXT_SPECIFIC = 2 ;
public const CLASS_PRIVATE = 3 ;
2014-12-04 02:08:22 +00:00
2020-12-30 15:08:05 +00:00
// Tag Classes
// http://www.obj-sys.com/asn1tutorial/node124.html
2022-07-07 01:43:09 +00:00
public const TYPE_BOOLEAN = 1 ;
public const TYPE_INTEGER = 2 ;
public const TYPE_BIT_STRING = 3 ;
public const TYPE_OCTET_STRING = 4 ;
public const TYPE_NULL = 5 ;
public const TYPE_OBJECT_IDENTIFIER = 6 ;
2014-12-04 02:08:22 +00:00
//const TYPE_OBJECT_DESCRIPTOR = 7;
//const TYPE_INSTANCE_OF = 8; // EXTERNAL
2022-07-07 01:43:09 +00:00
public const TYPE_REAL = 9 ;
public const TYPE_ENUMERATED = 10 ;
2014-12-04 02:08:22 +00:00
//const TYPE_EMBEDDED = 11;
2022-07-07 01:43:09 +00:00
public const TYPE_UTF8_STRING = 12 ;
2014-12-04 02:08:22 +00:00
//const TYPE_RELATIVE_OID = 13;
2022-07-07 01:43:09 +00:00
public const TYPE_SEQUENCE = 16 ; // SEQUENCE OF
public const TYPE_SET = 17 ; // SET OF
2020-12-30 15:08:05 +00:00
// More Tag Classes
// http://www.obj-sys.com/asn1tutorial/node10.html
2022-07-07 01:43:09 +00:00
public const TYPE_NUMERIC_STRING = 18 ;
public const TYPE_PRINTABLE_STRING = 19 ;
public const TYPE_TELETEX_STRING = 20 ; // T61String
public const TYPE_VIDEOTEX_STRING = 21 ;
public const TYPE_IA5_STRING = 22 ;
public const TYPE_UTC_TIME = 23 ;
public const TYPE_GENERALIZED_TIME = 24 ;
public const TYPE_GRAPHIC_STRING = 25 ;
public const TYPE_VISIBLE_STRING = 26 ; // ISO646String
public const TYPE_GENERAL_STRING = 27 ;
public const TYPE_UNIVERSAL_STRING = 28 ;
2014-12-04 02:08:22 +00:00
//const TYPE_CHARACTER_STRING = 29;
2022-07-07 01:43:09 +00:00
public const TYPE_BMP_STRING = 30 ;
2014-12-04 02:08:22 +00:00
2020-12-30 15:08:05 +00:00
// Tag Aliases
// These tags are kinda place holders for other tags.
2022-07-07 01:43:09 +00:00
public const TYPE_CHOICE = - 1 ;
public const TYPE_ANY = - 2 ;
2014-12-04 02:08:22 +00:00
2012-03-11 07:54:41 +00:00
/**
2016-11-23 05:55:33 +00:00
* ASN . 1 object identifiers
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
* @ link http :// en . wikipedia . org / wiki / Object_identifier
*/
2016-12-11 14:23:59 +00:00
private static $oids = [];
2012-03-11 07:54:41 +00:00
/**
2016-11-23 05:55:33 +00:00
* ASN . 1 object identifier reverse mapping
2012-03-11 07:54:41 +00:00
*
2016-11-23 05:55:33 +00:00
* @ var array
2012-03-11 07:54:41 +00:00
*/
2016-12-11 14:23:59 +00:00
private static $reverseOIDs = [];
2012-03-11 07:54:41 +00:00
/**
* Default date format
*
2016-11-23 05:55:33 +00:00
* @ var string
2012-03-11 07:54:41 +00:00
* @ link http :// php . net / class . datetime
*/
2016-12-11 14:23:59 +00:00
private static $format = 'D, d M Y H:i:s O' ;
2012-03-11 07:54:41 +00:00
/**
* 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
2016-12-11 14:23:59 +00:00
* @ see self :: encode_der ()
2012-03-11 07:54:41 +00:00
*/
2016-12-11 14:23:59 +00:00
private static $filters ;
2016-11-23 05:55:33 +00:00
/**
* Current Location of most recent ASN . 1 encode process
*
* Useful for debug purposes
*
* @ var array
2016-12-11 14:23:59 +00:00
* @ see self :: encode_der ()
2016-11-23 05:55:33 +00:00
*/
2016-12-11 14:23:59 +00:00
private static $location ;
2016-11-23 05:55:33 +00:00
/**
* DER Encoded String
*
* In case we need to create ASN1\Element object ' s ..
*
* @ var string
* @ see self :: decodeDER ()
*/
2016-12-11 14:23:59 +00:00
private static $encoded ;
2012-03-11 07:54:41 +00:00
2012-10-23 11:37:51 +00:00
/**
* Type mapping table for the ANY type .
*
2019-11-07 05:41:40 +00:00
* Structured or unknown types are mapped to a \phpseclib3\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-10-23 11:37:51 +00:00
*/
2022-07-07 01:43:09 +00:00
public const ANY_MAP = [
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',
2022-07-23 02:45:53 +00:00
self :: TYPE_BMP_STRING => 'bmpString' ,
2016-11-23 05:55:33 +00:00
];
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
*/
2022-07-07 01:43:09 +00:00
public const STRING_TYPE_SIZE = [
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 ,
2016-11-23 05:55:33 +00:00
];
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
*
2022-02-05 00:55:29 +00:00
* @ param Element | string $encoded
2012-03-11 07:54:41 +00:00
*/
2022-07-03 12:48:16 +00:00
public static function decodeBER ( $encoded ) : ? array
2012-03-11 07:54:41 +00:00
{
2014-12-24 21:05:56 +00:00
if ( $encoded instanceof Element ) {
2012-10-25 15:54:02 +00:00
$encoded = $encoded -> element ;
}
2016-11-23 05:55:33 +00:00
self :: $encoded = $encoded ;
2022-06-19 02:40:12 +00:00
$decoded = self :: decode_ber ( $encoded );
if ( $decoded === false ) {
return null ;
}
2016-11-23 05:55:33 +00:00
2023-03-09 10:16:45 +00:00
return [ $decoded ];
2016-11-23 05:55:33 +00:00
}
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
*
2017-08-03 07:12:56 +00:00
* @ return array | bool
2012-03-11 07:54:41 +00:00
*/
2022-06-04 15:31:21 +00:00
private static function decode_ber ( string $encoded , int $start = 0 , int $encoded_pos = 0 )
2012-03-11 07:54:41 +00:00
{
2016-11-23 05:55:33 +00:00
$current = [ 'start' => $start ];
2014-08-25 15:12:56 +00:00
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
}
2022-02-17 02:25:59 +00:00
$start += $startOffset ;
2021-04-02 18:46:14 +00:00
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.
2022-02-17 02:25:59 +00:00
$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
2022-02-17 02:25:59 +00:00
$current += [ 'headerlength' => $length + 2 ];
$start += $length ;
2014-08-25 15:12:56 +00:00
extract ( unpack ( 'Nlength' , substr ( str_pad ( $temp , 4 , chr ( 0 ), STR_PAD_LEFT ), - 4 )));
2017-12-21 09:14:53 +00:00
/** @var integer $length */
2014-08-25 15:12:56 +00:00
} else {
2022-02-17 02:25:59 +00:00
$current += [ 'headerlength' => 2 ];
2014-08-25 15:12:56 +00:00
}
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 ) {
2016-11-23 05:55:33 +00:00
return [
2015-01-11 05:58:50 +00:00
'type' => $class ,
'constant' => $tag ,
'content' => $content ,
2022-07-23 02:45:53 +00:00
'length' => $length + $start - $current [ 'start' ],
2016-11-23 05:55:33 +00:00
] + $current ;
2015-01-11 05:58:50 +00:00
}
2016-11-23 05:55:33 +00:00
$newcontent = [];
2015-06-04 04:48:43 +00:00
$remainingLength = $length ;
2015-06-07 14:59:13 +00:00
while ( $remainingLength > 0 ) {
2016-12-11 14:23:59 +00:00
$temp = self :: 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 " ) {
2022-02-17 02:25:59 +00:00
$length += 2 ;
$start += $length ;
2015-06-04 04:48:43 +00:00
$newcontent [] = $temp ;
break ;
2014-09-22 06:01:34 +00:00
}
2022-02-17 02:25:59 +00:00
$start += $length ;
$remainingLength -= $length ;
2015-06-04 04:48:43 +00:00
$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
2016-11-23 05:55:33 +00:00
return [
2014-08-25 15:12:56 +00:00
'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.
2022-07-23 02:45:53 +00:00
'length' => $start - $current [ 'start' ],
2016-11-23 05:55:33 +00:00
] + $current ;
2014-08-25 15:12:56 +00:00
}
2012-03-11 07:54:41 +00:00
2022-02-17 02:25:59 +00:00
$current += [ '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-12-11 14:23:59 +00:00
$temp = self :: decode_ber ( $content , $start , $content_pos );
2018-09-11 09:16:19 +00:00
if ( $temp === false ) {
return false ;
}
2022-02-17 02:25:59 +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 ;
}
2022-02-17 02:25:59 +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 " ) {
2016-12-11 14:23:59 +00:00
$temp = self :: 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 ;
}
2022-02-17 02:25:59 +00:00
$current [ 'content' ] .= $temp [ 'content' ];
$length += $temp [ 'length' ];
2014-08-25 15:12:56 +00:00
}
2016-06-19 04:34:49 +00:00
if ( substr ( $content , $content_pos , 2 ) == " \0 \0 " ) {
2022-02-17 02:25:59 +00:00
$length += 2 ; // +2 for the EOC
2014-08-25 15:12:56 +00:00
}
}
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 ;
2016-11-23 05:55:33 +00:00
$current [ 'content' ] = [];
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-12-11 14:23:59 +00:00
$temp = self :: 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 ;
2022-02-17 02:25:59 +00:00
$offset += $temp [ 'length' ];
2014-08-25 15:12:56 +00:00
}
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 ;
}
2021-04-03 18:48:40 +00:00
$current [ 'content' ] = self :: 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-12-11 14:23:59 +00:00
$current [ 'content' ] = self :: 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
}
2022-02-17 02:25:59 +00:00
$start += $length ;
2014-08-25 15:12:56 +00:00
// ie. length is the length of the full TLV encoding - it's not just the length of the value
2016-11-23 05:55:33 +00:00
return $current + [ '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 .
*
2022-06-04 15:31:21 +00:00
* @ param array | bool $decoded
2022-02-05 00:55:29 +00:00
* @ return array | bool | Element | string | null
2012-03-11 07:54:41 +00:00
*/
2022-07-03 12:48:16 +00:00
public static function asn1map ( array $decoded , array $mapping , array $special = [])
2012-03-11 07:54:41 +00:00
{
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-11-23 05:55:33 +00:00
// !isset(self::ANY_MAP[$intype]) produces a fatal error on PHP 5.6
if ( isset ( $decoded [ 'constant' ]) || ! array_key_exists ( $intype , self :: ANY_MAP ) || ( ord ( self :: $encoded [ $decoded [ 'start' ]]) & 0x20 )) {
return new Element ( substr ( self :: $encoded , $decoded [ 'start' ], $decoded [ 'length' ]));
2012-10-23 11:37:51 +00:00
}
2016-11-23 05:55:33 +00:00
$inmap = self :: ANY_MAP [ $intype ];
2012-10-23 11:37:51 +00:00
if ( is_string ( $inmap )) {
2016-11-23 05:55:33 +00:00
return [ $inmap => self :: asn1map ( $decoded , [ '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' ] :
2016-11-23 05:55:33 +00:00
$value = self :: 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 :
2016-11-23 05:55:33 +00:00
$v = self :: 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 ])) {
2020-04-05 15:56:30 +00:00
$value = $special [ $key ]( $value );
2013-10-09 19:25:52 +00:00
}
2016-11-23 05:55:33 +00:00
return [ $key => $value ];
2012-03-11 07:54:41 +00:00
}
}
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 :
2016-11-23 05:55:33 +00:00
$map = [];
2012-03-11 07:54:41 +00:00
// ignore the min and max
if ( isset ( $mapping [ 'min' ]) && isset ( $mapping [ 'max' ])) {
$child = $mapping [ 'children' ];
foreach ( $decoded [ 'content' ] as $content ) {
2016-11-23 05:55:33 +00:00
if (( $map [] = self :: asn1map ( $content , $child , $special )) === null ) {
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
}
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.
2016-11-23 05:55:33 +00:00
$maymatch = ! isset ( $child [ 'constant' ]) && array_search ( $child [ 'type' ], [ $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.
2016-11-23 05:55:33 +00:00
$candidate = self :: 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 ])) {
2020-04-05 15:56:30 +00:00
$candidate = $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' ])) {
2021-06-02 01:00:40 +00:00
$map [ $key ] = $child [ 'default' ];
2012-11-07 14:23:54 +00:00
} 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.
2022-02-17 02:25: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 :
2016-11-23 05:55:33 +00:00
$map = [];
2012-03-11 07:54:41 +00:00
// ignore the min and max
if ( isset ( $mapping [ 'min' ]) && isset ( $mapping [ 'max' ])) {
$child = $mapping [ 'children' ];
foreach ( $decoded [ 'content' ] as $content ) {
2016-11-23 05:55:33 +00:00
if (( $map [] = self :: asn1map ( $content , $child , $special )) === null ) {
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 ;
}
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.
2016-11-23 05:55:33 +00:00
$maymatch = ! isset ( $child [ 'constant' ]) && array_search ( $child [ 'type' ], [ $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.
2016-11-23 05:55:33 +00:00
$candidate = self :: 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 ])) {
2020-04-05 15:56:30 +00:00
$candidate = $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 :
2022-06-04 15:31:21 +00:00
return self :: $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' ])) {
2016-12-11 14:23:59 +00:00
$decoded [ 'content' ] = self :: decodeTime ( $decoded [ 'content' ], $decoded [ 'type' ]);
2012-03-11 07:54:41 +00:00
}
2017-08-24 17:50:47 +00:00
return $decoded [ 'content' ] ? $decoded [ 'content' ] -> format ( self :: $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 . "
*/
2016-11-23 05:55:33 +00:00
$bits = count ( $mapping [ 'mapping' ]) == $size ? [] : array_fill ( 0 , count ( $mapping [ 'mapping' ]) - $size , false );
2012-03-11 07:54:41 +00:00
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 ;
}
2016-11-23 05:55:33 +00:00
$values = [];
2012-03-11 07:54:41 +00:00
$map = array_reverse ( $mapping [ 'mapping' ]);
foreach ( $map as $i => $value ) {
if ( $bits [ $i ]) {
$values [] = $value ;
}
}
return $values ;
}
2022-02-23 02:48:51 +00:00
// fall-through
2014-12-04 02:08:22 +00:00
case self :: TYPE_OCTET_STRING :
2016-11-29 00:50:21 +00:00
return $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 :
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 ();
2022-06-04 15:31:21 +00:00
return $mapping [ 'mapping' ][ $temp ] ? ?
2012-03-11 07:54:41 +00:00
false ;
}
return $temp ;
}
}
2018-10-25 01:00:37 +00:00
/**
* DER - decode the length
*
* DER supports lengths up to ( 2 ** 8 ) ** 127 , however , we ' ll only support lengths up to ( 2 ** 8 ) ** 4. See
* { @ link http :// itu . int / ITU - T / studygroups / com17 / languages / X . 690 - 0207. pdf #p=13 X.690 paragraph 8.1.3} for more information.
*/
2022-06-04 15:31:21 +00:00
public static function decodeLength ( string & $string ) : int
2018-10-25 01:00:37 +00:00
{
$length = ord ( Strings :: shift ( $string ));
if ( $length & 0x80 ) { // definite length, long form
2022-02-17 02:25:59 +00:00
$length &= 0x7F ;
2018-10-25 01:00:37 +00:00
$temp = Strings :: shift ( $string , $length );
2022-06-04 15:31:21 +00:00
[, $length ] = unpack ( 'N' , substr ( str_pad ( $temp , 4 , chr ( 0 ), STR_PAD_LEFT ), - 4 ));
2018-10-25 01:00:37 +00:00
}
return $length ;
}
2012-03-11 07:54:41 +00:00
/**
* 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 .
*
2021-01-06 22:57:47 +00:00
* @ param Element | string | array $source
2016-04-10 16:30:59 +00:00
* @ return string
2012-03-11 07:54:41 +00:00
*/
2022-06-04 15:31:21 +00:00
public static function encodeDER ( $source , array $mapping , array $special = [])
2012-03-11 07:54:41 +00:00
{
2016-11-23 05:55:33 +00:00
self :: $location = [];
2016-12-11 14:23:59 +00:00
return self :: encode_der ( $source , $mapping , null , $special );
2012-03-11 07:54:41 +00:00
}
/**
* ASN . 1 Encode ( Helper function )
*
2022-02-05 00:55:29 +00:00
* @ param Element | string | array | null $source
2022-06-04 15:31:21 +00:00
* @ param string | int | null $idx
2016-04-10 16:30:59 +00:00
* @ return string
2012-03-11 07:54:41 +00:00
*/
2022-06-04 15:31:21 +00:00
private static function encode_der ( $source , array $mapping , $idx = null , array $special = [])
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 ])) {
2020-04-05 15:56:30 +00:00
$source = $special [ $idx ]( $source );
2013-10-09 19:25:52 +00:00
}
2016-11-23 05:55:33 +00:00
self :: $location [] = $idx ;
2012-03-11 07:54:41 +00:00
}
$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 :
2022-02-17 02:25:59 +00:00
$tag |= 0x20 ; // set the constructed bit
2012-03-11 07:54:41 +00:00
// ignore the min and max
if ( isset ( $mapping [ 'min' ]) && isset ( $mapping [ 'max' ])) {
2016-11-23 05:55:33 +00:00
$value = [];
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 ) {
2016-12-11 14:23:59 +00:00
$temp = self :: encode_der ( $content , $child , null , $special );
2012-03-11 07:54:41 +00:00
if ( $temp === false ) {
return false ;
}
2022-02-17 02:25:59 +00:00
$value [] = $temp ;
2016-07-19 21:27:09 +00:00
}
/* " 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 ;
}
2016-12-11 14:23:59 +00:00
$temp = self :: 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' ]);
2016-11-23 05:55:33 +00:00
$temp = $subtag . self :: encodeLength ( strlen ( $temp )) . $temp ;
2012-03-11 07:54:41 +00:00
} 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 );
}
}
2022-02-17 02:25:59 +00:00
$value .= $temp ;
2012-03-11 07:54:41 +00:00
}
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 ;
}
2016-12-11 14:23:59 +00:00
$temp = self :: 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' ]);
2016-11-23 05:55:33 +00:00
$temp = $subtag . self :: encodeLength ( strlen ( $temp )) . $temp ;
2012-03-11 07:54:41 +00:00
} 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 )) {
2016-11-23 05:55:33 +00:00
array_pop ( self :: $location );
2012-03-11 07:54:41 +00:00
}
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' ;
2022-02-17 02:25:59 +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
2022-01-30 15:34:42 +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
2022-01-30 15:34:42 +00:00
$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 ) {
2022-02-17 02:25:59 +00:00
$value .= chr ( bindec ( $byte ));
2012-03-11 07:54:41 +00:00
}
break ;
}
2022-02-23 02:48:51 +00:00
// fall-through
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 */
2016-11-29 00:50:21 +00:00
$value = $source ;
2012-03-11 07:54:41 +00:00
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_OBJECT_IDENTIFIER :
2019-05-02 12:28:27 +00:00
$value = self :: encodeOID ( $source );
2012-03-11 07:54:41 +00:00
break ;
2014-12-04 02:08:22 +00:00
case self :: TYPE_ANY :
2016-11-23 05:55:33 +00:00
$loc = self :: $location ;
2012-10-23 11:37:51 +00:00
if ( isset ( $idx )) {
2016-11-23 05:55:33 +00:00
array_pop ( self :: $location );
2012-03-11 07:54:41 +00:00
}
2012-10-23 11:37:51 +00:00
switch ( true ) {
case ! isset ( $source ) :
2016-12-11 14:23:59 +00:00
return self :: encode_der ( null , [ '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 :
2016-12-11 14:23:59 +00:00
return self :: encode_der ( $source , [ 'type' => self :: TYPE_INTEGER ] + $mapping , null , $special );
2012-10-23 11:37:51 +00:00
case is_float ( $source ) :
2016-12-11 14:23:59 +00:00
return self :: encode_der ( $source , [ 'type' => self :: TYPE_REAL ] + $mapping , null , $special );
2012-10-23 11:37:51 +00:00
case is_bool ( $source ) :
2016-12-11 14:23:59 +00:00
return self :: encode_der ( $source , [ '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 ));
2016-11-23 05:55:33 +00:00
$outtype = array_search ( $typename , self :: ANY_MAP , true );
2012-10-23 11:37:51 +00:00
if ( $outtype !== false ) {
2016-12-11 14:23:59 +00:00
return self :: encode_der ( $source [ $typename ], [ '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
2016-11-23 05:55:33 +00:00
$filters = self :: $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 ) {
2022-08-18 13:05:57 +00:00
throw new RuntimeException ( 'No filters defined for ' . implode ( '/' , $loc ));
2012-03-11 07:54:41 +00:00
}
2016-12-11 14:23:59 +00:00
return self :: 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 :
2022-08-18 13:05:57 +00:00
throw new RuntimeException ( 'Mapping provides no type definition for ' . implode ( '/' , self :: $location ));
2012-03-11 07:54:41 +00:00
}
if ( isset ( $idx )) {
2016-11-23 05:55:33 +00:00
array_pop ( self :: $location );
2012-03-11 07:54:41 +00:00
}
if ( isset ( $mapping [ 'cast' ])) {
2014-12-04 02:08:22 +00:00
if ( isset ( $mapping [ 'explicit' ]) || $mapping [ 'type' ] == self :: TYPE_CHOICE ) {
2016-11-23 05:55:33 +00:00
$value = chr ( $tag ) . self :: encodeLength ( strlen ( $value )) . $value ;
2014-03-30 06:11:47 +00:00
$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
}
2022-06-04 15:31:21 +00:00
return chr ( $tag ) . self :: encodeLength ( strlen (( string ) $value )) . $value ;
2012-03-11 07:54:41 +00:00
}
2019-05-01 03:37:19 +00:00
/**
* BER - decode the OID
*
* Called by _decode_ber ()
*
* @ return string
*/
2022-06-04 15:31:21 +00:00
public static function decodeOID ( string $content )
2019-05-01 03:37:19 +00:00
{
static $eighty ;
if ( ! $eighty ) {
2019-05-02 12:18:37 +00:00
$eighty = new BigInteger ( 80 );
2019-05-01 03:37:19 +00:00
}
2021-04-02 15:29:04 +00:00
$oid = [];
2019-05-01 03:37:19 +00:00
$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 ) {
2024-02-24 19:23:49 +00:00
//throw new \RuntimeException("Object identifier size is limited to 4096 bytes ($len bytes present)");
2024-02-24 19:07:01 +00:00
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 ()
*/
2022-06-04 15:31:21 +00:00
public static function encodeOID ( string $source ) : string
2019-05-01 03:37:19 +00:00
{
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
}
2019-05-02 12:28:27 +00:00
if ( ! preg_match ( '#(?:\d+\.)+#' , $source )) {
2022-06-04 15:31:21 +00:00
$oid = self :: $reverseOIDs [ $source ] ? ? false ;
2019-05-02 12:28:27 +00:00
} else {
$oid = $source ;
}
2019-05-01 03:37:19 +00:00
if ( $oid === false ) {
2022-08-18 13:05:57 +00:00
throw new RuntimeException ( 'Invalid OID' );
2019-05-01 03:37:19 +00:00
}
2019-05-02 12:28:27 +00:00
2019-05-01 03:37:19 +00:00
$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 );
}
2022-07-07 01:43:09 +00:00
$temp [ - 1 ] = $temp [ - 1 ] & chr ( 0x7F );
2019-05-01 03:37:19 +00:00
}
2022-02-17 02:25:59 +00:00
$value .= $temp ;
2019-05-01 03:37:19 +00:00
}
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 () .
*
2022-02-05 00:55:29 +00:00
* @ return \DateTime | false
2012-03-11 07:54:41 +00:00
*/
2022-06-04 15:31:21 +00:00
private static function decodeTime ( string $content , int $tag )
2012-03-11 07:54:41 +00:00
{
/* 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 ;
2022-08-11 13:25:16 +00:00
} elseif ( str_contains ( $content , '.' )) {
2022-02-17 02:25:59 +00:00
$format .= '.u' ;
2012-03-11 07:54:41 +00:00
}
2022-07-07 01:43:09 +00:00
if ( $content [ - 1 ] == 'Z' ) {
2017-08-19 05:42:36 +00:00
$content = substr ( $content , 0 , - 1 ) . '+0000' ;
}
2022-08-11 13:25:16 +00:00
if ( str_contains ( $content , '-' ) || str_contains ( $content , '+' )) {
2022-02-17 02:25:59 +00:00
$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
2022-01-30 15:34:42 +00:00
return @ \DateTime :: createFromFormat ( $format , $content );
2012-03-11 07:54:41 +00:00
}
/**
* Set the time format
*
* Sets the time / date format for asn1map () .
*/
2022-06-04 15:31:21 +00:00
public static function setTimeFormat ( string $format ) : void
2012-03-11 07:54:41 +00:00
{
2016-11-23 05:55:33 +00:00
self :: $format = $format ;
2012-03-11 07:54:41 +00:00
}
/**
* Load OIDs
*
* Load the relevant OIDs for a particular ASN . 1 semantic mapping .
2016-11-23 05:55:33 +00:00
* Previously loaded OIDs are retained .
2012-03-11 07:54:41 +00:00
*/
2022-06-04 15:31:21 +00:00
public static function loadOIDs ( array $oids ) : void
2012-03-11 07:54:41 +00:00
{
2022-02-17 02:25:59 +00:00
self :: $reverseOIDs += $oids ;
2018-10-25 01:00:37 +00:00
self :: $oids = array_flip ( self :: $reverseOIDs );
2012-03-11 07:54:41 +00:00
}
/**
2016-11-23 05:55:33 +00:00
* Set filters
2012-03-11 07:54:41 +00:00
*
2019-11-07 05:41:40 +00:00
* See \phpseclib3\File\X509 , etc , for an example .
2016-11-23 05:55:33 +00:00
* Previously loaded filters are not retained .
2012-03-11 07:54:41 +00:00
*/
2022-06-04 15:31:21 +00:00
public static function setFilters ( array $filters ) : void
2012-03-11 07:54:41 +00:00
{
2016-11-23 05:55:33 +00:00
self :: $filters = $filters ;
2012-03-11 07:54:41 +00:00
}
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
* @ return string
2012-11-02 15:53:32 +00:00
*/
2022-06-04 15:31:21 +00:00
public static function convert ( string $in , int $from = self :: TYPE_UTF8_STRING , int $to = self :: TYPE_UTF8_STRING )
2012-11-02 15:53:32 +00:00
{
2016-11-23 05:55:33 +00:00
// isset(self::STRING_TYPE_SIZE[$from] returns a fatal error on PHP 5.6
if ( ! array_key_exists ( $from , self :: STRING_TYPE_SIZE ) || ! array_key_exists ( $to , self :: STRING_TYPE_SIZE )) {
2012-11-02 15:53:32 +00:00
return false ;
}
2016-11-23 05:55:33 +00:00
$insize = self :: STRING_TYPE_SIZE [ $from ];
$outsize = self :: STRING_TYPE_SIZE [ $to ];
2012-11-02 15:53:32 +00:00
$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 ++ ]);
2022-02-23 02:48:51 +00:00
// fall-through
2012-11-02 15:53:32 +00:00
case $insize == 2 :
$c = ( $c << 8 ) | ord ( $in [ $i ++ ]);
2022-02-23 02:48:51 +00:00
// fall-through
2012-11-02 15:53:32 +00:00
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 ;
2022-02-23 02:48:51 +00:00
// fall-through
2012-11-02 15:53:32 +00:00
case $outsize == 2 :
$v .= chr ( $c & 0xFF );
$c >>= 8 ;
2022-02-23 02:48:51 +00:00
// fall-through
2012-11-02 15:53:32 +00:00
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 ;
2022-02-23 02:48:51 +00:00
// fall-through
2012-11-02 15:53:32 +00:00
case $c >= 0x00200000 :
$v .= chr ( 0x80 | ( $c & 0x3F ));
$c = ( $c >> 6 ) | 0x00200000 ;
2022-02-23 02:48:51 +00:00
// fall-through
2012-11-02 15:53:32 +00:00
case $c >= 0x00010000 :
$v .= chr ( 0x80 | ( $c & 0x3F ));
$c = ( $c >> 6 ) | 0x00010000 ;
2022-02-23 02:48:51 +00:00
// fall-through
2012-11-02 15:53:32 +00:00
case $c >= 0x00000800 :
$v .= chr ( 0x80 | ( $c & 0x3F ));
$c = ( $c >> 6 ) | 0x00000800 ;
2022-02-23 02:48:51 +00:00
// fall-through
2012-11-02 15:53:32 +00:00
case $c >= 0x00000080 :
$v .= chr ( 0x80 | ( $c & 0x3F ));
$c = ( $c >> 6 ) | 0x000000C0 ;
2022-02-23 02:48:51 +00:00
// fall-through
2012-11-02 15:53:32 +00:00
default :
$v .= chr ( $c );
break ;
}
$out .= strrev ( $v );
}
return $out ;
}
2016-10-19 12:45:42 +00:00
/**
* Extract raw BER from Base64 encoding
*/
2022-06-04 15:31:21 +00:00
public static function extractBER ( string $str ) : string
2016-10-19 12:45:42 +00:00
{
/* X . 509 certs are assumed to be base64 encoded but sometimes they ' ll have additional things in them
* above and beyond the ceritificate .
* ie . some may have the following preceding the ----- BEGIN CERTIFICATE ----- line :
*
* Bag Attributes
* localKeyID : 01 00 00 00
* subject =/ O = organization / OU = org unit / CN = common name
* issuer =/ O = organization / CN = common name
*/
2021-01-08 02:24:15 +00:00
if ( strlen ( $str ) > ini_get ( 'pcre.backtrack_limit' )) {
$temp = $str ;
} else {
$temp = preg_replace ( '#.*?^-+[^-]+-+[\r\n ]*$#ms' , '' , $str , 1 );
2021-05-23 15:39:25 +00:00
$temp = preg_replace ( '#-+END.*[\r\n ]*.*#ms' , '' , $temp , 1 );
2021-01-08 02:24:15 +00:00
}
2016-10-19 12:45:42 +00:00
// remove new lines
2016-11-23 05:55:33 +00:00
$temp = str_replace ([ " \r " , " \n " , ' ' ], '' , $temp );
2020-10-30 02:46:30 +00:00
// remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
$temp = preg_replace ( '#^-+[^-]+-+|-+[^-]+-+$#' , '' , $temp );
2022-08-19 02:52:09 +00:00
$temp = preg_match ( '#^[a-zA-Z\d/+]*={0,2}$#' , $temp ) ? Strings :: base64_decode ( $temp ) : false ;
2016-10-19 12:45:42 +00:00
return $temp != false ? $temp : $str ;
}
2016-11-23 05:55:33 +00:00
/**
* DER - encode the length
*
* DER supports lengths up to ( 2 ** 8 ) ** 127 , however , we ' ll only support lengths up to ( 2 ** 8 ) ** 4. See
* { @ link http :// itu . int / ITU - T / studygroups / com17 / languages / X . 690 - 0207. pdf #p=13 X.690 paragraph 8.1.3} for more information.
*/
2022-06-04 15:31:21 +00:00
public static function encodeLength ( int $length ) : string
2016-11-23 05:55:33 +00:00
{
if ( $length <= 0x7F ) {
return chr ( $length );
}
$temp = ltrim ( pack ( 'N' , $length ), chr ( 0 ));
return pack ( 'Ca*' , 0x80 | strlen ( $temp ), $temp );
}
/**
* Returns the OID corresponding to a name
*
* What ' s returned in the associative array returned by loadX509 () ( or load * ()) is either a name or an OID if
* no OID to name mapping is available . The problem with this is that what may be an unmapped OID in one version
* of phpseclib may not be unmapped in the next version , so apps that are looking at this OID may not be able
* to work from version to version .
*
* This method will return the OID if a name is passed to it and if no mapping is avialable it ' ll assume that
* what ' s being passed to it already is an OID and return that instead . A few examples .
*
* getOID ( '2.16.840.1.101.3.4.2.1' ) == '2.16.840.1.101.3.4.2.1'
* getOID ( 'id-sha256' ) == '2.16.840.1.101.3.4.2.1'
* getOID ( 'zzz' ) == 'zzz'
*/
2022-06-04 15:31:21 +00:00
public static function getOID ( string $name ) : string
2016-11-23 05:55:33 +00:00
{
2022-06-04 15:31:21 +00:00
return self :: $reverseOIDs [ $name ] ? ? $name ;
2016-11-23 05:55:33 +00:00
}
2012-03-11 07:54:41 +00:00
}