mirror of
https://github.com/PretendoNetwork/SSSL.git
synced 2026-04-24 07:16:48 -05:00
Merge pull request #4 from MatthewL246/non-interactive-mode
This commit is contained in:
commit
ee758b69e1
|
|
@ -39,4 +39,4 @@
|
|||
"always"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -130,4 +130,6 @@ dist
|
|||
.pnp.*
|
||||
|
||||
# custom
|
||||
*.pem
|
||||
*.pem
|
||||
*.der
|
||||
*.csr
|
||||
|
|
|
|||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
|
||||
RUN --mount=type=bind,source=package.json,target=package.json \
|
||||
--mount=type=bind,source=package-lock.json,target=package-lock.json \
|
||||
--mount=type=cache,target=/root/.npm \
|
||||
npm ci
|
||||
|
||||
COPY patch.js .
|
||||
# Using ENTRYPOINT instead of CMD to allow passing arguments to the script directly
|
||||
ENTRYPOINT ["node", "patch.js"]
|
||||
40
README.md
40
README.md
|
|
@ -9,12 +9,14 @@
|
|||
On March 1, 2021 Nintendo released Wii U firmware version [5.5.5](https://wiiubrew.org/wiki/5.5.5). This update updated the Wii U's SSL verification and recompiled all RPLs (though no code changes were made). The exact purpose for this update is unknown, as nothing of significance was changed, and no other changes were made in this update. With the changes to SSL verification, Nintendo introduced a bug which allows for the forging of SSL certificates. These forged certificates will be seen as "Nintendo Signed" and, due to an existing bug with how the Wii U handles CA common names, will be accepted by all domains.
|
||||
|
||||
## The bugs
|
||||
|
||||
There are 2 bugs at play:
|
||||
|
||||
1. Normally a CA common name does not accept a single wildcard (\*) value. They must contain a hostname, and optionally one or many wildcards for subdomains. The Wii U will accept a single \* wildcard in place of a hostname, which allows the CA to be accepted as any domain. This bug has existed since before 5.5.5, but was not useful until now.
|
||||
2. As of 5.5.5, CA's crafted in a specific way may take a newly introduced alternate path for verification. This allows for a CA's signature to not be verified correctly. Instead, the Wii U simply checks if the CA matches one already known by the system, but not the signature or contents of the CA. We have no idea why this change was made, as it does not benefit Nintendo at all. It almost feels intentional.
|
||||
|
||||
## Exploiting
|
||||
|
||||
Not any CA will work. There are 3 conditions for a CA which still need to be met even for a forged CA to be accepted:
|
||||
|
||||
1. The CA needs to be one which the Wii U would already accept. The signature is not validated in this case, so modifying an existing CA works.
|
||||
|
|
@ -24,14 +26,40 @@ Not any CA will work. There are 3 conditions for a CA which still need to be met
|
|||
The easiest way to exploit this bug is to use the Nintendo CA - G3 CA, and is what this script opts to do. This can be dumped from a Wii U's SSL certificates title at `/storage_mlc/sys/title/0005001b/10054000/content/scerts/CACERT_NINTENDO_CA_G3.der`. Changing the public key to a user-controlled key and changing the authority key identifier to anything else is all that is required. The resulting user-controlled private key and patched CA can be used to bypass SSL verification without any homebrew or CFW at all.
|
||||
|
||||
## The script
|
||||
This script takes in a PEM encoded copy of Nintendo CA - G3 and does the above patches and exports the patched CA and private key.
|
||||
|
||||
- Install [NodeJS](https://nodejs.org/)
|
||||
- `git clone https://github.com/PretendoNetwork/SSSL`
|
||||
- `cd SSSL`
|
||||
- `npm i` to install the dependencies
|
||||
- `node patch` to run the patching wizard
|
||||
This script takes in a copy of the Nintendo CA - G3 (in either DER or PEM format), does the above patches, and exports the patched CA and private key.
|
||||
|
||||
1. Install [NodeJS](https://nodejs.org/)
|
||||
2. `git clone https://github.com/PretendoNetwork/SSSL.git`
|
||||
3. `cd SSSL`
|
||||
4. `npm i` to install the dependencies
|
||||
5. `node patch` to use environment variables or `node patch -i` to interactively prompt for all configuration values
|
||||
|
||||
## Docker
|
||||
|
||||
This script can also be run in a Docker container.
|
||||
|
||||
1. `git clone https://github.com/PretendoNetwork/SSSL`
|
||||
2. `cd SSSL`
|
||||
3. `docker build . -t sssl`
|
||||
4. `docker run -it --rm -v .:/app sssl` or `docker run -it --rm -v .:/app sssl -i`
|
||||
|
||||
## Configuration
|
||||
|
||||
This script can be configured using environment variables, a `.env` file, or individual command-line arguments. Alternatively, it can be run with the `-i` or `--interactive` flag to interactively prompt for all configuration values. Command-line arguments always override environment variables.
|
||||
|
||||
| Environment Variable | Command-line Argument | Description | Default |
|
||||
| -------------------------- | --------------------------------------- | ------------------------------------------------------------------- | ----------------------------- |
|
||||
| N/A | `-i`, `--interactive` | Interactively prompt for all configuration values | N/A |
|
||||
| SSSL_NINTENDO_CA_G3_PATH | `-g`, `--nintendo-ca-g3-path <value>` | Path to Nintendo CA - G3 certificate (may be in DER or PEM format) | `./CACERT_NINTENDO_CA_G3.der` |
|
||||
| SSSL_NINTENDO_CA_G3_FORMAT | `-f`, `--nintendo-ca-g3-format <value>` | Nintendo CA - G3 certificate format (must be "der" or "pem") | `der` |
|
||||
| SSSL_CA_PRIVATE_KEY_PATH | `-c`, `--ca-private-key-path <value>` | Path to private key for forged CA (will generate if not set) | N/A |
|
||||
| SSSL_SITE_PRIVATE_KEY_PATH | `-s`, `--site-private-key-path <value>` | Path to private key for site certificate (will generate if not set) | N/A |
|
||||
| SSSL_CSR_PATH | `-r`, `--csr-path <value>` | Path to CSR (will generate if not set) | N/A |
|
||||
| SSSL_COMMON_NAME | `-n`, `--common-name <value>` | CN for site certificate (see [the bugs](#the-bugs)) | `*` |
|
||||
| SSSL_OUTPUT_FOLDER_PATH | `-o`, `--output-folder-path <value>` | Output folder | `./` |
|
||||
|
||||
## Credits
|
||||
|
||||
- Shutterbug for actually finding the new verification bug
|
||||
- Jemma and Quarky for decompiling the updated SSL functions
|
||||
|
|
|
|||
21
package-lock.json
generated
21
package-lock.json
generated
|
|
@ -10,6 +10,8 @@
|
|||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@colors/colors": "^1.6.0",
|
||||
"commander": "^12.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"node-forge": "^1.3.1",
|
||||
"prompt": "^1.3.0"
|
||||
},
|
||||
|
|
@ -303,6 +305,14 @@
|
|||
"node": ">=0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
|
|
@ -366,6 +376,17 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.5",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@colors/colors": "^1.6.0",
|
||||
"commander": "^12.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"node-forge": "^1.3.1",
|
||||
"prompt": "^1.3.0"
|
||||
},
|
||||
|
|
|
|||
195
patch.js
195
patch.js
|
|
@ -1,41 +1,114 @@
|
|||
const fs = require('node:fs');
|
||||
const crypto = require('node:crypto');
|
||||
const path = require('node:path');
|
||||
const { pki, md } = require('node-forge');
|
||||
const { asn1, pki, md } = require('node-forge');
|
||||
const prompt = require('prompt');
|
||||
const colors = require('@colors/colors/safe');
|
||||
const dotenv = require('dotenv');
|
||||
const { program, Option } = require('commander');
|
||||
|
||||
class KebabCaseOption extends Option {
|
||||
attributeName() {
|
||||
// "this.name().replace(/^no-/, '')" is from commander source code
|
||||
return this.name().replace(/^no-/, '').replace(/-/g, '_');
|
||||
}
|
||||
}
|
||||
|
||||
const optionsConfig = {
|
||||
nintendo_ca_g3_path: {
|
||||
shortOption: 'g3',
|
||||
default: './CACERT_NINTENDO_CA_G3.der',
|
||||
description: 'Path to Nintendo CA - G3 certificate (may be in DER or PEM format, default to this directory)'
|
||||
},
|
||||
nintendo_ca_g3_format: {
|
||||
shortOption: 'f',
|
||||
default: 'der',
|
||||
description: 'Nintendo CA - G3 certificate format (must be "der" or "pem")'
|
||||
},
|
||||
ca_private_key_path: {
|
||||
shortOption: 'cap',
|
||||
default: undefined,
|
||||
description: 'Path to private key for forged CA (will generate if not set)'
|
||||
},
|
||||
site_private_key_path: {
|
||||
shortOption: 'sp',
|
||||
default: undefined,
|
||||
description: 'Path to private key for site certificate (will generate if not set)'
|
||||
},
|
||||
csr_path: {
|
||||
shortOption: 'csrp',
|
||||
default: undefined,
|
||||
description: 'Path to CSR (will generate if not set)'
|
||||
},
|
||||
common_name: {
|
||||
shortOption: 'cn',
|
||||
default: '*',
|
||||
description: 'CN for site certificate (default to "*")'
|
||||
},
|
||||
output_folder_path: {
|
||||
shortOption: 'o',
|
||||
default: './',
|
||||
description: 'Output folder (default to this directory)'
|
||||
}
|
||||
};
|
||||
|
||||
async function main() {
|
||||
dotenv.config();
|
||||
|
||||
program.option('-i, --interactive', 'Interactively prompt for all configuration values');
|
||||
for (const [option, config] of Object.entries(optionsConfig)) {
|
||||
program.addOption(new KebabCaseOption(`-${config.shortOption}, --${option.replace(/_/g, '-')} <value>`, config.description));
|
||||
}
|
||||
|
||||
program.parse(process.argv);
|
||||
const commandOptions = program.opts();
|
||||
|
||||
if (commandOptions.interactive) {
|
||||
showPrompt();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {};
|
||||
for (const [option, config] of Object.entries(optionsConfig)) {
|
||||
options[option] = commandOptions[option] || process.env['SSSL_' + option.toUpperCase()] || config.default;
|
||||
}
|
||||
|
||||
if (validateOptions(options)) {
|
||||
forgeCertificateChain(options);
|
||||
} else {
|
||||
throw new Error('Invalid options specified.');
|
||||
}
|
||||
}
|
||||
|
||||
async function showPrompt() {
|
||||
prompt.message = colors.magenta('SSSL');
|
||||
|
||||
prompt.start();
|
||||
|
||||
const options = await prompt.get({
|
||||
properties: {
|
||||
nintendo_ca_g3_path: {
|
||||
description: colors.blue('Path to Nintendo CA - G3 (default to this directory)'),
|
||||
default: './CACERT_NINTENDO_CA_G3.pem'
|
||||
},
|
||||
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)'),
|
||||
default: './'
|
||||
}
|
||||
}
|
||||
});
|
||||
const properties = {};
|
||||
for (const [option, config] of Object.entries(optionsConfig)) {
|
||||
properties[option] = {
|
||||
description: colors.blue(config.description),
|
||||
default: config.default
|
||||
};
|
||||
}
|
||||
const options = await prompt.get({ properties });
|
||||
|
||||
if (validateOptions(options)) {
|
||||
try {
|
||||
forgeCertificateChain(options);
|
||||
} catch (error) {
|
||||
console.log(colors.bgRed(`Error patching CA: ${error}`));
|
||||
|
||||
showPrompt();
|
||||
}
|
||||
} else {
|
||||
showPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
function validateOptions(options) {
|
||||
options.nintendo_ca_g3_path = path.resolve(options.nintendo_ca_g3_path);
|
||||
|
||||
if (options.ca_private_key_path) {
|
||||
|
|
@ -55,63 +128,53 @@ async function showPrompt() {
|
|||
if (!fs.existsSync(options.nintendo_ca_g3_path)) {
|
||||
console.log(colors.bgRed('Invalid Nintendo CA - G3 path'));
|
||||
|
||||
showPrompt();
|
||||
return false;
|
||||
}
|
||||
|
||||
return;
|
||||
if (options.nintendo_ca_g3_format !== 'der' && options.nintendo_ca_g3_format !== 'pem') {
|
||||
console.log(colors.bgRed('Invalid Nintendo CA - G3 format: must be "der" or "pem"'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.ca_private_key_path && !fs.existsSync(options.ca_private_key_path)) {
|
||||
console.log(colors.bgRed('Invalid CA private key path'));
|
||||
|
||||
showPrompt();
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.csr_path && !fs.existsSync(options.csr_path)) {
|
||||
console.log(colors.bgRed('Invalid CSR key path'));
|
||||
|
||||
showPrompt();
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(options.output_folder_path)) {
|
||||
console.log(colors.bgRed('Invalid output folder path'));
|
||||
|
||||
showPrompt();
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
forgeCertificateChain(options);
|
||||
|
||||
console.log(colors.green(`Wrote forged CA to ${options.output_folder_path}/forged-ca.pem`));
|
||||
console.log(colors.green(`Wrote forged CA private key to ${options.output_folder_path}/forged-ca-private-key.pem`));
|
||||
console.log(colors.green(`Wrote SSL certificate to ${options.output_folder_path}/ssl-cert.pem`));
|
||||
console.log(colors.green(`Wrote SSL certificate private key to ${options.output_folder_path}/ssl-cert-private-key.pem`));
|
||||
console.log(colors.green(`Wrote CSR to ${options.output_folder_path}/csr.csr`));
|
||||
console.log(colors.green(`Wrote certificate chain to ${options.output_folder_path}/cert-chain.pem`));
|
||||
} catch (error) {
|
||||
console.log(colors.bgRed(`Error patching CA: ${error}`));
|
||||
|
||||
showPrompt();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function forgeCertificateChain(options) {
|
||||
// * Parse Nintendo CA - G3
|
||||
const nintendoCAG3PEM = fs.readFileSync(options.nintendo_ca_g3_path);
|
||||
const nintendoCAG3 = pki.certificateFromPem(nintendoCAG3PEM);
|
||||
let nintendoCAG3;
|
||||
if (options.nintendo_ca_g3_format === 'pem') {
|
||||
const nintendoCAG3PEM = fs.readFileSync(options.nintendo_ca_g3_path);
|
||||
nintendoCAG3 = pki.certificateFromPem(nintendoCAG3PEM);
|
||||
} else {
|
||||
const nintendoCAG3DER = fs.readFileSync(options.nintendo_ca_g3_path, 'binary');
|
||||
const nintendoCAG3ASN1 = asn1.fromDer(nintendoCAG3DER);
|
||||
nintendoCAG3 = pki.certificateFromAsn1(nintendoCAG3ASN1);
|
||||
}
|
||||
|
||||
let caPrivateKey;
|
||||
let caPublicKey;
|
||||
|
|
@ -145,7 +208,7 @@ function forgeCertificateChain(options) {
|
|||
// * https://github.com/digitalbazaar/forge/blob/2bb97afb5058285ef09bcf1d04d6bd6b87cffd58/tests/unit/x509.js#L324-L329
|
||||
// * https://github.com/digitalbazaar/forge/blob/2bb97afb5058285ef09bcf1d04d6bd6b87cffd58/lib/x509.js#L2204-L2233
|
||||
name: 'authorityKeyIdentifier',
|
||||
keyIdentifier: crypto.randomBytes(16).toString('ascii'),
|
||||
keyIdentifier: crypto.randomBytes(16).toString('ascii'),
|
||||
authorityCertIssuer: nintendoCAG3.issuer,
|
||||
serialNumber: nintendoCAG3.serialNumber
|
||||
}
|
||||
|
|
@ -184,7 +247,8 @@ function forgeCertificateChain(options) {
|
|||
|
||||
// * Update the CN and resign
|
||||
csr.publicKey = sitePublicKey;
|
||||
csr.setSubject([ // TODO - Add the ability to set more of these?
|
||||
csr.setSubject([
|
||||
// TODO - Add the ability to set more of these?
|
||||
{
|
||||
name: 'commonName',
|
||||
value: options.common_name
|
||||
|
|
@ -195,7 +259,7 @@ function forgeCertificateChain(options) {
|
|||
// * 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.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?
|
||||
|
|
@ -211,11 +275,22 @@ function forgeCertificateChain(options) {
|
|||
// * Save everything to disk
|
||||
// TODO - Write public keys?
|
||||
fs.writeFileSync(`${options.output_folder_path}/forged-ca.pem`, pki.certificateToPem(forgedCA), 'utf8');
|
||||
console.log(colors.green(`Wrote forged CA to ${options.output_folder_path}/forged-ca.pem`));
|
||||
|
||||
fs.writeFileSync(`${options.output_folder_path}/forged-ca-private-key.pem`, pki.privateKeyToPem(caPrivateKey), 'utf8');
|
||||
console.log(colors.green(`Wrote forged CA private key to ${options.output_folder_path}/forged-ca-private-key.pem`));
|
||||
|
||||
fs.writeFileSync(`${options.output_folder_path}/ssl-cert.pem`, pki.certificateToPem(siteCertificate), 'utf8');
|
||||
console.log(colors.green(`Wrote SSL certificate to ${options.output_folder_path}/ssl-cert.pem`));
|
||||
|
||||
fs.writeFileSync(`${options.output_folder_path}/ssl-cert-private-key.pem`, pki.privateKeyToPem(sitePrivateKey), 'utf8');
|
||||
console.log(colors.green(`Wrote SSL certificate private key to ${options.output_folder_path}/ssl-cert-private-key.pem`));
|
||||
|
||||
fs.writeFileSync(`${options.output_folder_path}/csr.csr`, pki.certificationRequestToPem(csr), 'utf8'); // TODO - Better name
|
||||
console.log(colors.green(`Wrote CSR to ${options.output_folder_path}/csr.csr`));
|
||||
|
||||
fs.writeFileSync(`${options.output_folder_path}/cert-chain.pem`, chain, 'utf8');
|
||||
console.log(colors.green(`Wrote certificate chain to ${options.output_folder_path}/cert-chain.pem`));
|
||||
}
|
||||
|
||||
showPrompt();
|
||||
main();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user