184 lines
9.1 KiB
JavaScript
184 lines
9.1 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const test = require('node:test');
|
|
const {
|
|
classifyPs5FpkgSyslogEvidence,
|
|
createPs5FpkgContentAuthTrace,
|
|
parseKstuffSemanticSnapshot,
|
|
} = require('../../src/services/ps5-fpkg-content-auth-trace');
|
|
|
|
const OPERATION_ID = '123e4567-e89b-42d3-a456-426614174000';
|
|
const REVISION = '3b2ab4a16ba43c8ab38daf7ec83cc297014e0e47';
|
|
const PAYLOAD_SHA256 = '1'.repeat(64);
|
|
const LOADER_SHA256 = '9'.repeat(64);
|
|
const FINGERPRINT = 'a'.repeat(64);
|
|
|
|
function runtime(overrides = {}) {
|
|
return {
|
|
product: 'EchoStretch/kstuff-lite', version: '1.10', revision: REVISION,
|
|
payloadSha256: PAYLOAD_SHA256, loaderSha256: LOADER_SHA256,
|
|
observabilityProfile: 'KSTUFF_OBS-v1.10-semantic-counters',
|
|
identityStatus: 'exact', traceEligible: true, firmware: '9.60',
|
|
fingerprint: FINGERPRINT,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function snapshot(sequence, metrics, overrides = {}) {
|
|
return { sequence, sampledAt: 1000 + sequence, runtimeFingerprint: FINGERPRINT, metrics, ...overrides };
|
|
}
|
|
|
|
function zeroMetrics() {
|
|
return {
|
|
nmountArmed: 0, nmountTrap: 0, nmountEmulated: 0, mailboxFpkg: 0,
|
|
verifySuperBlockMailbox: 0, verifySuperBlockEmulated: 0,
|
|
fakeKeyDerivationCalls: 0, fakeKeyDerivationSuccesses: 0, fakeKeyDerivationFailures: 0, fakeKeyRegisters: 0,
|
|
cryptoRequests: 0, cryptoRequestsEmulated: 0, cryptoRequestsFallback: 0, cryptoRequestsFailed: 0,
|
|
cryptoHmacMessages: 0, cryptoXtsMessages: 0, cryptoOtherMessages: 0, cryptoEmulatedMessages: 0,
|
|
rejectXtsNonFake: 0, rejectHmacNonFake: 0, rejectHmacBadShape: 0, rejectOtherMessage: 0,
|
|
requestNoEmulation: 0, requestFpuEnterFail: 0,
|
|
};
|
|
}
|
|
|
|
test('A6 network 0x8041013d stays separate from content authentication', () => {
|
|
const evidence = classifyPs5FpkgSyslogEvidence([
|
|
'[SceShellUI] ResourceArbitrator returned 0x8041013d',
|
|
'ERROR: verifyImage(989) ret -1',
|
|
'verify error=0xffffffff -> EICV',
|
|
'PfsMountGameData_PPR() ret = 0x80020060',
|
|
].join('\n'), 'FAKE00000');
|
|
assert.equal(evidence.events[0].subsystem, 'network');
|
|
assert.equal(evidence.events[0].category, 'connection_refused');
|
|
assert.equal(evidence.events[0].causalStatus, 'unknown');
|
|
assert.equal(evidence.firstContentAuthFailure.stage, 'verifyImage');
|
|
assert.equal(evidence.firstContentAuthFailure.code, 'PS5_FPKG_VERIFY_IMAGE_FAILED');
|
|
assert.notEqual(evidence.firstContentAuthFailure.code, 'PS5_FPKG_CONTENT_AUTH_AND_PFS_MOUNT_FAILED');
|
|
});
|
|
|
|
test('A6 parser accepts only the strict semantic-reader schema', () => {
|
|
const parsed = parseKstuffSemanticSnapshot([
|
|
'schema=chimera.p15-a6-semantic-metrics.v1',
|
|
'status=attached',
|
|
'sample=7',
|
|
'core mailbox_fpkg=4',
|
|
'fakekeys reg=2 derive=1 derive_ok=1 derive_fail=0',
|
|
'crypto req=3 emu=2 fallback=1 fail=0 xts_msg=1 hmac_msg=1 other_msg=1 emu_msg=2',
|
|
'fpkg verify=1 verify_emu=1',
|
|
'fpkg_rejects xts_non_fake=0 hmac_non_fake=0 hmac_shape=0 other=1 no_emu=1 fpu_fail=0',
|
|
'syscall[nmount] armed=5 trap=1 emu=1',
|
|
].join('\n'), { sequence: 7, sampledAt: 1007, runtimeFingerprint: FINGERPRINT });
|
|
assert.equal(parsed.metrics.nmountArmed, 5);
|
|
assert.equal(parsed.metrics.verifySuperBlockMailbox, 1);
|
|
assert.equal(parsed.metrics.cryptoHmacMessages, 1);
|
|
assert.equal(parsed.metrics.cryptoXtsMessages, 1);
|
|
assert.equal(parsed.metrics.rejectOtherMessage, 1);
|
|
});
|
|
|
|
test('A6 parser rejects unknown lines and extra fields before normalization', () => {
|
|
const base = [
|
|
'schema=chimera.p15-a6-semantic-metrics.v1',
|
|
'status=attached',
|
|
'sample=7',
|
|
'core mailbox_fpkg=4',
|
|
];
|
|
for (const unsafe of [
|
|
'log-word[0]=0xffff800012345678',
|
|
'mailbox-capture[0] request=0x1234 req0=0xdeadbeef',
|
|
'core mailbox_fpkg=4 mailbox_unhandled=9',
|
|
'fakekeys reg=2 derive=1 derive_ok=1 derive_fail=0 key=forbidden',
|
|
'crypto req=3 emu=2 fallback=1 fail=0 xts_msg=1 hmac_msg=1 other_msg=1 emu_msg=2 raw_key=forbidden',
|
|
]) {
|
|
assert.throws(
|
|
() => parseKstuffSemanticSnapshot([...base, unsafe].join('\n'), { sequence: 7, sampledAt: 1007, runtimeFingerprint: FINGERPRINT }),
|
|
error => error.code === 'PS5_FPKG_CONTENT_AUTH_TRACE_UNSAFE_LINE',
|
|
);
|
|
}
|
|
});
|
|
|
|
test('A6 launch delta excludes stale counters and preserves zero versus nonzero', () => {
|
|
const pre = snapshot(10, {
|
|
nmountArmed: 32, nmountTrap: 4, nmountEmulated: 4, mailboxFpkg: 8,
|
|
verifySuperBlockMailbox: 5, verifySuperBlockEmulated: 5,
|
|
fakeKeyDerivationCalls: 5, fakeKeyDerivationSuccesses: 5, fakeKeyDerivationFailures: 0, fakeKeyRegisters: 10,
|
|
cryptoRequests: 20, cryptoRequestsEmulated: 20, cryptoRequestsFallback: 0, cryptoRequestsFailed: 0,
|
|
cryptoHmacMessages: 10, cryptoXtsMessages: 10, cryptoOtherMessages: 0, cryptoEmulatedMessages: 20,
|
|
rejectXtsNonFake: 0, rejectHmacNonFake: 0, rejectHmacBadShape: 0, rejectOtherMessage: 0, requestNoEmulation: 0, requestFpuEnterFail: 0,
|
|
});
|
|
const post = snapshot(11, { ...pre.metrics, nmountArmed: 33 });
|
|
const trace = createPs5FpkgContentAuthTrace({
|
|
operationId: OPERATION_ID, titleId: 'FAKE00000', runtime: runtime(), pre, post,
|
|
syslog: 'verifyImage ret -1\nEICV\nPfsMountGameData_PPR() ret = 0x80020060',
|
|
});
|
|
assert.equal(trace.delta.nmountArmed, 1);
|
|
assert.equal(trace.delta.verifySuperBlockMailbox, 0);
|
|
assert.equal(trace.verifySuperBlock.reached, false);
|
|
assert.equal(trace.fakeKey.derivationAttempted, false);
|
|
assert.equal(trace.crypto.requests, 0);
|
|
assert.equal(trace.firstFailedStage, 'verify_superblock_mailbox_not_reached');
|
|
assert.equal(trace.decisionCase, 'A');
|
|
assert.equal(trace.productionRestorable, false);
|
|
});
|
|
|
|
test('A6 trace retains unknown values as null and rejects counter reset', () => {
|
|
const pre = snapshot(10, { nmountArmed: 4, nmountTrap: null });
|
|
const post = snapshot(11, { nmountArmed: 3, nmountTrap: null });
|
|
const trace = createPs5FpkgContentAuthTrace({ operationId: OPERATION_ID, titleId: 'FAKE00000', runtime: runtime(), pre, post, syslog: '' });
|
|
assert.equal(trace.delta.nmountArmed, null);
|
|
assert.equal(trace.delta.nmountTrap, null);
|
|
assert.equal(trace.metricContinuity, 'counter-reset-or-drift');
|
|
assert.equal(trace.status, 'outcome_unknown');
|
|
assert.equal(trace.verifyImage.reached, null);
|
|
assert.equal(trace.firstFailedStage, 'unknown');
|
|
});
|
|
|
|
test('A6 fake-key and crypto outcomes are semantic only', () => {
|
|
const pre = snapshot(1, zeroMetrics());
|
|
const post = snapshot(2, {
|
|
nmountArmed: 1, nmountTrap: 1, nmountEmulated: 1, mailboxFpkg: 1,
|
|
verifySuperBlockMailbox: 1, verifySuperBlockEmulated: 1,
|
|
fakeKeyDerivationCalls: 1, fakeKeyDerivationSuccesses: 1, fakeKeyDerivationFailures: 0, fakeKeyRegisters: 2,
|
|
cryptoRequests: 2, cryptoRequestsEmulated: 1, cryptoRequestsFallback: 1, cryptoRequestsFailed: 0,
|
|
cryptoHmacMessages: 1, cryptoXtsMessages: 1, cryptoOtherMessages: 0, cryptoEmulatedMessages: 1,
|
|
rejectXtsNonFake: 1, rejectHmacNonFake: 0, rejectHmacBadShape: 0, rejectOtherMessage: 0, requestNoEmulation: 1, requestFpuEnterFail: 0,
|
|
});
|
|
const trace = createPs5FpkgContentAuthTrace({ operationId: OPERATION_ID, titleId: 'FAKE00000', runtime: runtime(), pre, post, syslog: 'verifyImage ret -1\nEICV\nPFS mount 0x80020060' });
|
|
assert.equal(trace.fakeKey.derivationSucceeded, true);
|
|
assert.equal(trace.fakeKey.encryptionHandleValid, true);
|
|
assert.equal(trace.fakeKey.signingHandleValid, true);
|
|
assert.equal(trace.crypto.hmac, 1);
|
|
assert.equal(trace.crypto.xts, 1);
|
|
assert.equal(trace.crypto.fallback, 1);
|
|
assert.equal(trace.crypto.rejections.xtsNonFake, 1);
|
|
assert.equal(trace.firstFailedStage, 'crypto_request_rejected_or_not_emulated');
|
|
const serialized = JSON.stringify(trace);
|
|
for (const forbidden of ['keyBytes', 'eekpfs', 'ekpfs', 'dmem', 'mailboxBuffer']) assert.doesNotMatch(serialized, new RegExp(forbidden, 'i'));
|
|
});
|
|
|
|
test('A6 runtime revision and hashes are mandatory and snapshots bind to it', () => {
|
|
const pre = snapshot(1, {}); const post = snapshot(2, {});
|
|
assert.throws(
|
|
() => createPs5FpkgContentAuthTrace({ operationId: OPERATION_ID, titleId: 'FAKE00000', runtime: runtime({ identityStatus: 'unproven' }), pre, post, syslog: '' }),
|
|
error => error.code === 'PS5_FPKG_KSTUFF_RUNTIME_IDENTITY_UNPROVEN',
|
|
);
|
|
assert.throws(
|
|
() => createPs5FpkgContentAuthTrace({ operationId: OPERATION_ID, titleId: 'FAKE00000', runtime: runtime({ traceEligible: false }), pre, post, syslog: '' }),
|
|
error => error.code === 'PS5_FPKG_KSTUFF_RUNTIME_IDENTITY_UNPROVEN',
|
|
);
|
|
assert.throws(
|
|
() => createPs5FpkgContentAuthTrace({ operationId: OPERATION_ID, titleId: 'FAKE00000', runtime: runtime(), pre, post: { ...post, runtimeFingerprint: 'b'.repeat(64) }, syslog: '' }),
|
|
error => error.code === 'PS5_FPKG_CONTENT_AUTH_TRACE_RUNTIME_DRIFT',
|
|
);
|
|
});
|
|
|
|
test('A6 diagnostic profile remains distinct and interrupted result is outcome_unknown', () => {
|
|
const trace = createPs5FpkgContentAuthTrace({
|
|
operationId: OPERATION_ID, titleId: 'FAKE00000', runtime: runtime({ observabilityProfile: 'chimera-p15-fpkg-observe-v1' }),
|
|
pre: snapshot(1, {}), post: null, syslog: '', interrupted: true,
|
|
});
|
|
assert.equal(trace.status, 'outcome_unknown');
|
|
assert.equal(trace.runtime.observabilityProfile, 'chimera-p15-fpkg-observe-v1');
|
|
assert.equal(trace.launchRequests, 1);
|
|
assert.equal(trace.productionRestorable, false);
|
|
});
|