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 import { DER } from "resource://gre/modules/psm/DER.sys.mjs";
7 const ERROR_UNSUPPORTED_ASN1 = "unsupported asn.1";
8 const ERROR_TIME_NOT_VALID = "Time not valid";
9 const ERROR_LIBRARY_FAILURE = "library failure";
14 * Helper function to read a NULL tag from the given DER.
16 * @param {DER} der a DER object to read a NULL from
17 * @returns {null} an object representing an ASN.1 NULL
19 function readNULL(der) {
20 return new NULL(der.readTagAndGetContents(DER.NULL));
24 * Class representing an ASN.1 NULL. When encoded as DER, the only valid value
25 * is 05 00, and thus the contents should always be an empty array.
29 * @param {number[]} bytes the contents of the NULL tag (should be empty)
32 // Lint TODO: bytes should be an empty array
33 this._contents = bytes;
38 * Helper function to read an OBJECT IDENTIFIER from the given DER.
40 * @param {DER} der the DER to read an OBJECT IDENTIFIER from
41 * @returns {OID} the value of the OBJECT IDENTIFIER
43 function readOID(der) {
44 return new OID(der.readTagAndGetContents(DER.OBJECT_IDENTIFIER));
47 /** Class representing an ASN.1 OBJECT IDENTIFIER */
50 * @param {number[]} bytes the encoded contents of the OBJECT IDENTIFIER
51 * (not including the ASN.1 tag or length bytes)
55 // First octet has value 40 * value1 + value2
56 // Lint TODO: validate that value1 is one of {0, 1, 2}
57 // Lint TODO: validate that value2 is in [0, 39] if value1 is 0 or 1
58 let value1 = Math.floor(bytes[0] / 40);
59 let value2 = bytes[0] - 40 * value1;
60 this._values.push(value1);
61 this._values.push(value2);
64 // Lint TODO: prevent overflow here
65 while (bytes.length) {
66 let value = bytes.shift();
69 accumulator += value - 128;
72 this._values.push(accumulator);
80 * Class that serves as an abstract base class for more specific classes that
81 * represent datatypes from RFC 5280 and others. Given an array of bytes
82 * representing the DER encoding of such types, this framework simplifies the
83 * process of making a new DER object, attempting to parse the given bytes, and
84 * catching and stashing thrown exceptions. Subclasses are to implement
85 * parseOverride, which should read from this._der to fill out the structure's
95 * Returns the first exception encountered when decoding or null if none has
98 * @returns {Error} the first exception encountered when decoding or null
105 * Does the actual work of parsing the data. To be overridden by subclasses.
106 * If an implementation of parseOverride throws an exception, parse will catch
107 * that exception and stash it in the error property. This enables parent
108 * levels in a nested decoding hierarchy to continue to decode as much as
112 throw new Error(ERROR_LIBRARY_FAILURE);
116 * Public interface to be called to parse all data. Calls parseOverride inside
117 * a try/catch block. If an exception is thrown, stashes the error, which can
118 * be obtained via the error getter (above).
120 * @param {number[]} bytes encoded DER to be decoded
123 this._der = new DER.DERDecoder(bytes);
125 this.parseOverride();
133 * Helper function for reading the next SEQUENCE out of a DER and creating a new
134 * DER out of the resulting bytes.
136 * @param {DER} der the underlying DER object
137 * @returns {DER} the contents of the SEQUENCE
139 function readSEQUENCEAndMakeDER(der) {
140 return new DER.DERDecoder(der.readTagAndGetContents(DER.SEQUENCE));
144 * Helper function for reading the next item identified by tag out of a DER and
145 * creating a new DER out of the resulting bytes.
147 * @param {DER} der the underlying DER object
148 * @param {number} tag the expected next tag in the DER
149 * @returns {DER} the contents of the tag
151 function readTagAndMakeDER(der, tag) {
152 return new DER.DERDecoder(der.readTagAndGetContents(tag));
155 // Certificate ::= SEQUENCE {
156 // tbsCertificate TBSCertificate,
157 // signatureAlgorithm AlgorithmIdentifier,
158 // signatureValue BIT STRING }
159 class Certificate extends DecodedDER {
162 this._tbsCertificate = new TBSCertificate();
163 this._signatureAlgorithm = new AlgorithmIdentifier();
164 this._signatureValue = [];
167 get tbsCertificate() {
168 return this._tbsCertificate;
171 get signatureAlgorithm() {
172 return this._signatureAlgorithm;
175 get signatureValue() {
176 return this._signatureValue;
180 let contents = readSEQUENCEAndMakeDER(this._der);
181 this._tbsCertificate.parse(contents.readTLV());
182 this._signatureAlgorithm.parse(contents.readTLV());
184 let signatureValue = contents.readBIT_STRING();
185 if (signatureValue.unusedBits != 0) {
186 throw new Error(ERROR_UNSUPPORTED_ASN1);
188 this._signatureValue = signatureValue.contents;
189 contents.assertAtEnd();
190 this._der.assertAtEnd();
194 // TBSCertificate ::= SEQUENCE {
195 // version [0] EXPLICIT Version DEFAULT v1,
196 // serialNumber CertificateSerialNumber,
197 // signature AlgorithmIdentifier,
199 // validity Validity,
201 // subjectPublicKeyInfo SubjectPublicKeyInfo,
202 // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
203 // -- If present, version MUST be v2 or v3
204 // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
205 // -- If present, version MUST be v2 or v3
206 // extensions [3] EXPLICIT Extensions OPTIONAL
207 // -- If present, version MUST be v3
209 class TBSCertificate extends DecodedDER {
212 this._version = null;
213 this._serialNumber = [];
214 this._signature = new AlgorithmIdentifier();
215 this._issuer = new Name();
216 this._validity = new Validity();
217 this._subject = new Name();
218 this._subjectPublicKeyInfo = new SubjectPublicKeyInfo();
219 this._extensions = [];
223 return this._version;
227 return this._serialNumber;
231 return this._signature;
239 return this._validity;
243 return this._subject;
246 get subjectPublicKeyInfo() {
247 return this._subjectPublicKeyInfo;
251 return this._extensions;
255 let contents = readSEQUENCEAndMakeDER(this._der);
257 let versionTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 0;
258 if (!contents.peekTag(versionTag)) {
261 let versionContents = readTagAndMakeDER(contents, versionTag);
262 let versionBytes = versionContents.readTagAndGetContents(DER.INTEGER);
263 if (versionBytes.length == 1 && versionBytes[0] == X509v3) {
266 // Lint TODO: warn about non-v3 certificates (this INTEGER could take up
267 // multiple bytes, be negative, and so on).
268 this._version = versionBytes;
270 versionContents.assertAtEnd();
273 let serialNumberBytes = contents.readTagAndGetContents(DER.INTEGER);
274 this._serialNumber = serialNumberBytes;
275 this._signature.parse(contents.readTLV());
276 this._issuer.parse(contents.readTLV());
277 this._validity.parse(contents.readTLV());
278 this._subject.parse(contents.readTLV());
279 this._subjectPublicKeyInfo.parse(contents.readTLV());
281 // Lint TODO: warn about unsupported features
282 let issuerUniqueIDTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 1;
283 if (contents.peekTag(issuerUniqueIDTag)) {
284 contents.readTagAndGetContents(issuerUniqueIDTag);
286 let subjectUniqueIDTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 2;
287 if (contents.peekTag(subjectUniqueIDTag)) {
288 contents.readTagAndGetContents(subjectUniqueIDTag);
291 let extensionsTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 3;
292 if (contents.peekTag(extensionsTag)) {
293 let extensionsSequence = readTagAndMakeDER(contents, extensionsTag);
294 let extensionsContents = readSEQUENCEAndMakeDER(extensionsSequence);
295 while (!extensionsContents.atEnd()) {
296 // TODO: parse extensions
297 this._extensions.push(extensionsContents.readTLV());
299 extensionsContents.assertAtEnd();
300 extensionsSequence.assertAtEnd();
302 contents.assertAtEnd();
303 this._der.assertAtEnd();
307 // AlgorithmIdentifier ::= SEQUENCE {
308 // algorithm OBJECT IDENTIFIER,
309 // parameters ANY DEFINED BY algorithm OPTIONAL }
310 class AlgorithmIdentifier extends DecodedDER {
313 this._algorithm = null;
314 this._parameters = null;
318 return this._algorithm;
322 return this._parameters;
326 let contents = readSEQUENCEAndMakeDER(this._der);
327 this._algorithm = readOID(contents);
328 if (!contents.atEnd()) {
329 if (contents.peekTag(DER.NULL)) {
330 this._parameters = readNULL(contents);
331 } else if (contents.peekTag(DER.OBJECT_IDENTIFIER)) {
332 this._parameters = readOID(contents);
335 contents.assertAtEnd();
336 this._der.assertAtEnd();
340 // Name ::= CHOICE { -- only one possibility for now --
341 // rdnSequence RDNSequence }
343 // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
344 class Name extends DecodedDER {
355 let contents = readSEQUENCEAndMakeDER(this._der);
356 while (!contents.atEnd()) {
357 let rdn = new RelativeDistinguishedName();
358 rdn.parse(contents.readTLV());
359 this._rdns.push(rdn);
361 contents.assertAtEnd();
362 this._der.assertAtEnd();
366 // RelativeDistinguishedName ::=
367 // SET SIZE (1..MAX) OF AttributeTypeAndValue
368 class RelativeDistinguishedName extends DecodedDER {
379 let contents = readTagAndMakeDER(this._der, DER.SET);
380 // Lint TODO: enforce SET SIZE restrictions
381 while (!contents.atEnd()) {
382 let ava = new AttributeTypeAndValue();
383 ava.parse(contents.readTLV());
384 this._avas.push(ava);
386 contents.assertAtEnd();
387 this._der.assertAtEnd();
391 // AttributeTypeAndValue ::= SEQUENCE {
392 // type AttributeType,
393 // value AttributeValue }
395 // AttributeType ::= OBJECT IDENTIFIER
397 // AttributeValue ::= ANY -- DEFINED BY AttributeType
398 class AttributeTypeAndValue extends DecodedDER {
402 this._value = new DirectoryString();
414 let contents = readSEQUENCEAndMakeDER(this._der);
415 this._type = readOID(contents);
416 // We don't support universalString or bmpString.
417 // IA5String is supported because it is valid if `type == id-emailaddress`.
418 // Lint TODO: validate that the type of string is valid given `type`.
420 contents.readTLVChoice([
427 contents.assertAtEnd();
428 this._der.assertAtEnd();
432 // DirectoryString ::= CHOICE {
433 // teletexString TeletexString (SIZE (1..MAX)),
434 // printableString PrintableString (SIZE (1..MAX)),
435 // universalString UniversalString (SIZE (1..MAX)),
436 // utf8String UTF8String (SIZE (1..MAX)),
437 // bmpString BMPString (SIZE (1..MAX)) }
438 class DirectoryString extends DecodedDER {
454 if (this._der.peekTag(DER.UTF8String)) {
455 this._type = DER.UTF8String;
456 } else if (this._der.peekTag(DER.PrintableString)) {
457 this._type = DER.PrintableString;
458 } else if (this._der.peekTag(DER.TeletexString)) {
459 this._type = DER.TeletexString;
460 } else if (this._der.peekTag(DER.IA5String)) {
461 this._type = DER.IA5String;
463 // Lint TODO: validate that the contents are actually valid for the type
464 this._value = this._der.readTagAndGetContents(this._type);
465 this._der.assertAtEnd();
471 // generalTime GeneralizedTime }
472 class Time extends DecodedDER {
484 if (this._der.peekTag(DER.UTCTime)) {
485 this._type = DER.UTCTime;
486 } else if (this._der.peekTag(DER.GeneralizedTime)) {
487 this._type = DER.GeneralizedTime;
489 let contents = readTagAndMakeDER(this._der, this._type);
491 // Lint TODO: validate that the appropriate one of {UTCTime,GeneralizedTime}
492 // is used according to RFC 5280 and what the value of the date is.
493 // TODO TODO: explain this better (just quote the rfc).
494 if (this._type == DER.UTCTime) {
495 // UTCTime is YYMMDDHHMMSSZ in RFC 5280. If YY is greater than or equal
496 // to 50, the year is 19YY. Otherwise, it is 20YY.
497 let y1 = this._validateDigit(contents.readByte());
498 let y2 = this._validateDigit(contents.readByte());
499 let yy = y1 * 10 + y2;
506 // GeneralizedTime is YYYYMMDDHHMMSSZ in RFC 5280.
508 for (let i = 0; i < 4; i++) {
509 let y = this._validateDigit(contents.readByte());
510 year = year * 10 + y;
514 let m1 = this._validateDigit(contents.readByte());
515 let m2 = this._validateDigit(contents.readByte());
516 let month = m1 * 10 + m2;
517 if (month == 0 || month > 12) {
518 throw new Error(ERROR_TIME_NOT_VALID);
521 let d1 = this._validateDigit(contents.readByte());
522 let d2 = this._validateDigit(contents.readByte());
523 let day = d1 * 10 + d2;
524 if (day == 0 || day > 31) {
525 throw new Error(ERROR_TIME_NOT_VALID);
528 let h1 = this._validateDigit(contents.readByte());
529 let h2 = this._validateDigit(contents.readByte());
530 let hour = h1 * 10 + h2;
532 throw new Error(ERROR_TIME_NOT_VALID);
535 let min1 = this._validateDigit(contents.readByte());
536 let min2 = this._validateDigit(contents.readByte());
537 let minute = min1 * 10 + min2;
539 throw new Error(ERROR_TIME_NOT_VALID);
542 let s1 = this._validateDigit(contents.readByte());
543 let s2 = this._validateDigit(contents.readByte());
544 let second = s1 * 10 + s2;
546 // leap-seconds mean this can be as much as 60
547 throw new Error(ERROR_TIME_NOT_VALID);
550 let z = contents.readByte();
551 if (z != "Z".charCodeAt(0)) {
552 throw new Error(ERROR_TIME_NOT_VALID);
554 // Lint TODO: verify that the Time doesn't specify a nonsensical
556 // months are zero-indexed in JS
557 this._time = new Date(Date.UTC(year, month - 1, day, hour, minute, second));
559 contents.assertAtEnd();
560 this._der.assertAtEnd();
564 * Takes a byte that is supposed to be in the ASCII range for "0" to "9".
565 * Validates the range and then converts it to the range 0 to 9.
567 * @param {number} d the digit in question (as ASCII in the range ["0", "9"])
568 * @returns {number} the numerical value of the digit (in the range [0, 9])
571 if (d < "0".charCodeAt(0) || d > "9".charCodeAt(0)) {
572 throw new Error(ERROR_TIME_NOT_VALID);
574 return d - "0".charCodeAt(0);
578 // Validity ::= SEQUENCE {
581 class Validity extends DecodedDER {
584 this._notBefore = new Time();
585 this._notAfter = new Time();
589 return this._notBefore;
593 return this._notAfter;
597 let contents = readSEQUENCEAndMakeDER(this._der);
598 this._notBefore.parse(
599 contents.readTLVChoice([DER.UTCTime, DER.GeneralizedTime])
601 this._notAfter.parse(
602 contents.readTLVChoice([DER.UTCTime, DER.GeneralizedTime])
604 contents.assertAtEnd();
605 this._der.assertAtEnd();
609 // SubjectPublicKeyInfo ::= SEQUENCE {
610 // algorithm AlgorithmIdentifier,
611 // subjectPublicKey BIT STRING }
612 class SubjectPublicKeyInfo extends DecodedDER {
615 this._algorithm = new AlgorithmIdentifier();
616 this._subjectPublicKey = null;
620 return this._algorithm;
623 get subjectPublicKey() {
624 return this._subjectPublicKey;
628 let contents = readSEQUENCEAndMakeDER(this._der);
629 this._algorithm.parse(contents.readTLV());
630 let subjectPublicKeyBitString = contents.readBIT_STRING();
631 if (subjectPublicKeyBitString.unusedBits != 0) {
632 throw new Error(ERROR_UNSUPPORTED_ASN1);
634 this._subjectPublicKey = subjectPublicKeyBitString.contents;
636 contents.assertAtEnd();
637 this._der.assertAtEnd();
641 export var X509 = { Certificate };