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