summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/test_der.js
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/tests/unit/test_der.js')
-rw-r--r--security/manager/ssl/tests/unit/test_der.js380
1 files changed, 380 insertions, 0 deletions
diff --git a/security/manager/ssl/tests/unit/test_der.js b/security/manager/ssl/tests/unit/test_der.js
new file mode 100644
index 0000000000..bae18e98e3
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_der.js
@@ -0,0 +1,380 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests DER.jsm functionality.
+
+// Until DER.jsm is actually used in production code, this is where we have to
+// import it from.
+var { DER } = ChromeUtils.import("resource://gre/modules/psm/DER.jsm");
+
+function run_simple_tests() {
+ throws(
+ () => new DER.DERDecoder("this is not an array"),
+ /invalid input/,
+ "should throw given non-array input"
+ );
+ throws(
+ () => new DER.DERDecoder([0, "invalid input", 1]),
+ /invalid input/,
+ "should throw given non-byte data (string case)"
+ );
+ throws(
+ () => new DER.DERDecoder([31, 1, {}]),
+ /invalid input/,
+ "should throw given non-byte data (object case)"
+ );
+ throws(
+ () => new DER.DERDecoder([0.1, 3, 1]),
+ /invalid input/,
+ "should throw given non-byte data (non-integer case)"
+ );
+ throws(
+ () => new DER.DERDecoder([1, 3, -1]),
+ /invalid input/,
+ "should throw given non-byte data (negative integer case)"
+ );
+ throws(
+ () => new DER.DERDecoder([1, 300, 79]),
+ /invalid input/,
+ "should throw given non-byte data (large integer case)"
+ );
+
+ let testReadByte = new DER.DERDecoder([0x0a, 0x0b]);
+ equal(testReadByte.readByte(), 0x0a, "should read 0x0a");
+ equal(testReadByte.readByte(), 0x0b, "should read 0x0b");
+ throws(
+ () => testReadByte.readByte(),
+ /data truncated/,
+ "reading more data than is available should fail"
+ );
+
+ let testReadBytes = new DER.DERDecoder([0x0c, 0x0d, 0x0e]);
+ deepEqual(
+ testReadBytes.readBytes(3),
+ [0x0c, 0x0d, 0x0e],
+ "should read correct sequence of bytes"
+ );
+
+ let testReadNegativeBytes = new DER.DERDecoder([0xff, 0xaf]);
+ throws(
+ () => testReadNegativeBytes.readBytes(-4),
+ /invalid length/,
+ "reading a negative number of bytes should fail"
+ );
+
+ let testReadZeroBytes = new DER.DERDecoder([]);
+ equal(
+ testReadZeroBytes.readBytes(0).length,
+ 0,
+ "reading zero bytes should result in a zero-length array"
+ );
+
+ let testReadTooManyBytes = new DER.DERDecoder([0xab, 0xcd, 0xef]);
+ throws(
+ () => testReadTooManyBytes.readBytes(4),
+ /data truncated/,
+ "reading too many bytes should fail"
+ );
+
+ let testSEQUENCE = new DER.DERDecoder([0x30, 0x01, 0x01]);
+ let content = testSEQUENCE.readTagAndGetContents(DER.SEQUENCE);
+ equal(content.length, 1, "content should have length 1");
+ equal(content[0], 1, "value of content should be [1]");
+ ok(testSEQUENCE.atEnd(), "testSEQUENCE should be at the end of its input");
+ testSEQUENCE.assertAtEnd();
+
+ // The length purports to be 4 bytes, but there are only 2 available.
+ let truncatedSEQUENCE = new DER.DERDecoder([0x30, 0x04, 0x00, 0x00]);
+ throws(
+ () => truncatedSEQUENCE.readTagAndGetContents(DER.SEQUENCE),
+ /data truncated/,
+ "should get 'data truncated' error"
+ );
+
+ // With 2 bytes of content, there is 1 remaining after reading the content.
+ let extraDataSEQUENCE = new DER.DERDecoder([0x30, 0x02, 0xab, 0xcd, 0xef]);
+ content = extraDataSEQUENCE.readTagAndGetContents(DER.SEQUENCE);
+ equal(content.length, 2, "content should have length 2");
+ deepEqual(content, [0xab, 0xcd], "value of content should be [0xab, 0xcd]");
+ ok(
+ !extraDataSEQUENCE.atEnd(),
+ "extraDataSEQUENCE should not be at the end of its input"
+ );
+ throws(
+ () => extraDataSEQUENCE.assertAtEnd(),
+ /extra data/,
+ "should get 'extra data' error"
+ );
+
+ // The length of 0x81 0x01 is invalid because it could be encoded as just
+ // 0x01, which is shorter.
+ let invalidLengthSEQUENCE1 = new DER.DERDecoder([0x30, 0x81, 0x01, 0x00]);
+ throws(
+ () => invalidLengthSEQUENCE1.readTagAndGetContents(DER.SEQUENCE),
+ /invalid length/,
+ "should get 'invalid length' error"
+ );
+
+ // Similarly, 0x82 0x00 0x01 could be encoded as just 0x01, which is shorter.
+ let invalidLengthSEQUENCE2 = new DER.DERDecoder([
+ 0x30,
+ 0x82,
+ 0x00,
+ 0x01,
+ 0x00,
+ ]);
+ throws(
+ () => invalidLengthSEQUENCE2.readTagAndGetContents(DER.SEQUENCE),
+ /invalid length/,
+ "should get 'invalid length' error"
+ );
+
+ // Lengths requiring 4 bytes to encode are not supported.
+ let unsupportedLengthSEQUENCE = new DER.DERDecoder([
+ 0x30,
+ 0x83,
+ 0x01,
+ 0x01,
+ 0x01,
+ ]);
+ throws(
+ () => unsupportedLengthSEQUENCE.readTagAndGetContents(DER.SEQUENCE),
+ /unsupported length/,
+ "should get 'unsupported length' error"
+ );
+
+ // Indefinite lengths are not supported (and aren't DER anyway).
+ let unsupportedASN1SEQUENCE = new DER.DERDecoder([
+ 0x30,
+ 0x80,
+ 0x01,
+ 0x00,
+ 0x00,
+ ]);
+ throws(
+ () => unsupportedASN1SEQUENCE.readTagAndGetContents(DER.SEQUENCE),
+ /unsupported asn.1/,
+ "should get 'unsupported asn.1' error"
+ );
+
+ let unexpectedTag = new DER.DERDecoder([0x31, 0x01, 0x00]);
+ throws(
+ () => unexpectedTag.readTagAndGetContents(DER.SEQUENCE),
+ /unexpected tag/,
+ "should get 'unexpected tag' error"
+ );
+
+ let readTLVTestcase = new DER.DERDecoder([0x02, 0x03, 0x45, 0x67, 0x89]);
+ let bytes = readTLVTestcase.readTLV();
+ deepEqual(
+ bytes,
+ [0x02, 0x03, 0x45, 0x67, 0x89],
+ "bytes read with readTLV should be equal to expected value"
+ );
+
+ let peekTagTestcase = new DER.DERDecoder([0x30, 0x01, 0x00]);
+ ok(
+ peekTagTestcase.peekTag(DER.SEQUENCE),
+ "peekTag should return true for peeking with a SEQUENCE at a SEQUENCE"
+ );
+ ok(
+ !peekTagTestcase.peekTag(DER.SET),
+ "peekTag should return false for peeking with a SET at a SEQUENCE"
+ );
+ peekTagTestcase.readTLV();
+ ok(
+ !peekTagTestcase.peekTag(DER.SEQUENCE),
+ "peekTag should return false for peeking at a DER with no more data"
+ );
+
+ let tlvChoiceTestcase = new DER.DERDecoder([0x31, 0x02, 0xaa, 0xbb]);
+ let tlvChoiceContents = tlvChoiceTestcase.readTLVChoice([DER.NULL, DER.SET]);
+ deepEqual(
+ tlvChoiceContents,
+ [0x31, 0x02, 0xaa, 0xbb],
+ "readTLVChoice should return expected bytes"
+ );
+
+ let tlvChoiceNoMatchTestcase = new DER.DERDecoder([0x30, 0x01, 0xff]);
+ throws(
+ () => tlvChoiceNoMatchTestcase.readTLVChoice([DER.NULL, DER.SET]),
+ /unexpected tag/,
+ "readTLVChoice should throw if no matching tag is found"
+ );
+}
+
+function run_bit_string_tests() {
+ let bitstringDER = new DER.DERDecoder([0x03, 0x04, 0x03, 0x01, 0x02, 0xf8]);
+ let bitstring = bitstringDER.readBIT_STRING();
+ equal(bitstring.unusedBits, 3, "BIT STRING should have 3 unused bits");
+ deepEqual(
+ bitstring.contents,
+ [0x01, 0x02, 0xf8],
+ "BIT STRING should have expected contents"
+ );
+
+ let bitstringTooManyUnusedBits = new DER.DERDecoder([0x03, 0x02, 0x08, 0x00]);
+ throws(
+ () => bitstringTooManyUnusedBits.readBIT_STRING(),
+ /invalid BIT STRING encoding/,
+ "BIT STRING with too many unused bits should throw"
+ );
+
+ // A BIT STRING must have the unused bits byte, and so its length must be at
+ // least one.
+ let bitstringMissingUnusedBits = new DER.DERDecoder([0x03, 0x00]);
+ throws(
+ () => bitstringMissingUnusedBits.readBIT_STRING(),
+ /invalid BIT STRING encoding/,
+ "BIT STRING with missing unused bits (and no contents) should throw"
+ );
+
+ // The minimal BIT STRING is 03 01 00 (zero bits of padding and zero bytes of
+ // content).
+ let minimalBitstringDER = new DER.DERDecoder([0x03, 0x01, 0x00]);
+ let minimalBitstring = minimalBitstringDER.readBIT_STRING();
+ equal(
+ minimalBitstring.unusedBits,
+ 0,
+ "minimal BIT STRING should have 0 unused bits"
+ );
+ equal(
+ minimalBitstring.contents.length,
+ 0,
+ "minimal BIT STRING should have empty contents"
+ );
+
+ // However, a BIT STRING with zero bytes of content can't have any padding,
+ // because that makes no sense.
+ let noContentsPaddedBitstringDER = new DER.DERDecoder([0x03, 0x01, 0x03]);
+ throws(
+ () => noContentsPaddedBitstringDER.readBIT_STRING(),
+ /invalid BIT STRING encoding/,
+ "BIT STRING with no contents with non-zero padding should throw"
+ );
+}
+
+function run_compound_tests() {
+ let derBytes = [
+ 0x30,
+ 0x1a, // SEQUENCE
+ 0x02,
+ 0x02,
+ 0x77,
+ 0xff, // INTEGER
+ 0x06,
+ 0x03,
+ 0x2b,
+ 0x01,
+ 0x01, // OBJECT IDENTIFIER
+ 0x30,
+ 0x07, // SEQUENCE
+ 0x05,
+ 0x00, // NULL
+ 0x02,
+ 0x03,
+ 0x45,
+ 0x46,
+ 0x47, // INTEGER
+ 0x30,
+ 0x06, // SEQUENCE
+ 0x02,
+ 0x02,
+ 0x00,
+ 0xff, // INTEGER
+ 0x05,
+ 0x00,
+ ]; // NULL
+ let der = new DER.DERDecoder(derBytes);
+ let contents = new DER.DERDecoder(der.readTagAndGetContents(DER.SEQUENCE));
+ let firstINTEGER = contents.readTagAndGetContents(DER.INTEGER);
+ deepEqual(
+ firstINTEGER,
+ [0x77, 0xff],
+ "first INTEGER should have expected value"
+ );
+ let oid = contents.readTagAndGetContents(DER.OBJECT_IDENTIFIER);
+ deepEqual(
+ oid,
+ [0x2b, 0x01, 0x01],
+ "OBJECT IDENTIFIER should have expected value"
+ );
+
+ let firstNested = new DER.DERDecoder(
+ contents.readTagAndGetContents(DER.SEQUENCE)
+ );
+ let firstNestedNULL = firstNested.readTagAndGetContents(DER.NULL);
+ equal(
+ firstNestedNULL.length,
+ 0,
+ "first nested NULL should have expected value (empty array)"
+ );
+ let firstNestedINTEGER = firstNested.readTagAndGetContents(DER.INTEGER);
+ deepEqual(
+ firstNestedINTEGER,
+ [0x45, 0x46, 0x47],
+ "first nested INTEGER should have expected value"
+ );
+ firstNested.assertAtEnd();
+
+ let secondNested = new DER.DERDecoder(
+ contents.readTagAndGetContents(DER.SEQUENCE)
+ );
+ let secondNestedINTEGER = secondNested.readTagAndGetContents(DER.INTEGER);
+ deepEqual(
+ secondNestedINTEGER,
+ [0x00, 0xff],
+ "second nested INTEGER should have expected value"
+ );
+ let secondNestedNULL = secondNested.readTagAndGetContents(DER.NULL);
+ equal(
+ secondNestedNULL.length,
+ 0,
+ "second nested NULL should have expected value (empty array)"
+ );
+ secondNested.assertAtEnd();
+
+ contents.assertAtEnd();
+ der.assertAtEnd();
+
+ let invalidDERBytes = [
+ 0x30,
+ 0x06, // SEQUENCE
+ 0x30,
+ 0x02, // SEQUENCE
+ 0x02,
+ 0x01, // INTEGER (missing data)
+ 0x05,
+ 0x00, // NULL
+ 0x00,
+ 0x00,
+ ]; // (extra data)
+ let invalidDER = new DER.DERDecoder(invalidDERBytes);
+ let invalidContents = new DER.DERDecoder(
+ invalidDER.readTagAndGetContents(DER.SEQUENCE)
+ );
+ let invalidContentsContents = new DER.DERDecoder(
+ invalidContents.readTagAndGetContents(DER.SEQUENCE)
+ );
+ throws(
+ () => invalidContentsContents.readTagAndGetContents(DER.INTEGER),
+ /data truncated/,
+ "should throw due to missing data"
+ );
+ let nestedNULL = invalidContents.readTagAndGetContents(DER.NULL);
+ equal(nestedNULL.length, 0, "nested NULL should have expected value");
+ invalidContents.assertAtEnd();
+ throws(
+ () => invalidDER.assertAtEnd(),
+ /extra data/,
+ "should throw due to extra data"
+ );
+}
+
+function run_test() {
+ run_simple_tests();
+ run_bit_string_tests();
+ run_compound_tests();
+}