Bug 1928997: Update tabs icon in Unified Search popup r=desktop-theme-reviewers,daleh...
[gecko.git] / security / manager / ssl / DER.sys.mjs
blob3620cf9f29474c2f566cc0e83dda50b10e713949
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 // A minimal ASN.1 DER decoder. Supports input lengths up to 65539 (one byte for
6 // the outer tag, one byte for the 0x82 length-length indicator, two bytes
7 // indicating a contents length of 65535, and then the 65535 bytes of contents).
8 // Intended to be used like so:
9 //
10 // let bytes = <an array of bytes describing a SEQUENCE OF INTEGER>;
11 // let der = new DER.DERDecoder(bytes);
12 // let contents = new DER.DERDecoder(der.readTagAndGetContents(DER.SEQUENCE));
13 // while (!contents.atEnd()) {
14 //   let integerBytes = contents.readTagAndGetContents(DER.INTEGER);
15 //   <... do something with integerBytes ...>
16 // }
17 // der.assertAtEnd();
19 // For CHOICE, use readTLVChoice and pass an array of acceptable tags.
20 // The convenience function readBIT_STRING is provided to handle the unused bits
21 // aspect of BIT STRING. It returns an object that has the properties contents
22 // (an array of bytes consisting of the bytes making up the BIT STRING) and
23 // unusedBits (indicating the number of unused bits at the end).
24 // All other functions generally return an array of bytes or a single byte as
25 // appropriate.
26 // peekTag can be used to see if the next tag is an expected given tag.
27 // readTLV reads and returns an entire (tag, length, value) tuple (again
28 // returned as an array of bytes).
30 // NB: While DERDecoder must be given an array, it does not validate that each
31 // element in the array is an integer in the range [0, 255]. If the input to be
32 // decoded could conceivably violate this property, callers should perform this
33 // check before using DERDecoder.
35 const UNIVERSAL = 0 << 6;
36 const CONSTRUCTED = 1 << 5;
37 const CONTEXT_SPECIFIC = 2 << 6;
39 const INTEGER = UNIVERSAL | 0x02; // 0x02
40 const BIT_STRING = UNIVERSAL | 0x03; // 0x03
41 const NULL = UNIVERSAL | 0x05; // 0x05
42 const OBJECT_IDENTIFIER = UNIVERSAL | 0x06; // 0x06
43 const PrintableString = UNIVERSAL | 0x13; // 0x13
44 const TeletexString = UNIVERSAL | 0x14; // 0x14
45 const IA5String = UNIVERSAL | 0x16; // 0x16
46 const UTCTime = UNIVERSAL | 0x17; // 0x17
47 const GeneralizedTime = UNIVERSAL | 0x18; // 0x18
48 const UTF8String = UNIVERSAL | 0x0c; // 0x0c
49 const SEQUENCE = UNIVERSAL | CONSTRUCTED | 0x10; // 0x30
50 const SET = UNIVERSAL | CONSTRUCTED | 0x11; // 0x31
52 const ERROR_INVALID_INPUT = "invalid input";
53 const ERROR_DATA_TRUNCATED = "data truncated";
54 const ERROR_EXTRA_DATA = "extra data";
55 const ERROR_INVALID_LENGTH = "invalid length";
56 const ERROR_UNSUPPORTED_ASN1 = "unsupported asn.1";
57 const ERROR_UNSUPPORTED_LENGTH = "unsupported length";
58 const ERROR_INVALID_BIT_STRING = "invalid BIT STRING encoding";
60 /** Class representing a decoded BIT STRING. */
61 class BitString {
62   /**
63    * @param {number} unusedBits the number of unused bits
64    * @param {number[]} contents an array of bytes comprising the BIT STRING
65    */
66   constructor(unusedBits, contents) {
67     this._unusedBits = unusedBits;
68     this._contents = contents;
69   }
71   /**
72    * Get the number of unused bits in the BIT STRING
73    *
74    * @returns {number} the number of unused bits
75    */
76   get unusedBits() {
77     return this._unusedBits;
78   }
80   /**
81    * Get the contents of the BIT STRING
82    *
83    * @returns {number[]} an array of bytes representing the contents
84    */
85   get contents() {
86     return this._contents;
87   }
90 /** Class representing DER-encoded data. Provides methods for decoding it. */
91 class DERDecoder {
92   /**
93    * @param {number[]} bytes an array of bytes representing the encoded data
94    */
95   constructor(bytes) {
96     // Reject non-array inputs.
97     if (!Array.isArray(bytes)) {
98       throw new Error(ERROR_INVALID_INPUT);
99     }
100     if (bytes.length > 65539) {
101       throw new Error(ERROR_UNSUPPORTED_LENGTH);
102     }
103     this._bytes = bytes;
104     this._cursor = 0;
105   }
107   /**
108    * Asserts that the decoder is at the end of the given data. Throws an error
109    * if this is not the case.
110    */
111   assertAtEnd() {
112     if (!this.atEnd()) {
113       throw new Error(ERROR_EXTRA_DATA);
114     }
115   }
117   /**
118    * Determines whether or not the decoder is at the end of the given data.
119    *
120    * @returns {boolean} true if the decoder is at the end and false otherwise
121    */
122   atEnd() {
123     return this._cursor == this._bytes.length;
124   }
126   /**
127    * Reads the next byte of data. Throws if no more data is available.
128    *
129    * @returns {number} the next byte of data
130    */
131   readByte() {
132     if (this._cursor >= this._bytes.length) {
133       throw new Error(ERROR_DATA_TRUNCATED);
134     }
135     let val = this._bytes[this._cursor];
136     this._cursor++;
137     return val;
138   }
140   /**
141    * Given the next expected tag, reads and asserts that the next tag is in fact
142    * the given tag.
143    *
144    * @param {number} expectedTag the expected next tag
145    */
146   _readExpectedTag(expectedTag) {
147     let tag = this.readByte();
148     if (tag != expectedTag) {
149       throw new Error(`unexpected tag: found ${tag} instead of ${expectedTag}`);
150     }
151   }
153   /**
154    * Decodes and returns the length portion of an ASN.1 TLV tuple. Throws if the
155    * length is incorrectly encoded or if it describes a length greater than
156    * 65535 bytes. Indefinite-length encoding is not supported.
157    *
158    * @returns {number} the length of the value of the TLV tuple
159    */
160   _readLength() {
161     let nextByte = this.readByte();
162     if (nextByte < 0x80) {
163       return nextByte;
164     }
165     if (nextByte == 0x80) {
166       throw new Error(ERROR_UNSUPPORTED_ASN1);
167     }
168     if (nextByte == 0x81) {
169       let length = this.readByte();
170       if (length < 0x80) {
171         throw new Error(ERROR_INVALID_LENGTH);
172       }
173       return length;
174     }
175     if (nextByte == 0x82) {
176       let length1 = this.readByte();
177       let length2 = this.readByte();
178       let length = (length1 << 8) | length2;
179       if (length < 256) {
180         throw new Error(ERROR_INVALID_LENGTH);
181       }
182       return length;
183     }
184     throw new Error(ERROR_UNSUPPORTED_LENGTH);
185   }
187   /**
188    * Reads <length> bytes of data if available. Throws if less than <length>
189    * bytes are available.
190    *
191    * @param {number} length the number of bytes to read. Must be non-negative.
192    * @returns {number[]} the next <length> bytes of data
193    */
194   readBytes(length) {
195     if (length < 0) {
196       throw new Error(ERROR_INVALID_LENGTH);
197     }
198     if (this._cursor + length > this._bytes.length) {
199       throw new Error(ERROR_DATA_TRUNCATED);
200     }
201     let bytes = this._bytes.slice(this._cursor, this._cursor + length);
202     this._cursor += length;
203     return bytes;
204   }
206   /**
207    * Given an expected next ASN.1 tag, ensures that that tag is next and returns
208    * the contents of that tag. Throws if a different tag is encountered or if
209    * the data is otherwise incorrectly encoded.
210    *
211    * @param {number} tag the next expected ASN.1 tag
212    * @returns {number[]} the contents of the tag
213    */
214   readTagAndGetContents(tag) {
215     this._readExpectedTag(tag);
216     let length = this._readLength();
217     return this.readBytes(length);
218   }
220   /**
221    * Returns the next byte without advancing the decoder. Throws if no more data
222    * is available.
223    *
224    * @returns {number} the next available byte
225    */
226   _peekByte() {
227     if (this._cursor >= this._bytes.length) {
228       throw new Error(ERROR_DATA_TRUNCATED);
229     }
230     return this._bytes[this._cursor];
231   }
233   /**
234    * Given an expected tag, reads the next entire ASN.1 TLV tuple, asserting
235    * that the tag matches.
236    *
237    * @param {number} tag the expected tag
238    * @returns {number[]} an array of bytes representing the TLV tuple
239    */
240   _readExpectedTLV(tag) {
241     let mark = this._cursor;
242     this._readExpectedTag(tag);
243     let length = this._readLength();
244     // read the bytes so we know they're there (also to advance the cursor)
245     this.readBytes(length);
246     return this._bytes.slice(mark, this._cursor);
247   }
249   /**
250    * Reads the next ASN.1 tag, length, and value and returns them as an array of
251    * bytes.
252    *
253    * @returns {number[]} an array of bytes representing the next ASN.1 TLV
254    */
255   readTLV() {
256     let nextTag = this._peekByte();
257     return this._readExpectedTLV(nextTag);
258   }
260   /**
261    * Convenience function for decoding a BIT STRING. Reads and returns the
262    * contents of the expected next BIT STRING. Throws if the next TLV isn't a
263    * BIT STRING or if the BIT STRING is incorrectly encoded.
264    *
265    * @returns {BitString} the next BIT STRING
266    */
267   readBIT_STRING() {
268     let contents = this.readTagAndGetContents(BIT_STRING);
269     if (contents.length < 1) {
270       throw new Error(ERROR_INVALID_BIT_STRING);
271     }
272     let unusedBits = contents[0];
273     if (unusedBits > 7) {
274       throw new Error(ERROR_INVALID_BIT_STRING);
275     }
276     // Zero bytes of content but some amount of padding is invalid.
277     if (contents.length == 1 && unusedBits != 0) {
278       throw new Error(ERROR_INVALID_BIT_STRING);
279     }
280     return new BitString(unusedBits, contents.slice(1, contents.length));
281   }
283   /**
284    * Looks to see if the next ASN.1 tag is the expected given tag.
285    *
286    * @param {number} tag the expected next ASN.1 tag
287    * @returns {boolean} true if the next tag is the given one and false otherwise
288    */
289   peekTag(tag) {
290     if (this._cursor >= this._bytes.length) {
291       return false;
292     }
293     return this._bytes[this._cursor] == tag;
294   }
296   /**
297    * Given a list of possible next ASN.1 tags, returns the next TLV if the next
298    * tag is in the list. Throws if the next tag is not in the list or if the
299    * data is incorrectly encoded.
300    *
301    * @param {number[]} tagList the list of potential next tags
302    * @returns {number[]} the contents of the next TLV if the next tag is in
303    *                    <tagList>
304    */
305   readTLVChoice(tagList) {
306     let tag = this._peekByte();
307     if (!tagList.includes(tag)) {
308       throw new Error(
309         `unexpected tag: found ${tag} instead of one of ${tagList}`
310       );
311     }
312     return this._readExpectedTLV(tag);
313   }
316 export const DER = {
317   UNIVERSAL,
318   CONSTRUCTED,
319   CONTEXT_SPECIFIC,
320   INTEGER,
321   BIT_STRING,
322   NULL,
323   OBJECT_IDENTIFIER,
324   PrintableString,
325   TeletexString,
326   IA5String,
327   UTCTime,
328   GeneralizedTime,
329   UTF8String,
330   SEQUENCE,
331   SET,
332   DERDecoder,