Skip to content

Commit

Permalink
feat(package): Support SAN Certificate from CSR (#229)
Browse files Browse the repository at this point in the history
This relase will support CSR with SAN.

SAN Support was before only via config possible now, we create
the config on the fly to support SAN without config.
  • Loading branch information
Dexus authored Jan 25, 2019
1 parent af050ac commit fa450f5
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 71 deletions.
175 changes: 104 additions & 71 deletions lib/pem.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ function createCertificate (options, callback) {
return
}

if (!options.clientKey) {
options.clientKey = ''
}

if (!options.serviceKey) {
if (options.selfSigned) {
options.serviceKey = options.clientKey
Expand All @@ -339,95 +343,124 @@ function createCertificate (options, callback) {
}
}

var params = ['x509',
'-req',
'-' + (options.hash || 'sha256'),
'-days',
Number(options.days) || '365',
'-in',
'--TMPFILE--'
]
var tmpfiles = [options.csr]
var delTempPWFiles = []
readCertificateInfo(options.csr, function (error2, data2) {
if (error2) {
return callback(error2)
}

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)) {
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))
params.push('0x' + ('0000000000000000000000000000000000000000' + options.serial.toString(16)).slice(-40))
} else {
params.push('0x' + ('0000000000000000000000000000000000000000' + helper.toHex(options.serial)).slice(-40))
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[delTempPWFiles.length])
}
tmpfiles.push(options.serviceCertificate)
tmpfiles.push(options.serviceKey)
} else {
params.push('-CAcreateserial')
if (options.serialFile) {
params.push('-CAserial')
params.push(options.serialFile + '.srl')
params.push('-signkey')
params.push('--TMPFILE--')
if (options.serviceKeyPassword) {
helper.createPasswordFile({ 'cipher': '', 'password': options.serviceKeyPassword, 'passType': 'in' }, params, delTempPWFiles[delTempPWFiles.length])
}
tmpfiles.push(options.serviceKey)
}
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)
}
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)
}
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
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)
}
return callback(null, response)
}

helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
done(sslErr || fsErr)
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
done(sslErr || fsErr)
})
})
})
}
Expand Down Expand Up @@ -1127,7 +1160,7 @@ function fetchCertificateData (certData, callback) {
certValues.san = {}

// hostnames
tmp = pregMatchAll('DNS:([^,\\r\\n].*?)[,\\r\\n]', san)
tmp = pregMatchAll('DNS:([^,\\r\\n].*?)[,\\r\\n\\s]', san)
certValues.san.dns = tmp || ''

// IP-Addresses IPv4 & IPv6
Expand Down
84 changes: 84 additions & 0 deletions test/pem.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,90 @@ describe('General Tests', function () {
})
})

describe('SAN certificate', function () {
var cert
it('Create default certificate', function (done) {
var d = fs.readFileSync('./test/fixtures/ru_openssl.csr').toString()
pem.createCertificate({ csr: d }, function (error, data) {
hlp.checkError(error)
hlp.checkCertificate(data)
hlp.checkTmpEmpty()
cert = data
done()
})
})

it('get its fingerprint', function (done) {
pem.getFingerprint(cert.certificate, function (error, data) {
hlp.checkError(error)
hlp.checkFingerprint(data)
hlp.checkTmpEmpty()
done()
})
})

it('get its modulus [not hashed]', function (done) {
pem.getModulus(cert.certificate, function (error,
data) {
hlp.checkError(error)
hlp.checkModulus(data)
hlp.checkTmpEmpty()
done()
})
})

it('get its modulus [md5 hashed]', function (done) {
pem.getModulus(cert.certificate, null, 'md5',
function (error, data) {
hlp.checkError(error)
hlp.checkModulus(data, 'md5')
hlp.checkTmpEmpty()
done()
})
})

it('read its data', function (done) {
pem.readCertificateInfo(cert.certificate, function (
error, data) {
hlp.checkError(error);
['validity', 'serial', 'signatureAlgorithm',
'publicKeySize', 'publicKeyAlgorithm'
].forEach(function (k) {
if (data[k]) { delete data[k] }
})
hlp.checkCertificateData(data, {
'commonName': 'Описание сайта',
'country': 'RU',
'dc': '',
'emailAddress': 'envek@envek.name',
'issuer': {
'commonName': 'Описание сайта',
'country': 'RU',
'dc': '',
'locality': 'Москва',
'organization': 'Моя компания',
'organizationUnit': 'Моё подразделение',
'state': ''
},
'locality': 'Москва',
'organization': 'Моя компания',
'organizationUnit': 'Моё подразделение',
'san': {
'dns': [
'example.com',
'*.example.com'
],
'email': [],
'ip': []
},
'state': ''
})
hlp.checkTmpEmpty()
done()
})
})
})

describe('CA certificate', function () {
var ca
it('create ca certificate', function (done) {
Expand Down

0 comments on commit fa450f5

Please sign in to comment.