Subversion Repositories oidconverter

Rev

Rev 2 | Rev 5 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OidDerConverter.class.php, Version 1.1; Based on version 1.11 of oid.c
  5.  * Copyright 2014-2022 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20.  
  21. // Note: Leading zeros are permitted in dotted notation.
  22.  
  23. // TODO: define output format as parameter; don't force the user to use hexStrToArray
  24.  
  25. class OidDerConverter {
  26.  
  27.         /**
  28.          * @arg $str "\x12\x23" or "12 34"
  29.          * @return array(0x12, 0x23)
  30.          */
  31.         // Doesn't need to be public, but it is a nice handy function, especially in the testcases
  32.         public static function hexStrToArray($str) {
  33.                 $out = array();
  34.                 $str = str_replace('\x', '', $str);
  35.                 $str = trim($str);
  36.                 $str = str_replace(' ', '', $str);
  37.                 $ary = str_split($str, 2);
  38.                 foreach ($ary as &$a) {
  39.                         $a = hexdec($a);
  40.                 }
  41.                 return $ary;
  42.         }
  43.  
  44.         /**
  45.          * @return Outputs .<oid> for an absolute OID and <oid> for a relative OID.
  46.          */
  47.         public static function derToOID($abBinary, $verbose=false) {
  48.                 $output_oid = array();
  49.                 $output_absolute = true;
  50.  
  51.                 if (count($abBinary) < 3) {
  52.                         if ($verbose) fprintf(STDERR, "Encoded OID must have at least three bytes!\n");
  53.                         return false;
  54.                 }
  55.  
  56.                 $nBinary = count($abBinary);
  57.                 $ll = gmp_init(0);
  58.                 $fOK = false;
  59.                 $fSub = 0; // Subtract value from next number output. Used when encoding {2 48} and up
  60.  
  61.                 // 0 = Universal Class Identifier Tag (can be more than 1 byte, but not in our case)
  62.                 // 1 = Length part (may have more than 1 byte!)
  63.                 // 2 = First two arc encoding
  64.                 // 3 = Encoding of arc three and higher
  65.                 $part = 0;
  66.  
  67.                 $lengthbyte_count = 0;
  68.                 $lengthbyte_pos = 0;
  69.                 $lengthfinished = false;
  70.  
  71.                 $arcBeginning = true;
  72.  
  73.                 foreach ($abBinary as $nn => &$pb) {
  74.                         if ($part == 0) { // Class Tag
  75.                                 // Leading octet
  76.                                 // Bit 7 / Bit 6 = Universal (00), Application (01), Context (10), Private(11)
  77.                                 // Bit 5 = Primitive (0), Constructed (1)
  78.                                 // Bit 4..0 = 00000 .. 11110 => Tag 0..30, 11111 for Tag > 30 (following bytes with the highest bit as "more" bit)
  79.                                 // --> We don't need to respect 11111 (class-tag encodes in more than 1 octet)
  80.                                 //     as we terminate when the tag is not of type OID or RELATEIVE-OID
  81.                                 // See page 396 of "ASN.1 - Communication between Heterogeneous Systems" by Olivier Dubuisson.
  82.  
  83.                                 // Class: 8. - 7. bit
  84.                                 // 0 (00) = Universal
  85.                                 // 1 (01) = Application
  86.                                 // 2 (10) = Context
  87.                                 // 3 (11) = Private
  88.                                 $cl = (($pb & 0xC0) >> 6) & 0x03;
  89.                                 if ($cl != 0) {
  90.                                         if ($verbose) fprintf(STDERR, "Error at type: The OID tags are only defined as UNIVERSAL class tags.\n");
  91.                                         return false;
  92.                                 }
  93.  
  94.                                 // Primitive/Constructed: 6. bit
  95.                                 // 0 = Primitive
  96.                                 // 1 = Constructed
  97.                                 $pc = $pb & 0x20;
  98.                                 if ($pc != 0) {
  99.                                         if ($verbose) fprintf(STDERR, "Error at type: OIDs must be primitive, not constructed.\n");
  100.                                         return false;
  101.                                 }
  102.  
  103.                                 // Tag number: 5. - 1. bit
  104.                                 $tag = $pb & 0x1F;
  105.                                 if ($tag == 0x0D) {
  106.                                         $isRelative = true;
  107.                                 } else if ($tag == 0x06) {
  108.                                         $isRelative = false;
  109.                                 } else {
  110.                                         if ($verbose) fprintf(STDERR, "Error at type: The tag number is neither an absolute OID (0x06) nor a relative OID (0x0D).\n");
  111.                                         return false;
  112.                                 }
  113.  
  114.                                 // Output
  115.                                 $output_absolute = !$isRelative;
  116.  
  117.                                 $part++;
  118.                         } else if ($part == 1) { // Length
  119.                                 // Find out the length and save it into $ll
  120.  
  121.                                 // [length] is encoded as follows:
  122.                                 //  0x00 .. 0x7F = The actual length is in this byte, followed by [data].
  123.                                 //  0x80 + n     = The length of [data] is spread over the following 'n' bytes. (0 < n < 0x7F)
  124.                                 //  0x80         = "indefinite length" (only constructed form) -- Invalid
  125.                                 //  0xFF         = Reserved for further implementations -- Invalid
  126.                                 //  See page 396 of "ASN.1 - Communication between Heterogeneous Systems" by Olivier Dubuisson.
  127.  
  128.                                 if ($nn == 1) { // The first length byte
  129.                                         $lengthbyte_pos = 0;
  130.                                         if (($pb & 0x80) != 0) {
  131.                                                 // 0x80 + n => The length is spread over the following 'n' bytes
  132.                                                 $lengthfinished = false;
  133.                                                 $lengthbyte_count = $pb & 0x7F;
  134.                                                 if ($lengthbyte_count == 0x00) {
  135.                                                         if ($verbose) fprintf(STDERR, "Length value 0x80 is invalid (\"indefinite length\") for primitive types.\n");
  136.                                                         return false;
  137.                                                 } else if ($lengthbyte_count == 0x7F) {
  138.                                                         if ($verbose) fprintf(STDERR, "Length value 0xFF is reserved for further extensions.\n");
  139.                                                         return false;
  140.                                                 }
  141.                                                 $fOK = false;
  142.                                         } else {
  143.                                                 // 0x01 .. 0x7F => The actual length
  144.  
  145.                                                 if ($pb == 0x00) {
  146.                                                         if ($verbose) fprintf(STDERR, "Length value 0x00 is invalid for an OID.\n");
  147.                                                         return false;
  148.                                                 }
  149.  
  150.                                                 $ll = gmp_init($pb);
  151.                                                 $lengthfinished = true;
  152.                                                 $lengthbyte_count = 0;
  153.                                                 $fOK = true;
  154.                                         }
  155.                                 } else {
  156.                                         if ($lengthbyte_count > $lengthbyte_pos) {
  157.                                                 $ll = gmp_mul($ll, 0x100);
  158.                                                 $ll = gmp_add($ll, $pb);
  159.                                                 $lengthbyte_pos++;
  160.                                         }
  161.  
  162.                                         if ($lengthbyte_count == $lengthbyte_pos) {
  163.                                                 $lengthfinished = true;
  164.                                                 $fOK = true;
  165.                                         }
  166.                                 }
  167.  
  168.                                 if ($lengthfinished) { // The length is now in $ll
  169.                                         if (gmp_cmp($ll, $nBinary - 2 - $lengthbyte_count) != 0) {
  170.                                                 if ($verbose) fprintf(STDERR, "Invalid length (%d entered, but %s expected)\n", $nBinary - 2, gmp_strval($ll, 10));
  171.                                                 return false;
  172.                                         }
  173.                                         $ll = gmp_init(0); // reset for later usage
  174.                                         $fOK = true;
  175.                                         $part++;
  176.                                         if ($isRelative) $part++; // Goto step 3!
  177.                                 }
  178.                         } else if ($part == 2) { // First two arcs
  179.                                 $first = $pb / 40;
  180.                                 $second = $pb % 40;
  181.                                 if ($first > 2) {
  182.                                         $first = 2;
  183.                                         $output_oid[] = $first;
  184.                                         $arcBeginning = true;
  185.  
  186.                                         if (($pb & 0x80) != 0) {
  187.                                                 // 2.48 and up => 2+ octets
  188.                                                 // Output in "part 3"
  189.  
  190.                                                 if ($arcBeginning && ($pb == 0x80)) {
  191.                                                         if ($verbose) fprintf(STDERR, "Encoding error. Illegal 0x80 paddings. (See Rec. ITU-T X.690, clause 8.19.2)\n");
  192.                                                         return false;
  193.                                                 } else {
  194.                                                         $arcBeginning = false;
  195.                                                 }
  196.  
  197.                                                 $ll = gmp_add($ll, ($pb & 0x7F));
  198.                                                 $fSub = 80;
  199.                                                 $fOK = false;
  200.                                         } else {
  201.                                                 // 2.0 till 2.47 => 1 octet
  202.                                                 $second = $pb - 80;
  203.                                                 $output_oid[] = $second;
  204.                                                 $arcBeginning = true;
  205.                                                 $fOK = true;
  206.                                                 $ll = gmp_init(0);
  207.                                         }
  208.                                 } else {
  209.                                         // 0.0 till 0.37 => 1 octet
  210.                                         // 1.0 till 1.37 => 1 octet
  211.                                         $output_oid[] = $first;
  212.                                         $output_oid[] = $second;
  213.                                         $arcBeginning = true;
  214.                                         $fOK = true;
  215.                                         $ll = gmp_init(0);
  216.                                 }
  217.                                 $part++;
  218.                         } else if ($part == 3) { // Arc three and higher
  219.                                 if (($pb & 0x80) != 0) {
  220.                                         if ($arcBeginning && ($pb == 0x80)) {
  221.                                                 if ($verbose) fprintf(STDERR, "Encoding error. Illegal 0x80 paddings. (See Rec. ITU-T X.690, clause 8.19.2)\n");
  222.                                                 return false;
  223.                                         } else {
  224.                                                 $arcBeginning = false;
  225.                                         }
  226.  
  227.                                         $ll = gmp_mul($ll, 0x80);
  228.                                         $ll = gmp_add($ll, ($pb & 0x7F));
  229.                                         $fOK = false;
  230.                                 } else {
  231.                                         $fOK = true;
  232.                                         $ll = gmp_mul($ll, 0x80);
  233.                                         $ll = gmp_add($ll, $pb);
  234.                                         $ll = gmp_sub($ll, $fSub);
  235.                                         $output_oid[] = gmp_strval($ll, 10);
  236.  
  237.                                         // Happens only if 0x80 paddings are allowed
  238.                                         // $fOK = gmp_cmp($ll, 0) >= 0;
  239.                                         $ll = gmp_init(0);
  240.                                         $fSub = 0;
  241.                                         $arcBeginning = true;
  242.                                 }
  243.                         }
  244.                 }
  245.  
  246.                 if (!$fOK) {
  247.                         if ($verbose) fprintf(STDERR, "Encoding error. The OID is not constructed properly.\n");
  248.                         return false;
  249.                 }
  250.  
  251.                 return ($output_absolute ? '.' : '').implode('.', $output_oid);
  252.         }
  253.  
  254.         // Doesn't need to be public, but it is a nice handy function, especially in the testcases
  255.         public static function hexarrayToStr($ary, $nCHex=false) {
  256.                 $str = '';
  257.                 if ($ary === false) return false;
  258.                 foreach ($ary as &$a) {
  259.                         if ($nCHex) {
  260.                                 $str .= sprintf("\"\\x%02X", $a);
  261.                         } else {
  262.                                 $str .= sprintf("%02X ", $a);
  263.                         }
  264.                 }
  265.                 return trim($str);
  266.         }
  267.  
  268.         public static function oidToDER($oid, $isRelative=false, $verbose=false) {
  269.                 if ($oid[0] == '.') { // MIB notation
  270.                         $oid = substr($oid, 1);
  271.                         $isRelative = false;
  272.                 }
  273.  
  274.                 $cl = 0x00; // Class. Always UNIVERSAL (00)
  275.  
  276.                 // Tag for Universal Class
  277.                 if ($isRelative) {
  278.                         $cl |= 0x0D;
  279.                 } else {
  280.                         $cl |= 0x06;
  281.                 }
  282.  
  283.                 $arcs = explode('.', $oid);
  284.  
  285.                 $b = 0;
  286.                 $isjoint = false;
  287.                 $abBinary = array();
  288.  
  289.                 if ((!$isRelative) && (count($arcs) < 2)) {
  290.                         if ($verbose) fprintf(STDERR, "Encoding error. The minimum depth of an encodeable absolute OID is 2. (e.g. 2.999)\n");
  291.                         return false;
  292.                 }
  293.  
  294.                 foreach ($arcs as $n => &$arc) {
  295.                         if (!preg_match('@^\d+$@', $arc)) {
  296.                                 if ($verbose) fprintf(STDERR, "Encoding error. Arc '%s' is invalid.\n", $arc);
  297.                                 return false;
  298.                         }
  299.  
  300.                         $l = gmp_init($arc, 10);
  301.  
  302.                         if ((!$isRelative) && ($n == 0)) {
  303.                                 if (gmp_cmp($l, 2) > 0) {
  304.                                         if ($verbose) fprintf(STDERR, "Encoding error. The top arc is limited to 0, 1 and 2.\n");
  305.                                         return false;
  306.                                 }
  307.                                 $b += 40 * gmp_intval($l);
  308.                                 $isjoint = gmp_cmp($l, 2) == 0;
  309.                         } else if ((!$isRelative) && ($n == 1)) {
  310.                                 if ((!$isjoint) && (gmp_cmp($l, 39) > 0)) {
  311.                                         if ($verbose) fprintf(STDERR, "Encoding error. The second arc is limited to 0..39 for root arcs 0 and 1.\n");
  312.                                         return false;
  313.                                 }
  314.  
  315.                                 if (gmp_cmp($l, 47) > 0) {
  316.                                         $l = gmp_add($l, 80);
  317.                                         self::makeBase128($l, 1, $abBinary);
  318.                                 } else {
  319.                                         $b += gmp_intval($l);
  320.                                         $abBinary[] = $b;
  321.                                 }
  322.                         } else {
  323.                                 self::makeBase128($l, 1, $abBinary);
  324.                         }
  325.                 }
  326.  
  327.                 $output = array();
  328.  
  329.                 // Write class-tag
  330.                 $output[] = $cl;
  331.  
  332.                 // Write length
  333.                 $nBinary = count($abBinary);
  334.                 if ($nBinary <= 0x7F) {
  335.                         $output[] = $nBinary;
  336.                 } else {
  337.                         $lengthCount = 0;
  338.                         $nBinaryWork = $nBinary;
  339.                         do {
  340.                                 $lengthCount++;
  341.                                 $nBinaryWork /= 0x100;
  342.                         } while ($nBinaryWork > 0);
  343.  
  344.                         if ($lengthCount >= 0x7F) {
  345.                                 if ($verbose) fprintf(STDERR, "The length cannot be encoded.\n");
  346.                                 return false;
  347.                         }
  348.  
  349.                         $output[] = 0x80 + $lengthCount;
  350.  
  351.                         $nBinaryWork = $nBinary;
  352.                         do {
  353.                                 $output[] = nBinaryWork & 0xFF;
  354.                                 $nBinaryWork /= 0x100;
  355.                         } while ($nBinaryWork > 0);
  356.                 }
  357.  
  358.                 foreach ($abBinary as $b) {
  359.                         $output[] = $b;
  360.                 }
  361.  
  362.                 return $output;
  363.         }
  364.  
  365.         protected static function makeBase128($l, $first, &$abBinary) {
  366.                 if (gmp_cmp($l, 127) > 0) {
  367.                         $l2 = gmp_div($l, 128);
  368.                         self::makeBase128($l2 , 0, $abBinary);
  369.                 }
  370.                 $l = gmp_mod($l, 128);
  371.  
  372.                 if ($first) {
  373.                         $abBinary[] = gmp_intval($l);
  374.                 } else {
  375.                         $abBinary[] = 0x80 | gmp_intval($l);
  376.                 }
  377.         }
  378. }
  379.