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