var helper = require('./helper.js')
var {debug} = require('./debug.js')
var cpspawn = require('child_process').spawn
var spawnSync = require('child_process').spawnSync
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()
const versionRegEx = new RegExp('^(OpenSSL|LibreSSL) (((\\d+).(\\d+)).(\\d+))([a-z]+)?')
if ("CI" in process.env && process.env.CI === 'true') {
if ("LIBRARY" in process.env && "VERSION" in process.env && process.env.LIBRARY != "" && process.env.VERSION != "") {
const filePathOpenSSL=`./openssl/${process.env.LIBRARY}_v${process.env.VERSION}/bin/openssl`
if (fs.existsSync(filePathOpenSSL)) {
process.env.OPENSSL_BIN = filePathOpenSSL
}
}
}
/**
* 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 + '-+$', 'mu')))) {
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) {
debug("execBinary", {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 !== 0) {
if (code === 2 && (stderr === '' || /depth lookup: unable to/.test(stderr) || /depth lookup: self(-|\s)signed certificate/.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) {
debug(params[0], {
err: err,
fsErr: fsErr,
code: code,
stdout: stdout,
stderr: stderr
})
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. */
function setVersion() {
var pathBin = get('pathOpenSSL') || process.env.OPENSSL_BIN || 'openssl'
var output = spawnSync(pathBin, ['version'])
var text = String(output.stdout) + '\n' + String(output.stderr) + '\n' + String(output.error)
let version = versionRegEx.exec(text)
if (version === null || version.length <= 7) return
set('openSslVersion', (version[1]).toUpperCase())
set('Vendor', (version[1]).toUpperCase())
set('VendorVersion', version[2])
set('VendorVersionMajorMinor', version[3])
set('VendorVersionMajor', version[4])
set('VendorVersionMinor', version[5])
set('VendorVersionPatch', version[6])
set('VendorVersionBuildChar', typeof version[7] === 'undefined' ? '' : version[7])
};
setVersion();
module.exports = {
exec: exec,
execBinary: execBinary,
spawn: spawn,
spawnWrapper: spawnWrapper,
settings: settings,
set: set,
get: get
}