зеркало из https://github.com/mozilla/gecko-dev.git
bug 1304188 - introduce X509.jsm r=Cykesiopka,jcj
This is mostly a preliminary review request, although I think everything that should be done in this bug is present. This intentionally does not include support for decoding extensions or subject public keys. MozReview-Commit-ID: 4ewu66Xx411 --HG-- extra : rebase_source : 6105cf16e46d5d2cc9355cf38f8d0098a8a40462
This commit is contained in:
Родитель
e2dea2817b
Коммит
ee56723139
|
@ -0,0 +1,632 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
var { DER } = Cu.import("resource://gre/modules/psm/DER.jsm", {});
|
||||
|
||||
const ERROR_UNSUPPORTED_ASN1 = "unsupported asn.1";
|
||||
const ERROR_TIME_NOT_VALID = "Time not valid";
|
||||
const ERROR_LIBRARY_FAILURE = "library failure";
|
||||
|
||||
const X509v3 = 2;
|
||||
|
||||
/**
|
||||
* Helper function to read a NULL tag from the given DER.
|
||||
* @param {DER} der a DER object to read a NULL from
|
||||
* @return {NULL} an object representing an ASN.1 NULL
|
||||
*/
|
||||
function readNULL(der) {
|
||||
return new NULL(der.readTagAndGetContents(DER.NULL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing an ASN.1 NULL. When encoded as DER, the only valid value
|
||||
* is 05 00, and thus the contents should always be an empty array.
|
||||
*/
|
||||
class NULL {
|
||||
/**
|
||||
* @param {Number[]} bytes the contents of the NULL tag (should be empty)
|
||||
*/
|
||||
constructor(bytes) {
|
||||
// Lint TODO: bytes should be an empty array
|
||||
this._contents = bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to read an OBJECT IDENTIFIER from the given DER.
|
||||
* @param {DER} der the DER to read an OBJECT IDENTIFIER from
|
||||
* @return {OID} the value of the OBJECT IDENTIFIER
|
||||
*/
|
||||
function readOID(der) {
|
||||
return new OID(der.readTagAndGetContents(DER.OBJECT_IDENTIFIER));
|
||||
}
|
||||
|
||||
/** Class representing an ASN.1 OBJECT IDENTIFIER */
|
||||
class OID {
|
||||
/**
|
||||
* @param {Number[]} bytes the encoded contents of the OBJECT IDENTIFIER
|
||||
* (not including the ASN.1 tag or length bytes)
|
||||
*/
|
||||
constructor(bytes) {
|
||||
this._values = [];
|
||||
// First octet has value 40 * value1 + value2
|
||||
// Lint TODO: validate that value1 is one of {0, 1, 2}
|
||||
// Lint TODO: validate that value2 is in [0, 39] if value1 is 0 or 1
|
||||
let value1 = Math.floor(bytes[0] / 40);
|
||||
let value2 = bytes[0] - 40 * value1;
|
||||
this._values.push(value1);
|
||||
this._values.push(value2);
|
||||
bytes.shift();
|
||||
let accumulator = 0;
|
||||
// Lint TODO: prevent overflow here
|
||||
while (bytes.length > 0) {
|
||||
let value = bytes.shift();
|
||||
accumulator *= 128;
|
||||
if (value > 128) {
|
||||
accumulator += (value - 128);
|
||||
} else {
|
||||
accumulator += value;
|
||||
this._values.push(accumulator);
|
||||
accumulator = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that serves as an abstract base class for more specific classes that
|
||||
* represent datatypes from RFC 5280 and others. Given an array of bytes
|
||||
* representing the DER encoding of such types, this framework simplifies the
|
||||
* process of making a new DER object, attempting to parse the given bytes, and
|
||||
* catching and stashing thrown exceptions. Subclasses are to implement
|
||||
* parseOverride, which should read from this._der to fill out the structure's
|
||||
* values.
|
||||
*/
|
||||
class DecodedDER {
|
||||
constructor() {
|
||||
this._der = null;
|
||||
this._error = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first exception encountered when decoding or null if none has
|
||||
* been encountered.
|
||||
* @return {Error} the first exception encountered when decoding or null
|
||||
*/
|
||||
get error() {
|
||||
return this._error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual work of parsing the data. To be overridden by subclasses.
|
||||
* If an implementation of parseOverride throws an exception, parse will catch
|
||||
* that exception and stash it in the error property. This enables parent
|
||||
* levels in a nested decoding hierarchy to continue to decode as much as
|
||||
* possible.
|
||||
*/
|
||||
parseOverride() {
|
||||
throw new Error(ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public interface to be called to parse all data. Calls parseOverride inside
|
||||
* a try/catch block. If an exception is thrown, stashes the error, which can
|
||||
* be obtained via the error getter (above).
|
||||
* @param {Number[]} bytes encoded DER to be decoded
|
||||
*/
|
||||
parse(bytes) {
|
||||
this._der = new DER.DER(bytes);
|
||||
try {
|
||||
this.parseOverride();
|
||||
} catch (e) {
|
||||
this._error = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for reading the next SEQUENCE out of a DER and creating a new
|
||||
* DER out of the resulting bytes.
|
||||
* @param {DER} der the underlying DER object
|
||||
* @return {DER} the contents of the SEQUENCE
|
||||
*/
|
||||
function readSEQUENCEAndMakeDER(der) {
|
||||
return new DER.DER(der.readTagAndGetContents(DER.SEQUENCE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for reading the next item identified by tag out of a DER and
|
||||
* creating a new DER out of the resulting bytes.
|
||||
* @param {DER} der the underlying DER object
|
||||
* @param {Number} tag the expected next tag in the DER
|
||||
* @return {DER} the contents of the tag
|
||||
*/
|
||||
function readTagAndMakeDER(der, tag) {
|
||||
return new DER.DER(der.readTagAndGetContents(tag));
|
||||
}
|
||||
|
||||
// Certificate ::= SEQUENCE {
|
||||
// tbsCertificate TBSCertificate,
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
// signatureValue BIT STRING }
|
||||
class Certificate extends DecodedDER {
|
||||
constructor() {
|
||||
super();
|
||||
this._tbsCertificate = new TBSCertificate();
|
||||
this._signatureAlgorithm = new AlgorithmIdentifier();
|
||||
this._signatureValue = [];
|
||||
}
|
||||
|
||||
get tbsCertificate() {
|
||||
return this._tbsCertificate;
|
||||
}
|
||||
|
||||
get signatureAlgorithm() {
|
||||
return this._signatureAlgorithm;
|
||||
}
|
||||
|
||||
get signatureValue() {
|
||||
return this._signatureValue;
|
||||
}
|
||||
|
||||
parseOverride() {
|
||||
let contents = readSEQUENCEAndMakeDER(this._der);
|
||||
this._tbsCertificate.parse(contents.readTLV());
|
||||
this._signatureAlgorithm.parse(contents.readTLV());
|
||||
|
||||
let signatureValue = contents.readBIT_STRING();
|
||||
if (signatureValue.unusedBits != 0) {
|
||||
throw new Error(ERROR_UNSUPPORTED_ASN1);
|
||||
}
|
||||
this._signatureValue = signatureValue.contents;
|
||||
contents.assertAtEnd();
|
||||
this._der.assertAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// TBSCertificate ::= SEQUENCE {
|
||||
// version [0] EXPLICIT Version DEFAULT v1,
|
||||
// serialNumber CertificateSerialNumber,
|
||||
// signature AlgorithmIdentifier,
|
||||
// issuer Name,
|
||||
// validity Validity,
|
||||
// subject Name,
|
||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
// extensions [3] EXPLICIT Extensions OPTIONAL
|
||||
// -- If present, version MUST be v3
|
||||
// }
|
||||
class TBSCertificate extends DecodedDER {
|
||||
constructor() {
|
||||
super();
|
||||
this._version = null;
|
||||
this._serialNumber = [];
|
||||
this._signature = new AlgorithmIdentifier();
|
||||
this._issuer = new Name();
|
||||
this._validity = new Validity();
|
||||
this._subject = new Name();
|
||||
this._subjectPublicKeyInfo = new SubjectPublicKeyInfo();
|
||||
this._extensions = [];
|
||||
}
|
||||
|
||||
get version() {
|
||||
return this._version;
|
||||
}
|
||||
|
||||
get serialNumber() {
|
||||
return this._serialNumber;
|
||||
}
|
||||
|
||||
get signature() {
|
||||
return this._signature;
|
||||
}
|
||||
|
||||
get issuer() {
|
||||
return this._issuer;
|
||||
}
|
||||
|
||||
get validity() {
|
||||
return this._validity;
|
||||
}
|
||||
|
||||
get subject() {
|
||||
return this._subject;
|
||||
}
|
||||
|
||||
get subjectPublicKeyInfo() {
|
||||
return this._subjectPublicKeyInfo;
|
||||
}
|
||||
|
||||
get extensions() {
|
||||
return this._extensions;
|
||||
}
|
||||
|
||||
parseOverride() {
|
||||
let contents = readSEQUENCEAndMakeDER(this._der);
|
||||
|
||||
let versionTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 0;
|
||||
if (!contents.peekTag(versionTag)) {
|
||||
this._version = 1;
|
||||
} else {
|
||||
let versionContents = readTagAndMakeDER(contents, versionTag);
|
||||
let versionBytes = versionContents.readTagAndGetContents(DER.INTEGER);
|
||||
if (versionBytes.length == 1 && versionBytes[0] == X509v3) {
|
||||
this._version = 3;
|
||||
} else {
|
||||
// Lint TODO: warn about non-v3 certificates (this INTEGER could take up
|
||||
// multiple bytes, be negative, and so on).
|
||||
this._version = versionBytes;
|
||||
}
|
||||
versionContents.assertAtEnd();
|
||||
}
|
||||
|
||||
let serialNumberBytes = contents.readTagAndGetContents(DER.INTEGER);
|
||||
this._serialNumber = serialNumberBytes;
|
||||
this._signature.parse(contents.readTLV());
|
||||
this._issuer.parse(contents.readTLV());
|
||||
this._validity.parse(contents.readTLV());
|
||||
this._subject.parse(contents.readTLV());
|
||||
this._subjectPublicKeyInfo.parse(contents.readTLV());
|
||||
|
||||
// Lint TODO: warn about unsupported features
|
||||
let issuerUniqueIDTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 1;
|
||||
if (contents.peekTag(issuerUniqueIDTag)) {
|
||||
contents.readTagAndGetContents(issuerUniqueIDTag);
|
||||
}
|
||||
let subjectUniqueIDTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 2;
|
||||
if (contents.peekTag(subjectUniqueIDTag)) {
|
||||
contents.readTagAndGetContents(subjectUniqueIDTag);
|
||||
}
|
||||
|
||||
let extensionsTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 3;
|
||||
if (contents.peekTag(extensionsTag)) {
|
||||
let extensionsSequence = readTagAndMakeDER(contents, extensionsTag);
|
||||
let extensionsContents = readSEQUENCEAndMakeDER(extensionsSequence);
|
||||
while (!extensionsContents.atEnd()) {
|
||||
// TODO: parse extensions
|
||||
this._extensions.push(extensionsContents.readTLV());
|
||||
}
|
||||
extensionsContents.assertAtEnd();
|
||||
extensionsSequence.assertAtEnd();
|
||||
}
|
||||
contents.assertAtEnd();
|
||||
this._der.assertAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// AlgorithmIdentifier ::= SEQUENCE {
|
||||
// algorithm OBJECT IDENTIFIER,
|
||||
// parameters ANY DEFINED BY algorithm OPTIONAL }
|
||||
class AlgorithmIdentifier extends DecodedDER {
|
||||
constructor() {
|
||||
super();
|
||||
this._algorithm = null;
|
||||
this._parameters = null;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return this._algorithm;
|
||||
}
|
||||
|
||||
get parameters() {
|
||||
return this._parameters;
|
||||
}
|
||||
|
||||
parseOverride() {
|
||||
let contents = readSEQUENCEAndMakeDER(this._der);
|
||||
this._algorithm = readOID(contents);
|
||||
if (!contents.atEnd()) {
|
||||
if (contents.peekTag(DER.NULL)) {
|
||||
this._parameters = readNULL(contents);
|
||||
} else if (contents.peekTag(DER.OBJECT_IDENTIFIER)) {
|
||||
this._parameters = readOID(contents);
|
||||
}
|
||||
}
|
||||
contents.assertAtEnd();
|
||||
this._der.assertAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// Name ::= CHOICE { -- only one possibility for now --
|
||||
// rdnSequence RDNSequence }
|
||||
//
|
||||
// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
|
||||
class Name extends DecodedDER {
|
||||
constructor() {
|
||||
super();
|
||||
this._rdns = [];
|
||||
}
|
||||
|
||||
get rdns() {
|
||||
return this._rdns;
|
||||
}
|
||||
|
||||
parseOverride() {
|
||||
let contents = readSEQUENCEAndMakeDER(this._der);
|
||||
while (!contents.atEnd()) {
|
||||
let rdn = new RelativeDistinguishedName();
|
||||
rdn.parse(contents.readTLV());
|
||||
this._rdns.push(rdn);
|
||||
}
|
||||
contents.assertAtEnd();
|
||||
this._der.assertAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// RelativeDistinguishedName ::=
|
||||
// SET SIZE (1..MAX) OF AttributeTypeAndValue
|
||||
class RelativeDistinguishedName extends DecodedDER {
|
||||
constructor() {
|
||||
super();
|
||||
this._avas = [];
|
||||
}
|
||||
|
||||
get avas() {
|
||||
return this._avas;
|
||||
}
|
||||
|
||||
parseOverride() {
|
||||
let contents = readTagAndMakeDER(this._der, DER.SET);
|
||||
// Lint TODO: enforce SET SIZE restrictions
|
||||
while (!contents.atEnd()) {
|
||||
let ava = new AttributeTypeAndValue();
|
||||
ava.parse(contents.readTLV());
|
||||
this._avas.push(ava);
|
||||
}
|
||||
contents.assertAtEnd();
|
||||
this._der.assertAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// AttributeTypeAndValue ::= SEQUENCE {
|
||||
// type AttributeType,
|
||||
// value AttributeValue }
|
||||
//
|
||||
// AttributeType ::= OBJECT IDENTIFIER
|
||||
//
|
||||
// AttributeValue ::= ANY -- DEFINED BY AttributeType
|
||||
class AttributeTypeAndValue extends DecodedDER {
|
||||
constructor() {
|
||||
super();
|
||||
this._type = null;
|
||||
this._value = new DirectoryString();
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
parseOverride() {
|
||||
let contents = readSEQUENCEAndMakeDER(this._der);
|
||||
this._type = readOID(contents);
|
||||
// We don't support universalString or bmpString.
|
||||
// IA5String is supported because it is valid if `type == id-emailaddress`.
|
||||
// Lint TODO: validate that the type of string is valid given `type`.
|
||||
this._value.parse(contents.readTLVChoice([ DER.UTF8String,
|
||||
DER.PrintableString,
|
||||
DER.TeletexString,
|
||||
DER.IA5String ]));
|
||||
contents.assertAtEnd();
|
||||
this._der.assertAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// DirectoryString ::= CHOICE {
|
||||
// teletexString TeletexString (SIZE (1..MAX)),
|
||||
// printableString PrintableString (SIZE (1..MAX)),
|
||||
// universalString UniversalString (SIZE (1..MAX)),
|
||||
// utf8String UTF8String (SIZE (1..MAX)),
|
||||
// bmpString BMPString (SIZE (1..MAX)) }
|
||||
class DirectoryString extends DecodedDER {
|
||||
constructor() {
|
||||
super();
|
||||
this._type = null;
|
||||
this._value = null;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
parseOverride() {
|
||||
if (this._der.peekTag(DER.UTF8String)) {
|
||||
this._type = DER.UTF8String;
|
||||
} else if (this._der.peekTag(DER.PrintableString)) {
|
||||
this._type = DER.PrintableString;
|
||||
} else if (this._der.peekTag(DER.TeletexString)) {
|
||||
this._type = DER.TeletexString;
|
||||
} else if (this._der.peekTag(DER.IA5String)) {
|
||||
this._type = DER.IA5String;
|
||||
}
|
||||
// Lint TODO: validate that the contents are actually valid for the type
|
||||
this._value = this._der.readTagAndGetContents(this._type);
|
||||
this._der.assertAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// Time ::= CHOICE {
|
||||
// utcTime UTCTime,
|
||||
// generalTime GeneralizedTime }
|
||||
class Time extends DecodedDER {
|
||||
constructor() {
|
||||
super();
|
||||
this._type = null;
|
||||
this._time = null;
|
||||
}
|
||||
|
||||
get time() {
|
||||
return this._time;
|
||||
}
|
||||
|
||||
parseOverride() {
|
||||
if (this._der.peekTag(DER.UTCTime)) {
|
||||
this._type = DER.UTCTime;
|
||||
} else if (this._der.peekTag(DER.GeneralizedTime)) {
|
||||
this._type = DER.GeneralizedTime;
|
||||
}
|
||||
let contents = readTagAndMakeDER(this._der, this._type);
|
||||
let year;
|
||||
// Lint TODO: validate that the appropriate one of {UTCTime,GeneralizedTime}
|
||||
// is used according to RFC 5280 and what the value of the date is.
|
||||
// TODO TODO: explain this better (just quote the rfc).
|
||||
if (this._type == DER.UTCTime) {
|
||||
// UTCTime is YYMMDDHHMMSSZ in RFC 5280. If YY is greater than or equal
|
||||
// to 50, the year is 19YY. Otherwise, it is 20YY.
|
||||
let y1 = this._validateDigit(contents.readByte());
|
||||
let y2 = this._validateDigit(contents.readByte());
|
||||
let yy = y1 * 10 + y2;
|
||||
if (yy >= 50) {
|
||||
year = 1900 + yy;
|
||||
} else {
|
||||
year = 2000 + yy;
|
||||
}
|
||||
} else {
|
||||
// GeneralizedTime is YYYYMMDDHHMMSSZ in RFC 5280.
|
||||
year = 0;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
let y = this._validateDigit(contents.readByte());
|
||||
year = year * 10 + y;
|
||||
}
|
||||
}
|
||||
|
||||
let m1 = this._validateDigit(contents.readByte());
|
||||
let m2 = this._validateDigit(contents.readByte());
|
||||
let month = m1 * 10 + m2;
|
||||
if (month == 0 || month > 12) {
|
||||
throw new Error(ERROR_TIME_NOT_VALID);
|
||||
}
|
||||
|
||||
let d1 = this._validateDigit(contents.readByte());
|
||||
let d2 = this._validateDigit(contents.readByte());
|
||||
let day = d1 * 10 + d2;
|
||||
if (day == 0 || day > 31) {
|
||||
throw new Error(ERROR_TIME_NOT_VALID);
|
||||
}
|
||||
|
||||
let h1 = this._validateDigit(contents.readByte());
|
||||
let h2 = this._validateDigit(contents.readByte());
|
||||
let hour = h1 * 10 + h2;
|
||||
if (hour > 23) {
|
||||
throw new Error(ERROR_TIME_NOT_VALID);
|
||||
}
|
||||
|
||||
let min1 = this._validateDigit(contents.readByte());
|
||||
let min2 = this._validateDigit(contents.readByte());
|
||||
let minute = min1 * 10 + min2;
|
||||
if (minute > 59) {
|
||||
throw new Error(ERROR_TIME_NOT_VALID);
|
||||
}
|
||||
|
||||
let s1 = this._validateDigit(contents.readByte());
|
||||
let s2 = this._validateDigit(contents.readByte());
|
||||
let second = s1 * 10 + s2;
|
||||
if (second > 60) { // leap-seconds mean this can be as much as 60
|
||||
throw new Error(ERROR_TIME_NOT_VALID);
|
||||
}
|
||||
|
||||
let z = contents.readByte();
|
||||
if (z != "Z".charCodeAt(0)) {
|
||||
throw new Error(ERROR_TIME_NOT_VALID);
|
||||
}
|
||||
// Lint TODO: verify that the Time doesn't specify a nonsensical
|
||||
// month/day/etc.
|
||||
// months are zero-indexed in JS
|
||||
this._time = new Date(Date.UTC(year, month - 1, day, hour, minute,
|
||||
second));
|
||||
|
||||
contents.assertAtEnd();
|
||||
this._der.assertAtEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a byte that is supposed to be in the ASCII range for "0" to "9".
|
||||
* Validates the range and then converts it to the range 0 to 9.
|
||||
* @param {Number} the digit in question (as ASCII in the range ["0", "9"])
|
||||
* @return {Number} the numerical value of the digit (in the range [0, 9])
|
||||
*/
|
||||
_validateDigit(d) {
|
||||
if (d < "0".charCodeAt(0) || d > "9".charCodeAt(0)) {
|
||||
throw new Error(ERROR_TIME_NOT_VALID);
|
||||
}
|
||||
return d - "0".charCodeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Validity ::= SEQUENCE {
|
||||
// notBefore Time,
|
||||
// notAfter Time }
|
||||
class Validity extends DecodedDER {
|
||||
constructor() {
|
||||
super();
|
||||
this._notBefore = new Time();
|
||||
this._notAfter = new Time();
|
||||
}
|
||||
|
||||
get notBefore() {
|
||||
return this._notBefore;
|
||||
}
|
||||
|
||||
get notAfter() {
|
||||
return this._notAfter;
|
||||
}
|
||||
|
||||
parseOverride() {
|
||||
let contents = readSEQUENCEAndMakeDER(this._der);
|
||||
this._notBefore.parse(contents.readTLVChoice(
|
||||
[DER.UTCTime, DER.GeneralizedTime]));
|
||||
this._notAfter.parse(contents.readTLVChoice(
|
||||
[DER.UTCTime, DER.GeneralizedTime]));
|
||||
contents.assertAtEnd();
|
||||
this._der.assertAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// SubjectPublicKeyInfo ::= SEQUENCE {
|
||||
// algorithm AlgorithmIdentifier,
|
||||
// subjectPublicKey BIT STRING }
|
||||
class SubjectPublicKeyInfo extends DecodedDER {
|
||||
constructor() {
|
||||
super();
|
||||
this._algorithm = new AlgorithmIdentifier();
|
||||
this._subjectPublicKey = null;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return this._algorithm;
|
||||
}
|
||||
|
||||
get subjectPublicKey() {
|
||||
return this._subjectPublicKey;
|
||||
}
|
||||
|
||||
parseOverride() {
|
||||
let contents = readSEQUENCEAndMakeDER(this._der);
|
||||
this._algorithm.parse(contents.readTLV());
|
||||
let subjectPublicKeyBitString = contents.readBIT_STRING();
|
||||
if (subjectPublicKeyBitString.unusedBits != 0) {
|
||||
throw new Error(ERROR_UNSUPPORTED_ASN1);
|
||||
}
|
||||
this._subjectPublicKey = subjectPublicKeyBitString.contents;
|
||||
|
||||
contents.assertAtEnd();
|
||||
this._der.assertAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
this.X509 = { Certificate };
|
||||
this.EXPORTED_SYMBOLS = ["X509"];
|
|
@ -55,6 +55,7 @@ XPIDL_MODULE = 'pipnss'
|
|||
|
||||
EXTRA_JS_MODULES.psm += [
|
||||
'DER.jsm',
|
||||
'X509.jsm',
|
||||
]
|
||||
|
||||
EXPORTS += [
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests X509.jsm functionality.
|
||||
|
||||
var { X509 } = Cu.import("resource://gre/modules/psm/X509.jsm", {});
|
||||
|
||||
function stringToBytes(s) {
|
||||
let b = [];
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
b.push(s.charCodeAt(i));
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
function readPEMToBytes(filename) {
|
||||
return stringToBytes(atob(pemToBase64(readFile(do_get_file(filename)))));
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let certificate = new X509.Certificate();
|
||||
certificate.parse(readPEMToBytes("bad_certs/default-ee.pem"));
|
||||
|
||||
equal(certificate.tbsCertificate.version, 3,
|
||||
"default-ee.pem should be x509v3");
|
||||
|
||||
// serialNumber
|
||||
deepEqual(certificate.tbsCertificate.serialNumber,
|
||||
[ 0x1b, 0x27, 0x62, 0x4d, 0xc3, 0x70, 0xbf, 0x3d, 0xb6, 0x66,
|
||||
0x98, 0x33, 0xd8, 0x3c, 0x74, 0xd9, 0xee, 0x2c, 0x56, 0xc1 ],
|
||||
"default-ee.pem should have expected serialNumber");
|
||||
|
||||
deepEqual(certificate.tbsCertificate.signature.algorithm._values,
|
||||
[ 1, 2, 840, 113549, 1, 1, 11 ], // sha256WithRSAEncryption
|
||||
"default-ee.pem should have sha256WithRSAEncryption signature");
|
||||
// TODO: there should actually be an explicit encoded NULL here, but it looks
|
||||
// like pycert doesn't include it.
|
||||
deepEqual(certificate.tbsCertificate.signature.parameters, null,
|
||||
"default-ee.pem should have NULL parameters for signature");
|
||||
|
||||
equal(certificate.tbsCertificate.issuer.rdns.length, 1,
|
||||
"default-ee.pem should have one RDN in issuer");
|
||||
equal(certificate.tbsCertificate.issuer.rdns[0].avas.length, 1,
|
||||
"default-ee.pem should have one AVA in RDN in issuer");
|
||||
deepEqual(certificate.tbsCertificate.issuer.rdns[0].avas[0].value.value,
|
||||
stringToBytes("Test CA"),
|
||||
"default-ee.pem should have issuer 'Test CA'");
|
||||
|
||||
equal(certificate.tbsCertificate.validity.notBefore.time.getTime(),
|
||||
Date.parse("2014-11-27T00:00:00.000Z"),
|
||||
"default-ee.pem should have the correct value for notBefore");
|
||||
equal(certificate.tbsCertificate.validity.notAfter.time.getTime(),
|
||||
Date.parse("2017-02-04T00:00:00.000Z"),
|
||||
"default-ee.pem should have the correct value for notAfter");
|
||||
|
||||
equal(certificate.tbsCertificate.subject.rdns.length, 1,
|
||||
"default-ee.pem should have one RDN in subject");
|
||||
equal(certificate.tbsCertificate.subject.rdns[0].avas.length, 1,
|
||||
"default-ee.pem should have one AVA in RDN in subject");
|
||||
deepEqual(certificate.tbsCertificate.subject.rdns[0].avas[0].value.value,
|
||||
stringToBytes("Test End-entity"),
|
||||
"default-ee.pem should have subject 'Test End-entity'");
|
||||
|
||||
deepEqual(certificate.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm._values,
|
||||
[ 1, 2, 840, 113549, 1, 1, 1 ], // rsaEncryption
|
||||
"default-ee.pem should have a spki algorithm of rsaEncryption");
|
||||
|
||||
equal(certificate.tbsCertificate.extensions.length, 2,
|
||||
"default-ee.pem should have two extensions");
|
||||
|
||||
deepEqual(certificate.signatureAlgorithm.algorithm._values,
|
||||
[ 1, 2, 840, 113549, 1, 1, 11 ], // sha256WithRSAEncryption
|
||||
"default-ee.pem should have sha256WithRSAEncryption signatureAlgorithm");
|
||||
// TODO: there should actually be an explicit encoded NULL here, but it looks
|
||||
// like pycert doesn't include it.
|
||||
deepEqual(certificate.signatureAlgorithm.parameters, null,
|
||||
"default-ee.pem should have NULL parameters for signatureAlgorithm");
|
||||
|
||||
equal(certificate.signatureValue.length, 2048 / 8,
|
||||
"length of signature on default-ee.pem should be 2048 bits");
|
||||
}
|
|
@ -151,6 +151,7 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
|
|||
[test_sts_preloadlist_selfdestruct.js]
|
||||
[test_validity.js]
|
||||
run-sequentially = hardcoded ports
|
||||
[test_x509.js]
|
||||
|
||||
# The TLS error reporting functionality lives in /toolkit but needs tlsserver
|
||||
[test_toolkit_securityreporter.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче