mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-10-23 01:54:10 -05:00
Validate session tokens and znca id_tokens
This commit is contained in:
@@ -6,7 +6,10 @@ import frida, { Session } from 'frida';
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import type { Arguments as ParentArguments } from '../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, YargsArguments } from '../util.js';
|
||||
import { ArgumentsCamelCase, Argv, getJwks, initStorage, YargsArguments } from '../util.js';
|
||||
import { Jwt } from '../api/util.js';
|
||||
import { NintendoAccountIdTokenJwtPayload } from '../api/na.js';
|
||||
import { ZNCA_CLIENT_ID, ZncJwtPayload } from '../api/znc.js';
|
||||
|
||||
const debug = createDebug('cli:android-znca-api-server-frida');
|
||||
const debugApi = createDebug('cli:android-znca-api-server-frida:api');
|
||||
@@ -32,6 +35,7 @@ export function builder(yargs: Argv<ParentArguments>) {
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
await setup(argv);
|
||||
|
||||
let {session, script} = await attach(argv);
|
||||
@@ -43,7 +47,7 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
genAudioH2(token: string, timestamp: string, uuid: string): Promise<string>;
|
||||
} = script.exports as any;
|
||||
|
||||
process.on('beforeExit', () => {
|
||||
process.on('exit', () => {
|
||||
debug('Releasing wake lock', argv.device);
|
||||
execFileSync('adb', [
|
||||
'-s',
|
||||
@@ -103,6 +107,60 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const [jwt, sig] = Jwt.decode<NintendoAccountIdTokenJwtPayload | ZncJwtPayload>(data.token);
|
||||
|
||||
const check_signature = jwt.payload.iss === 'https://accounts.nintendo.com';
|
||||
|
||||
if (data.type === 'nso' && jwt.payload.iss !== 'https://accounts.nintendo.com') {
|
||||
throw new Error('Invalid token issuer');
|
||||
}
|
||||
if (data.type === 'nso' && jwt.payload.aud !== ZNCA_CLIENT_ID) {
|
||||
throw new Error('Invalid token audience');
|
||||
}
|
||||
if (data.type === 'app' && jwt.payload.iss !== 'api-lp1.znc.srv.nintendo.net') {
|
||||
throw new Error('Invalid token issuer');
|
||||
}
|
||||
|
||||
if (jwt.payload.exp <= (Date.now() / 1000)) {
|
||||
throw new Error('Token expired');
|
||||
}
|
||||
|
||||
const jwks = jwt.header.kid &&
|
||||
jwt.header.jku?.match(/^https\:\/\/([^/]+\.)?nintendo\.(com|net)(\/|$)/i) ?
|
||||
await getJwks(jwt.header.jku, storage) : null;
|
||||
|
||||
if (check_signature && !jwks) {
|
||||
throw new Error('Requires signature verification, but trusted JWKS URL and key ID not included in token');
|
||||
}
|
||||
|
||||
const jwk = jwks?.keys.find(jwk => jwk.use === 'sig' && jwk.alg === jwt.header.alg &&
|
||||
jwk.kid === jwt.header.kid && jwk.x5c?.length);
|
||||
const cert = jwk?.x5c?.[0] ? '-----BEGIN CERTIFICATE-----\n' +
|
||||
jwk.x5c[0].match(/.{1,64}/g)!.join('\n') + '\n-----END CERTIFICATE-----\n' : null;
|
||||
|
||||
if (!cert) {
|
||||
if (check_signature) throw new Error('Not verifying signature, no JKW found for this token');
|
||||
else debug('Not verifying signature, no JKW found for this token');
|
||||
}
|
||||
|
||||
const signature_valid = cert && jwt.verify(sig, cert);
|
||||
|
||||
if (check_signature && !signature_valid) {
|
||||
throw new Error('Invalid signature');
|
||||
}
|
||||
|
||||
if (!check_signature) {
|
||||
if (signature_valid) debug('JWT signature is valid');
|
||||
else debug('JWT signature is not valid or not checked');
|
||||
}
|
||||
} catch (err) {
|
||||
res.statusCode = 400;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({error: 'invalid_token', error_message: (err as Error).message}));
|
||||
return;
|
||||
}
|
||||
|
||||
debugApi('Calling %s', data.type === 'app' ? 'genAudioH2' : 'genAudioH');
|
||||
|
||||
const result = data.type === 'app' ?
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as crypto from 'crypto';
|
||||
import type { Arguments as ParentArguments } from '../../cli.js';
|
||||
import { ArgumentsCamelCase, Argv, getPctlToken, initStorage, YargsArguments } from '../../util.js';
|
||||
import { getNintendoAccountSessionToken } from '../../api/na.js';
|
||||
import { MOON_CLIENT_ID } from '../../api/moon.js';
|
||||
import { ZNMA_CLIENT_ID } from '../../api/moon.js';
|
||||
|
||||
const debug = createDebug('cli:pctl:auth');
|
||||
|
||||
@@ -87,7 +87,7 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
debug('Redirect URL parameters', [...authorisedparams.entries()]);
|
||||
|
||||
const code = authorisedparams.get('session_token_code')!;
|
||||
const token = await getNintendoAccountSessionToken(code, verifier, MOON_CLIENT_ID);
|
||||
const token = await getNintendoAccountSessionToken(code, verifier, ZNMA_CLIENT_ID);
|
||||
|
||||
console.log('Session token', token);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user