258 lines
6.7 KiB
JavaScript
258 lines
6.7 KiB
JavaScript
|
var helper = require('./helper.js')
|
||
|
var cpspawn = require('child_process').spawn
|
||
|
var pathlib = require('path')
|
||
|
var fs = require('fs')
|
||
|
var osTmpdir = require('os-tmpdir')
|
||
|
var crypto = require('crypto')
|
||
|
var which = require('which')
|
||
|
var settings = {}
|
||
|
var tempDir = process.env.PEMJS_TMPDIR || osTmpdir()
|
||
|
|
||
|
/**
|
||
|
* pem openssl module
|
||
|
*
|
||
|
* @module openssl
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* configue this openssl module
|
||
|
*
|
||
|
* @static
|
||
|
* @param {String} option name e.g. pathOpenSSL, openSslVersion; TODO rethink nomenclature
|
||
|
* @param {*} value value
|
||
|
*/
|
||
|
function set (option, value) {
|
||
|
settings[option] = value
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* get configuration setting value
|
||
|
*
|
||
|
* @static
|
||
|
* @param {String} option name
|
||
|
*/
|
||
|
function get (option) {
|
||
|
return settings[option] || null
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Spawn an openssl command
|
||
|
*
|
||
|
* @static
|
||
|
* @param {Array} params Array of openssl command line parameters
|
||
|
* @param {String} searchStr String to use to find data
|
||
|
* @param {Array} [tmpfiles] list of temporary files
|
||
|
* @param {Function} callback Called with (error, stdout-substring)
|
||
|
*/
|
||
|
function exec (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')))) {
|
||
|
start = start.index
|
||
|
} else {
|
||
|
start = -1
|
||
|
}
|
||
|
|
||
|
// To get the full EC key with parameters and private key
|
||
|
if (searchStr === 'EC PARAMETERS') {
|
||
|
searchStr = 'EC PRIVATE KEY'
|
||
|
}
|
||
|
|
||
|
if ((end = stdout.match(new RegExp('^\\-+END ' + searchStr + '\\-+', 'm')))) {
|
||
|
end = end.index + end[0].length
|
||
|
} else {
|
||
|
end = -1
|
||
|
}
|
||
|
|
||
|
if (start >= 0 && end >= 0) {
|
||
|
return callback(null, stdout.substring(start, end))
|
||
|
} else {
|
||
|
return callback(new Error(searchStr + ' not found from openssl output:\n---stdout---\n' + stdout + '\n---stderr---\n' + stderr + '\ncode: ' + code))
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Spawn an openssl command and get binary output
|
||
|
*
|
||
|
* @static
|
||
|
* @param {Array} params Array of openssl command line parameters
|
||
|
* @param {Array} [tmpfiles] list of temporary files
|
||
|
* @param {Function} callback Called with (error, stdout)
|
||
|
*/
|
||
|
function execBinary (params, tmpfiles, callback) {
|
||
|
if (!callback && typeof tmpfiles === 'function') {
|
||
|
callback = tmpfiles
|
||
|
tmpfiles = false
|
||
|
}
|
||
|
spawnWrapper(params, tmpfiles, true, function (err, code, stdout, stderr) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
return callback(null, stdout)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generically spawn openSSL, without processing the result
|
||
|
*
|
||
|
* @static
|
||
|
* @param {Array} params The parameters to pass to openssl
|
||
|
* @param {Boolean} binary Output of openssl is binary or text
|
||
|
* @param {Function} callback Called with (error, exitCode, stdout, stderr)
|
||
|
*/
|
||
|
function spawn (params, binary, callback) {
|
||
|
var pathBin = get('pathOpenSSL') || process.env.OPENSSL_BIN || 'openssl'
|
||
|
|
||
|
testOpenSSLPath(pathBin, function (err) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
var openssl = cpspawn(pathBin, params)
|
||
|
var stderr = ''
|
||
|
|
||
|
var stdout = (binary ? Buffer.alloc(0) : '')
|
||
|
openssl.stdout.on('data', function (data) {
|
||
|
if (!binary) {
|
||
|
stdout += data.toString('binary')
|
||
|
} else {
|
||
|
stdout = Buffer.concat([stdout, data])
|
||
|
}
|
||
|
})
|
||
|
|
||
|
openssl.stderr.on('data', function (data) {
|
||
|
stderr += data.toString('binary')
|
||
|
})
|
||
|
// 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 finished = false
|
||
|
var done = function (err) {
|
||
|
if (finished) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if (err) {
|
||
|
finished = true
|
||
|
return callback(err)
|
||
|
}
|
||
|
|
||
|
if (--needed < 1) {
|
||
|
finished = true
|
||
|
if (code) {
|
||
|
if (code === 2 && (stderr === '' || /depth lookup: unable to/.test(stderr))) {
|
||
|
return callback(null, code, stdout, stderr)
|
||
|
}
|
||
|
return callback(new Error('Invalid openssl exit code: ' + code + '\n% openssl ' + params.join(' ') + '\n' + stderr), code)
|
||
|
} else {
|
||
|
return callback(null, code, stdout, stderr)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
openssl.on('error', done)
|
||
|
|
||
|
openssl.on('exit', function (ret) {
|
||
|
code = ret
|
||
|
done()
|
||
|
})
|
||
|
|
||
|
openssl.on('close', function () {
|
||
|
stdout = (binary ? stdout : Buffer.from(stdout, 'binary').toString('utf-8'))
|
||
|
stderr = Buffer.from(stderr, 'binary').toString('utf-8')
|
||
|
done()
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Wrapper for spawn method
|
||
|
*
|
||
|
* @static
|
||
|
* @param {Array} params The parameters to pass to openssl
|
||
|
* @param {Array} [tmpfiles] list of temporary files
|
||
|
* @param {Boolean} [binary] Output of openssl is binary or text
|
||
|
* @param {Function} callback Called with (error, exitCode, stdout, stderr)
|
||
|
*/
|
||
|
function spawnWrapper (params, tmpfiles, binary, callback) {
|
||
|
if (!callback && typeof binary === 'function') {
|
||
|
callback = binary
|
||
|
binary = false
|
||
|
}
|
||
|
|
||
|
var files = []
|
||
|
var delTempPWFiles = []
|
||
|
|
||
|
if (tmpfiles) {
|
||
|
tmpfiles = [].concat(tmpfiles)
|
||
|
var fpath, i
|
||
|
for (i = 0; i < params.length; i++) {
|
||
|
if (params[i] === '--TMPFILE--') {
|
||
|
fpath = pathlib.join(tempDir, crypto.randomBytes(20).toString('hex'))
|
||
|
files.push({
|
||
|
path: fpath,
|
||
|
contents: tmpfiles.shift()
|
||
|
})
|
||
|
params[i] = fpath
|
||
|
delTempPWFiles.push(fpath)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var file
|
||
|
for (i = 0; i < files.length; i++) {
|
||
|
file = files[i]
|
||
|
fs.writeFileSync(file.path, file.contents)
|
||
|
}
|
||
|
|
||
|
spawn(params, binary, function (err, code, stdout, stderr) {
|
||
|
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
|
||
|
callback(err || fsErr, code, stdout, stderr)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validates the pathBin for the openssl command
|
||
|
*
|
||
|
* @private
|
||
|
* @param {String} pathBin The path to OpenSSL Bin
|
||
|
* @param {Function} callback Callback function with an error object
|
||
|
*/
|
||
|
function testOpenSSLPath (pathBin, callback) {
|
||
|
which(pathBin, function (error) {
|
||
|
if (error) {
|
||
|
return callback(new Error('Could not find openssl on your system on this path: ' + pathBin))
|
||
|
}
|
||
|
callback()
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/* Once PEM is imported, the openSslVersion is set with this function. */
|
||
|
spawn(['version'], false, function (err, code, stdout, stderr) {
|
||
|
var text = String(stdout) + '\n' + String(stderr) + '\n' + String(err)
|
||
|
var tmp = text.match(/^LibreSSL/i)
|
||
|
set('openSslVersion', (tmp && tmp[0] ? 'LibreSSL' : 'openssl').toUpperCase())
|
||
|
})
|
||
|
|
||
|
module.exports = {
|
||
|
exec: exec,
|
||
|
execBinary: execBinary,
|
||
|
spawn: spawn,
|
||
|
spawnWrapper: spawnWrapper,
|
||
|
set: set,
|
||
|
get: get
|
||
|
}
|