253 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			253 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| '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
 | |
|   , outputFile
 | |
|   , outputPemsDir
 | |
|   ;
 | |
| 
 | |
| 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 {
 | |
|     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
 | |
|   , HEADER
 | |
|       + 'var cas = module.exports = [\n'
 | |
|       + certs.map(function (cert) { return cert.quasiPEM().value; }).join(',\n\n')
 | |
|       + '\n];\n'
 | |
|       + "module.exports.rootCas = cas;\n"
 | |
|       + "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"
 | |
|       + "  return module.exports;\n"
 | |
|       + "};\n"
 | |
|       + "module.exports.addFile = function (filepath) {\n"
 | |
|       + "  var opts = require('https').globalAgent.options;\n"
 | |
|       + "  var root = filepath[0] === '/' ? '/' : '';\n"
 | |
|       + "  var filepaths = filepath.split(/\\//g);\n"
 | |
|       + "  if (root) { filepaths.unshift(root); }\n"
 | |
|       + "  opts.ca = opts.ca || [];\n"
 | |
|       + "  opts.ca.push(require('fs').readFileSync(require('path').join.apply(null, filepaths)));\n"
 | |
|       + "  return module.exports;\n"
 | |
|       + "};\n"
 | |
|   );
 | |
|   console.info("Wrote '" + filename.replace(/'/g, "\\'") + "'.");
 | |
| }
 | |
| 
 | |
| function run(filename) {
 | |
|   var PromiseA = require('bluebird').Promise;
 | |
| 
 | |
|   return new PromiseA(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;
 | |
|     }
 | |
| 
 | |
|     // 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.get(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'] !== 'text/plain') {
 | |
|         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);
 | |
|       }
 | |
|     );
 | |
| }
 |