1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types = 1); |
4
|
|
|
|
5
|
|
|
namespace ASN1\Component; |
6
|
|
|
|
7
|
|
|
use ASN1\Exception\DecodeException; |
8
|
|
|
use ASN1\Feature\Encodable; |
9
|
|
|
use ASN1\Util\BigInt; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Class to represent BER/DER identifier octets. |
13
|
|
|
*/ |
14
|
|
|
class Identifier implements Encodable |
15
|
|
|
{ |
16
|
|
|
// Type class enumerations |
17
|
|
|
const CLASS_UNIVERSAL = 0b00; |
18
|
|
|
const CLASS_APPLICATION = 0b01; |
19
|
|
|
const CLASS_CONTEXT_SPECIFIC = 0b10; |
20
|
|
|
const CLASS_PRIVATE = 0b11; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Mapping from type class to human readable name. |
24
|
|
|
* |
25
|
|
|
* @internal |
26
|
|
|
* |
27
|
|
|
* @var array |
28
|
|
|
*/ |
29
|
|
|
const MAP_CLASS_TO_NAME = [ /* @formatter:off */ |
30
|
|
|
self::CLASS_UNIVERSAL => "UNIVERSAL", |
31
|
|
|
self::CLASS_APPLICATION => "APPLICATION", |
32
|
|
|
self::CLASS_CONTEXT_SPECIFIC => "CONTEXT SPECIFIC", |
33
|
|
|
self::CLASS_PRIVATE => "PRIVATE", |
34
|
|
|
/* @formatter:on */ |
35
|
|
|
]; |
36
|
|
|
|
37
|
|
|
// P/C enumerations |
38
|
|
|
const PRIMITIVE = 0b0; |
39
|
|
|
const CONSTRUCTED = 0b1; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Type class. |
43
|
|
|
* |
44
|
|
|
* @var int |
45
|
|
|
*/ |
46
|
|
|
private $_class; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Primitive or Constructed. |
50
|
|
|
* |
51
|
|
|
* @var int |
52
|
|
|
*/ |
53
|
|
|
private $_pc; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Content type tag. |
57
|
|
|
* |
58
|
|
|
* @var BigInt |
59
|
|
|
*/ |
60
|
|
|
private $_tag; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Constructor. |
64
|
|
|
* |
65
|
|
|
* @param int $class Type class |
66
|
|
|
* @param int $pc Primitive / Constructed |
67
|
|
|
* @param int|string $tag Type tag number |
68
|
|
|
*/ |
69
|
315 |
|
public function __construct(int $class, int $pc, $tag) |
70
|
|
|
{ |
71
|
315 |
|
$this->_class = 0b11 & $class; |
72
|
315 |
|
$this->_pc = 0b1 & $pc; |
73
|
315 |
|
$this->_tag = new BigInt($tag); |
74
|
315 |
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* Decode identifier component from DER data. |
78
|
|
|
* |
79
|
|
|
* @param string $data DER encoded data |
80
|
|
|
* @param int|null $offset Reference to the variable that contains offset |
81
|
|
|
* into the data where to start parsing. Variable is updated to |
82
|
|
|
* the offset next to the parsed identifier. If null, start from |
83
|
|
|
* offset 0. |
84
|
|
|
* @throws DecodeException If decoding fails |
85
|
|
|
* @return self |
86
|
|
|
*/ |
87
|
217 |
|
public static function fromDER(string $data, int &$offset = null): self |
88
|
|
|
{ |
89
|
217 |
|
$idx = $offset ? $offset : 0; |
90
|
217 |
|
$datalen = strlen($data); |
91
|
217 |
|
if ($idx >= $datalen) { |
92
|
1 |
|
throw new DecodeException("Invalid offset."); |
93
|
|
|
} |
94
|
216 |
|
$byte = ord($data[$idx++]); |
95
|
|
|
// bits 8 and 7 (class) |
|
|
|
|
96
|
|
|
// 0 = universal, 1 = application, 2 = context-specific, 3 = private |
97
|
216 |
|
$class = (0b11000000 & $byte) >> 6; |
98
|
|
|
// bit 6 (0 = primitive / 1 = constructed) |
99
|
216 |
|
$pc = (0b00100000 & $byte) >> 5; |
100
|
|
|
// bits 5 to 1 (tag number) |
101
|
216 |
|
$tag = (0b00011111 & $byte); |
102
|
|
|
// long-form identifier |
103
|
216 |
|
if (0x1f == $tag) { |
104
|
8 |
|
$tag = self::_decodeLongFormTag($data, $idx); |
105
|
|
|
} |
106
|
215 |
|
if (isset($offset)) { |
107
|
201 |
|
$offset = $idx; |
108
|
|
|
} |
109
|
215 |
|
return new self($class, $pc, $tag); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Parse long form tag. |
114
|
|
|
* |
115
|
|
|
* @param string $data DER data |
116
|
|
|
* @param int $offset Reference to the variable containing offset to data |
117
|
|
|
* @throws DecodeException If decoding fails |
118
|
|
|
* @return string Tag number |
119
|
|
|
*/ |
120
|
8 |
|
private static function _decodeLongFormTag(string $data, int &$offset): string |
121
|
|
|
{ |
122
|
8 |
|
$datalen = strlen($data); |
123
|
8 |
|
$tag = gmp_init(0, 10); |
124
|
8 |
|
while (true) { |
125
|
8 |
|
if ($offset >= $datalen) { |
126
|
1 |
|
throw new DecodeException( |
127
|
|
|
"Unexpected end of data while decoding" . |
128
|
1 |
|
" long form identifier."); |
129
|
|
|
} |
130
|
8 |
|
$byte = ord($data[$offset++]); |
131
|
8 |
|
$tag <<= 7; |
132
|
8 |
|
$tag |= 0x7f & $byte; |
133
|
|
|
// last byte has bit 8 set to zero |
134
|
8 |
|
if (!(0x80 & $byte)) { |
135
|
7 |
|
break; |
136
|
|
|
} |
137
|
|
|
} |
138
|
7 |
|
return gmp_strval($tag, 10); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* |
143
|
|
|
* @see Encodable::toDER() |
144
|
|
|
* @return string |
145
|
|
|
*/ |
146
|
117 |
|
public function toDER(): string |
147
|
|
|
{ |
148
|
117 |
|
$bytes = []; |
149
|
117 |
|
$byte = $this->_class << 6 | $this->_pc << 5; |
150
|
117 |
|
$tag = $this->_tag->gmpObj(); |
151
|
117 |
|
if ($tag < 0x1f) { |
152
|
114 |
|
$bytes[] = $byte | $tag; |
153
|
|
|
} else { // long-form identifier |
154
|
4 |
|
$bytes[] = $byte | 0x1f; |
155
|
4 |
|
$octets = []; |
156
|
4 |
|
for (; $tag > 0; $tag >>= 7) { |
157
|
4 |
|
array_push($octets, gmp_intval(0x80 | ($tag & 0x7f))); |
158
|
|
|
} |
159
|
|
|
// last octet has bit 8 set to zero |
160
|
4 |
|
$octets[0] &= 0x7f; |
161
|
4 |
|
foreach (array_reverse($octets) as $octet) { |
162
|
4 |
|
$bytes[] = $octet; |
163
|
|
|
} |
164
|
|
|
} |
165
|
117 |
|
return pack("C*", ...$bytes); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Get class of the type. |
170
|
|
|
* |
171
|
|
|
* @return int |
172
|
|
|
*/ |
173
|
6 |
|
public function typeClass(): int |
174
|
|
|
{ |
175
|
6 |
|
return $this->_class; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Get P/C. |
180
|
|
|
* |
181
|
|
|
* @return int |
182
|
|
|
*/ |
183
|
1 |
|
public function pc(): int |
184
|
|
|
{ |
185
|
1 |
|
return $this->_pc; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Get the tag number. |
190
|
|
|
* |
191
|
|
|
* @return string Base 10 integer string |
192
|
|
|
*/ |
193
|
203 |
|
public function tag(): string |
194
|
|
|
{ |
195
|
203 |
|
return $this->_tag->base10(); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Get the tag as an integer. |
200
|
|
|
* |
201
|
|
|
* @return int |
202
|
|
|
*/ |
203
|
2 |
|
public function intTag(): int |
204
|
|
|
{ |
205
|
2 |
|
return $this->_tag->intVal(); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Check whether type is of an universal class. |
210
|
|
|
* |
211
|
|
|
* @return boolean |
212
|
|
|
*/ |
213
|
186 |
|
public function isUniversal(): bool |
214
|
|
|
{ |
215
|
186 |
|
return self::CLASS_UNIVERSAL == $this->_class; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Check whether type is of an application class. |
220
|
|
|
* |
221
|
|
|
* @return boolean |
222
|
|
|
*/ |
223
|
1 |
|
public function isApplication(): bool |
224
|
|
|
{ |
225
|
1 |
|
return self::CLASS_APPLICATION == $this->_class; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Check whether type is of a context specific class. |
230
|
|
|
* |
231
|
|
|
* @return boolean |
232
|
|
|
*/ |
233
|
194 |
|
public function isContextSpecific(): bool |
234
|
|
|
{ |
235
|
194 |
|
return self::CLASS_CONTEXT_SPECIFIC == $this->_class; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Check whether type is of a private class. |
240
|
|
|
* |
241
|
|
|
* @return boolean |
242
|
|
|
*/ |
243
|
1 |
|
public function isPrivate(): bool |
244
|
|
|
{ |
245
|
1 |
|
return self::CLASS_PRIVATE == $this->_class; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* Check whether content is primitive type. |
250
|
|
|
* |
251
|
|
|
* @return boolean |
252
|
|
|
*/ |
253
|
79 |
|
public function isPrimitive(): bool |
254
|
|
|
{ |
255
|
79 |
|
return self::PRIMITIVE == $this->_pc; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Check hether content is constructed type. |
260
|
|
|
* |
261
|
|
|
* @return boolean |
262
|
|
|
*/ |
263
|
23 |
|
public function isConstructed(): bool |
264
|
|
|
{ |
265
|
23 |
|
return self::CONSTRUCTED == $this->_pc; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Get self with given type class. |
270
|
|
|
* |
271
|
|
|
* @param int $class One of <code>CLASS_*</code> enumerations |
272
|
|
|
* @return self |
273
|
|
|
*/ |
274
|
6 |
|
public function withClass(int $class): self |
275
|
|
|
{ |
276
|
6 |
|
$obj = clone $this; |
277
|
6 |
|
$obj->_class = $class; |
278
|
6 |
|
return $obj; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Get self with given type tag. |
283
|
|
|
* |
284
|
|
|
* @param int|string $tag Tag number |
285
|
|
|
* @return self |
286
|
|
|
*/ |
287
|
6 |
|
public function withTag($tag): self |
288
|
|
|
{ |
289
|
6 |
|
$obj = clone $this; |
290
|
6 |
|
$obj->_tag = new BigInt($tag); |
291
|
6 |
|
return $obj; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Get human readable name of the type class. |
296
|
|
|
* |
297
|
|
|
* @param int $class |
298
|
|
|
* @return string |
299
|
|
|
*/ |
300
|
8 |
|
public static function classToName(int $class): string |
301
|
|
|
{ |
302
|
8 |
|
if (!array_key_exists($class, self::MAP_CLASS_TO_NAME)) { |
303
|
1 |
|
return "CLASS $class"; |
304
|
|
|
} |
305
|
7 |
|
return self::MAP_CLASS_TO_NAME[$class]; |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.