Comparing version 0.1.0 to 0.2.0
289
lib/pem.js
@@ -1,2 +0,7 @@ | ||
var spawn = require("child_process").spawn; | ||
var spawn = require("child_process").spawn, | ||
os = require("os"), | ||
pathlib = require("path"), | ||
fs = require("fs"), | ||
crypto = require("crypto"), | ||
tempDir = (os.tmpdir || os.tmpDir) && (os.tmpdir || os.tmpDir)() || "/tmp"; | ||
@@ -8,2 +13,3 @@ module.exports.createPrivateKey = createPrivateKey; | ||
module.exports.getPublicKey = getPublicKey; | ||
module.exports.getFingerprint = getFingerprint; | ||
@@ -14,3 +20,3 @@ // PUBLIC API | ||
* Creates a private key | ||
* | ||
* | ||
* @param {Number} [keyBitsize=1024] Size of the key, defaults to 1024bit | ||
@@ -24,3 +30,3 @@ * @param {Function} callback Callback function with an error object and {key} | ||
} | ||
keyBitsize = Number(keyBitsize) || 1024; | ||
@@ -33,3 +39,3 @@ | ||
]; | ||
execOpenSSL(params, "RSA PRIVATE KEY", function(error, key){ | ||
@@ -45,6 +51,6 @@ if(error){ | ||
* Creates a Certificate Signing Request | ||
* | ||
* | ||
* If client key is undefined, a new key is created automatically. The used key is included | ||
* in the callback return as clientKey | ||
* | ||
* | ||
* @param {Object} [options] Optional options object | ||
@@ -68,5 +74,5 @@ * @param {String} [options.clientKey] Optional client key to use | ||
} | ||
options = options || {}; | ||
if(!options.clientKey){ | ||
@@ -82,3 +88,3 @@ createPrivateKey(options.keyBitsize || 1024, function(error, keyData){ | ||
} | ||
var params = ["req", | ||
@@ -90,5 +96,5 @@ "-new", | ||
"-key", | ||
"/dev/stdin" | ||
"--TMPFILE--" | ||
]; | ||
execOpenSSL(params, "CERTIFICATE REQUEST", options.clientKey, function(error, data){ | ||
@@ -103,3 +109,3 @@ if(error){ | ||
return callback(null, response); | ||
}); | ||
@@ -112,3 +118,3 @@ } | ||
* can be used as with createCSR. | ||
* | ||
* | ||
* @param {Object} [options] Optional options object | ||
@@ -126,5 +132,5 @@ * @param {String} [options.serviceKey] Private key for signing the certificate, if not defined a new one is generated | ||
} | ||
options = options || {}; | ||
if(!options.csr){ | ||
@@ -141,5 +147,5 @@ createCSR(options, function(error, keyData){ | ||
} | ||
if(!options.serviceKey){ | ||
if(options.selfSigned){ | ||
@@ -158,3 +164,3 @@ options.serviceKey = options.clientKey; | ||
} | ||
var params = ["x509", | ||
@@ -165,8 +171,24 @@ "-req", | ||
"-in", | ||
"/dev/stdin", | ||
"-signkey", | ||
"/dev/stdin" | ||
"--TMPFILE--" | ||
]; | ||
var tmpfiles = [options.csr]; | ||
if (options.serviceCertificate) { | ||
if (!options.serial) { | ||
return callback(new Error("serial option required for CA signing")); | ||
} | ||
params.push("-CA"); | ||
params.push("--TMPFILE--"); | ||
params.push("-CAkey"); | ||
params.push("--TMPFILE--"); | ||
params.push("-set_serial"); | ||
params.push("0x" + ("00000000" + options.serial.toString(16)).slice(-8)); | ||
tmpfiles.push(options.serviceCertificate) | ||
tmpfiles.push(options.serviceKey) | ||
} else { | ||
params.push("-signkey"); | ||
params.push("--TMPFILE--"); | ||
tmpfiles.push(options.serviceKey) | ||
} | ||
execOpenSSL(params, "CERTIFICATE", [options.csr, options.serviceKey], function(error, data){ | ||
execOpenSSL(params, "CERTIFICATE", tmpfiles, function(error, data){ | ||
if(error){ | ||
@@ -187,3 +209,3 @@ return callback(error); | ||
* Exports a public key from a private key, CSR or certificate | ||
* | ||
* | ||
* @param {String} certificate PEM encoded private key, CSR or certificate | ||
@@ -197,11 +219,11 @@ * @param {Function} callback Callback function with an error object and {publicKey} | ||
} | ||
certificate = (certificate || "").toString(); | ||
var params; | ||
if(certificate.match(/BEGIN CERTIFICATE REQUEST/)){ | ||
params = ["req", | ||
"-in", | ||
"/dev/stdin", | ||
"--TMPFILE--", | ||
"-pubkey", | ||
@@ -212,3 +234,3 @@ "-noout"]; | ||
"-in", | ||
"/dev/stdin", | ||
"--TMPFILE--", | ||
"-pubout"]; | ||
@@ -218,3 +240,3 @@ }else{ | ||
"-in", | ||
"/dev/stdin", | ||
"--TMPFILE--", | ||
"-pubkey", | ||
@@ -234,3 +256,3 @@ "-noout"]; | ||
* Reads subject data from a certificate or a CSR | ||
* | ||
* | ||
* @param {String} certificate PEM encoded CSR or certificate | ||
@@ -244,5 +266,5 @@ * @param {Function} callback Callback function with an error object and {country, state, locality, organization, organizationUnit, commonName, emailAddress} | ||
} | ||
certificate = (certificate || "").toString(); | ||
var type = certificate.match(/BEGIN CERTIFICATE REQUEST/)?"req":"x509", | ||
@@ -253,28 +275,35 @@ params = [type, | ||
"-in", | ||
"/dev/stdin" | ||
], | ||
openssl = spawn("openssl", params), | ||
stdout = "", | ||
stderr = ""; | ||
openssl.stdin.write(certificate); | ||
openssl.stdin.end(); | ||
openssl.stdout.on('data', function (data) { | ||
stdout += (data || "").toString("binary"); | ||
"--TMPFILE--" | ||
]; | ||
spawnWrapper(params, certificate, function(err, code, stdout, stderr){ | ||
if (err) { | ||
return callback(err); | ||
} | ||
return fetchCertificateData(stdout, callback); | ||
}); | ||
} | ||
openssl.stderr.on('data', function (data) { | ||
stderr += (data || "").toString("binary"); | ||
}); | ||
/** | ||
* Gets the fingerprint for a certificate | ||
* | ||
* @param {String} PEM encoded certificate | ||
* @param {Function} callback Callback function with an error object and {fingerprint} | ||
*/ | ||
function getFingerprint(certificate, callback){ | ||
var params = ["x509", | ||
"-in", | ||
"--TMPFILE--", | ||
"-fingerprint", | ||
"-noout"]; | ||
openssl.on('exit', function (code) { | ||
if(code){ | ||
return callback(new Error("Invalid openssl exit code "+code)); | ||
spawnWrapper(params, certificate, function(err, code, stdout, stderr){ | ||
if (err) { | ||
return callback(err); | ||
} | ||
stdout = new Buffer(stdout, "binary").toString("utf-8"); | ||
stderr = new Buffer(stderr, "binary").toString("utf-8"); | ||
return fetchCertificateData(stdout, callback); | ||
var match = stdout.match(/Fingerprint=([0-9a-fA-F:]+)$/m); | ||
if (match){ | ||
return callback(null, {fingerprint: match[1]}); | ||
} else { | ||
return callback(new Error("No fingerprint")); | ||
} | ||
}); | ||
@@ -287,5 +316,5 @@ } | ||
certData = (certData || "").toString(); | ||
var subject, extra, tmp, certValues = {}; | ||
if((subject = certData.match(/Subject:([^\n]*)\n/)) && subject.length>1){ | ||
@@ -325,3 +354,3 @@ subject = subject[1]; | ||
options = options || {}; | ||
var csrData = { | ||
@@ -337,3 +366,3 @@ C: options.country || options.C || "", | ||
csrBuilder = []; | ||
Object.keys(csrData).forEach(function(key){ | ||
@@ -344,3 +373,3 @@ if(csrData[key]){ | ||
}); | ||
return csrBuilder.join(""); | ||
@@ -350,33 +379,13 @@ } | ||
/** | ||
* Spawn an openssl command | ||
* Generically spawn openSSL, without processing the result | ||
* | ||
* @param {Array} params The parameters to pass to openssl | ||
* @param {String|Array} tmpfiles Stuff to pass to tmpfiles | ||
* @param {Function} callback Called with (error, exitCode, stdout, stderr) | ||
*/ | ||
function execOpenSSL(params, searchStr, stdin, callback){ | ||
function spawnOpenSSL(params, callback) { | ||
var openssl = spawn("openssl", params), | ||
stdout = "", | ||
stderr = "", | ||
pushToStdin = function(){ | ||
var data = stdin.shift(); | ||
if(data){ | ||
openssl.stdin.write(data + "\n"); | ||
if(!stdin.length){ | ||
openssl.stdin.end(); | ||
} | ||
} | ||
}; | ||
if(!callback && typeof stdin == "function"){ | ||
callback = stdin; | ||
stdin = false; | ||
} | ||
if(stdin){ | ||
if(Array.isArray(stdin)){ | ||
pushToStdin(); | ||
}else{ | ||
openssl.stdin.write(stdin); | ||
openssl.stdin.end(); | ||
} | ||
} | ||
stderr = ""; | ||
openssl.stdout.on('data', function (data) { | ||
@@ -388,17 +397,96 @@ stdout += (data || "").toString("binary"); | ||
stderr += (data || "").toString("binary"); | ||
if(Array.isArray(stdin)){ | ||
pushToStdin(); | ||
}); | ||
// We need both the return code and access to all of stdout. Stdout isn't | ||
// *really* available until the close event fires; the timing nuance was | ||
// making this fail periodically. | ||
var needed = 2; // wait for both exit and close. | ||
var code = -1; | ||
var bothDone = function() { | ||
if (code) { | ||
callback(new Error("Invalid openssl exit code: " + code + "\n% openssl " + params.join(" ") + "\n" + stderr), code); | ||
} else { | ||
callback(null, code, stdout, stderr); | ||
} | ||
}; | ||
openssl.on('exit', function (ret) { | ||
code = ret; | ||
if (--needed < 1) { | ||
bothDone(); | ||
} | ||
}); | ||
openssl.on('exit', function (code) { | ||
var start, end; | ||
openssl.on('close', function () { | ||
stdout = new Buffer(stdout, "binary").toString("utf-8"); | ||
stderr = new Buffer(stderr, "binary").toString("utf-8"); | ||
if(code){ | ||
return callback(new Error("Invalid openssl exit code "+code+"\nSTDOUT:\n"+stdout+"\nSTDERR:\n"+stderr)); | ||
if (--needed < 1) { | ||
bothDone(); | ||
} | ||
}); | ||
} | ||
function spawnWrapper(params, tmpfiles, callback){ | ||
var files = []; | ||
if(tmpfiles){ | ||
tmpfiles = [].concat(tmpfiles || []); | ||
params.forEach(function(value, i){ | ||
var fname; | ||
if(value == "--TMPFILE--"){ | ||
fpath = pathlib.join(tempDir, crypto.randomBytes(20).toString("hex")); | ||
files.push({ | ||
path: fpath, | ||
contents: tmpfiles.shift() | ||
}); | ||
params[i] = fpath; | ||
} | ||
}) | ||
} | ||
var processFiles = function(){ | ||
var file = files.shift(); | ||
if(!file){ | ||
return spawnSSL(); | ||
} | ||
fs.writeFile(file.path, file.contents, function(err, bytes){ | ||
processFiles(); | ||
}); | ||
} | ||
var spawnSSL = function(){ | ||
spawnOpenSSL(params, function(err, code, stdout, stderr) { | ||
var start, end; | ||
files.forEach(function(file){ | ||
fs.unlink(file.path); | ||
}) | ||
callback(err, code, stdout, stderr); | ||
}); | ||
} | ||
processFiles(); | ||
} | ||
/** | ||
* Spawn an openssl command | ||
*/ | ||
function execOpenSSL(params, searchStr, tmpfiles, callback){ | ||
if(!callback && typeof tmpfiles == "function"){ | ||
callback = tmpfiles; | ||
tmpfiles = false; | ||
} | ||
spawnWrapper(params, tmpfiles, function(err, code, stdout, stderr) { | ||
var start, end; | ||
if (err) { | ||
return callback(err); | ||
} | ||
if((start = stdout.match(new RegExp("\\-+BEGIN "+searchStr+"\\-+$", "m")))){ | ||
@@ -409,3 +497,3 @@ start = start.index; | ||
} | ||
if((end = stdout.match(new RegExp("^\\-+END "+searchStr+"\\-+", "m")))){ | ||
@@ -416,10 +504,9 @@ end = end.index + (end[0] || "").length; | ||
} | ||
if(start >= 0 && end >=0){ | ||
return callback(null, stdout.substring(start, end)); | ||
}else{ | ||
return callback(new Error(searchStr + " not found from openssl output")); | ||
return callback(new Error(searchStr + " not found from openssl output:\n---stdout---\n" + stdout + "\n---stderr---\n" + stderr + "\ncode: " + code + "\nsignal: " + signal)); | ||
} | ||
}); | ||
} | ||
} |
@@ -5,3 +5,3 @@ { | ||
"description": "Create private keys and certificates with node.js", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"repository": { | ||
@@ -8,0 +8,0 @@ "type": "git", |
@@ -8,4 +8,2 @@ pem | ||
**NB!** This module does not yet support node v0.7+/0.8 or Windows. Sorry. | ||
## Installation | ||
@@ -97,4 +95,15 @@ | ||
### Get fingerprint | ||
Use `getFingerprint` to get the SHA1 fingerprint for a certificate | ||
pem.getFingerprint(certificate, callback) | ||
Where | ||
* **certificate** is a PEM encoded certificate | ||
* **callback** is a callback function with an error object and `{fingerprint}` | ||
## License | ||
**MIT** | ||
**MIT** |
126
test/pem.js
@@ -17,3 +17,3 @@ var pem = require(".."), | ||
}, | ||
"Create 2048bit Private key": function(test){ | ||
@@ -30,3 +30,3 @@ pem.createPrivateKey(2048, function(error, data){ | ||
}, | ||
"Create default CSR": function(test){ | ||
@@ -39,13 +39,13 @@ pem.createCSR(function(error, data){ | ||
test.ok(csr.match(/\n\-\-\-\-\-END CERTIFICATE REQUEST\-\-\-\-\-\n*$/)); | ||
test.ok(data && data.clientKey); | ||
test.done(); | ||
}); | ||
}, | ||
"Create CSR with own key": function(test){ | ||
pem.createPrivateKey(function(error, data){ | ||
var key = (data && data.key || "").toString(); | ||
pem.createCSR({clientKey: key}, function(error, data){ | ||
@@ -57,13 +57,13 @@ var csr = (data && data.csr || "").toString(); | ||
test.ok(csr.match(/\n\-\-\-\-\-END CERTIFICATE REQUEST\-\-\-\-\-\n*$/)); | ||
test.equal(data && data.clientKey, key); | ||
test.ok(data && data.clientKey); | ||
test.done(); | ||
}); | ||
}); | ||
}, | ||
"Create default certificate": function(test){ | ||
@@ -76,13 +76,13 @@ pem.createCertificate(function(error, data){ | ||
test.ok(certificate.match(/\n\-\-\-\-\-END CERTIFICATE\-\-\-\-\-\n*$/)); | ||
test.ok((data && data.clientKey) != (data && data.serviceKey)); | ||
test.ok(data && data.clientKey); | ||
test.ok(data && data.serviceKey); | ||
test.ok(data && data.csr); | ||
test.done(); | ||
}); | ||
}, | ||
"Create self signed certificate": function(test){ | ||
@@ -95,13 +95,13 @@ pem.createCertificate({selfSigned: true}, function(error, data){ | ||
test.ok(certificate.match(/\n\-\-\-\-\-END CERTIFICATE\-\-\-\-\-\n*$/)); | ||
test.ok((data && data.clientKey) == (data && data.serviceKey)); | ||
test.ok(data && data.clientKey); | ||
test.ok(data && data.serviceKey); | ||
test.ok(data && data.csr); | ||
test.done(); | ||
}); | ||
}, | ||
"Read default cert data from CSR": function(test){ | ||
@@ -111,3 +111,3 @@ pem.createCSR(function(error, data){ | ||
test.ifError(error); | ||
pem.readCertificateInfo(csr, function(error, data){ | ||
@@ -127,10 +127,10 @@ test.ifError(error); | ||
}, | ||
"Read edited cert data from CSR": function(test){ | ||
var certInfo = {country:"EE", | ||
state:"Harjumaa", | ||
locality:"Tallinn", | ||
organization:"Node.ee", | ||
organizationUnit:"test", | ||
commonName:"www.node.ee", | ||
var certInfo = {country:"EE", | ||
state:"Harjumaa", | ||
locality:"Tallinn", | ||
organization:"Node.ee", | ||
organizationUnit:"test", | ||
commonName:"www.node.ee", | ||
emailAddress:"andris@node.ee"}; | ||
@@ -140,3 +140,3 @@ pem.createCSR(Object.create(certInfo), function(error, data){ | ||
test.ifError(error); | ||
pem.readCertificateInfo(csr, function(error, data){ | ||
@@ -149,3 +149,3 @@ test.ifError(error); | ||
}, | ||
"Read default cert data from certificate": function(test){ | ||
@@ -155,3 +155,3 @@ pem.createCertificate(function(error, data){ | ||
test.ifError(error); | ||
pem.readCertificateInfo(certificate, function(error, data){ | ||
@@ -171,10 +171,10 @@ test.ifError(error); | ||
}, | ||
"Read edited cert data from certificate": function(test){ | ||
var certInfo = {country:"EE", | ||
state:"Harjumaa", | ||
locality:"Tallinn", | ||
organization:"Node.ee", | ||
organizationUnit:"test", | ||
commonName:"www.node.ee", | ||
var certInfo = {country:"EE", | ||
state:"Harjumaa", | ||
locality:"Tallinn", | ||
organization:"Node.ee", | ||
organizationUnit:"test", | ||
commonName:"www.node.ee", | ||
emailAddress:"andris@node.ee"}; | ||
@@ -184,3 +184,3 @@ pem.createCertificate(Object.create(certInfo), function(error, data){ | ||
test.ifError(error); | ||
pem.readCertificateInfo(certificate, function(error, data){ | ||
@@ -193,3 +193,3 @@ test.ifError(error); | ||
}, | ||
"Get public key from private key": function(test){ | ||
@@ -200,3 +200,3 @@ pem.createPrivateKey(function(error, data){ | ||
test.ok(key); | ||
pem.getPublicKey(key, function(error, data){ | ||
@@ -206,12 +206,12 @@ var pubkey = (data && data.publicKey || "").toString(); | ||
test.ok(pubkey); | ||
test.ok(pubkey.match(/^\n*\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-\n/)); | ||
test.ok(pubkey.match(/\n\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-\n*$/)); | ||
test.done(); | ||
}); | ||
}); | ||
}, | ||
"Get public key from CSR": function(test){ | ||
@@ -222,3 +222,3 @@ pem.createCSR(function(error, data){ | ||
test.ok(key); | ||
pem.getPublicKey(key, function(error, data){ | ||
@@ -228,12 +228,12 @@ var pubkey = (data && data.publicKey || "").toString(); | ||
test.ok(pubkey); | ||
test.ok(pubkey.match(/^\n*\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-\n/)); | ||
test.ok(pubkey.match(/\n\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-\n*$/)); | ||
test.done(); | ||
}); | ||
}); | ||
}, | ||
"Get public key from certificate": function(test){ | ||
@@ -244,3 +244,3 @@ pem.createCertificate(function(error, data){ | ||
test.ok(key); | ||
pem.getPublicKey(key, function(error, data){ | ||
@@ -250,11 +250,29 @@ var pubkey = (data && data.publicKey || "").toString(); | ||
test.ok(pubkey); | ||
test.ok(pubkey.match(/^\n*\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-\n/)); | ||
test.ok(pubkey.match(/\n\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-\n*$/)); | ||
test.done(); | ||
}); | ||
}); | ||
}, | ||
"Get fingerprint from certificate": function(test){ | ||
pem.createCertificate(function(error, data){ | ||
var certificate = (data && data.certificate || "").toString(); | ||
test.ifError(error); | ||
test.ok(certificate); | ||
pem.getFingerprint(certificate, function(error, data){ | ||
var fingerprint = (data && data.fingerprint || "").toString(); | ||
test.ifError(error); | ||
test.ok(fingerprint); | ||
test.ok(fingerprint.match(/^[0-9A-F]{2}(:[0-9A-F]{2}){19}$/)); | ||
test.done(); | ||
}); | ||
}); | ||
} | ||
}; | ||
}; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
29058
632
108
1