tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 };