/* 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.importESModule( "resource://gre/modules/psm/DER.sys.mjs" ); function run_simple_tests() { throws( () => new DER.DERDecoder("this is not an array"), /invalid input/, "should throw given non-array input" ); 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(); }