ssl-root-cas.js/ca-store-generator.js

178 lines
5.0 KiB
JavaScript
Raw Normal View History

2014-03-08 00:14:13 +00:00
'use strict';
// Explained here: https://groups.google.com/d/msg/nodejs/AjkHSYmiGYs/1LfNHbMhd48J
var fs = require('fs')
, path = require('path')
, request = require('request')
, CERTDB_URL = 'https://mxr.mozilla.org/nss/source/lib/ckfw/builtins/certdata.txt?raw=1'
, HEADER
;
HEADER =
"/**\n" +
" * Mozilla's root CA store\n" +
" *\n" +
" * generated from " + CERTDB_URL + "\n" +
" */\n\n";
function Certificate() {
this.name = null;
this.body = '';
this.trusted = true;
}
Certificate.prototype.quasiPEM = function quasiPEM() {
var bytes = this.body.split('\\')
, offset = 0
, converted
;
bytes.shift();
converted = new Buffer(bytes.length);
while(bytes.length > 0) {
converted.writeUInt8(parseInt(bytes.shift(), 8), offset++);
}
return ' // ' + this.name + '\n' +
' "-----BEGIN CERTIFICATE-----\\n" +\n' +
converted.toString('base64').replace(/(.{1,76})/g, ' "$1\\n" +\n') +
' "-----END CERTIFICATE-----\\n"';
};
function parseBody(current, lines) {
var line
;
while (lines.length > 0) {
line = lines.shift();
if (line.match(/^END/)) { break; }
current.body += line;
}
while (lines.length > 0) {
line = lines.shift();
if (line.match(/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/)) { break; }
}
while (lines.length > 0) {
line = lines.shift();
if (line.match(/^#|^\s*$/)) { break; }
if (line.match(/^CKA_TRUST_SERVER_AUTH\s+CK_TRUST\s+CKT_NSS_NOT_TRUSTED$/) ||
line.match(/^CKA_TRUST_SERVER_AUTH\s+CK_TRUST\s+CKT_NSS_TRUST_UNKNOWN$/)) {
current.trusted = false;
}
}
if (current.trusted) return current;
}
function parseCertData(lines) {
var certs = []
, line
, current
, skipped = 0
, match
, finished
;
while (lines.length > 0) {
line = lines.shift();
// nuke whitespace and comments
if (line.match(/^#|^\s*$/)) continue;
if (line.match(/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/)) {
current = new Certificate();
}
if (current) {
match = line.match(/^CKA_LABEL UTF8 \"(.*)\"/);
if (match) {
current.name = match[1];
}
if (line.match(/^CKA_VALUE MULTILINE_OCTAL/)) {
finished = parseBody(current, lines);
if (finished) {
certs.push(finished);
}
else {
skipped++;
}
current = null;
}
}
}
console.info("Skipped %s untrusted certificates.", skipped);
console.info("Processed %s certificates.", certs.length);
return certs;
}
function dumpCerts(certs, filename, pemsDir) {
2014-03-08 00:48:44 +00:00
certs.forEach(function (cert, i) {
var pemsFile = path.join(pemsDir, 'ca-' + i + '.pem');
fs.writeFileSync(pemsFile, cert.quasiPEM());
2014-03-08 00:48:44 +00:00
});
console.info("Wrote " + certs.length + " certificates in '"
+ path.join(__dirname, 'pems/').replace(/'/g, "\\'") + "'.");
2014-03-08 00:14:13 +00:00
fs.writeFileSync(
filename
2014-03-08 00:48:44 +00:00
, HEADER
2014-03-08 02:02:25 +00:00
+ 'var cas = module.exports = [\n'
2014-03-08 00:48:44 +00:00
+ certs.map(function (cert) { return cert.quasiPEM(); }).join(',\n\n')
+ '\n];\n'
2014-04-25 22:14:42 +00:00
+ "module.exports.rootCas = cas;\n"
2014-03-08 02:02:25 +00:00
+ "module.exports.inject = function () {\n"
+ " var opts = require('https').globalAgent.options;\n"
+ " if (!opts.ca || !opts.ca.__injected) { opts.ca = (opts.ca||[]).concat(cas); }\n"
+ " opts.ca.__injected = true;\n"
2014-04-25 21:33:01 +00:00
+ " return module.exports;\n"
2014-03-08 02:02:25 +00:00
+ "};\n"
2014-04-25 21:31:57 +00:00
+ "module.exports.addFile = function (filepath) {\n"
+ " var opts = require('https').globalAgent.options;\n"
2014-04-25 22:34:53 +00:00
+ " var root = filepath[0] === '/' ? '/' : '';\n"
+ " var filepaths = filepath.split(/\\//g);\n"
+ " if (root) { filepaths.unshift(root); }\n"
2014-04-25 21:31:57 +00:00
+ " opts.ca = opts.ca || [];\n"
+ " opts.ca.push(require('fs').readFileSync(require('path').join.apply(null, filepaths)));\n"
2014-04-25 21:33:01 +00:00
+ " return module.exports;\n"
2014-04-25 21:31:57 +00:00
+ "};\n"
2014-03-08 00:14:13 +00:00
);
console.info("Wrote '" + filename.replace(/'/g, "\\'") + "'.");
2014-03-08 00:14:13 +00:00
}
if (process.argv[2] == null) {
console.error("Error: No file specified");
console.info("Usage: %s <outputfile>", process.argv[1]);
console.info(" where <outputfile> is the name of the file to write to, relative to %s", process.argv[1]);
console.info("Note that a 'pems/' directory will also be created at the same location as the <outputfile>, containing individual .pem files.");
process.exit(3);
}
// main (combined) output file location, relative to this script's location
var outputFile = path.resolve(__dirname, process.argv[2]);
// pems/ output directory, in the same directory as the outputFile
var outputPemsDir = path.resolve(outputFile, '../pems')
console.info("Loading latest certificates from " + CERTDB_URL);
2014-03-08 00:14:13 +00:00
request(CERTDB_URL, function (error, response, body) {
if (error) {
console.error(error.stacktrace);
process.exit(1);
}
if (response.statusCode !== 200) {
console.error("Fetching failed with status code %s", response.statusCode);
process.exit(2);
}
var lines = body.split("\n");
dumpCerts(parseCertData(lines), outputFile, outputPemsDir);
2014-03-08 00:14:13 +00:00
});