DER.sys.mjs (10228B)
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/. */ 4 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(); 18 // 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). 29 // 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. 34 35 const UNIVERSAL = 0 << 6; 36 const CONSTRUCTED = 1 << 5; 37 const CONTEXT_SPECIFIC = 2 << 6; 38 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 51 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"; 59 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 } 70 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 } 79 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 } 88 } 89 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 } 106 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 } 116 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 } 125 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 } 139 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 } 152 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 } 186 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 } 205 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 } 219 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 } 232 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 } 248 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 } 259 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 } 282 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 } 295 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 } 314 } 315 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, 333 };