Skip to content

feat: [SIW-2087] Update PID data model to 0.9.1 #208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6dee770
chore: update credential issuer entity config types
gispada Mar 11, 2025
afd398e
chore: update PAR request
gispada Mar 11, 2025
d3f7673
chore: use OAuth-Client-Attestation headers in token endpoint
gispada Mar 11, 2025
ff21d2c
Revert "chore: use OAuth-Client-Attestation headers in token endpoint"
gispada Mar 11, 2025
f494b65
Revert "chore: update PAR request"
gispada Mar 11, 2025
647f832
chore: modify PAR request
gispada Mar 11, 2025
2313b80
chore: request object type
gispada Mar 12, 2025
743a478
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Mar 14, 2025
b8eadc9
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Mar 17, 2025
9cae318
chore: use the new structure of claims in credential_configurations_s…
gispada Mar 17, 2025
56dfc01
chore: update SdJwt4VC type
gispada Mar 18, 2025
9bce387
chore: fetch type metadata function
gispada Mar 18, 2025
a3832cc
chore: verify VCT integrity
gispada Mar 20, 2025
d2e728a
chore(sd-jwt): add issuing_authority and issuing_country
gispada Mar 20, 2025
59530e4
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Mar 25, 2025
56215b2
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Mar 31, 2025
b457507
Merge branch 'master' into SIW-2087-pid-0.9.x
RiccardoMolinari95 Mar 31, 2025
a93e0e0
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Apr 4, 2025
5b13b20
chore: remove comments
gispada Apr 4, 2025
23702ab
chore: switch vc+sd-jwt with dc+sd-jwt
gispada Apr 4, 2025
5d83716
Merge branch 'master' into SIW-2087-pid-0.9.x
RiccardoMolinari95 Apr 8, 2025
83fdf4a
Merge branch 'master' into SIW-2087-pid-0.9.x
gispada Apr 14, 2025
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
46 changes: 46 additions & 0 deletions src/credential/issuance/06-obtain-credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { EvaluateIssuerTrust } from "./02-evaluate-issuer-trust";
import { hasStatusOrThrow, type Out } from "../../utils/misc";
import type { StartUserAuthorization } from "./03-start-user-authorization";
import {
IoWalletError,
IssuerResponseError,
IssuerResponseErrorCodes,
ResponseErrorBuilder,
Expand All @@ -16,6 +17,7 @@ import {
} from "../../utils/errors";
import { CredentialResponse } from "./types";
import { createDPopToken } from "../../utils/dpop";
import { TypeMetadata } from "../../sd-jwt/types";
import { v4 as uuidv4 } from "uuid";
import { LogLevel, Logger } from "../../utils/logging";

Expand Down Expand Up @@ -213,3 +215,47 @@ const handleObtainCredentialError = (e: unknown) => {
})
.buildFrom(e);
};

/**
* Retrieve the Type Metadata for a credential and verify its integrity.
* @param vct The VCT as a valid HTTPS url
* @param vctIntegrity The integrity hash
* @param context.appFetch (optional) fetch api implementation. Default: built-in fetch
* @returns The credential metadata {@link TypeMetadata}
*/
export const fetchTypeMetadata = async (
vct: string,
vctIntegrity: string,
context: {
appFetch?: GlobalFetch["fetch"];
} = {}
): Promise<TypeMetadata> => {
const { appFetch = fetch } = context;
const { origin, pathname } = new URL(vct);

const metadata = await appFetch(`${origin}/.well-known/vct${pathname}`, {
headers: {
"Content-Type": "application/json",
},
})
.then(hasStatusOrThrow(200, IssuerResponseError))
.then((res) => TypeMetadata.parse(res.json()));

const [alg, hash] = vctIntegrity.split(/-(.*)/s);

if (alg !== "sha256") {
throw new IoWalletError(`${alg} algorithm is not supported`);
}

// TODO: check if the hash is correctly calculated
const metadataHash = await sha256ToBase64(JSON.stringify(metadata));

if (metadataHash !== hash) {
throw new ValidationFailed({
message: "Unable to verify VCT integrity",
reason: "vct#integrity does not match the metadata hash",
});
}

return metadata;
};
16 changes: 8 additions & 8 deletions src/credential/issuance/07-verify-and-parse-credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ const parseCredentialSdJwt = (
Logger.log(LogLevel.ERROR, "Missing claims in the credential subject");
throw new IoWalletError("Missing claims in the credential subject"); // TODO [SIW-1268]: should not be optional
}
const attrDefinitions = Object.entries(credentialSubject.claims);
const attrDefinitions = credentialSubject.claims;

// the key of the attribute defintion must match the disclosure's name
const attrsNotInDisclosures = attrDefinitions.filter(
([attrKey]) => !disclosures.some(([, name]) => name === attrKey)
(definition) => !disclosures.some(([, name]) => name === definition.path[0]) // Ignore nested paths for now, see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-15.html#name-claims-path-pointer
);
if (attrsNotInDisclosures.length > 0) {
const missing = attrsNotInDisclosures.map((_) => _[0 /* key */]).join(", ");
const missing = attrsNotInDisclosures.map((_) => _.path[0]).join(", ");
const received = disclosures.map((_) => _[1 /* name */]).join(", ");
if (!ignoreMissingAttributes) {
Logger.log(
Expand All @@ -111,13 +111,13 @@ const parseCredentialSdJwt = (
attrDefinitions
// retrieve the value from the disclosure set
.map(
([attrKey, definition]) =>
({ path, ...definition }) =>
[
attrKey,
path[0],
{
...definition,
value: disclosures.find(
(_) => _[1 /* name */] === attrKey
(_) => _[1 /* name */] === path[0]
)?.[2 /* value */],
},
] as const
Expand Down Expand Up @@ -206,7 +206,7 @@ type WithFormat<Format extends Parameters<VerifyAndParseCredential>[2]> = (
_3: Parameters<VerifyAndParseCredential>[3]
) => ReturnType<VerifyAndParseCredential>;

const verifyAndParseCredentialSdJwt: WithFormat<"vc+sd-jwt"> = async (
const verifyAndParseCredentialSdJwt: WithFormat<"dc+sd-jwt"> = async (
issuerConf,
credential,
_,
Expand Down Expand Up @@ -264,7 +264,7 @@ export const verifyAndParseCredential: VerifyAndParseCredential = async (
format,
context
) => {
if (format === "vc+sd-jwt") {
if (format === "dc+sd-jwt") {
Logger.log(LogLevel.DEBUG, "Parsing credential in vc+sd-jwt format");
return verifyAndParseCredentialSdJwt(
issuerConf,
Expand Down
2 changes: 1 addition & 1 deletion src/credential/issuance/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export type SupportedCredentialFormat = z.infer<
typeof SupportedCredentialFormat
>;
export const SupportedCredentialFormat = z.union([
z.literal("vc+sd-jwt"),
z.literal("dc+sd-jwt"),
z.literal("vc+mdoc-cbor"),
]);
2 changes: 2 additions & 0 deletions src/credential/issuance/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { authorizeAccess, type AuthorizeAccess } from "./05-authorize-access";
import {
obtainCredential,
fetchTypeMetadata,
type ObtainCredential,
} from "./06-obtain-credential";
import {
Expand All @@ -38,6 +39,7 @@ export {
completeUserAuthorizationWithFormPostJwtMode,
authorizeAccess,
obtainCredential,
fetchTypeMetadata,
verifyAndParseCredential,
parseAuthorizationResponse,
Errors,
Expand Down
6 changes: 3 additions & 3 deletions src/credential/presentation/07-evaluate-dcql-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const mapCredentialToObject = (jwt: string) => {
const credentialFormat = sdJwt.header.typ;

// TODO [SIW-2082]: support MDOC credentials
if (credentialFormat !== "vc+sd-jwt") {
if (credentialFormat !== "dc+sd-jwt") {
throw new Error(`Unsupported credential format: ${credentialFormat}`);
}

Expand Down Expand Up @@ -106,7 +106,7 @@ export const evaluateDcqlQuery: EvaluateDcqlQuery = (
);

return getDcqlQueryMatches(queryResult).map(([id, match]) => {
if (match.output.credential_format !== "vc+sd-jwt") {
if (match.output.credential_format !== "dc+sd-jwt") {
throw new Error("Unsupported format"); // TODO [SIW-2082]: support MDOC credentials
}
const { vct, claims } = match.output;
Expand Down Expand Up @@ -167,7 +167,7 @@ export const prepareRemotePresentations: PrepareRemotePresentations = async (
credentialId: item.id,
requestedClaims: item.requestedClaims,
vpToken: vp_token,
format: "vc+sd-jwt",
format: "dc+sd-jwt",
};
})
);
Expand Down
10 changes: 5 additions & 5 deletions src/credential/presentation/07-evaluate-input-descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ export const findCredentialSdJwt = (
}

throw new CredentialNotFoundError(
"None of the vc+sd-jwt credentials satisfy the requirements."
"None of the dc+sd-jwt credentials satisfy the requirements."
);
};

Expand Down Expand Up @@ -323,10 +323,10 @@ export const evaluateInputDescriptors: EvaluateInputDescriptors = async (

return Promise.all(
inputDescriptors.map(async (descriptor) => {
if (descriptor.format?.["vc+sd-jwt"]) {
if (descriptor.format?.["dc+sd-jwt"]) {
if (!decodedSdJwtCredentials.length) {
throw new CredentialNotFoundError(
"vc+sd-jwt credential is not supported."
"dc+sd-jwt credential is not supported."
);
}

Expand Down Expand Up @@ -370,7 +370,7 @@ export const prepareLegacyRemotePresentations: PrepareLegacyRemotePresentations
credentialAndDescriptors.map(async (item) => {
const descriptor = item.inputDescriptor;

if (descriptor.format?.["vc+sd-jwt"]) {
if (descriptor.format?.["dc+sd-jwt"]) {
const { vp_token } = await prepareVpToken(nonce, client_id, [
item.credential,
item.requestedClaims,
Expand All @@ -381,7 +381,7 @@ export const prepareLegacyRemotePresentations: PrepareLegacyRemotePresentations
requestedClaims: item.requestedClaims,
inputDescriptor: descriptor,
vpToken: vp_token,
format: "vc+sd-jwt",
format: "dc+sd-jwt",
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("evaluateDcqlQuery", () => {
credentials: [
{
id: "PersonIdentificationData",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
// @ts-expect-error invalid query on purpose
claims: [{ id: "tax_id_code", path: "tax_id_code" }],
},
Expand All @@ -34,7 +34,7 @@ describe("evaluateDcqlQuery", () => {
credentials: [
{
id: "PersonIdentificationData",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
claims: [{ id: "tax_id_code", path: ["tax_id_code"] }],
claim_sets: [["missing_claim", "tax_id_code"]],
},
Expand All @@ -49,7 +49,7 @@ describe("evaluateDcqlQuery", () => {
credentials: [
{
id: "PersonIdentificationData",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["MissingPID"],
},
Expand All @@ -61,7 +61,7 @@ describe("evaluateDcqlQuery", () => {
credentials: [
{
id: "PersonIdentificationData",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["MissingPID"],
},
Expand All @@ -85,7 +85,7 @@ describe("evaluateDcqlQuery", () => {
credentials: [
{
id: "PID",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["PersonIdentificationData"],
},
Expand Down Expand Up @@ -122,7 +122,7 @@ describe("evaluateDcqlQuery", () => {
credentials: [
{
id: "PID",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["PersonIdentificationData"],
},
Expand All @@ -134,7 +134,7 @@ describe("evaluateDcqlQuery", () => {
},
{
id: "DrivingLicense",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["MDL"],
},
Expand Down Expand Up @@ -177,15 +177,15 @@ describe("evaluateDcqlQuery", () => {
credentials: [
{
id: "PID",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["PersonIdentificationData"],
},
claims: [{ path: ["given_name"] }, { path: ["family_name"] }],
},
{
id: "optional",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["other_credential"],
},
Expand Down Expand Up @@ -221,15 +221,15 @@ describe("evaluateDcqlQuery", () => {
credentials: [
{
id: "PID",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["PersonIdentificationData"],
},
claims: [{ path: ["given_name"] }, { path: ["family_name"] }],
},
{
id: "MDL",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["MDL"],
},
Expand Down Expand Up @@ -275,7 +275,7 @@ describe("evaluateDcqlQuery", () => {
credentials: [
{
id: "PID",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["PersonIdentificationData"],
},
Expand All @@ -287,7 +287,7 @@ describe("evaluateDcqlQuery", () => {
},
{
id: "MDL",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["MDL"],
},
Expand All @@ -307,7 +307,7 @@ describe("evaluateDcqlQuery", () => {
},
{
id: "optional",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["other_credential"],
},
Expand Down Expand Up @@ -359,7 +359,7 @@ describe("evaluateDcqlQuery", () => {
credentials: [
{
id: "PID",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["PersonIdentificationData"],
},
Expand All @@ -371,7 +371,7 @@ describe("evaluateDcqlQuery", () => {
},
{
id: "MDL",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["MDL"],
},
Expand All @@ -383,7 +383,7 @@ describe("evaluateDcqlQuery", () => {
},
{
id: "optional",
format: "vc+sd-jwt",
format: "dc+sd-jwt",
meta: {
vct_values: ["other_credential"],
},
Expand Down
Loading
Loading