'use strict'; // Explained here: https://groups.google.com/d/msg/nodejs/AjkHSYmiGYs/1LfNHbMhd48J var fs = require('fs') , path = require('path') , request = require('@coolaj86/urequest') , CERTDB_URL = 'https://mxr.mozilla.org/nss/source/lib/ckfw/builtins/certdata.txt?raw=1' , outputFile , outputPemsDir ; 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 = Buffer.alloc(bytes.length); while(bytes.length > 0) { converted.writeUInt8(parseInt(bytes.shift(), 8), offset++); } 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"' }; }; 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 ; function parseLine(line) { // // Find & nuke whitespace and comments // if (line.match(/^#|^\s*$/)) { return; } // // Find CERT // if (line.match(/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/)) { current = new Certificate(); return; } if (!current) { return; } // // 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; } current = null; return; } } while (lines.length > 0) { parseLine(lines.shift()); } console.info("Skipped %s untrusted certificates.", skipped); console.info("Processed %s certificates.", certs.length); return certs; } function dumpCerts(certs, filename, pemsDir) { certs.forEach(function (cert) { var pem = cert.quasiPEM() , pemName = pem.name.toLowerCase().replace(/[\\\s\/\(\)\.]+/g, '-').replace(/-+/g, '-') , pemsFile = path.join(pemsDir, pemName + '.pem') ; /* if (/[^\w\-]/.test(pemName)) { //pemName = pemName.replace(/\\/g, '-'); //pemName = pemName.replace(/[^\w-]/g, '-'); console.log(pemName); } */ fs.writeFileSync(pemsFile, pem.value); }); console.info("Wrote " + certs.length + " certificates in '" + path.join(__dirname, 'pems/').replace(/'/g, "\\'") + "'."); fs.writeFileSync( filename , fs.readFileSync(path.join(__dirname, 'ssl-root-cas.tpl.js'), 'utf8') .replace(/\/\*TPL\*\//, certs.map(function (cert) { return cert.quasiPEM().value; }).join(',\n\n')) , 'utf8' ); console.info("Wrote '" + filename.replace(/'/g, "\\'") + "'."); } /*global Promise*/ function run(filename) { var PromiseA; if ('undefined' !== typeof Promise) { PromiseA = Promise; } else { try { PromiseA = require('bluebird').Promise; } catch(e) { console.error("node " + process.version + " lacks Promise support. Please add bluebird to your project:"); console.error(" npm install --save bluebird"); process.exit(1); } } return new PromiseA(function (resolve, reject) { if (!filename) { console.error("Error: No file specified"); console.info("Usage: %s ", process.argv[1]); console.info(" where 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 , containing individual .pem files."); reject(3); return; } // main (combined) output file location, relative to this script's location outputFile = path.resolve(__dirname, filename); // pems/ output directory, in the same directory as the outputFile outputPemsDir = path.resolve(outputFile, '../pems'); if (!fs.existsSync(outputPemsDir)) { fs.mkdirSync(outputPemsDir); } console.info("Loading latest certificates from " + CERTDB_URL); request({ url: CERTDB_URL }, function (error, response, body) { if (error) { console.error(error); console.error(error.stacktrace); reject({ code: 1, error: error }); return; } 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; } if (response.headers['content-type'].indexOf('text/plain') !== 0) { console.error("Fetching failed with incorrect content type %s", response.headers['content-type']); reject({ code: 2, error: "Fetching failed with incorrect content type " + response.headers['content-type'] }); return; } var lines = body.split("\n") , certs = parseCertData(lines) , pemsFile = path.join(outputPemsDir, 'mozilla-certdata.txt') ; 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); } ); }