| 
									
										
										
										
											2014-03-07 17:14:13 -07: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-17 20:07:25 -06:00
										 |  |  |   , outputFile | 
					
						
							|  |  |  |   , outputPemsDir | 
					
						
							| 
									
										
										
										
											2014-07-16 11:57:51 -06:00
										 |  |  |   , Promise = require('es6-promise').Promise | 
					
						
							| 
									
										
										
										
											2014-03-07 17:14:13 -07: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-17 20:03:47 -06: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-07 17:14:13 -07: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-17 20:11:50 -06:00
										 |  |  |   function parseLine(line) { | 
					
						
							| 
									
										
										
										
											2014-06-17 20:36:53 -06:00
										 |  |  |     //
 | 
					
						
							|  |  |  |     // Find & nuke whitespace and comments
 | 
					
						
							|  |  |  |     //
 | 
					
						
							| 
									
										
										
										
											2014-06-17 20:11:50 -06:00
										 |  |  |     if (line.match(/^#|^\s*$/)) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2014-03-07 17:14:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-17 20:36:53 -06:00
										 |  |  |     //
 | 
					
						
							|  |  |  |     // Find CERT
 | 
					
						
							|  |  |  |     //
 | 
					
						
							| 
									
										
										
										
											2014-03-07 17:14:13 -07:00
										 |  |  |     if (line.match(/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/)) { | 
					
						
							|  |  |  |       current = new Certificate(); | 
					
						
							| 
									
										
										
										
											2014-06-17 20:36:53 -06:00
										 |  |  |       return; | 
					
						
							| 
									
										
										
										
											2014-03-07 17:14:13 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-17 20:36:53 -06:00
										 |  |  |     if (!current) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2014-03-07 17:14:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-17 20:36:53 -06: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-07 17:14:13 -07:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2014-06-17 20:36:53 -06:00
										 |  |  |       current = null; | 
					
						
							|  |  |  |       return; | 
					
						
							| 
									
										
										
										
											2014-03-07 17:14:13 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-17 20:11:50 -06:00
										 |  |  |   while (lines.length > 0) { | 
					
						
							|  |  |  |     parseLine(lines.shift()); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-07 17:14:13 -07:00
										 |  |  |   console.info("Skipped %s untrusted certificates.", skipped); | 
					
						
							|  |  |  |   console.info("Processed %s certificates.", certs.length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return certs; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-16 14:26:45 -04:00
										 |  |  | function dumpCerts(certs, filename, pemsDir) { | 
					
						
							| 
									
										
										
										
											2014-06-17 20:05:45 -06:00
										 |  |  |   certs.forEach(function (cert) { | 
					
						
							| 
									
										
										
										
											2014-06-17 20:03:47 -06:00
										 |  |  |     var pem = cert.quasiPEM() | 
					
						
							| 
									
										
										
										
											2014-06-17 20:36:53 -06:00
										 |  |  |       , pemName = pem.name.toLowerCase().replace(/[\s\/]+/g, '-').replace(/-+/g, '-') | 
					
						
							| 
									
										
										
										
											2014-06-17 20:03:47 -06:00
										 |  |  |       , pemsFile = path.join(pemsDir, pemName + '.pem') | 
					
						
							|  |  |  |       ; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     fs.writeFileSync(pemsFile, pem.value); | 
					
						
							| 
									
										
										
										
											2014-03-07 17:48:44 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2014-07-16 11:57:51 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-07 17:48:44 -07:00
										 |  |  |   console.info("Wrote " + certs.length + " certificates in '" | 
					
						
							|  |  |  |     + path.join(__dirname, 'pems/').replace(/'/g, "\\'") + "'."); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-07 17:14:13 -07:00
										 |  |  |   fs.writeFileSync( | 
					
						
							| 
									
										
										
										
											2014-06-16 14:11:15 -04:00
										 |  |  |     filename | 
					
						
							| 
									
										
										
										
											2014-03-07 17:48:44 -07:00
										 |  |  |   , HEADER | 
					
						
							| 
									
										
										
										
											2014-03-07 19:02:25 -07:00
										 |  |  |       + 'var cas = module.exports = [\n' | 
					
						
							| 
									
										
										
										
											2014-06-17 20:03:47 -06:00
										 |  |  |       + certs.map(function (cert) { return cert.quasiPEM().value; }).join(',\n\n') | 
					
						
							| 
									
										
										
										
											2014-03-07 17:48:44 -07:00
										 |  |  |       + '\n];\n' | 
					
						
							| 
									
										
										
										
											2014-04-25 16:14:42 -06:00
										 |  |  |       + "module.exports.rootCas = cas;\n" | 
					
						
							| 
									
										
										
										
											2014-03-07 19:02:25 -07:00
										 |  |  |       + "module.exports.inject = function () {\n" | 
					
						
							|  |  |  |       + "  var opts = require('https').globalAgent.options;\n" | 
					
						
							| 
									
										
										
										
											2014-03-07 19:12:36 -07:00
										 |  |  |       + "  if (!opts.ca || !opts.ca.__injected) { opts.ca = (opts.ca||[]).concat(cas); }\n" | 
					
						
							|  |  |  |       + "  opts.ca.__injected = true;\n" | 
					
						
							| 
									
										
										
										
											2014-04-25 16:33:01 -05:00
										 |  |  |       + "  return module.exports;\n" | 
					
						
							| 
									
										
										
										
											2014-03-07 19:02:25 -07:00
										 |  |  |       + "};\n" | 
					
						
							| 
									
										
										
										
											2014-04-25 16:31:57 -05:00
										 |  |  |       + "module.exports.addFile = function (filepath) {\n" | 
					
						
							|  |  |  |       + "  var opts = require('https').globalAgent.options;\n" | 
					
						
							| 
									
										
										
										
											2014-04-25 16:34:53 -06:00
										 |  |  |       + "  var root = filepath[0] === '/' ? '/' : '';\n" | 
					
						
							| 
									
										
										
										
											2014-04-25 16:41:48 -06:00
										 |  |  |       + "  var filepaths = filepath.split(/\\//g);\n" | 
					
						
							|  |  |  |       + "  if (root) { filepaths.unshift(root); }\n" | 
					
						
							| 
									
										
										
										
											2014-04-25 16:31:57 -05:00
										 |  |  |       + "  opts.ca = opts.ca || [];\n" | 
					
						
							| 
									
										
										
										
											2014-04-25 16:41:48 -06:00
										 |  |  |       + "  opts.ca.push(require('fs').readFileSync(require('path').join.apply(null, filepaths)));\n" | 
					
						
							| 
									
										
										
										
											2014-04-25 16:33:01 -05:00
										 |  |  |       + "  return module.exports;\n" | 
					
						
							| 
									
										
										
										
											2014-04-25 16:31:57 -05:00
										 |  |  |       + "};\n" | 
					
						
							| 
									
										
										
										
											2014-03-07 17:14:13 -07:00
										 |  |  |   ); | 
					
						
							| 
									
										
										
										
											2014-06-16 14:11:15 -04:00
										 |  |  |   console.info("Wrote '" + filename.replace(/'/g, "\\'") + "'."); | 
					
						
							| 
									
										
										
										
											2014-03-07 17:14:13 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-16 11:57:51 -06: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 14:11:15 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-16 11:57:51 -06:00
										 |  |  |     // main (combined) output file location, relative to this script's location
 | 
					
						
							|  |  |  |     outputFile = path.resolve(__dirname, filename); | 
					
						
							| 
									
										
										
										
											2014-06-16 14:21:06 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-16 11:57:51 -06:00
										 |  |  |     // pems/ output directory, in the same directory as the outputFile
 | 
					
						
							|  |  |  |     outputPemsDir = path.resolve(outputFile, '../pems'); | 
					
						
							| 
									
										
										
										
											2014-06-16 14:26:45 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-16 11:57:51 -06: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-07 17:14:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-16 11:57:51 -06: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-07 17:14:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-16 11:57:51 -06:00
										 |  |  |       var lines = body.split("\n") | 
					
						
							|  |  |  |         , certs = parseCertData(lines) | 
					
						
							|  |  |  |         , pemsFile = path.join(outputPemsDir, 'mozilla-certdata.txt') | 
					
						
							|  |  |  |         ; | 
					
						
							| 
									
										
										
										
											2014-06-17 20:07:25 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-16 11:57:51 -06: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); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | } |