blob: b946b3b20ffd61de1ba3279a1175651c6367b73c [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Provides basic ability to read ASN.1 format into a
* JSON object.
*/
const ASN1_SEQUENCE = 0x30;
const ASN1_SET = 0x31;
const ASN1_OBJECT_ID = 0x6;
const ASN1_PRINTABLE = 0x13;
/**
* Namespace for ASN.1 functions.
*/
var asn1 = {};
/**
* Parse object id out of byte ASN.1 byte stream.
*
* @param {String} bytes Byte stream of ASN.1
* @param {Number} offset Offset within bytes where object ID begins.
* @param {Number} len Length of object ID.
* @return {String} Human readable object ID with dots.
*/
asn1.parseObjectId = function(bytes, offset, len) {
var oid = '';
var accumulated = 0;
for (var i = 0; i < len; ++i) {
var byte = bytes[offset + i];
accumulated <<= 7;
accumulated |= (byte & ~0x80);
if (!(byte & 0x80)) {
if (oid == '') {
oid = Math.floor(accumulated / 40) + '.' + (accumulated % 40);
} else {
oid = oid + '.' + accumulated;
}
accumulated = 0;
}
}
return oid;
};
/**
* Parse entire ASN.1 byte stream.
*
* @param {String} bytes Byte stream of ASN.1
* @return {Object} Parsed JSON representation of ASN.1
*/
asn1.parseAsn1 = function(bytes) {
if (typeof(bytes) == 'string') {
bytes = bytes.split('').map(function(c) { return c.charCodeAt(0) });
}
var offset = 0;
var result = [];
while (offset < bytes.length) {
var kind = bytes[offset];
var len = bytes[offset+1];
var prefix_length = 2;
if (len & 0x80) {
len &= ~0x80;
prefix_length += len;
if (len == 1)
len = bytes[offset+2];
else if (len == 2)
len = (bytes[offset+2] << 8) | bytes[offset+3];
else
return false;
}
var inner_result;
switch(kind) {
case ASN1_SEQUENCE:
case ASN1_SET:
inner_result = asn1.parseAsn1(bytes.slice(offset + prefix_length,
offset + prefix_length + len))
if (typeof(inner_result) == 'boolean' && inner_result == false)
return false;
result.push(inner_result);
break;
case ASN1_OBJECT_ID:
inner_result = asn1.parseObjectId(bytes, offset + prefix_length, len);
if (typeof(inner_result) == 'boolean' && inner_result == false)
return false;
result.push({'object_id': inner_result});
break;
case ASN1_PRINTABLE:
var string = '';
for (var i = 0; i < len; ++i) {
string += String.fromCharCode(bytes[offset + prefix_length + i]);
}
result.push(string);
break;
}
offset += prefix_length + len;
}
return result;
};
/**
* Convert OID to a mnemonic identifier if possible.
*
* @param {String} oid Human readable form of object id.
* @returns {String} Mnemonic identifier or original dot form if not known.
*/
asn1.convertOID = function(oid) {
if (!oid.object_id) return false;
switch(oid.object_id) {
case '2.5.4.3': return 'commonName';
case '2.5.4.5': return 'serialNumber';
case '2.5.4.6': return 'countryName';
case '2.5.4.7': return 'localityName';
case '2.5.4.8': return 'stateOrProvidenceName';
case '2.5.4.10': return 'organizationName';
case '2.5.4.11': return 'organizationalUnit';
default: return oid;
}
};
/**
* Interpret given ASN.1 JSON object as an X.520 name.
*
* @param {Object} name JSON ASN.1 that should be a X.520 name.
* @returns {Object} Dictionary of name attributes (like commonName) to values.
*/
asn1.interpretName = function(name) {
// See structure below.
if (!(name instanceof Array))
return false;
var result = {};
for (i = 0; i < name.length; ++i) {
if (!(name[i] instanceof Array)) {
continue;
}
if (!(name[i][0] instanceof Array)) {
continue;
}
var pair = name[i][0];
result[asn1.convertOID(pair[0])] = pair[1];
}
return result;
};
/**
* Interpret a given ASN.1 JSON object as an X.509 certificate.
*
* @param {Object} asn1 JSON ASN.1 that should be a X.509 certificate.
* @returns {Object} A dictionary including issuer and subject name
* information.
*/
asn1.interpretCert = function(asn1Data) {
// See RFC5280. Expect asn of format somewhat like:
// struct Certificate {
// struct TbsCertificate {
// int version;
// int serialNumber;
// OID algorithm;
// struct Name {
// struct RNSequence {
// struct AttributeTypeAndValue {
// OID attribute;
// string value;
// } attributesAndValues[];
// } rnSequence;
// } issuer;
// struct Validity {
// UTCTime;
// GeneralizedTime;
// };
// struct Name subject;
// ...
// } tbsCertificate;
// ...
// }
// We are interested in the issuer and subject attributes and values.
//
if (!(asn1Data instanceof Array) ||
!(asn1Data[0] instanceof Array) ||
!(asn1Data[0][0] instanceof Array)) {
return false;
}
var tbsCertificate = asn1Data[0][0];
var result;
// Find issuer - currently always at the same offset since
// we ignore integer types currently. 0 is signing algorithm,
// 1 is issuer, 2 is timestamps, 3 is subject.
if (tbsCertificate.length < 5)
return false;
return {
'issuer': asn1.interpretName(tbsCertificate[1]),
'subject': asn1.interpretName(tbsCertificate[3])
};
};