From e793461543d16a5d52f8805882bf6d45bf8c6cde Mon Sep 17 00:00:00 2001 From: terrafrost Date: Tue, 30 Apr 2019 22:37:19 -0500 Subject: [PATCH] ASN1: revamp how OIDs are handled --- phpseclib/File/ASN1.php | 143 +++++++++++++++++++++++++---------- tests/Unit/File/ASN1Test.php | 22 ++++++ 2 files changed, 126 insertions(+), 39 deletions(-) diff --git a/phpseclib/File/ASN1.php b/phpseclib/File/ASN1.php index 04287c4f..d76306f9 100644 --- a/phpseclib/File/ASN1.php +++ b/phpseclib/File/ASN1.php @@ -515,24 +515,7 @@ class File_ASN1 } break; case FILE_ASN1_TYPE_OBJECT_IDENTIFIER: - $temp = ord($content[$content_pos++]); - $current['content'] = sprintf('%d.%d', floor($temp / 40), $temp % 40); - $valuen = 0; - // process septets - $content_len = strlen($content); - while ($content_pos < $content_len) { - $temp = ord($content[$content_pos++]); - $valuen <<= 7; - $valuen |= $temp & 0x7F; - if (~$temp & 0x80) { - $current['content'].= ".$valuen"; - $valuen = 0; - } - } - // the eighth bit of the last byte should not be 1 - //if ($temp >> 7) { - // return false; - //} + $current['content'] = $this->_decodeOID(substr($content, $content_pos)); break; /* Each character string type shall be encoded as if it had been declared: [UNIVERSAL x] IMPLICIT OCTET STRING @@ -1111,27 +1094,7 @@ class File_ASN1 $value = base64_decode($source); break; case FILE_ASN1_TYPE_OBJECT_IDENTIFIER: - $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids); - if ($oid === false) { - user_error('Invalid OID'); - return false; - } - $value = ''; - $parts = explode('.', $oid); - $value = chr(40 * $parts[0] + $parts[1]); - for ($i = 2; $i < count($parts); $i++) { - $temp = ''; - if (!$parts[$i]) { - $temp = "\0"; - } else { - while ($parts[$i]) { - $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp; - $parts[$i] >>= 7; - } - $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F); - } - $value.= $temp; - } + $value = $this->_encodeOID($source); break; case FILE_ASN1_TYPE_ANY: $loc = $this->location; @@ -1230,6 +1193,108 @@ class File_ASN1 return pack('Ca*', 0x80 | strlen($temp), $temp); } + /** + * BER-decode the OID + * + * Called by _decode_ber() + * + * @access private + * @param string $content + * @return string + */ + function _decodeOID($content) + { + static $eighty; + if (!$eighty) { + $eighty = new Math_BigInteger(80); + } + + $oid = array(); + $pos = 0; + $len = strlen($content); + $n = new Math_BigInteger(); + while ($pos < $len) { + $temp = ord($content[$pos++]); + $n = $n->bitwise_leftShift(7); + $n = $n->bitwise_or(new Math_BigInteger($temp & 0x7F)); + if (~$temp & 0x80) { + $oid[] = $n; + $n = new Math_BigInteger(); + } + } + $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 + * @param string $content + * @return string + */ + function _encodeOID($source) + { + static $mask, $zero, $forty; + if (!$mask) { + $mask = new Math_BigInteger(0x7F); + $zero = new Math_BigInteger(); + $forty = new Math_BigInteger(40); + } + + $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); + + $first = new Math_BigInteger($part1); + $first = $first->multiply($forty); + $first = $first->add(new Math_BigInteger($part2)); + + array_unshift($parts, $first->toString()); + + $value = ''; + foreach ($parts as $part) { + if (!$part) { + $temp = "\0"; + } else { + $temp = ''; + $part = new Math_BigInteger($part); + 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; + } + /** * BER-decode the time (using UNIX time) * diff --git a/tests/Unit/File/ASN1Test.php b/tests/Unit/File/ASN1Test.php index 4cf549a5..1a84035d 100644 --- a/tests/Unit/File/ASN1Test.php +++ b/tests/Unit/File/ASN1Test.php @@ -341,4 +341,26 @@ class Unit_File_ASN1Test extends PhpseclibTestCase $asn1 = new File_ASN1(); $asn1->decodeBER($data); } + + /** + * @group github1367 + */ + public function testOIDs() + { + // from the example in 8.19.5 in the following: + // https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=22 + $orig = pack('H*', '813403'); + $asn1 = new File_ASN1(); + $new = $asn1->_decodeOID($orig); + $this->assertSame('2.100.3', $new); + $this->assertSame($orig, $asn1->_encodeOID($new)); + + // UUID OID from the following: + // https://healthcaresecprivacy.blogspot.com/2011/02/creating-and-using-unique-id-uuid-oid.html + $orig = '2.25.329800735698586629295641978511506172918'; + $asn1 = new File_ASN1(); + $new = $asn1->_encodeOID($orig); + $this->assertSame(pack('H*', '6983f09da7ebcfdee0c7a1a7b2c0948cc8f9d776'), $new); + $this->assertSame($orig, $asn1->_decodeOID($new)); + } }