Subversion Repositories oidconverter

Rev

Rev 2 | Rev 5 | 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
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
        /**
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
}