Created
July 22, 2024 02:08
-
-
Save mbarneyjr/7d954bf665ec97247ff3176423dc2f37 to your computer and use it in GitHub Desktop.
A script that will create keypairs and certificates in the form of a root CA, and intermediate CA, and a leaf certificate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import forge from "node-forge"; | |
import crypto, { randomUUID } from "crypto"; | |
import { existsSync, mkdirSync, writeFileSync } from "fs"; | |
const pki = forge.pki; | |
function generateKeyPair() { | |
const forgeKeypair = pki.rsa.generateKeyPair(4096); | |
return { | |
publicKey: pki.publicKeyToPem(forgeKeypair.publicKey), | |
privateKey: pki.privateKeyToPem(forgeKeypair.privateKey), | |
}; | |
} | |
/** | |
* @param {crypto.KeyPairSyncResult<string, string>} keyPair | |
* @param {forge.pki.CertificateField[]} attributes | |
*/ | |
function generateSelfSignedCertificate(keyPair, attributes) { | |
const privateKey = pki.privateKeyFromPem(keyPair.privateKey); | |
const publicKey = pki.publicKeyFromPem(keyPair.publicKey); | |
const cert = pki.createCertificate(); | |
cert.publicKey = publicKey; | |
cert.serialNumber = randomUUID().replace(/-/g, ""); | |
cert.validity.notBefore = new Date(); | |
cert.validity.notAfter = new Date(); | |
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); | |
cert.setSubject(attributes); | |
cert.setIssuer(attributes); | |
cert.setExtensions([ | |
{ | |
name: "basicConstraints", | |
cA: true, | |
}, | |
{ | |
name: "keyUsage", | |
digitalSignature: true, | |
keyCertSign: true, | |
cRLSign: true, | |
}, | |
]); | |
cert.sign(privateKey, forge.md.sha256.create()); | |
return pki.certificateToPem(cert); | |
} | |
/** | |
* @param {crypto.KeyPairSyncResult<string, string>} keyPair | |
* @param {forge.pki.CertificateField[]} attributes | |
*/ | |
function generateCSR(keyPair, attributes) { | |
const privateKey = pki.privateKeyFromPem(keyPair.privateKey); | |
const publicKey = pki.publicKeyFromPem(keyPair.publicKey); | |
const csr = pki.createCertificationRequest(); | |
csr.publicKey = publicKey; | |
csr.setSubject(attributes); | |
csr.sign(privateKey, forge.md.sha256.create()); | |
const pem = pki.certificationRequestToPem(csr); | |
return pem; | |
} | |
/** | |
* @param {{ | |
* csrPem: string | |
* issuingKeypair: crypto.KeyPairSyncResult<string, string> | |
* issuingCaPem: string | |
* issueCaCert: boolean | |
* }} | |
* @param {crypto.KeyPairSyncResult<string, string>} issuingKeypair | |
* @param {string} issuingCaPem | |
*/ | |
function issueCert({ csrPem, caCert, issuingKeypair, issuingCaPem }) { | |
const csr = pki.certificationRequestFromPem(csrPem); | |
const issuingCa = pki.certificateFromPem(issuingCaPem); | |
const issuingPrivateKey = pki.privateKeyFromPem(issuingKeypair.privateKey); | |
if (!csr.verify()) { | |
throw new Error("CSR not verified"); | |
} | |
const cert = pki.createCertificate(); | |
cert.serialNumber = randomUUID().replace(/-/g, ""); | |
cert.validity.notBefore = new Date(); | |
cert.validity.notAfter = new Date(); | |
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); | |
cert.setSubject(csr.subject.attributes); | |
cert.setIssuer(issuingCa.subject.attributes); | |
cert.setExtensions([ | |
{ | |
name: "basicConstraints", | |
cA: caCert === true, | |
}, | |
{ | |
name: "keyUsage", | |
digitalSignature: true, | |
keyCertSign: caCert === true, | |
cRLSign: caCert === true, | |
}, | |
]); | |
cert.publicKey = csr.publicKey; | |
cert.sign(issuingPrivateKey, forge.md.sha256.create()); | |
return pki.certificateToPem(cert); | |
} | |
/** | |
* @param {Record<string, string>} attributes | |
*/ | |
function getAttributes(options) { | |
const result = []; | |
for (let [key, value] of Object.entries(options)) { | |
result.push({ | |
name: key, | |
value, | |
}); | |
} | |
return result; | |
} | |
function main() { | |
if (!existsSync("out")) { | |
mkdirSync("out"); | |
} | |
const rootKeypair = generateKeyPair(); | |
writeFileSync("out/root-private-key.pem", rootKeypair.privateKey); | |
writeFileSync("out/root-public-key.pem", rootKeypair.publicKey); | |
const rootCert = generateSelfSignedCertificate( | |
rootKeypair, | |
getAttributes({ commonName: "root" }), | |
); | |
writeFileSync("out/root-cert.pem", rootCert); | |
const intKeypair = generateKeyPair(); | |
writeFileSync("out/int-private-key.pem", intKeypair.privateKey); | |
writeFileSync("out/int-public-key.pem", intKeypair.publicKey); | |
const intCsr = generateCSR( | |
intKeypair, | |
getAttributes({ | |
commonName: "int", | |
}), | |
); | |
const intCert = issueCert({ | |
caCert: true, | |
csrPem: intCsr, | |
issuingKeypair: rootKeypair, | |
issuingCaPem: rootCert, | |
}); | |
writeFileSync("out/int-cert.pem", intCert); | |
const leafKeypair = generateKeyPair(); | |
writeFileSync("out/leaf-private-key.pem", leafKeypair.privateKey); | |
writeFileSync("out/leaf-public-key.pem", leafKeypair.publicKey); | |
const leafCsr = generateCSR( | |
leafKeypair, | |
getAttributes({ | |
commonName: "leaf", | |
}), | |
); | |
const leafCert = issueCert({ | |
caCert: false, | |
csrPem: leafCsr, | |
issuingKeypair: intKeypair, | |
issuingCaPem: intCert, | |
}); | |
writeFileSync("out/leaf-cert.pem", leafCert); | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment