twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

x509.js (19556B)


      1 // Copyright 2017 Joyent, Inc.
      2 
      3 module.exports = {
      4 	read: read,
      5 	verify: verify,
      6 	sign: sign,
      7 	signAsync: signAsync,
      8 	write: write
      9 };
     10 
     11 var assert = require('assert-plus');
     12 var asn1 = require('asn1');
     13 var Buffer = require('safer-buffer').Buffer;
     14 var algs = require('../algs');
     15 var utils = require('../utils');
     16 var Key = require('../key');
     17 var PrivateKey = require('../private-key');
     18 var pem = require('./pem');
     19 var Identity = require('../identity');
     20 var Signature = require('../signature');
     21 var Certificate = require('../certificate');
     22 var pkcs8 = require('./pkcs8');
     23 
     24 /*
     25  * This file is based on RFC5280 (X.509).
     26  */
     27 
     28 /* Helper to read in a single mpint */
     29 function readMPInt(der, nm) {
     30 	assert.strictEqual(der.peek(), asn1.Ber.Integer,
     31 	    nm + ' is not an Integer');
     32 	return (utils.mpNormalize(der.readString(asn1.Ber.Integer, true)));
     33 }
     34 
     35 function verify(cert, key) {
     36 	var sig = cert.signatures.x509;
     37 	assert.object(sig, 'x509 signature');
     38 
     39 	var algParts = sig.algo.split('-');
     40 	if (algParts[0] !== key.type)
     41 		return (false);
     42 
     43 	var blob = sig.cache;
     44 	if (blob === undefined) {
     45 		var der = new asn1.BerWriter();
     46 		writeTBSCert(cert, der);
     47 		blob = der.buffer;
     48 	}
     49 
     50 	var verifier = key.createVerify(algParts[1]);
     51 	verifier.write(blob);
     52 	return (verifier.verify(sig.signature));
     53 }
     54 
     55 function Local(i) {
     56 	return (asn1.Ber.Context | asn1.Ber.Constructor | i);
     57 }
     58 
     59 function Context(i) {
     60 	return (asn1.Ber.Context | i);
     61 }
     62 
     63 var SIGN_ALGS = {
     64 	'rsa-md5': '1.2.840.113549.1.1.4',
     65 	'rsa-sha1': '1.2.840.113549.1.1.5',
     66 	'rsa-sha256': '1.2.840.113549.1.1.11',
     67 	'rsa-sha384': '1.2.840.113549.1.1.12',
     68 	'rsa-sha512': '1.2.840.113549.1.1.13',
     69 	'dsa-sha1': '1.2.840.10040.4.3',
     70 	'dsa-sha256': '2.16.840.1.101.3.4.3.2',
     71 	'ecdsa-sha1': '1.2.840.10045.4.1',
     72 	'ecdsa-sha256': '1.2.840.10045.4.3.2',
     73 	'ecdsa-sha384': '1.2.840.10045.4.3.3',
     74 	'ecdsa-sha512': '1.2.840.10045.4.3.4',
     75 	'ed25519-sha512': '1.3.101.112'
     76 };
     77 Object.keys(SIGN_ALGS).forEach(function (k) {
     78 	SIGN_ALGS[SIGN_ALGS[k]] = k;
     79 });
     80 SIGN_ALGS['1.3.14.3.2.3'] = 'rsa-md5';
     81 SIGN_ALGS['1.3.14.3.2.29'] = 'rsa-sha1';
     82 
     83 var EXTS = {
     84 	'issuerKeyId': '2.5.29.35',
     85 	'altName': '2.5.29.17',
     86 	'basicConstraints': '2.5.29.19',
     87 	'keyUsage': '2.5.29.15',
     88 	'extKeyUsage': '2.5.29.37'
     89 };
     90 
     91 function read(buf, options) {
     92 	if (typeof (buf) === 'string') {
     93 		buf = Buffer.from(buf, 'binary');
     94 	}
     95 	assert.buffer(buf, 'buf');
     96 
     97 	var der = new asn1.BerReader(buf);
     98 
     99 	der.readSequence();
    100 	if (Math.abs(der.length - der.remain) > 1) {
    101 		throw (new Error('DER sequence does not contain whole byte ' +
    102 		    'stream'));
    103 	}
    104 
    105 	var tbsStart = der.offset;
    106 	der.readSequence();
    107 	var sigOffset = der.offset + der.length;
    108 	var tbsEnd = sigOffset;
    109 
    110 	if (der.peek() === Local(0)) {
    111 		der.readSequence(Local(0));
    112 		var version = der.readInt();
    113 		assert.ok(version <= 3,
    114 		    'only x.509 versions up to v3 supported');
    115 	}
    116 
    117 	var cert = {};
    118 	cert.signatures = {};
    119 	var sig = (cert.signatures.x509 = {});
    120 	sig.extras = {};
    121 
    122 	cert.serial = readMPInt(der, 'serial');
    123 
    124 	der.readSequence();
    125 	var after = der.offset + der.length;
    126 	var certAlgOid = der.readOID();
    127 	var certAlg = SIGN_ALGS[certAlgOid];
    128 	if (certAlg === undefined)
    129 		throw (new Error('unknown signature algorithm ' + certAlgOid));
    130 
    131 	der._offset = after;
    132 	cert.issuer = Identity.parseAsn1(der);
    133 
    134 	der.readSequence();
    135 	cert.validFrom = readDate(der);
    136 	cert.validUntil = readDate(der);
    137 
    138 	cert.subjects = [Identity.parseAsn1(der)];
    139 
    140 	der.readSequence();
    141 	after = der.offset + der.length;
    142 	cert.subjectKey = pkcs8.readPkcs8(undefined, 'public', der);
    143 	der._offset = after;
    144 
    145 	/* issuerUniqueID */
    146 	if (der.peek() === Local(1)) {
    147 		der.readSequence(Local(1));
    148 		sig.extras.issuerUniqueID =
    149 		    buf.slice(der.offset, der.offset + der.length);
    150 		der._offset += der.length;
    151 	}
    152 
    153 	/* subjectUniqueID */
    154 	if (der.peek() === Local(2)) {
    155 		der.readSequence(Local(2));
    156 		sig.extras.subjectUniqueID =
    157 		    buf.slice(der.offset, der.offset + der.length);
    158 		der._offset += der.length;
    159 	}
    160 
    161 	/* extensions */
    162 	if (der.peek() === Local(3)) {
    163 		der.readSequence(Local(3));
    164 		var extEnd = der.offset + der.length;
    165 		der.readSequence();
    166 
    167 		while (der.offset < extEnd)
    168 			readExtension(cert, buf, der);
    169 
    170 		assert.strictEqual(der.offset, extEnd);
    171 	}
    172 
    173 	assert.strictEqual(der.offset, sigOffset);
    174 
    175 	der.readSequence();
    176 	after = der.offset + der.length;
    177 	var sigAlgOid = der.readOID();
    178 	var sigAlg = SIGN_ALGS[sigAlgOid];
    179 	if (sigAlg === undefined)
    180 		throw (new Error('unknown signature algorithm ' + sigAlgOid));
    181 	der._offset = after;
    182 
    183 	var sigData = der.readString(asn1.Ber.BitString, true);
    184 	if (sigData[0] === 0)
    185 		sigData = sigData.slice(1);
    186 	var algParts = sigAlg.split('-');
    187 
    188 	sig.signature = Signature.parse(sigData, algParts[0], 'asn1');
    189 	sig.signature.hashAlgorithm = algParts[1];
    190 	sig.algo = sigAlg;
    191 	sig.cache = buf.slice(tbsStart, tbsEnd);
    192 
    193 	return (new Certificate(cert));
    194 }
    195 
    196 function readDate(der) {
    197 	if (der.peek() === asn1.Ber.UTCTime) {
    198 		return (utcTimeToDate(der.readString(asn1.Ber.UTCTime)));
    199 	} else if (der.peek() === asn1.Ber.GeneralizedTime) {
    200 		return (gTimeToDate(der.readString(asn1.Ber.GeneralizedTime)));
    201 	} else {
    202 		throw (new Error('Unsupported date format'));
    203 	}
    204 }
    205 
    206 function writeDate(der, date) {
    207 	if (date.getUTCFullYear() >= 2050 || date.getUTCFullYear() < 1950) {
    208 		der.writeString(dateToGTime(date), asn1.Ber.GeneralizedTime);
    209 	} else {
    210 		der.writeString(dateToUTCTime(date), asn1.Ber.UTCTime);
    211 	}
    212 }
    213 
    214 /* RFC5280, section 4.2.1.6 (GeneralName type) */
    215 var ALTNAME = {
    216 	OtherName: Local(0),
    217 	RFC822Name: Context(1),
    218 	DNSName: Context(2),
    219 	X400Address: Local(3),
    220 	DirectoryName: Local(4),
    221 	EDIPartyName: Local(5),
    222 	URI: Context(6),
    223 	IPAddress: Context(7),
    224 	OID: Context(8)
    225 };
    226 
    227 /* RFC5280, section 4.2.1.12 (KeyPurposeId) */
    228 var EXTPURPOSE = {
    229 	'serverAuth': '1.3.6.1.5.5.7.3.1',
    230 	'clientAuth': '1.3.6.1.5.5.7.3.2',
    231 	'codeSigning': '1.3.6.1.5.5.7.3.3',
    232 
    233 	/* See https://github.com/joyent/oid-docs/blob/master/root.md */
    234 	'joyentDocker': '1.3.6.1.4.1.38678.1.4.1',
    235 	'joyentCmon': '1.3.6.1.4.1.38678.1.4.2'
    236 };
    237 var EXTPURPOSE_REV = {};
    238 Object.keys(EXTPURPOSE).forEach(function (k) {
    239 	EXTPURPOSE_REV[EXTPURPOSE[k]] = k;
    240 });
    241 
    242 var KEYUSEBITS = [
    243 	'signature', 'identity', 'keyEncryption',
    244 	'encryption', 'keyAgreement', 'ca', 'crl'
    245 ];
    246 
    247 function readExtension(cert, buf, der) {
    248 	der.readSequence();
    249 	var after = der.offset + der.length;
    250 	var extId = der.readOID();
    251 	var id;
    252 	var sig = cert.signatures.x509;
    253 	if (!sig.extras.exts)
    254 		sig.extras.exts = [];
    255 
    256 	var critical;
    257 	if (der.peek() === asn1.Ber.Boolean)
    258 		critical = der.readBoolean();
    259 
    260 	switch (extId) {
    261 	case (EXTS.basicConstraints):
    262 		der.readSequence(asn1.Ber.OctetString);
    263 		der.readSequence();
    264 		var bcEnd = der.offset + der.length;
    265 		var ca = false;
    266 		if (der.peek() === asn1.Ber.Boolean)
    267 			ca = der.readBoolean();
    268 		if (cert.purposes === undefined)
    269 			cert.purposes = [];
    270 		if (ca === true)
    271 			cert.purposes.push('ca');
    272 		var bc = { oid: extId, critical: critical };
    273 		if (der.offset < bcEnd && der.peek() === asn1.Ber.Integer)
    274 			bc.pathLen = der.readInt();
    275 		sig.extras.exts.push(bc);
    276 		break;
    277 	case (EXTS.extKeyUsage):
    278 		der.readSequence(asn1.Ber.OctetString);
    279 		der.readSequence();
    280 		if (cert.purposes === undefined)
    281 			cert.purposes = [];
    282 		var ekEnd = der.offset + der.length;
    283 		while (der.offset < ekEnd) {
    284 			var oid = der.readOID();
    285 			cert.purposes.push(EXTPURPOSE_REV[oid] || oid);
    286 		}
    287 		/*
    288 		 * This is a bit of a hack: in the case where we have a cert
    289 		 * that's only allowed to do serverAuth or clientAuth (and not
    290 		 * the other), we want to make sure all our Subjects are of
    291 		 * the right type. But we already parsed our Subjects and
    292 		 * decided if they were hosts or users earlier (since it appears
    293 		 * first in the cert).
    294 		 *
    295 		 * So we go through and mutate them into the right kind here if
    296 		 * it doesn't match. This might not be hugely beneficial, as it
    297 		 * seems that single-purpose certs are not often seen in the
    298 		 * wild.
    299 		 */
    300 		if (cert.purposes.indexOf('serverAuth') !== -1 &&
    301 		    cert.purposes.indexOf('clientAuth') === -1) {
    302 			cert.subjects.forEach(function (ide) {
    303 				if (ide.type !== 'host') {
    304 					ide.type = 'host';
    305 					ide.hostname = ide.uid ||
    306 					    ide.email ||
    307 					    ide.components[0].value;
    308 				}
    309 			});
    310 		} else if (cert.purposes.indexOf('clientAuth') !== -1 &&
    311 		    cert.purposes.indexOf('serverAuth') === -1) {
    312 			cert.subjects.forEach(function (ide) {
    313 				if (ide.type !== 'user') {
    314 					ide.type = 'user';
    315 					ide.uid = ide.hostname ||
    316 					    ide.email ||
    317 					    ide.components[0].value;
    318 				}
    319 			});
    320 		}
    321 		sig.extras.exts.push({ oid: extId, critical: critical });
    322 		break;
    323 	case (EXTS.keyUsage):
    324 		der.readSequence(asn1.Ber.OctetString);
    325 		var bits = der.readString(asn1.Ber.BitString, true);
    326 		var setBits = readBitField(bits, KEYUSEBITS);
    327 		setBits.forEach(function (bit) {
    328 			if (cert.purposes === undefined)
    329 				cert.purposes = [];
    330 			if (cert.purposes.indexOf(bit) === -1)
    331 				cert.purposes.push(bit);
    332 		});
    333 		sig.extras.exts.push({ oid: extId, critical: critical,
    334 		    bits: bits });
    335 		break;
    336 	case (EXTS.altName):
    337 		der.readSequence(asn1.Ber.OctetString);
    338 		der.readSequence();
    339 		var aeEnd = der.offset + der.length;
    340 		while (der.offset < aeEnd) {
    341 			switch (der.peek()) {
    342 			case ALTNAME.OtherName:
    343 			case ALTNAME.EDIPartyName:
    344 				der.readSequence();
    345 				der._offset += der.length;
    346 				break;
    347 			case ALTNAME.OID:
    348 				der.readOID(ALTNAME.OID);
    349 				break;
    350 			case ALTNAME.RFC822Name:
    351 				/* RFC822 specifies email addresses */
    352 				var email = der.readString(ALTNAME.RFC822Name);
    353 				id = Identity.forEmail(email);
    354 				if (!cert.subjects[0].equals(id))
    355 					cert.subjects.push(id);
    356 				break;
    357 			case ALTNAME.DirectoryName:
    358 				der.readSequence(ALTNAME.DirectoryName);
    359 				id = Identity.parseAsn1(der);
    360 				if (!cert.subjects[0].equals(id))
    361 					cert.subjects.push(id);
    362 				break;
    363 			case ALTNAME.DNSName:
    364 				var host = der.readString(
    365 				    ALTNAME.DNSName);
    366 				id = Identity.forHost(host);
    367 				if (!cert.subjects[0].equals(id))
    368 					cert.subjects.push(id);
    369 				break;
    370 			default:
    371 				der.readString(der.peek());
    372 				break;
    373 			}
    374 		}
    375 		sig.extras.exts.push({ oid: extId, critical: critical });
    376 		break;
    377 	default:
    378 		sig.extras.exts.push({
    379 			oid: extId,
    380 			critical: critical,
    381 			data: der.readString(asn1.Ber.OctetString, true)
    382 		});
    383 		break;
    384 	}
    385 
    386 	der._offset = after;
    387 }
    388 
    389 var UTCTIME_RE =
    390     /^([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})?Z$/;
    391 function utcTimeToDate(t) {
    392 	var m = t.match(UTCTIME_RE);
    393 	assert.ok(m, 'timestamps must be in UTC');
    394 	var d = new Date();
    395 
    396 	var thisYear = d.getUTCFullYear();
    397 	var century = Math.floor(thisYear / 100) * 100;
    398 
    399 	var year = parseInt(m[1], 10);
    400 	if (thisYear % 100 < 50 && year >= 60)
    401 		year += (century - 1);
    402 	else
    403 		year += century;
    404 	d.setUTCFullYear(year, parseInt(m[2], 10) - 1, parseInt(m[3], 10));
    405 	d.setUTCHours(parseInt(m[4], 10), parseInt(m[5], 10));
    406 	if (m[6] && m[6].length > 0)
    407 		d.setUTCSeconds(parseInt(m[6], 10));
    408 	return (d);
    409 }
    410 
    411 var GTIME_RE =
    412     /^([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})?Z$/;
    413 function gTimeToDate(t) {
    414 	var m = t.match(GTIME_RE);
    415 	assert.ok(m);
    416 	var d = new Date();
    417 
    418 	d.setUTCFullYear(parseInt(m[1], 10), parseInt(m[2], 10) - 1,
    419 	    parseInt(m[3], 10));
    420 	d.setUTCHours(parseInt(m[4], 10), parseInt(m[5], 10));
    421 	if (m[6] && m[6].length > 0)
    422 		d.setUTCSeconds(parseInt(m[6], 10));
    423 	return (d);
    424 }
    425 
    426 function zeroPad(n, m) {
    427 	if (m === undefined)
    428 		m = 2;
    429 	var s = '' + n;
    430 	while (s.length < m)
    431 		s = '0' + s;
    432 	return (s);
    433 }
    434 
    435 function dateToUTCTime(d) {
    436 	var s = '';
    437 	s += zeroPad(d.getUTCFullYear() % 100);
    438 	s += zeroPad(d.getUTCMonth() + 1);
    439 	s += zeroPad(d.getUTCDate());
    440 	s += zeroPad(d.getUTCHours());
    441 	s += zeroPad(d.getUTCMinutes());
    442 	s += zeroPad(d.getUTCSeconds());
    443 	s += 'Z';
    444 	return (s);
    445 }
    446 
    447 function dateToGTime(d) {
    448 	var s = '';
    449 	s += zeroPad(d.getUTCFullYear(), 4);
    450 	s += zeroPad(d.getUTCMonth() + 1);
    451 	s += zeroPad(d.getUTCDate());
    452 	s += zeroPad(d.getUTCHours());
    453 	s += zeroPad(d.getUTCMinutes());
    454 	s += zeroPad(d.getUTCSeconds());
    455 	s += 'Z';
    456 	return (s);
    457 }
    458 
    459 function sign(cert, key) {
    460 	if (cert.signatures.x509 === undefined)
    461 		cert.signatures.x509 = {};
    462 	var sig = cert.signatures.x509;
    463 
    464 	sig.algo = key.type + '-' + key.defaultHashAlgorithm();
    465 	if (SIGN_ALGS[sig.algo] === undefined)
    466 		return (false);
    467 
    468 	var der = new asn1.BerWriter();
    469 	writeTBSCert(cert, der);
    470 	var blob = der.buffer;
    471 	sig.cache = blob;
    472 
    473 	var signer = key.createSign();
    474 	signer.write(blob);
    475 	cert.signatures.x509.signature = signer.sign();
    476 
    477 	return (true);
    478 }
    479 
    480 function signAsync(cert, signer, done) {
    481 	if (cert.signatures.x509 === undefined)
    482 		cert.signatures.x509 = {};
    483 	var sig = cert.signatures.x509;
    484 
    485 	var der = new asn1.BerWriter();
    486 	writeTBSCert(cert, der);
    487 	var blob = der.buffer;
    488 	sig.cache = blob;
    489 
    490 	signer(blob, function (err, signature) {
    491 		if (err) {
    492 			done(err);
    493 			return;
    494 		}
    495 		sig.algo = signature.type + '-' + signature.hashAlgorithm;
    496 		if (SIGN_ALGS[sig.algo] === undefined) {
    497 			done(new Error('Invalid signing algorithm "' +
    498 			    sig.algo + '"'));
    499 			return;
    500 		}
    501 		sig.signature = signature;
    502 		done();
    503 	});
    504 }
    505 
    506 function write(cert, options) {
    507 	var sig = cert.signatures.x509;
    508 	assert.object(sig, 'x509 signature');
    509 
    510 	var der = new asn1.BerWriter();
    511 	der.startSequence();
    512 	if (sig.cache) {
    513 		der._ensure(sig.cache.length);
    514 		sig.cache.copy(der._buf, der._offset);
    515 		der._offset += sig.cache.length;
    516 	} else {
    517 		writeTBSCert(cert, der);
    518 	}
    519 
    520 	der.startSequence();
    521 	der.writeOID(SIGN_ALGS[sig.algo]);
    522 	if (sig.algo.match(/^rsa-/))
    523 		der.writeNull();
    524 	der.endSequence();
    525 
    526 	var sigData = sig.signature.toBuffer('asn1');
    527 	var data = Buffer.alloc(sigData.length + 1);
    528 	data[0] = 0;
    529 	sigData.copy(data, 1);
    530 	der.writeBuffer(data, asn1.Ber.BitString);
    531 	der.endSequence();
    532 
    533 	return (der.buffer);
    534 }
    535 
    536 function writeTBSCert(cert, der) {
    537 	var sig = cert.signatures.x509;
    538 	assert.object(sig, 'x509 signature');
    539 
    540 	der.startSequence();
    541 
    542 	der.startSequence(Local(0));
    543 	der.writeInt(2);
    544 	der.endSequence();
    545 
    546 	der.writeBuffer(utils.mpNormalize(cert.serial), asn1.Ber.Integer);
    547 
    548 	der.startSequence();
    549 	der.writeOID(SIGN_ALGS[sig.algo]);
    550 	if (sig.algo.match(/^rsa-/))
    551 		der.writeNull();
    552 	der.endSequence();
    553 
    554 	cert.issuer.toAsn1(der);
    555 
    556 	der.startSequence();
    557 	writeDate(der, cert.validFrom);
    558 	writeDate(der, cert.validUntil);
    559 	der.endSequence();
    560 
    561 	var subject = cert.subjects[0];
    562 	var altNames = cert.subjects.slice(1);
    563 	subject.toAsn1(der);
    564 
    565 	pkcs8.writePkcs8(der, cert.subjectKey);
    566 
    567 	if (sig.extras && sig.extras.issuerUniqueID) {
    568 		der.writeBuffer(sig.extras.issuerUniqueID, Local(1));
    569 	}
    570 
    571 	if (sig.extras && sig.extras.subjectUniqueID) {
    572 		der.writeBuffer(sig.extras.subjectUniqueID, Local(2));
    573 	}
    574 
    575 	if (altNames.length > 0 || subject.type === 'host' ||
    576 	    (cert.purposes !== undefined && cert.purposes.length > 0) ||
    577 	    (sig.extras && sig.extras.exts)) {
    578 		der.startSequence(Local(3));
    579 		der.startSequence();
    580 
    581 		var exts = [];
    582 		if (cert.purposes !== undefined && cert.purposes.length > 0) {
    583 			exts.push({
    584 				oid: EXTS.basicConstraints,
    585 				critical: true
    586 			});
    587 			exts.push({
    588 				oid: EXTS.keyUsage,
    589 				critical: true
    590 			});
    591 			exts.push({
    592 				oid: EXTS.extKeyUsage,
    593 				critical: true
    594 			});
    595 		}
    596 		exts.push({ oid: EXTS.altName });
    597 		if (sig.extras && sig.extras.exts)
    598 			exts = sig.extras.exts;
    599 
    600 		for (var i = 0; i < exts.length; ++i) {
    601 			der.startSequence();
    602 			der.writeOID(exts[i].oid);
    603 
    604 			if (exts[i].critical !== undefined)
    605 				der.writeBoolean(exts[i].critical);
    606 
    607 			if (exts[i].oid === EXTS.altName) {
    608 				der.startSequence(asn1.Ber.OctetString);
    609 				der.startSequence();
    610 				if (subject.type === 'host') {
    611 					der.writeString(subject.hostname,
    612 					    Context(2));
    613 				}
    614 				for (var j = 0; j < altNames.length; ++j) {
    615 					if (altNames[j].type === 'host') {
    616 						der.writeString(
    617 						    altNames[j].hostname,
    618 						    ALTNAME.DNSName);
    619 					} else if (altNames[j].type ===
    620 					    'email') {
    621 						der.writeString(
    622 						    altNames[j].email,
    623 						    ALTNAME.RFC822Name);
    624 					} else {
    625 						/*
    626 						 * Encode anything else as a
    627 						 * DN style name for now.
    628 						 */
    629 						der.startSequence(
    630 						    ALTNAME.DirectoryName);
    631 						altNames[j].toAsn1(der);
    632 						der.endSequence();
    633 					}
    634 				}
    635 				der.endSequence();
    636 				der.endSequence();
    637 			} else if (exts[i].oid === EXTS.basicConstraints) {
    638 				der.startSequence(asn1.Ber.OctetString);
    639 				der.startSequence();
    640 				var ca = (cert.purposes.indexOf('ca') !== -1);
    641 				var pathLen = exts[i].pathLen;
    642 				der.writeBoolean(ca);
    643 				if (pathLen !== undefined)
    644 					der.writeInt(pathLen);
    645 				der.endSequence();
    646 				der.endSequence();
    647 			} else if (exts[i].oid === EXTS.extKeyUsage) {
    648 				der.startSequence(asn1.Ber.OctetString);
    649 				der.startSequence();
    650 				cert.purposes.forEach(function (purpose) {
    651 					if (purpose === 'ca')
    652 						return;
    653 					if (KEYUSEBITS.indexOf(purpose) !== -1)
    654 						return;
    655 					var oid = purpose;
    656 					if (EXTPURPOSE[purpose] !== undefined)
    657 						oid = EXTPURPOSE[purpose];
    658 					der.writeOID(oid);
    659 				});
    660 				der.endSequence();
    661 				der.endSequence();
    662 			} else if (exts[i].oid === EXTS.keyUsage) {
    663 				der.startSequence(asn1.Ber.OctetString);
    664 				/*
    665 				 * If we parsed this certificate from a byte
    666 				 * stream (i.e. we didn't generate it in sshpk)
    667 				 * then we'll have a ".bits" property on the
    668 				 * ext with the original raw byte contents.
    669 				 *
    670 				 * If we have this, use it here instead of
    671 				 * regenerating it. This guarantees we output
    672 				 * the same data we parsed, so signatures still
    673 				 * validate.
    674 				 */
    675 				if (exts[i].bits !== undefined) {
    676 					der.writeBuffer(exts[i].bits,
    677 					    asn1.Ber.BitString);
    678 				} else {
    679 					var bits = writeBitField(cert.purposes,
    680 					    KEYUSEBITS);
    681 					der.writeBuffer(bits,
    682 					    asn1.Ber.BitString);
    683 				}
    684 				der.endSequence();
    685 			} else {
    686 				der.writeBuffer(exts[i].data,
    687 				    asn1.Ber.OctetString);
    688 			}
    689 
    690 			der.endSequence();
    691 		}
    692 
    693 		der.endSequence();
    694 		der.endSequence();
    695 	}
    696 
    697 	der.endSequence();
    698 }
    699 
    700 /*
    701  * Reads an ASN.1 BER bitfield out of the Buffer produced by doing
    702  * `BerReader#readString(asn1.Ber.BitString)`. That function gives us the raw
    703  * contents of the BitString tag, which is a count of unused bits followed by
    704  * the bits as a right-padded byte string.
    705  *
    706  * `bits` is the Buffer, `bitIndex` should contain an array of string names
    707  * for the bits in the string, ordered starting with bit #0 in the ASN.1 spec.
    708  *
    709  * Returns an array of Strings, the names of the bits that were set to 1.
    710  */
    711 function readBitField(bits, bitIndex) {
    712 	var bitLen = 8 * (bits.length - 1) - bits[0];
    713 	var setBits = {};
    714 	for (var i = 0; i < bitLen; ++i) {
    715 		var byteN = 1 + Math.floor(i / 8);
    716 		var bit = 7 - (i % 8);
    717 		var mask = 1 << bit;
    718 		var bitVal = ((bits[byteN] & mask) !== 0);
    719 		var name = bitIndex[i];
    720 		if (bitVal && typeof (name) === 'string') {
    721 			setBits[name] = true;
    722 		}
    723 	}
    724 	return (Object.keys(setBits));
    725 }
    726 
    727 /*
    728  * `setBits` is an array of strings, containing the names for each bit that
    729  * sould be set to 1. `bitIndex` is same as in `readBitField()`.
    730  *
    731  * Returns a Buffer, ready to be written out with `BerWriter#writeString()`.
    732  */
    733 function writeBitField(setBits, bitIndex) {
    734 	var bitLen = bitIndex.length;
    735 	var blen = Math.ceil(bitLen / 8);
    736 	var unused = blen * 8 - bitLen;
    737 	var bits = Buffer.alloc(1 + blen); // zero-filled
    738 	bits[0] = unused;
    739 	for (var i = 0; i < bitLen; ++i) {
    740 		var byteN = 1 + Math.floor(i / 8);
    741 		var bit = 7 - (i % 8);
    742 		var mask = 1 << bit;
    743 		var name = bitIndex[i];
    744 		if (name === undefined)
    745 			continue;
    746 		var bitVal = (setBits.indexOf(name) !== -1);
    747 		if (bitVal) {
    748 			bits[byteN] |= mask;
    749 		}
    750 	}
    751 	return (bits);
    752 }