X509.sys.mjs (18327B)
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 import { DER } from "resource://gre/modules/psm/DER.sys.mjs"; 6 7 const ERROR_UNSUPPORTED_ASN1 = "unsupported asn.1"; 8 const ERROR_TIME_NOT_VALID = "Time not valid"; 9 const ERROR_LIBRARY_FAILURE = "library failure"; 10 11 const X509v3 = 2; 12 13 /** 14 * Helper function to read a NULL tag from the given DER. 15 * 16 * @param {DER} der a DER object to read a NULL from 17 * @returns {null} an object representing an ASN.1 NULL 18 */ 19 function readNULL(der) { 20 return new NULL(der.readTagAndGetContents(DER.NULL)); 21 } 22 23 /** 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. 26 */ 27 class NULL { 28 /** 29 * @param {number[]} bytes the contents of the NULL tag (should be empty) 30 */ 31 constructor(bytes) { 32 // Lint TODO: bytes should be an empty array 33 this._contents = bytes; 34 } 35 } 36 37 /** 38 * Helper function to read an OBJECT IDENTIFIER from the given DER. 39 * 40 * @param {DER} der the DER to read an OBJECT IDENTIFIER from 41 * @returns {OID} the value of the OBJECT IDENTIFIER 42 */ 43 function readOID(der) { 44 return new OID(der.readTagAndGetContents(DER.OBJECT_IDENTIFIER)); 45 } 46 47 /** Class representing an ASN.1 OBJECT IDENTIFIER */ 48 class OID { 49 /** 50 * @param {number[]} bytes the encoded contents of the OBJECT IDENTIFIER 51 * (not including the ASN.1 tag or length bytes) 52 */ 53 constructor(bytes) { 54 this._values = []; 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); 62 bytes.shift(); 63 let accumulator = 0; 64 // Lint TODO: prevent overflow here 65 while (bytes.length) { 66 let value = bytes.shift(); 67 accumulator *= 128; 68 if (value > 128) { 69 accumulator += value - 128; 70 } else { 71 accumulator += value; 72 this._values.push(accumulator); 73 accumulator = 0; 74 } 75 } 76 } 77 } 78 79 /** 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 86 * values. 87 */ 88 class DecodedDER { 89 constructor() { 90 this._der = null; 91 this._error = null; 92 } 93 94 /** 95 * Returns the first exception encountered when decoding or null if none has 96 * been encountered. 97 * 98 * @returns {Error} the first exception encountered when decoding or null 99 */ 100 get error() { 101 return this._error; 102 } 103 104 /** 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 109 * possible. 110 */ 111 parseOverride() { 112 throw new Error(ERROR_LIBRARY_FAILURE); 113 } 114 115 /** 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). 119 * 120 * @param {number[]} bytes encoded DER to be decoded 121 */ 122 parse(bytes) { 123 this._der = new DER.DERDecoder(bytes); 124 try { 125 this.parseOverride(); 126 } catch (e) { 127 this._error = e; 128 } 129 } 130 } 131 132 /** 133 * Helper function for reading the next SEQUENCE out of a DER and creating a new 134 * DER out of the resulting bytes. 135 * 136 * @param {DER} der the underlying DER object 137 * @returns {DER} the contents of the SEQUENCE 138 */ 139 function readSEQUENCEAndMakeDER(der) { 140 return new DER.DERDecoder(der.readTagAndGetContents(DER.SEQUENCE)); 141 } 142 143 /** 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. 146 * 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 150 */ 151 function readTagAndMakeDER(der, tag) { 152 return new DER.DERDecoder(der.readTagAndGetContents(tag)); 153 } 154 155 // Certificate ::= SEQUENCE { 156 // tbsCertificate TBSCertificate, 157 // signatureAlgorithm AlgorithmIdentifier, 158 // signatureValue BIT STRING } 159 class Certificate extends DecodedDER { 160 constructor() { 161 super(); 162 this._tbsCertificate = new TBSCertificate(); 163 this._signatureAlgorithm = new AlgorithmIdentifier(); 164 this._signatureValue = []; 165 } 166 167 get tbsCertificate() { 168 return this._tbsCertificate; 169 } 170 171 get signatureAlgorithm() { 172 return this._signatureAlgorithm; 173 } 174 175 get signatureValue() { 176 return this._signatureValue; 177 } 178 179 parseOverride() { 180 let contents = readSEQUENCEAndMakeDER(this._der); 181 this._tbsCertificate.parse(contents.readTLV()); 182 this._signatureAlgorithm.parse(contents.readTLV()); 183 184 let signatureValue = contents.readBIT_STRING(); 185 if (signatureValue.unusedBits != 0) { 186 throw new Error(ERROR_UNSUPPORTED_ASN1); 187 } 188 this._signatureValue = signatureValue.contents; 189 contents.assertAtEnd(); 190 this._der.assertAtEnd(); 191 } 192 } 193 194 // TBSCertificate ::= SEQUENCE { 195 // version [0] EXPLICIT Version DEFAULT v1, 196 // serialNumber CertificateSerialNumber, 197 // signature AlgorithmIdentifier, 198 // issuer Name, 199 // validity Validity, 200 // subject Name, 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 208 // } 209 class TBSCertificate extends DecodedDER { 210 constructor() { 211 super(); 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 = []; 220 } 221 222 get version() { 223 return this._version; 224 } 225 226 get serialNumber() { 227 return this._serialNumber; 228 } 229 230 get signature() { 231 return this._signature; 232 } 233 234 get issuer() { 235 return this._issuer; 236 } 237 238 get validity() { 239 return this._validity; 240 } 241 242 get subject() { 243 return this._subject; 244 } 245 246 get subjectPublicKeyInfo() { 247 return this._subjectPublicKeyInfo; 248 } 249 250 get extensions() { 251 return this._extensions; 252 } 253 254 parseOverride() { 255 let contents = readSEQUENCEAndMakeDER(this._der); 256 257 let versionTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 0; 258 if (!contents.peekTag(versionTag)) { 259 this._version = 1; 260 } else { 261 let versionContents = readTagAndMakeDER(contents, versionTag); 262 let versionBytes = versionContents.readTagAndGetContents(DER.INTEGER); 263 if (versionBytes.length == 1 && versionBytes[0] == X509v3) { 264 this._version = 3; 265 } else { 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; 269 } 270 versionContents.assertAtEnd(); 271 } 272 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()); 280 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); 285 } 286 let subjectUniqueIDTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 2; 287 if (contents.peekTag(subjectUniqueIDTag)) { 288 contents.readTagAndGetContents(subjectUniqueIDTag); 289 } 290 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()); 298 } 299 extensionsContents.assertAtEnd(); 300 extensionsSequence.assertAtEnd(); 301 } 302 contents.assertAtEnd(); 303 this._der.assertAtEnd(); 304 } 305 } 306 307 // AlgorithmIdentifier ::= SEQUENCE { 308 // algorithm OBJECT IDENTIFIER, 309 // parameters ANY DEFINED BY algorithm OPTIONAL } 310 class AlgorithmIdentifier extends DecodedDER { 311 constructor() { 312 super(); 313 this._algorithm = null; 314 this._parameters = null; 315 } 316 317 get algorithm() { 318 return this._algorithm; 319 } 320 321 get parameters() { 322 return this._parameters; 323 } 324 325 parseOverride() { 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); 333 } 334 } 335 contents.assertAtEnd(); 336 this._der.assertAtEnd(); 337 } 338 } 339 340 // Name ::= CHOICE { -- only one possibility for now -- 341 // rdnSequence RDNSequence } 342 // 343 // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName 344 class Name extends DecodedDER { 345 constructor() { 346 super(); 347 this._rdns = []; 348 } 349 350 get rdns() { 351 return this._rdns; 352 } 353 354 parseOverride() { 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); 360 } 361 contents.assertAtEnd(); 362 this._der.assertAtEnd(); 363 } 364 } 365 366 // RelativeDistinguishedName ::= 367 // SET SIZE (1..MAX) OF AttributeTypeAndValue 368 class RelativeDistinguishedName extends DecodedDER { 369 constructor() { 370 super(); 371 this._avas = []; 372 } 373 374 get avas() { 375 return this._avas; 376 } 377 378 parseOverride() { 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); 385 } 386 contents.assertAtEnd(); 387 this._der.assertAtEnd(); 388 } 389 } 390 391 // AttributeTypeAndValue ::= SEQUENCE { 392 // type AttributeType, 393 // value AttributeValue } 394 // 395 // AttributeType ::= OBJECT IDENTIFIER 396 // 397 // AttributeValue ::= ANY -- DEFINED BY AttributeType 398 class AttributeTypeAndValue extends DecodedDER { 399 constructor() { 400 super(); 401 this._type = null; 402 this._value = new DirectoryString(); 403 } 404 405 get type() { 406 return this._type; 407 } 408 409 get value() { 410 return this._value; 411 } 412 413 parseOverride() { 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`. 419 this._value.parse( 420 contents.readTLVChoice([ 421 DER.UTF8String, 422 DER.PrintableString, 423 DER.TeletexString, 424 DER.IA5String, 425 ]) 426 ); 427 contents.assertAtEnd(); 428 this._der.assertAtEnd(); 429 } 430 } 431 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 { 439 constructor() { 440 super(); 441 this._type = null; 442 this._value = null; 443 } 444 445 get type() { 446 return this._type; 447 } 448 449 get value() { 450 return this._value; 451 } 452 453 parseOverride() { 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; 462 } 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(); 466 } 467 } 468 469 // Time ::= CHOICE { 470 // utcTime UTCTime, 471 // generalTime GeneralizedTime } 472 class Time extends DecodedDER { 473 constructor() { 474 super(); 475 this._type = null; 476 this._time = null; 477 } 478 479 get time() { 480 return this._time; 481 } 482 483 parseOverride() { 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; 488 } 489 let contents = readTagAndMakeDER(this._der, this._type); 490 let year; 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; 500 if (yy >= 50) { 501 year = 1900 + yy; 502 } else { 503 year = 2000 + yy; 504 } 505 } else { 506 // GeneralizedTime is YYYYMMDDHHMMSSZ in RFC 5280. 507 year = 0; 508 for (let i = 0; i < 4; i++) { 509 let y = this._validateDigit(contents.readByte()); 510 year = year * 10 + y; 511 } 512 } 513 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); 519 } 520 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); 526 } 527 528 let h1 = this._validateDigit(contents.readByte()); 529 let h2 = this._validateDigit(contents.readByte()); 530 let hour = h1 * 10 + h2; 531 if (hour > 23) { 532 throw new Error(ERROR_TIME_NOT_VALID); 533 } 534 535 let min1 = this._validateDigit(contents.readByte()); 536 let min2 = this._validateDigit(contents.readByte()); 537 let minute = min1 * 10 + min2; 538 if (minute > 59) { 539 throw new Error(ERROR_TIME_NOT_VALID); 540 } 541 542 let s1 = this._validateDigit(contents.readByte()); 543 let s2 = this._validateDigit(contents.readByte()); 544 let second = s1 * 10 + s2; 545 if (second > 60) { 546 // leap-seconds mean this can be as much as 60 547 throw new Error(ERROR_TIME_NOT_VALID); 548 } 549 550 let z = contents.readByte(); 551 if (z != "Z".charCodeAt(0)) { 552 throw new Error(ERROR_TIME_NOT_VALID); 553 } 554 // Lint TODO: verify that the Time doesn't specify a nonsensical 555 // month/day/etc. 556 // months are zero-indexed in JS 557 this._time = new Date(Date.UTC(year, month - 1, day, hour, minute, second)); 558 559 contents.assertAtEnd(); 560 this._der.assertAtEnd(); 561 } 562 563 /** 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. 566 * 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]) 569 */ 570 _validateDigit(d) { 571 if (d < "0".charCodeAt(0) || d > "9".charCodeAt(0)) { 572 throw new Error(ERROR_TIME_NOT_VALID); 573 } 574 return d - "0".charCodeAt(0); 575 } 576 } 577 578 // Validity ::= SEQUENCE { 579 // notBefore Time, 580 // notAfter Time } 581 class Validity extends DecodedDER { 582 constructor() { 583 super(); 584 this._notBefore = new Time(); 585 this._notAfter = new Time(); 586 } 587 588 get notBefore() { 589 return this._notBefore; 590 } 591 592 get notAfter() { 593 return this._notAfter; 594 } 595 596 parseOverride() { 597 let contents = readSEQUENCEAndMakeDER(this._der); 598 this._notBefore.parse( 599 contents.readTLVChoice([DER.UTCTime, DER.GeneralizedTime]) 600 ); 601 this._notAfter.parse( 602 contents.readTLVChoice([DER.UTCTime, DER.GeneralizedTime]) 603 ); 604 contents.assertAtEnd(); 605 this._der.assertAtEnd(); 606 } 607 } 608 609 // SubjectPublicKeyInfo ::= SEQUENCE { 610 // algorithm AlgorithmIdentifier, 611 // subjectPublicKey BIT STRING } 612 class SubjectPublicKeyInfo extends DecodedDER { 613 constructor() { 614 super(); 615 this._algorithm = new AlgorithmIdentifier(); 616 this._subjectPublicKey = null; 617 } 618 619 get algorithm() { 620 return this._algorithm; 621 } 622 623 get subjectPublicKey() { 624 return this._subjectPublicKey; 625 } 626 627 parseOverride() { 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); 633 } 634 this._subjectPublicKey = subjectPublicKeyBitString.contents; 635 636 contents.assertAtEnd(); 637 this._der.assertAtEnd(); 638 } 639 } 640 641 export var X509 = { Certificate };