You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
6.3 KiB
205 lines
6.3 KiB
var forge = require('node-forge'); |
|
|
|
// a hexString is considered negative if it's most significant bit is 1 |
|
// because serial numbers use ones' complement notation |
|
// this RFC in section 4.1.2.2 requires serial numbers to be positive |
|
// http://www.ietf.org/rfc/rfc5280.txt |
|
function toPositiveHex(hexString){ |
|
var mostSiginficativeHexAsInt = parseInt(hexString[0], 16); |
|
if (mostSiginficativeHexAsInt < 8){ |
|
return hexString; |
|
} |
|
|
|
mostSiginficativeHexAsInt -= 8; |
|
return mostSiginficativeHexAsInt.toString() + hexString.substring(1); |
|
} |
|
|
|
function getAlgorithm(key) { |
|
switch (key) { |
|
case 'sha256': |
|
return forge.md.sha256.create(); |
|
default: |
|
return forge.md.sha1.create(); |
|
} |
|
} |
|
|
|
/** |
|
* |
|
* @param {forge.pki.CertificateField[]} attrs Attributes used for subject and issuer. |
|
* @param {object} options |
|
* @param {number} [options.days=365] the number of days before expiration |
|
* @param {number} [options.keySize=1024] the size for the private key in bits |
|
* @param {object} [options.extensions] additional extensions for the certificate |
|
* @param {string} [options.algorithm="sha1"] The signature algorithm sha256 or sha1 |
|
* @param {boolean} [options.pkcs7=false] include PKCS#7 as part of the output |
|
* @param {boolean} [options.clientCertificate=false] generate client cert signed by the original key |
|
* @param {string} [options.clientCertificateCN="John Doe jdoe123"] client certificate's common name |
|
* @param {function} [done] Optional callback, if not provided the generation is synchronous |
|
* @returns |
|
*/ |
|
exports.generate = function generate(attrs, options, done) { |
|
if (typeof attrs === 'function') { |
|
done = attrs; |
|
attrs = undefined; |
|
} else if (typeof options === 'function') { |
|
done = options; |
|
options = {}; |
|
} |
|
|
|
options = options || {}; |
|
|
|
var generatePem = function (keyPair) { |
|
var cert = forge.pki.createCertificate(); |
|
|
|
cert.serialNumber = toPositiveHex(forge.util.bytesToHex(forge.random.getBytesSync(9))); // the serial number can be decimal or hex (if preceded by 0x) |
|
|
|
cert.validity.notBefore = new Date(); |
|
cert.validity.notAfter = new Date(); |
|
cert.validity.notAfter.setDate(cert.validity.notBefore.getDate() + (options.days || 365)); |
|
|
|
attrs = attrs || [{ |
|
name: 'commonName', |
|
value: 'example.org' |
|
}, { |
|
name: 'countryName', |
|
value: 'US' |
|
}, { |
|
shortName: 'ST', |
|
value: 'Virginia' |
|
}, { |
|
name: 'localityName', |
|
value: 'Blacksburg' |
|
}, { |
|
name: 'organizationName', |
|
value: 'Test' |
|
}, { |
|
shortName: 'OU', |
|
value: 'Test' |
|
}]; |
|
|
|
cert.setSubject(attrs); |
|
cert.setIssuer(attrs); |
|
|
|
cert.publicKey = keyPair.publicKey; |
|
|
|
cert.setExtensions(options.extensions || [{ |
|
name: 'basicConstraints', |
|
cA: true |
|
}, { |
|
name: 'keyUsage', |
|
keyCertSign: true, |
|
digitalSignature: true, |
|
nonRepudiation: true, |
|
keyEncipherment: true, |
|
dataEncipherment: true |
|
}, { |
|
name: 'subjectAltName', |
|
altNames: [{ |
|
type: 6, // URI |
|
value: 'http://example.org/webid#me' |
|
}] |
|
}]); |
|
|
|
cert.sign(keyPair.privateKey, getAlgorithm(options && options.algorithm)); |
|
|
|
const fingerprint = forge.md.sha1 |
|
.create() |
|
.update(forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes()) |
|
.digest() |
|
.toHex() |
|
.match(/.{2}/g) |
|
.join(':'); |
|
|
|
var pem = { |
|
private: forge.pki.privateKeyToPem(keyPair.privateKey), |
|
public: forge.pki.publicKeyToPem(keyPair.publicKey), |
|
cert: forge.pki.certificateToPem(cert), |
|
fingerprint: fingerprint, |
|
}; |
|
|
|
if (options && options.pkcs7) { |
|
var p7 = forge.pkcs7.createSignedData(); |
|
p7.addCertificate(cert); |
|
pem.pkcs7 = forge.pkcs7.messageToPem(p7); |
|
} |
|
|
|
if (options && options.clientCertificate) { |
|
var clientkeys = forge.pki.rsa.generateKeyPair(1024); |
|
var clientcert = forge.pki.createCertificate(); |
|
clientcert.serialNumber = toPositiveHex(forge.util.bytesToHex(forge.random.getBytesSync(9))); |
|
clientcert.validity.notBefore = new Date(); |
|
clientcert.validity.notAfter = new Date(); |
|
clientcert.validity.notAfter.setFullYear(clientcert.validity.notBefore.getFullYear() + 1); |
|
|
|
var clientAttrs = JSON.parse(JSON.stringify(attrs)); |
|
|
|
for(var i = 0; i < clientAttrs.length; i++) { |
|
if(clientAttrs[i].name === 'commonName') { |
|
if( options.clientCertificateCN ) |
|
clientAttrs[i] = { name: 'commonName', value: options.clientCertificateCN }; |
|
else |
|
clientAttrs[i] = { name: 'commonName', value: 'John Doe jdoe123' }; |
|
} |
|
} |
|
|
|
clientcert.setSubject(clientAttrs); |
|
|
|
// Set the issuer to the parent key |
|
clientcert.setIssuer(attrs); |
|
|
|
clientcert.publicKey = clientkeys.publicKey; |
|
|
|
// Sign client cert with root cert |
|
clientcert.sign(keyPair.privateKey); |
|
|
|
pem.clientprivate = forge.pki.privateKeyToPem(clientkeys.privateKey); |
|
pem.clientpublic = forge.pki.publicKeyToPem(clientkeys.publicKey); |
|
pem.clientcert = forge.pki.certificateToPem(clientcert); |
|
|
|
if (options.pkcs7) { |
|
var clientp7 = forge.pkcs7.createSignedData(); |
|
clientp7.addCertificate(clientcert); |
|
pem.clientpkcs7 = forge.pkcs7.messageToPem(clientp7); |
|
} |
|
} |
|
|
|
var caStore = forge.pki.createCaStore(); |
|
caStore.addCertificate(cert); |
|
|
|
try { |
|
forge.pki.verifyCertificateChain(caStore, [cert], |
|
function (vfd, depth, chain) { |
|
if (vfd !== true) { |
|
throw new Error('Certificate could not be verified.'); |
|
} |
|
return true; |
|
}); |
|
} |
|
catch(ex) { |
|
throw new Error(ex); |
|
} |
|
|
|
return pem; |
|
}; |
|
|
|
var keySize = options.keySize || 1024; |
|
|
|
if (done) { // async scenario |
|
return forge.pki.rsa.generateKeyPair({ bits: keySize }, function (err, keyPair) { |
|
if (err) { return done(err); } |
|
|
|
try { |
|
return done(null, generatePem(keyPair)); |
|
} catch (ex) { |
|
return done(ex); |
|
} |
|
}); |
|
} |
|
|
|
var keyPair = options.keyPair ? { |
|
privateKey: forge.pki.privateKeyFromPem(options.keyPair.privateKey), |
|
publicKey: forge.pki.publicKeyFromPem(options.keyPair.publicKey) |
|
} : forge.pki.rsa.generateKeyPair(keySize); |
|
|
|
return generatePem(keyPair); |
|
};
|
|
|