Subversion Repositories oidconverter

Rev

Rev 9 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
<?php
2
 
3
/*
11 daniel-mar 4
 * OidDerConverter.class.php, Version 1.1.1; Based on version 1.3+viathinksoft11 of oid.c
4 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();
4 daniel-mar 34
                $str = str_replace('\x', '', $str);
2 daniel-mar 35
                $str = trim($str);
4 daniel-mar 36
                $str = str_replace(' ', '', $str);
37
                $ary = str_split($str, 2);
2 daniel-mar 38
                foreach ($ary as &$a) {
4 daniel-mar 39
                        $a = hexdec($a);
2 daniel-mar 40
                }
4 daniel-mar 41
                return $ary;
2 daniel-mar 42
        }
43
 
44
        /**
5 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
 
5 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
11 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
 
5 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 {
5 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
}