1<?php
2/*
3 * Copyright 2008-2010 GuardTime AS
4 *
5 * This file is part of the GuardTime PHP SDK.
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 * @package asn1
22 */
23
24/**
25 * Class implementing DER helper methods for encoding and decoding.
26 *
27 * @package asn1
28 */
29class ASN1DER {
30
31    /**
32     * Encodes object as DER.
33     *
34     * @static
35     * @throws GTException
36     * @param  ASN1DEREncodable $object
37     * @return array der encoding bytes
38     */
39    public static function encode($object) {
40
41        if (!($object instanceof ASN1DEREncodable)) {
42            throw new GTException("Unable to encode object that is not DEREncodable");
43        }
44
45        return $object->encodeDER();
46
47    }
48
49    /**
50     * Encodes the length using DER (L in TLV).
51     *
52     * @static
53     * @param  int $length the length to encode
54     * @return array der encoding bytes
55     */
56    public static function encodeLength($length) {
57
58        $bytes = array();
59
60        if ($length > 127) {
61
62            $size = 1;
63            $value = $length;
64            while (($value >>= 8) != 0) {
65                $size++;
66            }
67
68            array_push($bytes, $size | 0x80);
69
70            for ($i = ($size - 1) * 8; $i >= 0; $i -= 8) {
71
72                $byte = $length >> $i;
73                $byte = $byte & 0xFF;
74
75                array_push($bytes, $byte);
76            }
77
78        } else {
79
80            array_push($bytes, $length);
81
82        }
83
84        return $bytes;
85
86    }
87
88    /**
89     * Encodes the type using DER (T in TLV).
90     *
91     * @static
92     * @param  int $type  the type to encode
93     * @return array der encoding bytes
94     */
95    public static function encodeType($type) {
96
97        if ($type == ASN1_TAG_SET || $type == ASN1_TAG_SEQUENCE) {
98            $type = $type | 0x20;
99        }
100
101        return array($type);
102    }
103
104    /**
105     * Decodes an ASN1Object from the given byte stream.
106     *
107     * @static
108     * @throws GTException
109     * @param  array $bytes the byte stream
110     * @return ASN1Object decoded object
111     */
112    public static function decode($bytes) {
113
114        if (empty($bytes)) {
115            throw new GTException("parameter bytes is required");
116        }
117
118        $object = ASN1DER::decodeType($bytes);
119        $length = ASN1DER::decodeLength($bytes);
120
121        if (count($bytes) < $length) {
122            throw new GTException("Invalid DER stream, not enough bytes");
123        }
124
125        if (count($bytes) > $length) {
126            throw new GTException("Invalid DER stream, too many trailing bytes");
127        }
128
129        $object->decodeDER($bytes);
130
131        return $object;
132    }
133
134    /**
135     * Decodes the length from the given byte stream (L in TLV).
136     *
137     * @static
138     * @throws GTException
139     * @param  arrayref &$bytes the byte stream
140     * @return int decoded length
141     */
142    public static function decodeLength(&$bytes) {
143
144        if (!is_array($bytes)) {
145            throw new GTException("parameter bytes must be an array of bytes");
146        }
147
148        if (count($bytes) == 0) {
149            throw new GTException("Invalid DER stream, not enough bytes to decode length");
150        }
151
152        $length = array_shift($bytes);
153
154        if ($length == 128) {
155            throw new GTException("indefinite length encoding not allowed for DER");
156        }
157
158        if ($length > 127) {
159
160            $size = $length & 0x7F;
161
162            if ($size > 4) {
163                throw new GTException("size > 4");
164            }
165
166            $length = 0;
167
168            for ($i = 0; $i < $size; $i++) {
169                if (count($bytes) == 0) {
170                    throw new GTException("Invalid DER stream, not enough bytes to decode length");
171                }
172
173                $length = ($length << 8) + array_shift($bytes);
174            }
175
176        }
177
178        return $length;
179
180    }
181
182    /**
183     * Decodes type from the given byte stream (T in TLV).
184     *
185     * @static
186     * @throws GTException
187     * @param  arrayref &$bytes the byte stream
188     * @return ASN1Object a concrete subclass instance
189     */
190    public static function decodeType(&$bytes) {
191
192        if (!is_array($bytes)) {
193            throw new GTException("parameter bytes must be an array of bytes");
194        }
195
196        if (count($bytes) < 1) {
197            throw new GTException("unexpected EOF");
198        }
199
200        $byte = array_shift($bytes);
201
202        $bit8 = ($byte >> 7) & 0x1;
203        $bit7 = ($byte >> 6) & 0x1;
204        $bit6 = ($byte >> 5) & 0x1;
205
206        $tagClass = "";
207
208        if ($bit8 == 0 && $bit7 == 0) {
209
210            // 00xx xxxx UNIVERSAL
211            $tagClass = ASN1_TAG_UNIVERSAL;
212
213        } else if ($bit8 == 0 && $bit7 == 1) {
214
215            // 01xx xxxx APPLICATION
216            $tagClass = ASN1_TAG_APPLICATION;
217
218        } else if ($bit8 == 1 && $bit7 == 0) {
219
220            // 10xx xxxx CONTEXT
221            $tagClass = ASN1_TAG_CONTEXT;
222
223        } else if ($bit7 == 1 && $bit8 == 1) {
224
225            // 11xx xxxx PRIVATE
226            $tagClass = ASN1_TAG_PRIVATE;
227
228        }
229
230        if ($bit6) {
231
232            // xx1x xxxx CONSTRUCTED
233            $tagType = ASN1_TAG_CONSTRUCTED;
234
235        } else {
236
237            // xx0x xxxx PRIMITIVE
238            $tagType = ASN1_TAG_PRIMITIVE;
239
240        }
241
242        $byte = $byte & 0x1F; // clear tags: 000x xxxx
243
244        if ($byte == 0x1F) {
245            throw new GTException("Multibyte tags not yet supported!");
246        }
247
248        $object = null;
249
250        if ($tagClass == ASN1_TAG_UNIVERSAL) {
251
252            switch ($byte) {
253
254                case ASN1_TAG_BOOLEAN:
255
256                    $object = new ASN1Boolean();
257                    break;
258
259                case ASN1_TAG_INTEGER:
260
261                    $object = new ASN1Integer();
262                    break;
263
264                case ASN1_TAG_BIT_STRING:
265
266                    $object = new ASN1BitString();
267                    break;
268
269                case ASN1_TAG_OCTET_STRING:
270
271                    $object = new ASN1OctetString();
272                    break;
273
274                case ASN1_TAG_NULL:
275
276                    $object = new ASN1Null();
277                    break;
278
279                case ASN1_TAG_OBJECT_ID:
280
281                    $object = new ASN1ObjectId();
282                    break;
283
284                case ASN1_TAG_UTF8_STRING:
285
286                    $object = new ASN1UTF8String();
287                    break;
288
289                case ASN1_TAG_PRINTABLE_STRING:
290
291                    $object = new ASN1PrintableString();
292                    break;
293
294                case ASN1_TAG_T61_STRING:
295
296                    $object = new ASN1T61String();
297                    break;
298
299                case ASN1_TAG_IA5_STRING:
300
301                    $object = new ASN1IA5String();
302                    break;
303
304                case ASN1_TAG_BMP_STRING:
305
306                    $object = new ASN1BMPString();
307                    break;
308
309                case ASN1_TAG_UTC_TIME:
310
311                    $object = new ASN1UTCTime();
312                    break;
313
314                case ASN1_TAG_GENERALIZED_TIME:
315
316                    $object = new ASN1GeneralizedTime();
317                    break;
318
319                case ASN1_TAG_SEQUENCE:
320
321                    $object = new ASN1Sequence();
322                    break;
323
324                case ASN1_TAG_SET:
325
326                    $object = new ASN1Set();
327                    break;
328
329                default:
330
331                    throw new GTException("Unsupported ASN.1 UNIVERSAL type: {$byte}");
332            }
333
334        } else {
335
336            $object = new ASN1Tag();
337        }
338
339        $object->setTagClass($tagClass);
340        $object->setTagType($tagType);
341        $object->setTagValue($byte);
342
343        return $object;
344
345    }
346
347}
348
349?>
350