123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- // Copyright 2016 Joyent, Inc.
- module.exports = {
- read: read,
- verify: verify,
- sign: sign,
- write: write
- };
- var assert = require('assert-plus');
- var asn1 = require('asn1');
- var algs = require('../algs');
- var utils = require('../utils');
- var Key = require('../key');
- var PrivateKey = require('../private-key');
- var pem = require('./pem');
- var Identity = require('../identity');
- var Signature = require('../signature');
- var Certificate = require('../certificate');
- var pkcs8 = require('./pkcs8');
- /*
- * This file is based on RFC5280 (X.509).
- */
- /* Helper to read in a single mpint */
- function readMPInt(der, nm) {
- assert.strictEqual(der.peek(), asn1.Ber.Integer,
- nm + ' is not an Integer');
- return (utils.mpNormalize(der.readString(asn1.Ber.Integer, true)));
- }
- function verify(cert, key) {
- var sig = cert.signatures.x509;
- assert.object(sig, 'x509 signature');
- var algParts = sig.algo.split('-');
- if (algParts[0] !== key.type)
- return (false);
- var blob = sig.cache;
- if (blob === undefined) {
- var der = new asn1.BerWriter();
- writeTBSCert(cert, der);
- blob = der.buffer;
- }
- var verifier = key.createVerify(algParts[1]);
- verifier.write(blob);
- return (verifier.verify(sig.signature));
- }
- function Local(i) {
- return (asn1.Ber.Context | asn1.Ber.Constructor | i);
- }
- function Context(i) {
- return (asn1.Ber.Context | i);
- }
- var SIGN_ALGS = {
- 'rsa-md5': '1.2.840.113549.1.1.4',
- 'rsa-sha1': '1.2.840.113549.1.1.5',
- 'rsa-sha256': '1.2.840.113549.1.1.11',
- 'rsa-sha384': '1.2.840.113549.1.1.12',
- 'rsa-sha512': '1.2.840.113549.1.1.13',
- 'dsa-sha1': '1.2.840.10040.4.3',
- 'dsa-sha256': '2.16.840.1.101.3.4.3.2',
- 'ecdsa-sha1': '1.2.840.10045.4.1',
- 'ecdsa-sha256': '1.2.840.10045.4.3.2',
- 'ecdsa-sha384': '1.2.840.10045.4.3.3',
- 'ecdsa-sha512': '1.2.840.10045.4.3.4'
- };
- Object.keys(SIGN_ALGS).forEach(function (k) {
- SIGN_ALGS[SIGN_ALGS[k]] = k;
- });
- SIGN_ALGS['1.3.14.3.2.3'] = 'rsa-md5';
- SIGN_ALGS['1.3.14.3.2.29'] = 'rsa-sha1';
- var EXTS = {
- 'issuerKeyId': '2.5.29.35',
- 'altName': '2.5.29.17'
- };
- function read(buf, options) {
- if (typeof (buf) === 'string') {
- buf = new Buffer(buf, 'binary');
- }
- assert.buffer(buf, 'buf');
- var der = new asn1.BerReader(buf);
- der.readSequence();
- if (Math.abs(der.length - der.remain) > 1) {
- throw (new Error('DER sequence does not contain whole byte ' +
- 'stream'));
- }
- var tbsStart = der.offset;
- der.readSequence();
- var sigOffset = der.offset + der.length;
- var tbsEnd = sigOffset;
- if (der.peek() === Local(0)) {
- der.readSequence(Local(0));
- var version = der.readInt();
- assert.ok(version <= 3,
- 'only x.509 versions up to v3 supported');
- }
- var cert = {};
- cert.signatures = {};
- var sig = (cert.signatures.x509 = {});
- sig.extras = {};
- cert.serial = readMPInt(der, 'serial');
- der.readSequence();
- var after = der.offset + der.length;
- var certAlgOid = der.readOID();
- var certAlg = SIGN_ALGS[certAlgOid];
- if (certAlg === undefined)
- throw (new Error('unknown signature algorithm ' + certAlgOid));
- der._offset = after;
- cert.issuer = Identity.parseAsn1(der);
- der.readSequence();
- cert.validFrom = readDate(der);
- cert.validUntil = readDate(der);
- cert.subjects = [Identity.parseAsn1(der)];
- der.readSequence();
- after = der.offset + der.length;
- cert.subjectKey = pkcs8.readPkcs8(undefined, 'public', der);
- der._offset = after;
- /* issuerUniqueID */
- if (der.peek() === Local(1)) {
- der.readSequence(Local(1));
- sig.extras.issuerUniqueID =
- buf.slice(der.offset, der.offset + der.length);
- der._offset += der.length;
- }
- /* subjectUniqueID */
- if (der.peek() === Local(2)) {
- der.readSequence(Local(2));
- sig.extras.subjectUniqueID =
- buf.slice(der.offset, der.offset + der.length);
- der._offset += der.length;
- }
- /* extensions */
- if (der.peek() === Local(3)) {
- der.readSequence(Local(3));
- var extEnd = der.offset + der.length;
- der.readSequence();
- while (der.offset < extEnd)
- readExtension(cert, buf, der);
- assert.strictEqual(der.offset, extEnd);
- }
- assert.strictEqual(der.offset, sigOffset);
- der.readSequence();
- after = der.offset + der.length;
- var sigAlgOid = der.readOID();
- var sigAlg = SIGN_ALGS[sigAlgOid];
- if (sigAlg === undefined)
- throw (new Error('unknown signature algorithm ' + sigAlgOid));
- der._offset = after;
- var sigData = der.readString(asn1.Ber.BitString, true);
- if (sigData[0] === 0)
- sigData = sigData.slice(1);
- var algParts = sigAlg.split('-');
- sig.signature = Signature.parse(sigData, algParts[0], 'asn1');
- sig.signature.hashAlgorithm = algParts[1];
- sig.algo = sigAlg;
- sig.cache = buf.slice(tbsStart, tbsEnd);
- return (new Certificate(cert));
- }
- function readDate(der) {
- if (der.peek() === asn1.Ber.UTCTime) {
- return (utcTimeToDate(der.readString(asn1.Ber.UTCTime)));
- } else if (der.peek() === asn1.Ber.GeneralizedTime) {
- return (gTimeToDate(der.readString(asn1.Ber.GeneralizedTime)));
- } else {
- throw (new Error('Unsupported date format'));
- }
- }
- /* RFC5280, section 4.2.1.6 (GeneralName type) */
- var ALTNAME = {
- OtherName: Local(0),
- RFC822Name: Context(1),
- DNSName: Context(2),
- X400Address: Local(3),
- DirectoryName: Local(4),
- EDIPartyName: Local(5),
- URI: Context(6),
- IPAddress: Context(7),
- OID: Context(8)
- };
- function readExtension(cert, buf, der) {
- der.readSequence();
- var after = der.offset + der.length;
- var extId = der.readOID();
- var id;
- var sig = cert.signatures.x509;
- sig.extras.exts = [];
- var critical;
- if (der.peek() === asn1.Ber.Boolean)
- critical = der.readBoolean();
- switch (extId) {
- case (EXTS.altName):
- der.readSequence(asn1.Ber.OctetString);
- der.readSequence();
- var aeEnd = der.offset + der.length;
- while (der.offset < aeEnd) {
- switch (der.peek()) {
- case ALTNAME.OtherName:
- case ALTNAME.EDIPartyName:
- der.readSequence();
- der._offset += der.length;
- break;
- case ALTNAME.OID:
- der.readOID(ALTNAME.OID);
- break;
- case ALTNAME.RFC822Name:
- /* RFC822 specifies email addresses */
- var email = der.readString(ALTNAME.RFC822Name);
- id = Identity.forEmail(email);
- if (!cert.subjects[0].equals(id))
- cert.subjects.push(id);
- break;
- case ALTNAME.DirectoryName:
- der.readSequence(ALTNAME.DirectoryName);
- id = Identity.parseAsn1(der);
- if (!cert.subjects[0].equals(id))
- cert.subjects.push(id);
- break;
- case ALTNAME.DNSName:
- var host = der.readString(
- ALTNAME.DNSName);
- id = Identity.forHost(host);
- if (!cert.subjects[0].equals(id))
- cert.subjects.push(id);
- break;
- default:
- der.readString(der.peek());
- break;
- }
- }
- sig.extras.exts.push({ oid: extId, critical: critical });
- break;
- default:
- sig.extras.exts.push({
- oid: extId,
- critical: critical,
- data: der.readString(asn1.Ber.OctetString, true)
- });
- break;
- }
- der._offset = after;
- }
- var UTCTIME_RE =
- /^([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})?Z$/;
- function utcTimeToDate(t) {
- var m = t.match(UTCTIME_RE);
- assert.ok(m, 'timestamps must be in UTC');
- var d = new Date();
- var thisYear = d.getUTCFullYear();
- var century = Math.floor(thisYear / 100) * 100;
- var year = parseInt(m[1], 10);
- if (thisYear % 100 < 50 && year >= 60)
- year += (century - 1);
- else
- year += century;
- d.setUTCFullYear(year, parseInt(m[2], 10) - 1, parseInt(m[3], 10));
- d.setUTCHours(parseInt(m[4], 10), parseInt(m[5], 10));
- if (m[6] && m[6].length > 0)
- d.setUTCSeconds(parseInt(m[6], 10));
- return (d);
- }
- var GTIME_RE =
- /^([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})?Z$/;
- function gTimeToDate(t) {
- var m = t.match(GTIME_RE);
- assert.ok(m);
- var d = new Date();
- d.setUTCFullYear(parseInt(m[1], 10), parseInt(m[2], 10) - 1,
- parseInt(m[3], 10));
- d.setUTCHours(parseInt(m[4], 10), parseInt(m[5], 10));
- if (m[6] && m[6].length > 0)
- d.setUTCSeconds(parseInt(m[6], 10));
- return (d);
- }
- function zeroPad(n) {
- var s = '' + n;
- while (s.length < 2)
- s = '0' + s;
- return (s);
- }
- function dateToUTCTime(d) {
- var s = '';
- s += zeroPad(d.getUTCFullYear() % 100);
- s += zeroPad(d.getUTCMonth() + 1);
- s += zeroPad(d.getUTCDate());
- s += zeroPad(d.getUTCHours());
- s += zeroPad(d.getUTCMinutes());
- s += zeroPad(d.getUTCSeconds());
- s += 'Z';
- return (s);
- }
- function sign(cert, key) {
- if (cert.signatures.x509 === undefined)
- cert.signatures.x509 = {};
- var sig = cert.signatures.x509;
- sig.algo = key.type + '-' + key.defaultHashAlgorithm();
- if (SIGN_ALGS[sig.algo] === undefined)
- return (false);
- var der = new asn1.BerWriter();
- writeTBSCert(cert, der);
- var blob = der.buffer;
- sig.cache = blob;
- var signer = key.createSign();
- signer.write(blob);
- cert.signatures.x509.signature = signer.sign();
- return (true);
- }
- function write(cert, options) {
- var sig = cert.signatures.x509;
- assert.object(sig, 'x509 signature');
- var der = new asn1.BerWriter();
- der.startSequence();
- if (sig.cache) {
- der._ensure(sig.cache.length);
- sig.cache.copy(der._buf, der._offset);
- der._offset += sig.cache.length;
- } else {
- writeTBSCert(cert, der);
- }
- der.startSequence();
- der.writeOID(SIGN_ALGS[sig.algo]);
- if (sig.algo.match(/^rsa-/))
- der.writeNull();
- der.endSequence();
- var sigData = sig.signature.toBuffer('asn1');
- var data = new Buffer(sigData.length + 1);
- data[0] = 0;
- sigData.copy(data, 1);
- der.writeBuffer(data, asn1.Ber.BitString);
- der.endSequence();
- return (der.buffer);
- }
- function writeTBSCert(cert, der) {
- var sig = cert.signatures.x509;
- assert.object(sig, 'x509 signature');
- der.startSequence();
- der.startSequence(Local(0));
- der.writeInt(2);
- der.endSequence();
- der.writeBuffer(utils.mpNormalize(cert.serial), asn1.Ber.Integer);
- der.startSequence();
- der.writeOID(SIGN_ALGS[sig.algo]);
- der.endSequence();
- cert.issuer.toAsn1(der);
- der.startSequence();
- der.writeString(dateToUTCTime(cert.validFrom), asn1.Ber.UTCTime);
- der.writeString(dateToUTCTime(cert.validUntil), asn1.Ber.UTCTime);
- der.endSequence();
- var subject = cert.subjects[0];
- var altNames = cert.subjects.slice(1);
- subject.toAsn1(der);
- pkcs8.writePkcs8(der, cert.subjectKey);
- if (sig.extras && sig.extras.issuerUniqueID) {
- der.writeBuffer(sig.extras.issuerUniqueID, Local(1));
- }
- if (sig.extras && sig.extras.subjectUniqueID) {
- der.writeBuffer(sig.extras.subjectUniqueID, Local(2));
- }
- if (altNames.length > 0 || subject.type === 'host' ||
- (sig.extras && sig.extras.exts)) {
- der.startSequence(Local(3));
- der.startSequence();
- var exts = [
- { oid: EXTS.altName }
- ];
- if (sig.extras && sig.extras.exts)
- exts = sig.extras.exts;
- for (var i = 0; i < exts.length; ++i) {
- der.startSequence();
- der.writeOID(exts[i].oid);
- if (exts[i].critical !== undefined)
- der.writeBoolean(exts[i].critical);
- if (exts[i].oid === EXTS.altName) {
- der.startSequence(asn1.Ber.OctetString);
- der.startSequence();
- if (subject.type === 'host') {
- der.writeString(subject.hostname,
- Context(2));
- }
- for (var j = 0; j < altNames.length; ++j) {
- if (altNames[j].type === 'host') {
- der.writeString(
- altNames[j].hostname,
- ALTNAME.DNSName);
- } else if (altNames[j].type ===
- 'email') {
- der.writeString(
- altNames[j].email,
- ALTNAME.RFC822Name);
- } else {
- /*
- * Encode anything else as a
- * DN style name for now.
- */
- der.startSequence(
- ALTNAME.DirectoryName);
- altNames[j].toAsn1(der);
- der.endSequence();
- }
- }
- der.endSequence();
- der.endSequence();
- } else {
- der.writeBuffer(exts[i].data,
- asn1.Ber.OctetString);
- }
- der.endSequence();
- }
- der.endSequence();
- der.endSequence();
- }
- der.endSequence();
- }
|