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
|
2014-06-18 02:07:25 +00:00
|
|
|
, outputFile
|
|
|
|
, outputPemsDir
|
2014-07-16 17:57:51 +00:00
|
|
|
, Promise = require('es6-promise').Promise
|
2014-03-08 00:14:13 +00:00
|
|
|
;
|
|
|
|
|
|
|
|
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++);
|
|
|
|
}
|
|
|
|
|
2014-06-18 02:03:47 +00:00
|
|
|
return {
|
|
|
|
name: this.name
|
|
|
|
, value: ' // ' + this.name + '\n'
|
|
|
|
+ ' "-----BEGIN CERTIFICATE-----\\n" +\n'
|
|
|
|
+ converted.toString('base64').replace(/(.{1,76})/g, ' "$1\\n" +\n')
|
|
|
|
+ ' "-----END CERTIFICATE-----\\n"'
|
|
|
|
};
|
2014-03-08 00:14:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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 = []
|
|
|
|
, current
|
|
|
|
, skipped = 0
|
|
|
|
, match
|
|
|
|
, finished
|
|
|
|
;
|
|
|
|
|
2014-06-18 02:11:50 +00:00
|
|
|
function parseLine(line) {
|
2014-06-18 02:36:53 +00:00
|
|
|
//
|
|
|
|
// Find & nuke whitespace and comments
|
|
|
|
//
|
2014-06-18 02:11:50 +00:00
|
|
|
if (line.match(/^#|^\s*$/)) {
|
|
|
|
return;
|
|
|
|
}
|
2014-03-08 00:14:13 +00:00
|
|
|
|
2014-06-18 02:36:53 +00:00
|
|
|
//
|
|
|
|
// Find CERT
|
|
|
|
//
|
2014-03-08 00:14:13 +00:00
|
|
|
if (line.match(/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/)) {
|
|
|
|
current = new Certificate();
|
2014-06-18 02:36:53 +00:00
|
|
|
return;
|
2014-03-08 00:14:13 +00:00
|
|
|
}
|
|
|
|
|
2014-06-18 02:36:53 +00:00
|
|
|
if (!current) {
|
|
|
|
return;
|
|
|
|
}
|
2014-03-08 00:14:13 +00:00
|
|
|
|
2014-06-18 02:36:53 +00:00
|
|
|
//
|
|
|
|
// Find Name
|
|
|
|
//
|
|
|
|
match = line.match(/^CKA_LABEL UTF8 \"(.*)\"/);
|
|
|
|
if (match) {
|
|
|
|
current.name = decodeURIComponent(match[1]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Find Body
|
|
|
|
//
|
|
|
|
if (line.match(/^CKA_VALUE MULTILINE_OCTAL/)) {
|
|
|
|
finished = parseBody(current, lines);
|
|
|
|
if (finished) {
|
|
|
|
certs.push(finished);
|
|
|
|
} else {
|
|
|
|
skipped += 1;
|
2014-03-08 00:14:13 +00:00
|
|
|
}
|
2014-06-18 02:36:53 +00:00
|
|
|
current = null;
|
|
|
|
return;
|
2014-03-08 00:14:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-18 02:11:50 +00:00
|
|
|
while (lines.length > 0) {
|
|
|
|
parseLine(lines.shift());
|
|
|
|
}
|
|
|
|
|
2014-03-08 00:14:13 +00:00
|
|
|
console.info("Skipped %s untrusted certificates.", skipped);
|
|
|
|
console.info("Processed %s certificates.", certs.length);
|
|
|
|
|
|
|
|
return certs;
|
|
|
|
}
|
|
|
|
|
2014-06-16 18:26:45 +00:00
|
|
|
function dumpCerts(certs, filename, pemsDir) {
|
2014-06-18 02:05:45 +00:00
|
|
|
certs.forEach(function (cert) {
|
2014-06-18 02:03:47 +00:00
|
|
|
var pem = cert.quasiPEM()
|
2014-07-30 17:22:41 +00:00
|
|
|
, pemName = pem.name.toLowerCase().replace(/[\\\s\/\(\)\.]+/g, '-').replace(/-+/g, '-')
|
2014-06-18 02:03:47 +00:00
|
|
|
, pemsFile = path.join(pemsDir, pemName + '.pem')
|
|
|
|
;
|
|
|
|
|
2014-07-30 17:22:41 +00:00
|
|
|
/*
|
|
|
|
if (/[^\w\-]/.test(pemName)) {
|
|
|
|
//pemName = pemName.replace(/\\/g, '-');
|
|
|
|
//pemName = pemName.replace(/[^\w-]/g, '-');
|
|
|
|
console.log(pemName);
|
|
|
|
}
|
|
|
|
*/
|
2014-06-18 02:03:47 +00:00
|
|
|
fs.writeFileSync(pemsFile, pem.value);
|
2014-03-08 00:48:44 +00:00
|
|
|
});
|
2014-07-16 17:57:51 +00:00
|
|
|
|
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(
|
2014-06-16 18:11:15 +00:00
|
|
|
filename
|
2014-03-08 00:48:44 +00:00
|
|
|
, HEADER
|
2014-03-08 02:02:25 +00:00
|
|
|
+ 'var cas = module.exports = [\n'
|
2014-06-18 02:03:47 +00:00
|
|
|
+ certs.map(function (cert) { return cert.quasiPEM().value; }).join(',\n\n')
|
2014-03-08 00:48:44 +00:00
|
|
|
+ '\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"
|
2014-03-08 02:12:36 +00:00
|
|
|
+ " 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"
|
2014-04-25 22:41:48 +00:00
|
|
|
+ " 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"
|
2014-04-25 22:41:48 +00:00
|
|
|
+ " 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
|
|
|
);
|
2014-06-16 18:11:15 +00:00
|
|
|
console.info("Wrote '" + filename.replace(/'/g, "\\'") + "'.");
|
2014-03-08 00:14:13 +00:00
|
|
|
}
|
|
|
|
|
2014-07-16 17:57:51 +00:00
|
|
|
function run(filename) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
if (!filename) {
|
|
|
|
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.");
|
|
|
|
reject(3);
|
|
|
|
return;
|
|
|
|
}
|
2014-06-16 18:11:15 +00:00
|
|
|
|
2014-07-16 17:57:51 +00:00
|
|
|
// main (combined) output file location, relative to this script's location
|
|
|
|
outputFile = path.resolve(__dirname, filename);
|
2014-06-16 18:21:06 +00:00
|
|
|
|
2014-07-16 17:57:51 +00:00
|
|
|
// pems/ output directory, in the same directory as the outputFile
|
|
|
|
outputPemsDir = path.resolve(outputFile, '../pems');
|
2014-06-16 18:26:45 +00:00
|
|
|
|
2014-07-17 23:53:47 +00:00
|
|
|
if (!fs.existsSync(outputPemsDir)) {
|
|
|
|
fs.mkdirSync(outputPemsDir);
|
|
|
|
}
|
2014-06-16 18:26:45 +00:00
|
|
|
|
2014-07-16 17:57:51 +00:00
|
|
|
console.info("Loading latest certificates from " + CERTDB_URL);
|
|
|
|
request.get(CERTDB_URL, function (error, response, body) {
|
|
|
|
if (error) {
|
|
|
|
console.error(error);
|
|
|
|
console.error(error.stacktrace);
|
|
|
|
reject({ code: 1, error: error });
|
|
|
|
return;
|
|
|
|
}
|
2014-03-08 00:14:13 +00:00
|
|
|
|
2014-07-16 17:57:51 +00:00
|
|
|
if (response.statusCode !== 200) {
|
|
|
|
console.error("Fetching failed with status code %s", response.statusCode);
|
|
|
|
reject({ code: 2, error: "Fetching failed with status code " + response.statusCode });
|
|
|
|
return;
|
|
|
|
}
|
2014-03-08 00:14:13 +00:00
|
|
|
|
2014-07-16 17:57:51 +00:00
|
|
|
var lines = body.split("\n")
|
|
|
|
, certs = parseCertData(lines)
|
|
|
|
, pemsFile = path.join(outputPemsDir, 'mozilla-certdata.txt')
|
|
|
|
;
|
2014-06-18 02:07:25 +00:00
|
|
|
|
2014-07-16 17:57:51 +00:00
|
|
|
fs.writeFileSync(pemsFile, body);
|
|
|
|
dumpCerts(certs, outputFile, outputPemsDir);
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.generate = run;
|
|
|
|
|
|
|
|
if (require.main === module) {
|
|
|
|
run(process.argv[2])
|
|
|
|
.then
|
|
|
|
(function () {
|
|
|
|
// something
|
|
|
|
}
|
|
|
|
, function (errcode) {
|
|
|
|
process.exit(errcode);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|