Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,100 @@ describe(`[${testConfig.name}] Presentation Authorization Request Validation`, (
}
});

// -----------------------------------------------------------------------
// RPR-53 — SD-JWT integrity verification failure
// -----------------------------------------------------------------------

test("RPR_053: SD-JWT integrity verification failure | RP rejects a response containing a tampered KB-JWT signature", async () => {
const log = baseLog.withTag("RPR_053");
const DESCRIPTION =
"RP correctly rejected response with tampered SD-JWT KB-JWT signature";
log.start("Conformance test: SD-JWT integrity verification failure");

let testSuccess = false;
try {
const authResult = await runAuthorizationStep(
testConfig.authorizeStepClass,
);
expect(authResult.success).toBe(true);
if (!authResult.response) {
throw new Error("auth request was not successful");
}

const { requestObject, responseUri } = authResult.response;
const rawVpToken = authResult.response.authorizationResponse
.authorizationResponsePayload.vp_token as Record<
string,
string | string[]
>;

log.info("→ Tampering KB-JWT signature inside the SD-JWT VP vp_token...");

const tamperedVpToken: Record<string, string | string[]> = {};
for (const [credId, sdJwtVp] of Object.entries(rawVpToken)) {
const tamperSdJwt = (sdJwt: string): string => {
const parts = sdJwt.split("~");
const kbJwt = parts[parts.length - 1] ?? "";
const jwtParts = kbJwt.split(".");
if (jwtParts.length !== 3) return sdJwt;
const sig = jwtParts[2] ?? "";
const tamperedSig =
sig.slice(0, -4) + (sig.endsWith("AAAA") ? "BBBB" : "AAAA");
jwtParts[2] = tamperedSig;
parts[parts.length - 1] = jwtParts.join(".");
return parts.join("~");
};

tamperedVpToken[credId] = Array.isArray(sdJwtVp)
? sdJwtVp.map(tamperSdJwt)
: tamperSdJwt(sdJwtVp);
}

log.info(
"→ Re-building JARM with tampered vp_token and posting to response_uri...",
);

const metadata = {
...verifierMetadata,
authorization_encrypted_response_alg:
verifierMetadata.authorization_encrypted_response_alg || "ECDH-ES",
authorization_encrypted_response_enc:
verifierMetadata.authorization_encrypted_response_enc ||
"A128CBC-HS256",
};

const tamperedAuthorizationResponse = await createAuthorizationResponse({
authorization_encrypted_response_alg:
metadata.authorization_encrypted_response_alg,
authorization_encrypted_response_enc:
metadata.authorization_encrypted_response_enc,
callbacks: {
...partialCallbacks,
encryptJwe: getEncryptJweCallback(),
},
config: ioWalletSdkConfig,
requestObject,
rpJwks: { jwks: metadata.jwks },
vp_token: tamperedVpToken,
} as CreateAuthorizationResponseVersionedOptions);

const formBody = new URLSearchParams({
response: tamperedAuthorizationResponse.jarm.responseJwe,
});
const response = await postToResponseUri(responseUri, {
body: formBody.toString(),
});

log.debug(` Response status: ${response.status}`);
log.info("→ Validating RP rejected the tampered SD-JWT...");
expect(response.ok).toBe(false);

testSuccess = true;
} finally {
log.testCompleted(DESCRIPTION, testSuccess);
}
});

// -----------------------------------------------------------------------
// RPR-60 — Invalid HTTP methods
// -----------------------------------------------------------------------
Expand Down
77 changes: 77 additions & 0 deletions tests/conformance/presentation/happy.presentation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable max-lines-per-function */
import { definePresentationTest } from "#/config/test-metadata";
import { postToResponseUri } from "#/helpers";
import { assertPresentationFlowSuccess } from "#/helpers/flow-assertion-helpers";
import { useTestSummary } from "#/helpers/use-test-summary";
import { extractClientIdPrefix } from "@pagopa/io-wallet-oid4vp";
Expand Down Expand Up @@ -225,6 +226,36 @@ describe(`[${testConfig.name}] Credential Presentation Tests`, () => {
}
});

test("RPR028: response_code has sufficient entropy with at least 32 URL-safe characters.", () => {
const log = baseLog.withTag("RPR028");

log.start("Conformance test: Verifying response_code entropy requirements");

const DESCRIPTION =
"Relying Party correctly provides response_code with sufficient entropy (≥32 characters, URL-safe charset)";
let testSuccess = false;
try {
expect(redirectUriResult.success).toBe(true);
expect(redirectUriResult.response?.responseCode).toBeDefined();

const responseCode = redirectUriResult.response?.responseCode ?? "";
log.debug(` response_code: ${responseCode}`);
log.debug(` Length: ${responseCode.length} characters`);

expect(responseCode.length).toBeGreaterThanOrEqual(32);
log.debug(
" ✅ response_code length meets minimum entropy requirement (≥32)",
);

expect(responseCode).toMatch(/^[a-zA-Z0-9_-]+$/);
log.debug(" ✅ response_code uses URL-safe characters only");

testSuccess = true;
} finally {
log.testCompleted(DESCRIPTION, testSuccess);
}
});

test("RPR078: Wallet Attestation request correctly uses standard DCQL query.", () => {
const log = baseLog.withTag("RPR078");

Expand Down Expand Up @@ -622,4 +653,50 @@ describe(`[${testConfig.name}] Credential Presentation Tests`, () => {
log.testCompleted(DESCRIPTION, testSuccess);
}
});

test("RPR_110: HTTP 200 on valid response_uri submission | RP returns HTTP 200 for a well-formed authorization response", async () => {
const log = baseLog.withTag("RPR_110");
const DESCRIPTION =
"RP correctly returns HTTP 200 for a valid authorization response";
log.start(
"Conformance test: HTTP 200 on valid response_uri submission (success path)",
);

let testSuccess = false;
try {
log.info(
"→ Running a fresh authorization step to obtain a valid JARM and response_uri...",
);
const ctx = await orchestrator.runThroughAuthorize();

const authResponse = ctx.authorizationRequestResponse.response;
if (!authResponse) {
throw new Error(
"Fresh authorization step failed: response is undefined",
);
}

const freshResponseUri = authResponse.responseUri;
const freshJarm = authResponse.authorizationResponse.jarm.responseJwe;

log.info("→ Posting valid JARM to response_uri...");
log.debug(` response_uri: ${freshResponseUri}`);
const formBody = new URLSearchParams({ response: freshJarm });
const response = await postToResponseUri(freshResponseUri, {
body: formBody.toString(),
});

log.debug(` Response status: ${response.status}`);
log.info("→ Validating RP returned HTTP 200...");
expect(
response.status,
"RP must return HTTP 200 for a valid authorization response per OID4VP spec",
).toBe(200);
log.debug(" ✅ response_uri returned HTTP 200");

testSuccess = true;
} finally {
log.testCompleted(DESCRIPTION, testSuccess);
}
});
});
Loading