Skip to content

Commit fa450f5

Browse files
authored
feat(package): Support SAN Certificate from CSR (#229)
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.
1 parent af050ac commit fa450f5

File tree

2 files changed

+188
-71
lines changed

2 files changed

+188
-71
lines changed

lib/pem.js

+104-71
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,10 @@ function createCertificate (options, callback) {
324324
return
325325
}
326326

327+
if (!options.clientKey) {
328+
options.clientKey = ''
329+
}
330+
327331
if (!options.serviceKey) {
328332
if (options.selfSigned) {
329333
options.serviceKey = options.clientKey
@@ -339,95 +343,124 @@ function createCertificate (options, callback) {
339343
}
340344
}
341345

342-
var params = ['x509',
343-
'-req',
344-
'-' + (options.hash || 'sha256'),
345-
'-days',
346-
Number(options.days) || '365',
347-
'-in',
348-
'--TMPFILE--'
349-
]
350-
var tmpfiles = [options.csr]
351-
var delTempPWFiles = []
346+
readCertificateInfo(options.csr, function (error2, data2) {
347+
if (error2) {
348+
return callback(error2)
349+
}
352350

353-
if (options.serviceCertificate) {
354-
params.push('-CA')
355-
params.push('--TMPFILE--')
356-
params.push('-CAkey')
357-
params.push('--TMPFILE--')
358-
if (options.serial) {
359-
params.push('-set_serial')
360-
if (helper.isNumber(options.serial)) {
351+
var params = ['x509',
352+
'-req',
353+
'-' + (options.hash || 'sha256'),
354+
'-days',
355+
Number(options.days) || '365',
356+
'-in',
357+
'--TMPFILE--'
358+
]
359+
var tmpfiles = [options.csr]
360+
var delTempPWFiles = []
361+
362+
if (options.serviceCertificate) {
363+
params.push('-CA')
364+
params.push('--TMPFILE--')
365+
params.push('-CAkey')
366+
params.push('--TMPFILE--')
367+
if (options.serial) {
368+
params.push('-set_serial')
369+
if (helper.isNumber(options.serial)) {
361370
// set the serial to the max lenth of 20 octets ()
362371
// A certificate serial number is not decimal conforming. That is the
363372
// bytes in a serial number do not necessarily map to a printable ASCII
364373
// character.
365374
// eg: 0x00 is a valid serial number and can not be represented in a
366375
// human readable format (atleast one that can be directly mapped to
367376
// the ACSII table).
368-
params.push('0x' + ('0000000000000000000000000000000000000000' + options.serial.toString(16)).slice(-40))
369-
} else {
370-
if (helper.isHex(options.serial)) {
371-
if (options.serial.startsWith('0x')) {
372-
options.serial = options.serial.substring(2, options.serial.length)
373-
}
374-
params.push('0x' + ('0000000000000000000000000000000000000000' + options.serial).slice(-40))
377+
params.push('0x' + ('0000000000000000000000000000000000000000' + options.serial.toString(16)).slice(-40))
375378
} else {
376-
params.push('0x' + ('0000000000000000000000000000000000000000' + helper.toHex(options.serial)).slice(-40))
379+
if (helper.isHex(options.serial)) {
380+
if (options.serial.startsWith('0x')) {
381+
options.serial = options.serial.substring(2, options.serial.length)
382+
}
383+
params.push('0x' + ('0000000000000000000000000000000000000000' + options.serial).slice(-40))
384+
} else {
385+
params.push('0x' + ('0000000000000000000000000000000000000000' + helper.toHex(options.serial)).slice(-40))
386+
}
387+
}
388+
} else {
389+
params.push('-CAcreateserial')
390+
if (options.serialFile) {
391+
params.push('-CAserial')
392+
params.push(options.serialFile + '.srl')
377393
}
378394
}
395+
if (options.serviceKeyPassword) {
396+
helper.createPasswordFile({ 'cipher': '', 'password': options.serviceKeyPassword, 'passType': 'in' }, params, delTempPWFiles[delTempPWFiles.length])
397+
}
398+
tmpfiles.push(options.serviceCertificate)
399+
tmpfiles.push(options.serviceKey)
379400
} else {
380-
params.push('-CAcreateserial')
381-
if (options.serialFile) {
382-
params.push('-CAserial')
383-
params.push(options.serialFile + '.srl')
401+
params.push('-signkey')
402+
params.push('--TMPFILE--')
403+
if (options.serviceKeyPassword) {
404+
helper.createPasswordFile({ 'cipher': '', 'password': options.serviceKeyPassword, 'passType': 'in' }, params, delTempPWFiles[delTempPWFiles.length])
384405
}
406+
tmpfiles.push(options.serviceKey)
385407
}
386-
if (options.serviceKeyPassword) {
387-
helper.createPasswordFile({ 'cipher': '', 'password': options.serviceKeyPassword, 'passType': 'in' }, params, delTempPWFiles)
388-
}
389-
tmpfiles.push(options.serviceCertificate)
390-
tmpfiles.push(options.serviceKey)
391-
} else {
392-
params.push('-signkey')
393-
params.push('--TMPFILE--')
394-
if (options.serviceKeyPassword) {
395-
helper.createPasswordFile({ 'cipher': '', 'password': options.serviceKeyPassword, 'passType': 'in' }, params, delTempPWFiles)
396-
}
397-
tmpfiles.push(options.serviceKey)
398-
}
399408

400-
if (options.config) {
401-
params.push('-extensions')
402-
params.push('v3_req')
403-
params.push('-extfile')
404-
params.push('--TMPFILE--')
405-
tmpfiles.push(options.config)
406-
} else if (options.extFile) {
407-
params.push('-extfile')
408-
params.push(options.extFile)
409-
}
409+
if (options.config) {
410+
params.push('-extensions')
411+
params.push('v3_req')
412+
params.push('-extfile')
413+
params.push('--TMPFILE--')
414+
tmpfiles.push(options.config)
415+
} else if (options.extFile) {
416+
params.push('-extfile')
417+
params.push(options.extFile)
418+
} else {
419+
var altNamesRep = []
420+
if (data2 && data2.san) {
421+
for (var i = 0; i < data2.san.dns.length; i++) {
422+
altNamesRep.push('DNS' + '.' + (i + 1) + ' = ' + data2.san.dns[i])
423+
}
424+
for (var i2 = 0; i2 < data2.san.ip.length; i2++) {
425+
altNamesRep.push('IP' + '.' + (i2 + 1) + ' = ' + data2.san.ip[i2])
426+
}
427+
for (var i3 = 0; i3 < data2.san.email.length; i3++) {
428+
altNamesRep.push('email' + '.' + (i3 + 1) + ' = ' + data2.san.email[i3])
429+
}
430+
params.push('-extensions')
431+
params.push('v3_req')
432+
params.push('-extfile')
433+
params.push('--TMPFILE--')
434+
tmpfiles.push([
435+
'[v3_req]',
436+
'subjectAltName = @alt_names',
437+
'[alt_names]',
438+
altNamesRep.join('\n')
439+
].join('\n'))
440+
}
441+
}
410442

411-
if (options.clientKeyPassword) {
412-
helper.createPasswordFile({ 'cipher': '', 'password': options.clientKeyPassword, 'passType': 'in' }, params, delTempPWFiles)
413-
}
443+
if (options.clientKeyPassword) {
444+
helper.createPasswordFile({ 'cipher': '', 'password': options.clientKeyPassword, 'passType': 'in' }, params, delTempPWFiles)
445+
}
414446

415-
openssl.exec(params, 'CERTIFICATE', tmpfiles, function (sslErr, data) {
416-
function done (err) {
417-
if (err) {
418-
return callback(err)
419-
}
420-
var response = {
421-
csr: options.csr,
422-
clientKey: options.clientKey,
423-
certificate: data,
424-
serviceKey: options.serviceKey
447+
openssl.exec(params, 'CERTIFICATE', tmpfiles, function (sslErr, data) {
448+
function done (err) {
449+
if (err) {
450+
return callback(err)
451+
}
452+
var response = {
453+
csr: options.csr,
454+
clientKey: options.clientKey,
455+
certificate: data,
456+
serviceKey: options.serviceKey
457+
}
458+
return callback(null, response)
425459
}
426-
return callback(null, response)
427-
}
428460

429-
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
430-
done(sslErr || fsErr)
461+
helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
462+
done(sslErr || fsErr)
463+
})
431464
})
432465
})
433466
}
@@ -1127,7 +1160,7 @@ function fetchCertificateData (certData, callback) {
11271160
certValues.san = {}
11281161

11291162
// hostnames
1130-
tmp = pregMatchAll('DNS:([^,\\r\\n].*?)[,\\r\\n]', san)
1163+
tmp = pregMatchAll('DNS:([^,\\r\\n].*?)[,\\r\\n\\s]', san)
11311164
certValues.san.dns = tmp || ''
11321165

11331166
// IP-Addresses IPv4 & IPv6

test/pem.spec.js

+84
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,90 @@ describe('General Tests', function () {
440440
})
441441
})
442442

443+
describe('SAN certificate', function () {
444+
var cert
445+
it('Create default certificate', function (done) {
446+
var d = fs.readFileSync('./test/fixtures/ru_openssl.csr').toString()
447+
pem.createCertificate({ csr: d }, function (error, data) {
448+
hlp.checkError(error)
449+
hlp.checkCertificate(data)
450+
hlp.checkTmpEmpty()
451+
cert = data
452+
done()
453+
})
454+
})
455+
456+
it('get its fingerprint', function (done) {
457+
pem.getFingerprint(cert.certificate, function (error, data) {
458+
hlp.checkError(error)
459+
hlp.checkFingerprint(data)
460+
hlp.checkTmpEmpty()
461+
done()
462+
})
463+
})
464+
465+
it('get its modulus [not hashed]', function (done) {
466+
pem.getModulus(cert.certificate, function (error,
467+
data) {
468+
hlp.checkError(error)
469+
hlp.checkModulus(data)
470+
hlp.checkTmpEmpty()
471+
done()
472+
})
473+
})
474+
475+
it('get its modulus [md5 hashed]', function (done) {
476+
pem.getModulus(cert.certificate, null, 'md5',
477+
function (error, data) {
478+
hlp.checkError(error)
479+
hlp.checkModulus(data, 'md5')
480+
hlp.checkTmpEmpty()
481+
done()
482+
})
483+
})
484+
485+
it('read its data', function (done) {
486+
pem.readCertificateInfo(cert.certificate, function (
487+
error, data) {
488+
hlp.checkError(error);
489+
['validity', 'serial', 'signatureAlgorithm',
490+
'publicKeySize', 'publicKeyAlgorithm'
491+
].forEach(function (k) {
492+
if (data[k]) { delete data[k] }
493+
})
494+
hlp.checkCertificateData(data, {
495+
'commonName': 'Описание сайта',
496+
'country': 'RU',
497+
'dc': '',
498+
'emailAddress': '[email protected]',
499+
'issuer': {
500+
'commonName': 'Описание сайта',
501+
'country': 'RU',
502+
'dc': '',
503+
'locality': 'Москва',
504+
'organization': 'Моя компания',
505+
'organizationUnit': 'Моё подразделение',
506+
'state': ''
507+
},
508+
'locality': 'Москва',
509+
'organization': 'Моя компания',
510+
'organizationUnit': 'Моё подразделение',
511+
'san': {
512+
'dns': [
513+
'example.com',
514+
'*.example.com'
515+
],
516+
'email': [],
517+
'ip': []
518+
},
519+
'state': ''
520+
})
521+
hlp.checkTmpEmpty()
522+
done()
523+
})
524+
})
525+
})
526+
443527
describe('CA certificate', function () {
444528
var ca
445529
it('create ca certificate', function (done) {

0 commit comments

Comments
 (0)