forked from arne/asdf-games
1324 lines
40 KiB
JavaScript
1324 lines
40 KiB
JavaScript
|
'use strict'
|
||
|
|
||
|
/**
|
||
|
* pem module
|
||
|
*
|
||
|
* @module pem
|
||
|
*/
|
||
|
|
||
|
const { promisify } = require('es6-promisify')
|
||
|
var net = require('net')
|
||
|
var helper = require('./helper.js')
|
||
|
var openssl = require('./openssl.js')
|
||
|
|
||
|
module.exports.createPrivateKey = createPrivateKey
|
||
|
module.exports.createDhparam = createDhparam
|
||
|
module.exports.createEcparam = createEcparam
|
||
|
module.exports.createCSR = createCSR
|
||
|
module.exports.createCertificate = createCertificate
|
||
|
module.exports.readCertificateInfo = readCertificateInfo
|
||
|
module.exports.getPublicKey = getPublicKey
|
||
|
module.exports.getFingerprint = getFingerprint
|
||
|
module.exports.getModulus = getModulus
|
||
|
module.exports.getDhparamInfo = getDhparamInfo
|
||
|
module.exports.createPkcs12 = createPkcs12
|
||
|
module.exports.readPkcs12 = readPkcs12
|
||
|
module.exports.verifySigningChain = verifySigningChain
|
||
|
module.exports.checkCertificate = checkCertificate
|
||
|
module.exports.checkPkcs12 = checkPkcs12
|
||
|
module.exports.config = config
|
||
|
|
||
|
/**
|
||
|
* quick access the convert module
|
||
|
* @type {module:convert}
|
||
|
*/
|
||
|
module.exports.convert = require('./convert.js')
|
||
|
|
||
|
var KEY_START = '-----BEGIN PRIVATE KEY-----'
|
||
|
var KEY_END = '-----END PRIVATE KEY-----'
|
||
|
var RSA_KEY_START = '-----BEGIN RSA PRIVATE KEY-----'
|
||
|
var RSA_KEY_END = '-----END RSA PRIVATE KEY-----'
|
||
|
var ENCRYPTED_KEY_START = '-----BEGIN ENCRYPTED PRIVATE KEY-----'
|
||
|
var ENCRYPTED_KEY_END = '-----END ENCRYPTED PRIVATE KEY-----'
|
||
|
var CERT_START = '-----BEGIN CERTIFICATE-----'
|
||
|
var CERT_END = '-----END CERTIFICATE-----'
|
||
|
|
||
|
/**
|
||
|
* Creates a private key
|
||
|
*
|
||
|
* @static
|
||
|
* @param {Number} [keyBitsize=2048] Size of the key, defaults to 2048bit
|
||
|
* @param {Object} [options] object of cipher and password {cipher:'aes128',password:'xxx'}, defaults empty object
|
||
|
* @param {String} [options.cipher] string of the cipher for the encryption - needed with password
|
||
|
* @param {String} [options.password] string of the cipher password for the encryption needed with cipher
|
||
|
* @param {Function} callback Callback function with an error object and {key}
|
||
|
*/
|
||
|
function createPrivateKey (keyBitsize, options, callback) {
|
||
|
if (!callback && !options && typeof keyBitsize === 'function') {
|
||
|
callback = keyBitsize
|
||
|
keyBitsize = undefined
|
||
|
options = {}
|
||
|
} else if (!callback && keyBitsize && typeof options === 'function') {
|
||
|
callback = options
|
||
|
options = {}
|
||
|
}
|
||
|
|
||
|
keyBitsize = Number(keyBitsize) || 2048
|
||
|
|
||
|
var params = ['genrsa']
|
||
|
var delTempPWFiles = []
|
||
|
|
||
|
if (options && options.cipher && (Number(helper.ciphers.indexOf(options.cipher)) !== -1) && options.password) {
|
||
|
helper.createPasswordFile({ 'cipher': options.cipher, 'password': options.password, 'passType': 'out' }, params, delTempPWFiles)
|
||
|
}
|
||
|
|
||
|
params.push(keyBitsize)
|
||
|
|
||
|
openssl.exec(params, 'RSA PRIVATE KEY', function (sslErr, key) {
|
||
|
function done (err) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
callback(null, {
|
||
|
key: key
|
||
|
})
|
||
|
}
|
||
|
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
|
||
|
done(sslErr || fsErr)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a dhparam key
|
||
|
*
|
||
|
* @static
|
||
|
* @param {Number} [keyBitsize=512] Size of the key, defaults to 512bit
|
||
|
* @param {Function} callback Callback function with an error object and {dhparam}
|
||
|
*/
|
||
|
function createDhparam (keyBitsize, callback) {
|
||
|
if (!callback && typeof keyBitsize === 'function') {
|
||
|
callback = keyBitsize
|
||
|
keyBitsize = undefined
|
||
|
}
|
||
|
|
||
|
keyBitsize = Number(keyBitsize) || 512
|
||
|
|
||
|
var params = ['dhparam',
|
||
|
'-outform',
|
||
|
'PEM',
|
||
|
keyBitsize
|
||
|
]
|
||
|
|
||
|
openssl.exec(params, 'DH PARAMETERS', function (error, dhparam) {
|
||
|
if (error) {
|
||
|
return callback(error)
|
||
|
}
|
||
|
return callback(null, {
|
||
|
dhparam: dhparam
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a ecparam key
|
||
|
* @static
|
||
|
* @param {String} [keyName=secp256k1] Name of the key, defaults to secp256k1
|
||
|
* @param {String} [paramEnc=explicit] Encoding of the elliptic curve parameters, defaults to explicit
|
||
|
* @param {Boolean} [noOut=false] This option inhibits the output of the encoded version of the parameters.
|
||
|
* @param {Function} callback Callback function with an error object and {ecparam}
|
||
|
*/
|
||
|
function createEcparam (keyName, paramEnc, noOut, callback) {
|
||
|
if (!callback && typeof noOut === 'undefined' && !paramEnc && typeof keyName === 'function') {
|
||
|
callback = keyName
|
||
|
keyName = undefined
|
||
|
} else if (!callback && typeof noOut === 'undefined' && keyName && typeof paramEnc === 'function') {
|
||
|
callback = paramEnc
|
||
|
paramEnc = undefined
|
||
|
} else if (!callback && typeof noOut === 'function' && keyName && paramEnc) {
|
||
|
callback = noOut
|
||
|
noOut = undefined
|
||
|
}
|
||
|
|
||
|
keyName = keyName || 'secp256k1'
|
||
|
paramEnc = paramEnc || 'explicit'
|
||
|
noOut = noOut || false
|
||
|
|
||
|
var params = ['ecparam',
|
||
|
'-name',
|
||
|
keyName,
|
||
|
'-genkey',
|
||
|
'-param_enc',
|
||
|
paramEnc
|
||
|
]
|
||
|
|
||
|
var searchString = 'EC PARAMETERS'
|
||
|
if (noOut) {
|
||
|
params.push('-noout')
|
||
|
searchString = 'EC PRIVATE KEY'
|
||
|
}
|
||
|
|
||
|
openssl.exec(params, searchString, function (error, ecparam) {
|
||
|
if (error) {
|
||
|
return callback(error)
|
||
|
}
|
||
|
return callback(null, {
|
||
|
ecparam: ecparam
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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
|
||
|
* @static
|
||
|
* @param {Object} [options] Optional options object
|
||
|
* @param {String} [options.clientKey] Optional client key to use
|
||
|
* @param {Number} [options.keyBitsize] If clientKey is undefined, bit size to use for generating a new key (defaults to 2048)
|
||
|
* @param {String} [options.hash] Hash function to use (either md5 sha1 or sha256, defaults to sha256)
|
||
|
* @param {String} [options.country] CSR country field
|
||
|
* @param {String} [options.state] CSR state field
|
||
|
* @param {String} [options.locality] CSR locality field
|
||
|
* @param {String} [options.organization] CSR organization field
|
||
|
* @param {String} [options.organizationUnit] CSR organizational unit field
|
||
|
* @param {String} [options.commonName='localhost'] CSR common name field
|
||
|
* @param {String} [options.emailAddress] CSR email address field
|
||
|
* @param {String} [options.csrConfigFile] CSR config file
|
||
|
* @param {Array} [options.altNames] is a list of subjectAltNames in the subjectAltName field
|
||
|
* @param {Function} callback Callback function with an error object and {csr, clientKey}
|
||
|
*/
|
||
|
function createCSR (options, callback) {
|
||
|
if (!callback && typeof options === 'function') {
|
||
|
callback = options
|
||
|
options = undefined
|
||
|
}
|
||
|
|
||
|
options = options || {}
|
||
|
|
||
|
// http://stackoverflow.com/questions/14089872/why-does-node-js-accept-ip-addresses-in-certificates-only-for-san-not-for-cn
|
||
|
if (options.commonName && (net.isIPv4(options.commonName) || net.isIPv6(options.commonName))) {
|
||
|
if (!options.altNames) {
|
||
|
options.altNames = [options.commonName]
|
||
|
} else if (options.altNames.indexOf(options.commonName) === -1) {
|
||
|
options.altNames = options.altNames.concat([options.commonName])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!options.clientKey) {
|
||
|
createPrivateKey(options.keyBitsize || 2048, function (error, keyData) {
|
||
|
if (error) {
|
||
|
return callback(error)
|
||
|
}
|
||
|
options.clientKey = keyData.key
|
||
|
createCSR(options, callback)
|
||
|
})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var params = ['req',
|
||
|
'-new',
|
||
|
'-' + (options.hash || 'sha256')
|
||
|
]
|
||
|
|
||
|
if (options.csrConfigFile) {
|
||
|
params.push('-config')
|
||
|
params.push(options.csrConfigFile)
|
||
|
} else {
|
||
|
params.push('-subj')
|
||
|
params.push(generateCSRSubject(options))
|
||
|
}
|
||
|
|
||
|
params.push('-key')
|
||
|
params.push('--TMPFILE--')
|
||
|
|
||
|
var tmpfiles = [options.clientKey]
|
||
|
var config = null
|
||
|
|
||
|
if (options.altNames && Array.isArray(options.altNames) && options.altNames.length) {
|
||
|
params.push('-extensions')
|
||
|
params.push('v3_req')
|
||
|
params.push('-config')
|
||
|
params.push('--TMPFILE--')
|
||
|
var altNamesRep = []
|
||
|
for (var i = 0; i < options.altNames.length; i++) {
|
||
|
altNamesRep.push((net.isIP(options.altNames[i]) ? 'IP' : 'DNS') + '.' + (i + 1) + ' = ' + options.altNames[i])
|
||
|
}
|
||
|
|
||
|
tmpfiles.push(config = [
|
||
|
'[req]',
|
||
|
'req_extensions = v3_req',
|
||
|
'distinguished_name = req_distinguished_name',
|
||
|
'[v3_req]',
|
||
|
'subjectAltName = @alt_names',
|
||
|
'[alt_names]',
|
||
|
altNamesRep.join('\n'),
|
||
|
'[req_distinguished_name]',
|
||
|
'commonName = Common Name',
|
||
|
'commonName_max = 64'
|
||
|
].join('\n'))
|
||
|
} else if (options.config) {
|
||
|
config = options.config
|
||
|
}
|
||
|
|
||
|
var delTempPWFiles = []
|
||
|
if (options.clientKeyPassword) {
|
||
|
helper.createPasswordFile({ 'cipher': '', 'password': options.clientKeyPassword, 'passType': 'in' }, params, delTempPWFiles)
|
||
|
}
|
||
|
|
||
|
openssl.exec(params, 'CERTIFICATE REQUEST', tmpfiles, function (sslErr, data) {
|
||
|
function done (err) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
callback(null, {
|
||
|
csr: data,
|
||
|
config: config,
|
||
|
clientKey: options.clientKey
|
||
|
})
|
||
|
}
|
||
|
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
|
||
|
done(sslErr || fsErr)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a certificate based on a CSR. If CSR is not defined, a new one
|
||
|
* will be generated automatically. For CSR generation all the options values
|
||
|
* can be used as with createCSR.
|
||
|
* @static
|
||
|
* @param {Object} [options] Optional options object
|
||
|
* @param {String} [options.serviceCertificate] PEM encoded certificate
|
||
|
* @param {String} [options.serviceKey] Private key for signing the certificate, if not defined a new one is generated
|
||
|
* @param {String} [options.serviceKeyPassword] Password of the service key
|
||
|
* @param {Boolean} [options.selfSigned] If set to true and serviceKey is not defined, use clientKey for signing
|
||
|
* @param {String|Number} [options.serial] Set a serial max. 20 octets - only together with options.serviceCertificate
|
||
|
* @param {String} [options.serialFile] Set the name of the serial file, without extension. - only together with options.serviceCertificate and never in tandem with options.serial
|
||
|
* @param {String} [options.hash] Hash function to use (either md5 sha1 or sha256, defaults to sha256)
|
||
|
* @param {String} [options.csr] CSR for the certificate, if not defined a new one is generated
|
||
|
* @param {Number} [options.days] Certificate expire time in days
|
||
|
* @param {String} [options.clientKeyPassword] Password of the client key
|
||
|
* @param {String} [options.extFile] extension config file - without '-extensions v3_req'
|
||
|
* @param {String} [options.config] extension config file - with '-extensions v3_req'
|
||
|
* @param {String} [options.csrConfigFile] CSR config file - only used if no options.csr is provided
|
||
|
* @param {Array} [options.altNames] is a list of subjectAltNames in the subjectAltName field - only used if no options.csr is provided
|
||
|
* @param {Function} callback Callback function with an error object and {certificate, csr, clientKey, serviceKey}
|
||
|
*/
|
||
|
function createCertificate (options, callback) {
|
||
|
if (!callback && typeof options === 'function') {
|
||
|
callback = options
|
||
|
options = undefined
|
||
|
}
|
||
|
|
||
|
options = options || {}
|
||
|
|
||
|
if (!options.csr) {
|
||
|
createCSR(options, function (error, keyData) {
|
||
|
if (error) {
|
||
|
return callback(error)
|
||
|
}
|
||
|
options.csr = keyData.csr
|
||
|
options.config = keyData.config
|
||
|
options.clientKey = keyData.clientKey
|
||
|
createCertificate(options, callback)
|
||
|
})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if (!options.clientKey) {
|
||
|
options.clientKey = ''
|
||
|
}
|
||
|
|
||
|
if (!options.serviceKey) {
|
||
|
if (options.selfSigned) {
|
||
|
options.serviceKey = options.clientKey
|
||
|
} else {
|
||
|
createPrivateKey(options.keyBitsize || 2048, function (error, keyData) {
|
||
|
if (error) {
|
||
|
return callback(error)
|
||
|
}
|
||
|
options.serviceKey = keyData.key
|
||
|
createCertificate(options, callback)
|
||
|
})
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
readCertificateInfo(options.csr, function (error2, data2) {
|
||
|
if (error2) {
|
||
|
return callback(error2)
|
||
|
}
|
||
|
|
||
|
var params = ['x509',
|
||
|
'-req',
|
||
|
'-' + (options.hash || 'sha256'),
|
||
|
'-days',
|
||
|
Number(options.days) || '365',
|
||
|
'-in',
|
||
|
'--TMPFILE--'
|
||
|
]
|
||
|
var tmpfiles = [options.csr]
|
||
|
var delTempPWFiles = []
|
||
|
|
||
|
if (options.serviceCertificate) {
|
||
|
params.push('-CA')
|
||
|
params.push('--TMPFILE--')
|
||
|
params.push('-CAkey')
|
||
|
params.push('--TMPFILE--')
|
||
|
if (options.serial) {
|
||
|
params.push('-set_serial')
|
||
|
if (helper.isNumber(options.serial)) {
|
||
|
// set the serial to the max lenth of 20 octets ()
|
||
|
// A certificate serial number is not decimal conforming. That is the
|
||
|
// bytes in a serial number do not necessarily map to a printable ASCII
|
||
|
// character.
|
||
|
// eg: 0x00 is a valid serial number and can not be represented in a
|
||
|
// human readable format (atleast one that can be directly mapped to
|
||
|
// the ACSII table).
|
||
|
params.push('0x' + ('0000000000000000000000000000000000000000' + options.serial.toString(16)).slice(-40))
|
||
|
} else {
|
||
|
if (helper.isHex(options.serial)) {
|
||
|
if (options.serial.startsWith('0x')) {
|
||
|
options.serial = options.serial.substring(2, options.serial.length)
|
||
|
}
|
||
|
params.push('0x' + ('0000000000000000000000000000000000000000' + options.serial).slice(-40))
|
||
|
} else {
|
||
|
params.push('0x' + ('0000000000000000000000000000000000000000' + helper.toHex(options.serial)).slice(-40))
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
params.push('-CAcreateserial')
|
||
|
if (options.serialFile) {
|
||
|
params.push('-CAserial')
|
||
|
params.push(options.serialFile + '.srl')
|
||
|
}
|
||
|
}
|
||
|
if (options.serviceKeyPassword) {
|
||
|
helper.createPasswordFile({ 'cipher': '', 'password': options.serviceKeyPassword, 'passType': 'in' }, params, delTempPWFiles)
|
||
|
}
|
||
|
tmpfiles.push(options.serviceCertificate)
|
||
|
tmpfiles.push(options.serviceKey)
|
||
|
} else {
|
||
|
params.push('-signkey')
|
||
|
params.push('--TMPFILE--')
|
||
|
if (options.serviceKeyPassword) {
|
||
|
helper.createPasswordFile({ 'cipher': '', 'password': options.serviceKeyPassword, 'passType': 'in' }, params, delTempPWFiles)
|
||
|
}
|
||
|
tmpfiles.push(options.serviceKey)
|
||
|
}
|
||
|
|
||
|
if (options.config) {
|
||
|
params.push('-extensions')
|
||
|
params.push('v3_req')
|
||
|
params.push('-extfile')
|
||
|
params.push('--TMPFILE--')
|
||
|
tmpfiles.push(options.config)
|
||
|
} else if (options.extFile) {
|
||
|
params.push('-extfile')
|
||
|
params.push(options.extFile)
|
||
|
} else {
|
||
|
var altNamesRep = []
|
||
|
if (data2 && data2.san) {
|
||
|
for (var i = 0; i < data2.san.dns.length; i++) {
|
||
|
altNamesRep.push('DNS' + '.' + (i + 1) + ' = ' + data2.san.dns[i])
|
||
|
}
|
||
|
for (var i2 = 0; i2 < data2.san.ip.length; i2++) {
|
||
|
altNamesRep.push('IP' + '.' + (i2 + 1) + ' = ' + data2.san.ip[i2])
|
||
|
}
|
||
|
for (var i3 = 0; i3 < data2.san.email.length; i3++) {
|
||
|
altNamesRep.push('email' + '.' + (i3 + 1) + ' = ' + data2.san.email[i3])
|
||
|
}
|
||
|
params.push('-extensions')
|
||
|
params.push('v3_req')
|
||
|
params.push('-extfile')
|
||
|
params.push('--TMPFILE--')
|
||
|
tmpfiles.push([
|
||
|
'[v3_req]',
|
||
|
'subjectAltName = @alt_names',
|
||
|
'[alt_names]',
|
||
|
altNamesRep.join('\n')
|
||
|
].join('\n'))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (options.clientKeyPassword) {
|
||
|
helper.createPasswordFile({ 'cipher': '', 'password': options.clientKeyPassword, 'passType': 'in' }, params, delTempPWFiles)
|
||
|
}
|
||
|
|
||
|
openssl.exec(params, 'CERTIFICATE', tmpfiles, function (sslErr, data) {
|
||
|
function done (err) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
var response = {
|
||
|
csr: options.csr,
|
||
|
clientKey: options.clientKey,
|
||
|
certificate: data,
|
||
|
serviceKey: options.serviceKey
|
||
|
}
|
||
|
return callback(null, response)
|
||
|
}
|
||
|
|
||
|
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
|
||
|
done(sslErr || fsErr)
|
||
|
})
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Exports a public key from a private key, CSR or certificate
|
||
|
* @static
|
||
|
* @param {String} certificate PEM encoded private key, CSR or certificate
|
||
|
* @param {Function} callback Callback function with an error object and {publicKey}
|
||
|
*/
|
||
|
function getPublicKey (certificate, callback) {
|
||
|
if (!callback && typeof certificate === 'function') {
|
||
|
callback = certificate
|
||
|
certificate = undefined
|
||
|
}
|
||
|
|
||
|
certificate = (certificate || '').toString()
|
||
|
|
||
|
var params
|
||
|
|
||
|
if (certificate.match(/BEGIN(\sNEW)? CERTIFICATE REQUEST/)) {
|
||
|
params = ['req',
|
||
|
'-in',
|
||
|
'--TMPFILE--',
|
||
|
'-pubkey',
|
||
|
'-noout'
|
||
|
]
|
||
|
} else if (certificate.match(/BEGIN RSA PRIVATE KEY/) || certificate.match(/BEGIN PRIVATE KEY/)) {
|
||
|
params = ['rsa',
|
||
|
'-in',
|
||
|
'--TMPFILE--',
|
||
|
'-pubout'
|
||
|
]
|
||
|
} else {
|
||
|
params = ['x509',
|
||
|
'-in',
|
||
|
'--TMPFILE--',
|
||
|
'-pubkey',
|
||
|
'-noout'
|
||
|
]
|
||
|
}
|
||
|
|
||
|
openssl.exec(params, 'PUBLIC KEY', certificate, function (error, key) {
|
||
|
if (error) {
|
||
|
return callback(error)
|
||
|
}
|
||
|
return callback(null, {
|
||
|
publicKey: key
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reads subject data from a certificate or a CSR
|
||
|
* @static
|
||
|
* @param {String} certificate PEM encoded CSR or certificate
|
||
|
* @param {Function} callback Callback function with an error object and {country, state, locality, organization, organizationUnit, commonName, emailAddress}
|
||
|
*/
|
||
|
function readCertificateInfo (certificate, callback) {
|
||
|
if (!callback && typeof certificate === 'function') {
|
||
|
callback = certificate
|
||
|
certificate = undefined
|
||
|
}
|
||
|
|
||
|
certificate = (certificate || '').toString()
|
||
|
var isMatch = certificate.match(/BEGIN(\sNEW)? CERTIFICATE REQUEST/)
|
||
|
var type = isMatch ? 'req' : 'x509'
|
||
|
var params = [type,
|
||
|
'-noout',
|
||
|
'-nameopt',
|
||
|
'RFC2253,sep_multiline,space_eq,-esc_msb,utf8',
|
||
|
'-text',
|
||
|
'-in',
|
||
|
'--TMPFILE--'
|
||
|
]
|
||
|
openssl.spawnWrapper(params, certificate, function (err, code, stdout, stderr) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
} else if (stderr) {
|
||
|
return callback(stderr)
|
||
|
}
|
||
|
return fetchCertificateData(stdout, callback)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* get the modulus from a certificate, a CSR or a private key
|
||
|
* @static
|
||
|
* @param {String} certificate PEM encoded, CSR PEM encoded, or private key
|
||
|
* @param {String} [password] password for the certificate
|
||
|
* @param {String} [hash] hash function to use (up to now `md5` supported) (default: none)
|
||
|
* @param {Function} callback Callback function with an error object and {modulus}
|
||
|
*/
|
||
|
function getModulus (certificate, password, hash, callback) {
|
||
|
if (!callback && !hash && typeof password === 'function') {
|
||
|
callback = password
|
||
|
password = undefined
|
||
|
hash = false
|
||
|
} else if (!callback && hash && typeof hash === 'function') {
|
||
|
callback = hash
|
||
|
hash = false
|
||
|
// password will be falsy if not provided
|
||
|
}
|
||
|
// adding hash function to params, is not supported by openssl.
|
||
|
// process piping would be the right way (... | openssl md5)
|
||
|
// No idea how this can be achieved in easy with the current build in methods
|
||
|
// of pem.
|
||
|
if (hash && hash !== 'md5') {
|
||
|
hash = false
|
||
|
}
|
||
|
|
||
|
certificate = (Buffer.isBuffer(certificate) && certificate.toString()) || certificate
|
||
|
|
||
|
var type = ''
|
||
|
if (certificate.match(/BEGIN(\sNEW)? CERTIFICATE REQUEST/)) {
|
||
|
type = 'req'
|
||
|
} else if (certificate.match(/BEGIN RSA PRIVATE KEY/) || certificate.match(/BEGIN PRIVATE KEY/)) {
|
||
|
type = 'rsa'
|
||
|
} else {
|
||
|
type = 'x509'
|
||
|
}
|
||
|
var params = [
|
||
|
type,
|
||
|
'-noout',
|
||
|
'-modulus',
|
||
|
'-in',
|
||
|
'--TMPFILE--'
|
||
|
]
|
||
|
var delTempPWFiles = []
|
||
|
if (password) {
|
||
|
helper.createPasswordFile({ 'cipher': '', 'password': password, 'passType': 'in' }, params, delTempPWFiles)
|
||
|
}
|
||
|
|
||
|
openssl.spawnWrapper(params, certificate, function (sslErr, code, stdout, stderr) {
|
||
|
function done (err) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
var match = stdout.match(/Modulus=([0-9a-fA-F]+)$/m)
|
||
|
if (match) {
|
||
|
return callback(null, {
|
||
|
modulus: hash ? require(hash)(match[1]) : match[1]
|
||
|
})
|
||
|
} else {
|
||
|
return callback(new Error('No modulus'))
|
||
|
}
|
||
|
}
|
||
|
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
|
||
|
done(sslErr || fsErr || stderr)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* get the size and prime of DH parameters
|
||
|
* @static
|
||
|
* @param {String} DH parameters PEM encoded
|
||
|
* @param {Function} callback Callback function with an error object and {size, prime}
|
||
|
*/
|
||
|
function getDhparamInfo (dh, callback) {
|
||
|
dh = (Buffer.isBuffer(dh) && dh.toString()) || dh
|
||
|
|
||
|
var params = [
|
||
|
'dhparam',
|
||
|
'-text',
|
||
|
'-in',
|
||
|
'--TMPFILE--'
|
||
|
]
|
||
|
|
||
|
openssl.spawnWrapper(params, dh, function (err, code, stdout, stderr) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
} else if (stderr) {
|
||
|
return callback(stderr)
|
||
|
}
|
||
|
|
||
|
var result = {}
|
||
|
var match = stdout.match(/Parameters: \((\d+) bit\)/)
|
||
|
|
||
|
if (match) {
|
||
|
result.size = Number(match[1])
|
||
|
}
|
||
|
|
||
|
var prime = ''
|
||
|
stdout.split('\n').forEach(function (line) {
|
||
|
if (/\s+([0-9a-f][0-9a-f]:)+[0-9a-f]?[0-9a-f]?/g.test(line)) {
|
||
|
prime += line.trim()
|
||
|
}
|
||
|
})
|
||
|
|
||
|
if (prime) {
|
||
|
result.prime = prime
|
||
|
}
|
||
|
|
||
|
if (!match && !prime) {
|
||
|
return callback(new Error('No DH info found'))
|
||
|
}
|
||
|
|
||
|
return callback(null, result)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* config the pem module
|
||
|
* @static
|
||
|
* @param {Object} options
|
||
|
*/
|
||
|
function config (options) {
|
||
|
Object.keys(options).forEach(function (k) {
|
||
|
openssl.set(k, options[k])
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the fingerprint for a certificate
|
||
|
* @static
|
||
|
* @param {String} PEM encoded certificate
|
||
|
* @param {String} [hash] hash function to use (either `md5`, `sha1` or `sha256`, defaults to `sha1`)
|
||
|
* @param {Function} callback Callback function with an error object and {fingerprint}
|
||
|
*/
|
||
|
function getFingerprint (certificate, hash, callback) {
|
||
|
if (!callback && typeof hash === 'function') {
|
||
|
callback = hash
|
||
|
hash = undefined
|
||
|
}
|
||
|
|
||
|
hash = hash || 'sha1'
|
||
|
|
||
|
var params = ['x509',
|
||
|
'-in',
|
||
|
'--TMPFILE--',
|
||
|
'-fingerprint',
|
||
|
'-noout',
|
||
|
'-' + hash
|
||
|
]
|
||
|
|
||
|
openssl.spawnWrapper(params, certificate, function (err, code, stdout, stderr) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
} else if (stderr) {
|
||
|
return callback(stderr)
|
||
|
}
|
||
|
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'))
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Export private key and certificate to a PKCS12 keystore
|
||
|
* @static
|
||
|
* @param {String} PEM encoded private key
|
||
|
* @param {String} PEM encoded certificate
|
||
|
* @param {String} Password of the result PKCS12 file
|
||
|
* @param {Object} [options] object of cipher and optional client key password {cipher:'aes128', clientKeyPassword: 'xxxx', certFiles: ['file1','file2']}
|
||
|
* @param {Function} callback Callback function with an error object and {pkcs12}
|
||
|
*/
|
||
|
function createPkcs12 (key, certificate, password, options, callback) {
|
||
|
if (!callback && typeof options === 'function') {
|
||
|
callback = options
|
||
|
options = {}
|
||
|
}
|
||
|
|
||
|
var params = ['pkcs12', '-export']
|
||
|
var delTempPWFiles = []
|
||
|
|
||
|
if (options.cipher && options.clientKeyPassword) {
|
||
|
// NOTICE: The password field is needed! self if it is empty.
|
||
|
// create password file for the import "-passin"
|
||
|
helper.createPasswordFile({ 'cipher': options.cipher, 'password': options.clientKeyPassword, 'passType': 'in' }, params, delTempPWFiles)
|
||
|
}
|
||
|
// NOTICE: The password field is needed! self if it is empty.
|
||
|
// create password file for the password "-password"
|
||
|
helper.createPasswordFile({ 'cipher': '', 'password': password, 'passType': 'word' }, params, delTempPWFiles)
|
||
|
|
||
|
params.push('-in')
|
||
|
params.push('--TMPFILE--')
|
||
|
params.push('-inkey')
|
||
|
params.push('--TMPFILE--')
|
||
|
|
||
|
var tmpfiles = [certificate, key]
|
||
|
|
||
|
if (options.certFiles) {
|
||
|
tmpfiles.push(options.certFiles.join(''))
|
||
|
|
||
|
params.push('-certfile')
|
||
|
params.push('--TMPFILE--')
|
||
|
}
|
||
|
|
||
|
openssl.execBinary(params, tmpfiles, function (sslErr, pkcs12) {
|
||
|
function done (err) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
return callback(null, {
|
||
|
pkcs12: pkcs12
|
||
|
})
|
||
|
}
|
||
|
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
|
||
|
done(sslErr || fsErr)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* read sslcert data from Pkcs12 file. Results are provided in callback response in object notation ({cert: .., ca:..., key:...})
|
||
|
* @static
|
||
|
* @param {Buffer|String} bufferOrPath Buffer or path to file
|
||
|
* @param {Object} [options] openssl options
|
||
|
* @param {Function} callback Called with error object and sslcert bundle object
|
||
|
*/
|
||
|
function readPkcs12 (bufferOrPath, options, callback) {
|
||
|
if (!callback && typeof options === 'function') {
|
||
|
callback = options
|
||
|
options = {}
|
||
|
}
|
||
|
|
||
|
options.p12Password = options.p12Password || ''
|
||
|
|
||
|
var tmpfiles = []
|
||
|
var delTempPWFiles = []
|
||
|
var args = ['pkcs12', '-in', bufferOrPath]
|
||
|
|
||
|
helper.createPasswordFile({ 'cipher': '', 'password': options.p12Password, 'passType': 'in' }, args, delTempPWFiles)
|
||
|
|
||
|
if (Buffer.isBuffer(bufferOrPath)) {
|
||
|
tmpfiles = [bufferOrPath]
|
||
|
args[2] = '--TMPFILE--'
|
||
|
}
|
||
|
|
||
|
if (options.clientKeyPassword) {
|
||
|
helper.createPasswordFile({ 'cipher': '', 'password': options.clientKeyPassword, 'passType': 'out' }, args, delTempPWFiles)
|
||
|
} else {
|
||
|
args.push('-nodes')
|
||
|
}
|
||
|
|
||
|
openssl.execBinary(args, tmpfiles, function (sslErr, stdout) {
|
||
|
function done (err) {
|
||
|
var keybundle = {}
|
||
|
|
||
|
if (err && err.message.indexOf('No such file or directory') !== -1) {
|
||
|
err.code = 'ENOENT'
|
||
|
}
|
||
|
|
||
|
if (!err) {
|
||
|
var certs = readFromString(stdout, CERT_START, CERT_END)
|
||
|
keybundle.cert = certs.shift()
|
||
|
keybundle.ca = certs
|
||
|
keybundle.key = readFromString(stdout, KEY_START, KEY_END).pop()
|
||
|
|
||
|
if (keybundle.key) {
|
||
|
// convert to RSA key
|
||
|
return openssl.exec(['rsa', '-in', '--TMPFILE--'], 'RSA PRIVATE KEY', [keybundle.key], function (err, key) {
|
||
|
keybundle.key = key
|
||
|
|
||
|
return callback(err, keybundle)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if (options.clientKeyPassword) {
|
||
|
keybundle.key = readFromString(stdout, ENCRYPTED_KEY_START, ENCRYPTED_KEY_END).pop()
|
||
|
} else {
|
||
|
keybundle.key = readFromString(stdout, RSA_KEY_START, RSA_KEY_END).pop()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return callback(err, keybundle)
|
||
|
}
|
||
|
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
|
||
|
done(sslErr || fsErr)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check a certificate
|
||
|
* @static
|
||
|
* @param {String} PEM encoded certificate
|
||
|
* @param {String} [passphrase] password for the certificate
|
||
|
* @param {Function} callback Callback function with an error object and a boolean valid
|
||
|
*/
|
||
|
function checkCertificate (certificate, passphrase, callback) {
|
||
|
var params
|
||
|
var delTempPWFiles = []
|
||
|
|
||
|
if (!callback && typeof passphrase === 'function') {
|
||
|
callback = passphrase
|
||
|
passphrase = undefined
|
||
|
}
|
||
|
certificate = (certificate || '').toString()
|
||
|
|
||
|
if (certificate.match(/BEGIN(\sNEW)? CERTIFICATE REQUEST/)) {
|
||
|
params = ['req', '-text', '-noout', '-verify', '-in', '--TMPFILE--']
|
||
|
} else if (certificate.match(/BEGIN RSA PRIVATE KEY/) || certificate.match(/BEGIN PRIVATE KEY/)) {
|
||
|
params = ['rsa', '-noout', '-check', '-in', '--TMPFILE--']
|
||
|
} else {
|
||
|
params = ['x509', '-text', '-noout', '-in', '--TMPFILE--']
|
||
|
}
|
||
|
if (passphrase) {
|
||
|
helper.createPasswordFile({ 'cipher': '', 'password': passphrase, 'passType': 'in' }, params, delTempPWFiles)
|
||
|
}
|
||
|
|
||
|
openssl.spawnWrapper(params, certificate, function (sslErr, code, stdout, stderr) {
|
||
|
function done (err) {
|
||
|
if (err && err.toString().trim() !== 'verify OK') {
|
||
|
return callback(err)
|
||
|
}
|
||
|
var result
|
||
|
switch (params[0]) {
|
||
|
case 'rsa':
|
||
|
result = /^Rsa key ok$/i.test(stdout.trim())
|
||
|
break
|
||
|
default:
|
||
|
result = /Signature Algorithm/im.test(stdout)
|
||
|
break
|
||
|
}
|
||
|
|
||
|
callback(null, result)
|
||
|
}
|
||
|
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
|
||
|
done(sslErr || fsErr || stderr)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* check a PKCS#12 file (.pfx or.p12)
|
||
|
* @static
|
||
|
* @param {Buffer|String} bufferOrPath PKCS#12 certificate
|
||
|
* @param {String} [passphrase] optional passphrase which will be used to open the keystore
|
||
|
* @param {Function} callback Callback function with an error object and a boolean valid
|
||
|
*/
|
||
|
function checkPkcs12 (bufferOrPath, passphrase, callback) {
|
||
|
if (!callback && typeof passphrase === 'function') {
|
||
|
callback = passphrase
|
||
|
passphrase = ''
|
||
|
}
|
||
|
|
||
|
var tmpfiles = []
|
||
|
var delTempPWFiles = []
|
||
|
var args = ['pkcs12', '-info', '-in', bufferOrPath, '-noout', '-maciter', '-nodes']
|
||
|
|
||
|
helper.createPasswordFile({ 'cipher': '', 'password': passphrase, 'passType': 'in' }, args, delTempPWFiles)
|
||
|
|
||
|
if (Buffer.isBuffer(bufferOrPath)) {
|
||
|
tmpfiles = [bufferOrPath]
|
||
|
args[3] = '--TMPFILE--'
|
||
|
}
|
||
|
|
||
|
openssl.spawnWrapper(args, tmpfiles, function (sslErr, code, stdout, stderr) {
|
||
|
function done (err) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
callback(null, (/MAC verified OK/im.test(stderr) || (!(/MAC verified OK/im.test(stderr)) && !(/Mac verify error/im.test(stderr)))))
|
||
|
}
|
||
|
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
|
||
|
done(sslErr || fsErr)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Verifies the signing chain of the passed certificate
|
||
|
* @static
|
||
|
* @param {String|Array} PEM encoded certificate include intermediate certificates
|
||
|
* @param {String|Array} [List] of CA certificates
|
||
|
* @param {Function} callback Callback function with an error object and a boolean valid
|
||
|
*/
|
||
|
function verifySigningChain (certificate, ca, callback) {
|
||
|
if (!callback && typeof ca === 'function') {
|
||
|
callback = ca
|
||
|
ca = undefined
|
||
|
}
|
||
|
if (!Array.isArray(certificate)) {
|
||
|
certificate = [certificate]
|
||
|
}
|
||
|
if (!Array.isArray(ca) && ca !== undefined) {
|
||
|
if (ca !== '') {
|
||
|
ca = [ca]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var files = []
|
||
|
|
||
|
if (ca !== undefined) {
|
||
|
// ca certificates
|
||
|
files.push(ca.join('\n'))
|
||
|
}
|
||
|
// certificate incl. intermediate certificates
|
||
|
files.push(certificate.join('\n'))
|
||
|
|
||
|
var params = ['verify']
|
||
|
|
||
|
if (ca !== undefined) {
|
||
|
// ca certificates
|
||
|
params.push('-CAfile')
|
||
|
params.push('--TMPFILE--')
|
||
|
}
|
||
|
// certificate incl. intermediate certificates
|
||
|
params.push('--TMPFILE--')
|
||
|
|
||
|
openssl.spawnWrapper(params, files, function (err, code, stdout, stderr) {
|
||
|
if (err) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
|
||
|
callback(null, stdout.trim().slice(-4) === ': OK')
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// HELPER FUNCTIONS
|
||
|
function fetchCertificateData (certData, callback) {
|
||
|
certData = (certData || '').toString()
|
||
|
|
||
|
var serial, subject, tmp, issuer
|
||
|
var certValues = {
|
||
|
issuer: {}
|
||
|
}
|
||
|
var validity = {}
|
||
|
var san
|
||
|
|
||
|
var ky, i
|
||
|
|
||
|
// serial
|
||
|
if ((serial = certData.match(/\s*Serial Number:\r?\n?\s*([^\r\n]*)\r?\n\s*\b/)) && serial.length > 1) {
|
||
|
certValues.serial = serial[1]
|
||
|
}
|
||
|
|
||
|
if ((subject = certData.match(/\s*Subject:\r?\n(\s*(([a-zA-Z0-9.]+)\s=\s[^\r\n]+\r?\n))*\s*\b/)) && subject.length > 1) {
|
||
|
subject = subject[0]
|
||
|
tmp = matchAll(subject, /\s([a-zA-Z0-9.]+)\s=\s([^\r\n].*)/g)
|
||
|
for (i = 0; i < tmp.length; i++) {
|
||
|
ky = tmp[i][1].trim()
|
||
|
if (ky.match('(C|ST|L|O|OU|CN|emailAddress|DC)') || ky === '') {
|
||
|
continue
|
||
|
}
|
||
|
certValues[ky] = tmp[i][2].trim()
|
||
|
}
|
||
|
|
||
|
// country
|
||
|
tmp = subject.match(/\sC\s=\s([^\r\n].*?)[\r\n]/)
|
||
|
certValues.country = (tmp && tmp[1]) || ''
|
||
|
|
||
|
// state
|
||
|
tmp = subject.match(/\sST\s=\s([^\r\n].*?)[\r\n]/)
|
||
|
certValues.state = (tmp && tmp[1]) || ''
|
||
|
|
||
|
// locality
|
||
|
tmp = subject.match(/\sL\s=\s([^\r\n].*?)[\r\n]/)
|
||
|
certValues.locality = (tmp && tmp[1]) || ''
|
||
|
|
||
|
// organization
|
||
|
tmp = matchAll(subject, /\sO\s=\s([^\r\n].*)/g)
|
||
|
certValues.organization = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
|
||
|
var e = t[1].toUpperCase()
|
||
|
var r = n[1].toUpperCase()
|
||
|
return r > e ? -1 : e > r ? 1 : 0
|
||
|
}).sort(function (t, n) {
|
||
|
return t[1].length - n[1].length
|
||
|
}).map(function (t) {
|
||
|
return t[1]
|
||
|
}) : tmp[0][1]) : ''
|
||
|
|
||
|
// unit
|
||
|
tmp = matchAll(subject, /\sOU\s=\s([^\r\n].*)/g)
|
||
|
certValues.organizationUnit = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
|
||
|
var e = t[1].toUpperCase()
|
||
|
var r = n[1].toUpperCase()
|
||
|
return r > e ? -1 : e > r ? 1 : 0
|
||
|
}).sort(function (t, n) {
|
||
|
return t[1].length - n[1].length
|
||
|
}).map(function (t) {
|
||
|
return t[1]
|
||
|
}) : tmp[0][1]) : ''
|
||
|
|
||
|
// common name
|
||
|
tmp = matchAll(subject, /\sCN\s=\s([^\r\n].*)/g)
|
||
|
certValues.commonName = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
|
||
|
var e = t[1].toUpperCase()
|
||
|
var r = n[1].toUpperCase()
|
||
|
return r > e ? -1 : e > r ? 1 : 0
|
||
|
}).sort(function (t, n) {
|
||
|
return t[1].length - n[1].length
|
||
|
}).map(function (t) {
|
||
|
return t[1]
|
||
|
}) : tmp[0][1]) : ''
|
||
|
|
||
|
// email
|
||
|
tmp = matchAll(subject, /emailAddress\s=\s([^\r\n].*)/g)
|
||
|
certValues.emailAddress = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
|
||
|
var e = t[1].toUpperCase()
|
||
|
var r = n[1].toUpperCase()
|
||
|
return r > e ? -1 : e > r ? 1 : 0
|
||
|
}).sort(function (t, n) {
|
||
|
return t[1].length - n[1].length
|
||
|
}).map(function (t) {
|
||
|
return t[1]
|
||
|
}) : tmp[0][1]) : ''
|
||
|
|
||
|
// DC name
|
||
|
tmp = matchAll(subject, /\sDC\s=\s([^\r\n].*)/g)
|
||
|
certValues.dc = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
|
||
|
var e = t[1].toUpperCase()
|
||
|
var r = n[1].toUpperCase()
|
||
|
return r > e ? -1 : e > r ? 1 : 0
|
||
|
}).sort(function (t, n) {
|
||
|
return t[1].length - n[1].length
|
||
|
}).map(function (t) {
|
||
|
return t[1]
|
||
|
}) : tmp[0][1]) : ''
|
||
|
}
|
||
|
|
||
|
if ((issuer = certData.match(/\s*Issuer:\r?\n(\s*([a-zA-Z0-9.]+)\s=\s[^\r\n].*\r?\n)*\s*\b/)) && issuer.length > 1) {
|
||
|
issuer = issuer[0]
|
||
|
tmp = matchAll(issuer, /\s([a-zA-Z0-9.]+)\s=\s([^\r\n].*)/g)
|
||
|
for (i = 0; i < tmp.length; i++) {
|
||
|
ky = tmp[i][1].toString()
|
||
|
if (ky.match('(C|ST|L|O|OU|CN|emailAddress|DC)')) {
|
||
|
continue
|
||
|
}
|
||
|
certValues.issuer[ky] = tmp[i][2].toString()
|
||
|
}
|
||
|
|
||
|
// country
|
||
|
tmp = issuer.match(/\sC\s=\s([^\r\n].*?)[\r\n]/)
|
||
|
certValues.issuer.country = (tmp && tmp[1]) || ''
|
||
|
|
||
|
// state
|
||
|
tmp = issuer.match(/\sST\s=\s([^\r\n].*?)[\r\n]/)
|
||
|
certValues.issuer.state = (tmp && tmp[1]) || ''
|
||
|
|
||
|
// locality
|
||
|
tmp = issuer.match(/\sL\s=\s([^\r\n].*?)[\r\n]/)
|
||
|
certValues.issuer.locality = (tmp && tmp[1]) || ''
|
||
|
|
||
|
// organization
|
||
|
tmp = matchAll(issuer, /\sO\s=\s([^\r\n].*)/g)
|
||
|
certValues.issuer.organization = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
|
||
|
var e = t[1].toUpperCase()
|
||
|
var r = n[1].toUpperCase()
|
||
|
return r > e ? -1 : e > r ? 1 : 0
|
||
|
}).sort(function (t, n) {
|
||
|
return t[1].length - n[1].length
|
||
|
}).map(function (t) {
|
||
|
return t[1]
|
||
|
}) : tmp[0][1]) : ''
|
||
|
|
||
|
// unit
|
||
|
tmp = matchAll(issuer, /\sOU\s=\s([^\r\n].*)/g)
|
||
|
certValues.issuer.organizationUnit = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
|
||
|
var e = t[1].toUpperCase()
|
||
|
var
|
||
|
r = n[1].toUpperCase()
|
||
|
return r > e ? -1 : e > r ? 1 : 0
|
||
|
}).sort(function (t, n) {
|
||
|
return t[1].length - n[1].length
|
||
|
}).map(function (t) {
|
||
|
return t[1]
|
||
|
}) : tmp[0][1]) : ''
|
||
|
|
||
|
// common name
|
||
|
tmp = matchAll(issuer, /\sCN\s=\s([^\r\n].*)/g)
|
||
|
certValues.issuer.commonName = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
|
||
|
var e = t[1].toUpperCase()
|
||
|
var
|
||
|
r = n[1].toUpperCase()
|
||
|
return r > e ? -1 : e > r ? 1 : 0
|
||
|
}).sort(function (t, n) {
|
||
|
return t[1].length - n[1].length
|
||
|
}).map(function (t) {
|
||
|
return t[1]
|
||
|
}) : tmp[0][1]) : ''
|
||
|
|
||
|
// DC name
|
||
|
tmp = matchAll(issuer, /\sDC\s=\s([^\r\n].*)/g)
|
||
|
certValues.issuer.dc = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
|
||
|
var e = t[1].toUpperCase()
|
||
|
var
|
||
|
r = n[1].toUpperCase()
|
||
|
return r > e ? -1 : e > r ? 1 : 0
|
||
|
}).sort(function (t, n) {
|
||
|
return t[1].length - n[1].length
|
||
|
}).map(function (t) {
|
||
|
return t[1]
|
||
|
}) : tmp[0][1]) : ''
|
||
|
}
|
||
|
|
||
|
// SAN
|
||
|
if ((san = certData.match(/X509v3 Subject Alternative Name: \r?\n([^\r\n]*)\r?\n/)) && san.length > 1) {
|
||
|
san = san[1].trim() + '\n'
|
||
|
certValues.san = {}
|
||
|
|
||
|
// hostnames
|
||
|
tmp = pregMatchAll('DNS:([^,\\r\\n].*?)[,\\r\\n\\s]', san)
|
||
|
certValues.san.dns = tmp || ''
|
||
|
|
||
|
// IP-Addresses IPv4 & IPv6
|
||
|
tmp = pregMatchAll('IP Address:([^,\\r\\n].*?)[,\\r\\n\\s]', san)
|
||
|
certValues.san.ip = tmp || ''
|
||
|
|
||
|
// Email Addresses
|
||
|
tmp = pregMatchAll('email:([^,\\r\\n].*?)[,\\r\\n\\s]', san)
|
||
|
certValues.san.email = tmp || ''
|
||
|
}
|
||
|
|
||
|
// Validity
|
||
|
if ((tmp = certData.match(/Not Before\s?:\s?([^\r\n]*)\r?\n/)) && tmp.length > 1) {
|
||
|
validity.start = Date.parse((tmp && tmp[1]) || '')
|
||
|
}
|
||
|
|
||
|
if ((tmp = certData.match(/Not After\s?:\s?([^\r\n]*)\r?\n/)) && tmp.length > 1) {
|
||
|
validity.end = Date.parse((tmp && tmp[1]) || '')
|
||
|
}
|
||
|
|
||
|
if (validity.start && validity.end) {
|
||
|
certValues.validity = validity
|
||
|
}
|
||
|
// Validity end
|
||
|
|
||
|
// Signature Algorithm
|
||
|
if ((tmp = certData.match(/Signature Algorithm: ([^\r\n]*)\r?\n/)) && tmp.length > 1) {
|
||
|
certValues.signatureAlgorithm = (tmp && tmp[1]) || ''
|
||
|
}
|
||
|
|
||
|
// Public Key
|
||
|
if ((tmp = certData.match(/Public[ -]Key: ([^\r\n]*)\r?\n/)) && tmp.length > 1) {
|
||
|
certValues.publicKeySize = ((tmp && tmp[1]) || '').replace(/[()]/g, '')
|
||
|
}
|
||
|
|
||
|
// Public Key Algorithm
|
||
|
if ((tmp = certData.match(/Public Key Algorithm: ([^\r\n]*)\r?\n/)) && tmp.length > 1) {
|
||
|
certValues.publicKeyAlgorithm = (tmp && tmp[1]) || ''
|
||
|
}
|
||
|
|
||
|
callback(null, certValues)
|
||
|
}
|
||
|
|
||
|
function matchAll (str, regexp) {
|
||
|
var matches = []
|
||
|
str.replace(regexp, function () {
|
||
|
var arr = ([]).slice.call(arguments, 0)
|
||
|
var extras = arr.splice(-2)
|
||
|
arr.index = extras[0]
|
||
|
arr.input = extras[1]
|
||
|
matches.push(arr)
|
||
|
})
|
||
|
return matches.length ? matches : null
|
||
|
}
|
||
|
|
||
|
function pregMatchAll (regex, haystack) {
|
||
|
var globalRegex = new RegExp(regex, 'g')
|
||
|
var globalMatch = haystack.match(globalRegex) || []
|
||
|
var matchArray = []
|
||
|
var nonGlobalRegex, nonGlobalMatch
|
||
|
for (var i = 0; i < globalMatch.length; i++) {
|
||
|
nonGlobalRegex = new RegExp(regex)
|
||
|
nonGlobalMatch = globalMatch[i].match(nonGlobalRegex)
|
||
|
matchArray.push(nonGlobalMatch[1])
|
||
|
}
|
||
|
return matchArray
|
||
|
}
|
||
|
|
||
|
function generateCSRSubject (options) {
|
||
|
options = options || {}
|
||
|
|
||
|
var csrData = {
|
||
|
C: options.country || options.C,
|
||
|
ST: options.state || options.ST,
|
||
|
L: options.locality || options.L,
|
||
|
O: options.organization || options.O,
|
||
|
OU: options.organizationUnit || options.OU,
|
||
|
CN: options.commonName || options.CN || 'localhost',
|
||
|
DC: options.dc || options.DC || '',
|
||
|
emailAddress: options.emailAddress
|
||
|
}
|
||
|
|
||
|
var csrBuilder = Object.keys(csrData).map(function (key) {
|
||
|
if (csrData[key]) {
|
||
|
if (typeof csrData[key] === 'object' && csrData[key].length >= 1) {
|
||
|
var tmpStr = ''
|
||
|
csrData[key].map(function (o) {
|
||
|
tmpStr += '/' + key + '=' + o.replace(/[^\w .*\-,@']+/g, ' ').trim()
|
||
|
})
|
||
|
return tmpStr
|
||
|
} else {
|
||
|
return '/' + key + '=' + csrData[key].replace(/[^\w .*\-,@']+/g, ' ').trim()
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
|
||
|
return csrBuilder.join('')
|
||
|
}
|
||
|
|
||
|
function readFromString (string, start, end) {
|
||
|
if (Buffer.isBuffer(string)) {
|
||
|
string = string.toString('utf8')
|
||
|
}
|
||
|
|
||
|
var output = []
|
||
|
|
||
|
if (!string) {
|
||
|
return output
|
||
|
}
|
||
|
|
||
|
var offset = string.indexOf(start)
|
||
|
|
||
|
while (offset !== -1) {
|
||
|
string = string.substring(offset)
|
||
|
|
||
|
var endOffset = string.indexOf(end)
|
||
|
|
||
|
if (endOffset === -1) {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
endOffset += end.length
|
||
|
|
||
|
output.push(string.substring(0, endOffset))
|
||
|
offset = string.indexOf(start, endOffset)
|
||
|
}
|
||
|
|
||
|
return output
|
||
|
}
|
||
|
|
||
|
// promisify not tested yet
|
||
|
/**
|
||
|
* Verifies the signing chain of the passed certificate
|
||
|
* @namespace
|
||
|
* @name promisified
|
||
|
* @property {function} createPrivateKey @see createPrivateKey
|
||
|
* @property {function} createDhparam - The default number of players.
|
||
|
* @property {function} createEcparam - The default level for the party.
|
||
|
* @property {function} createCSR - The default treasure.
|
||
|
* @property {function} createCertificate - How much gold the party starts with.
|
||
|
*/
|
||
|
module.exports.promisified = {
|
||
|
createPrivateKey: promisify(createPrivateKey),
|
||
|
createDhparam: promisify(createDhparam),
|
||
|
createEcparam: promisify(createEcparam),
|
||
|
createCSR: promisify(createCSR),
|
||
|
createCertificate: promisify(createCertificate),
|
||
|
readCertificateInfo: promisify(readCertificateInfo),
|
||
|
getPublicKey: promisify(getPublicKey),
|
||
|
getFingerprint: promisify(getFingerprint),
|
||
|
getModulus: promisify(getModulus),
|
||
|
getDhparamInfo: promisify(getDhparamInfo),
|
||
|
createPkcs12: promisify(createPkcs12),
|
||
|
readPkcs12: promisify(readPkcs12),
|
||
|
verifySigningChain: promisify(verifySigningChain),
|
||
|
checkCertificate: promisify(checkCertificate),
|
||
|
checkPkcs12: promisify(checkPkcs12)
|
||
|
}
|