diff --git a/patch.js b/patch.js index 07c5593..aed9b6e 100644 --- a/patch.js +++ b/patch.js @@ -16,8 +16,18 @@ async function showPrompt() { description: colors.blue('Path to Nintendo CA - G3 (default to this directory)'), default: './CACERT_NINTENDO_CA_G3.pem' }, - private_key_path: { - description: colors.blue('Path to certificate private key (will generate if not set)') + ca_private_key_path: { + description: colors.blue('Path to private key for forged CA (will generate if not set)') + }, + site_private_key_path: { + description: colors.blue('Path to private key for site certificate (will generate if not set)') + }, + csr_path: { + description: colors.blue('Path to CSR (will generate if not set)') + }, + common_name: { + description: colors.blue('CN for site certificate (default to "*")'), + default: '*' }, output_folder_path: { description: colors.blue('Output folder (default to this directory)'), @@ -26,6 +36,22 @@ async function showPrompt() { } }); + options.nintendo_ca_g3_path = path.resolve(options.nintendo_ca_g3_path); + + if (options.ca_private_key_path) { + options.ca_private_key_path = path.resolve(options.ca_private_key_path); + } + + if (options.site_private_key_path) { + options.site_private_key_path = path.resolve(options.site_private_key_path); + } + + if (options.csr_path) { + options.csr_path = path.resolve(options.csr_path); + } + + options.output_folder_path = path.resolve(options.output_folder_path); + if (!fs.existsSync(options.nintendo_ca_g3_path)) { console.log(colors.bgRed('Invalid Nintendo CA - G3 path')); @@ -34,8 +60,24 @@ async function showPrompt() { return; } - if (options.private_key_path && !fs.existsSync(options.private_key_path)) { - console.log(colors.bgRed('Invalid certificate private key path')); + if (options.ca_private_key_path && !fs.existsSync(options.ca_private_key_path)) { + console.log(colors.bgRed('Invalid CA private key path')); + + showPrompt(); + + return; + } + + if (options.site_private_key_path && !fs.existsSync(options.site_private_key_path)) { + console.log(colors.bgRed('Invalid site certificate private key path')); + + showPrompt(); + + return; + } + + if (options.csr_path && !fs.existsSync(options.csr_path)) { + console.log(colors.bgRed('Invalid CSR key path')); showPrompt(); @@ -50,9 +92,6 @@ async function showPrompt() { return; } - options.output_folder_path = path.resolve(options.output_folder_path); - options.private_key_path = path.resolve(options.private_key_path); - try { patchCA(options); @@ -71,30 +110,30 @@ function patchCA(options) { const nintendoCAG3PEM = fs.readFileSync(options.nintendo_ca_g3_path); const nintendoCAG3 = pki.certificateFromPem(nintendoCAG3PEM); - let privateKey; - let publicKey; + let caPrivateKey; + let caPublicKey; - if (options.private_key_path) { - const privateKeyPEM = fs.readFileSync(options.private_key_path); - privateKey = pki.privateKeyFromPem(privateKeyPEM); - publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e); + if (options.ca_private_key_path) { + const privateKeyPEM = fs.readFileSync(options.ca_private_key_path); + caPrivateKey = pki.privateKeyFromPem(privateKeyPEM); + caPublicKey = pki.setRsaPublicKey(caPrivateKey.n, caPrivateKey.e); } else { const keyPair = pki.rsa.generateKeyPair(2048); - privateKey = keyPair.privateKey; - publicKey = keyPair.publicKey; + caPrivateKey = keyPair.privateKey; + caPublicKey = keyPair.publicKey; } // * Patch Nintendo CA - G3 with our new keys and identifer - const patchedCA = pki.createCertificate(); + const forgedCA = pki.createCertificate(); - patchedCA.publicKey = publicKey; // * Condition 1, set a new CA public key - patchedCA.serialNumber = nintendoCAG3.serialNumber; - patchedCA.validity.notBefore = nintendoCAG3.validity.notBefore; // TODO - Make this configurable? - patchedCA.validity.notAfter = nintendoCAG3.validity.notAfter; // TODO - Make this configurable? - patchedCA.setIssuer(nintendoCAG3.subject.attributes); - patchedCA.setSubject(nintendoCAG3.subject.attributes); - patchedCA.setExtensions([ + forgedCA.publicKey = caPublicKey; // * Condition 1, set a new CA public key + forgedCA.serialNumber = nintendoCAG3.serialNumber; + forgedCA.validity.notBefore = nintendoCAG3.validity.notBefore; // TODO - Make this configurable? + forgedCA.validity.notAfter = nintendoCAG3.validity.notAfter; // TODO - Make this configurable? + forgedCA.setIssuer(nintendoCAG3.subject.attributes); + forgedCA.setSubject(nintendoCAG3.subject.attributes); + forgedCA.setExtensions([ ...nintendoCAG3.extensions.filter(({ name }) => name !== 'authorityKeyIdentifier'), // * Remove old one { // * Condition 2, set a new authority key identifier extension @@ -110,11 +149,70 @@ function patchCA(options) { ]); // * Self-sign the CA patched with the private key - patchedCA.sign(privateKey, md.sha256.create()); // * sha256WithRSAEncryption + forgedCA.sign(caPrivateKey, md.sha256.create()); // * sha256WithRSAEncryption - // * Save the private key and patched CA - fs.writeFileSync(`${options.output_folder_path}/patched-ca.pem`, pki.certificateToPem(patchedCA), 'utf8'); - fs.writeFileSync(`${options.output_folder_path}/private-key.pem`, pki.privateKeyToPem(privateKey), 'utf8'); + // * The below SSL certificate settings from from https://github.com/KaeruTeam/nds-constraint?tab=readme-ov-file#generating-trusted-certificates + // TODO - Check other certificate settings and update these + + // * Read or create the site RSA key pair + let sitePrivateKey; + let sitePublicKey; + + if (options.site_private_key_path) { + const privateKeyPEM = fs.readFileSync(options.site_private_key_path); + sitePrivateKey = pki.privateKeyFromPem(privateKeyPEM); + sitePublicKey = pki.setRsaPublicKey(sitePrivateKey.n, sitePrivateKey.e); + } else { + const keyPair = pki.rsa.generateKeyPair(1024); // TODO - Make this configurable? + + sitePrivateKey = keyPair.privateKey; + sitePublicKey = keyPair.publicKey; + } + + // * Read or create the CSR (Certificate Signing Request) + let csr; + + if (options.csr_path) { + const csrPEM = fs.readFileSync(options.csr_path); + csr = pki.certificationRequestFromPem(csrPEM); + } else { + csr = pki.createCertificationRequest(); + } + + // * Update the CN and resign + csr.publicKey = sitePublicKey; + csr.setSubject([ // TODO - Add the ability to set more of these? + { + name: 'commonName', + value: options.common_name + } + ]); + csr.sign(sitePrivateKey); + + // * Create the new site SSL certificate and sign it with the forged CA + const siteCertificate = pki.createCertificate(); + + siteCertificate.serialNumber = (new Date()).getTime().toString(); // TODO - Make this configurable? + siteCertificate.validity.notBefore = new Date(); // TODO - Make this configurable? + siteCertificate.validity.notAfter = new Date(); // TODO - Make this configurable? + siteCertificate.validity.notAfter.setDate(siteCertificate.validity.notBefore.getDate() + 3650); // TODO - Make this configurable? + siteCertificate.setSubject(csr.subject.attributes); + siteCertificate.setIssuer(forgedCA.subject.attributes); + siteCertificate.publicKey = csr.publicKey; + + siteCertificate.sign(caPrivateKey, md.sha1.create()); // TODO - Make this configurable? What other signatures work for the Wii U + + // * Create the cert chain + const chain = `${pki.certificateToPem(siteCertificate)}\n${pki.certificateToPem(forgedCA)}\n` + + // * Save everything to disk + // TODO - Write public keys? + fs.writeFileSync(`${options.output_folder_path}/forged-ca.pem`, pki.certificateToPem(forgedCA), 'utf8'); + fs.writeFileSync(`${options.output_folder_path}/forged-ca-private-key.pem`, pki.privateKeyToPem(caPrivateKey), 'utf8'); + fs.writeFileSync(`${options.output_folder_path}/ssl-cert.pem`, pki.certificateToPem(siteCertificate), 'utf8'); + fs.writeFileSync(`${options.output_folder_path}/ssl-cert-private-key.pem`, pki.privateKeyToPem(sitePrivateKey), 'utf8'); + fs.writeFileSync(`${options.output_folder_path}/csr.csr`, pki.certificationRequestToPem(csr), 'utf8'); // TODO - Better name + fs.writeFileSync(`${options.output_folder_path}/cert-chain.pem`, chain, 'utf8'); } showPrompt(); \ No newline at end of file