Skip to content
This repository has been archived by the owner on Oct 2, 2024. It is now read-only.

Commit

Permalink
feat: added the schema version SIOPv2_D13_OID4VP_D20 implementing to …
Browse files Browse the repository at this point in the history
…oid4vp01_0_20
  • Loading branch information
sksadjad committed Apr 22, 2024
1 parent d1aca96 commit 9cd97fe
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 1,079 deletions.
10 changes: 5 additions & 5 deletions generator/schemaGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,20 +165,20 @@ const authorizationRequestPayloadVD11 = {
skipTypeCheck: true
};

const authorizationRequestPayloadVD12OID4VPD18 = {
const authorizationRequestPayloadVD13OID4VPD20 = {
path: '../src/types/SIOP.types.ts',
tsconfig: 'tsconfig.json',
type: 'AuthorizationRequestPayloadVD12OID4VPD18', // Or <type-name> if you want to generate schema for that one type only
schemaId: 'AuthorizationRequestPayloadVD12OID4VPD18Schema',
outputPath: 'src/schemas/AuthorizationRequestPayloadVD12OID4VPD18.schema.ts',
type: 'AuthorizationRequestPayloadVD13OID4VPD20', // Or <type-name> if you want to generate schema for that one type only
schemaId: 'AuthorizationRequestPayloadVD13OID4VPD20Schema',
outputPath: 'src/schemas/AuthorizationRequestPayloadVD13OID4VPD20.schema.ts',
// outputConstName: 'AuthorizationRequestPayloadSchemaVD11',
skipTypeCheck: true
};

let schemas: Schema[] = [
writeSchema(authorizationRequestPayloadVID1),
writeSchema(authorizationRequestPayloadVD11),
writeSchema(authorizationRequestPayloadVD12OID4VPD18),
writeSchema(authorizationRequestPayloadVD13OID4VPD20),
// writeSchema(requestOptsConf),
writeSchema(responseOptsConf),
writeSchema(rPRegistrationMetadataPayload),
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"events": "^3.3.0",
"language-tags": "^1.0.9",
"multiformats": "^12.1.3",
"node-forge": "^1.3.1",
"qs": "^6.11.2",
"sha.js": "^2.4.11",
"uint8arrays": "^3.1.1",
Expand All @@ -64,6 +65,7 @@
"@transmute/ed25519-signature-2018": "^0.7.0-unstable.82",
"@types/jest": "^29.5.11",
"@types/language-tags": "^1.0.4",
"@types/node-forge": "^1.3.11",
"@types/qs": "^6.9.11",
"@types/sha.js": "^2.4.4",
"@types/uuid": "^9.0.7",
Expand Down
75 changes: 72 additions & 3 deletions src/authorization-request/AuthorizationRequest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { JWTVerifyOptions } from 'did-jwt';
import { decodeJWT } from 'did-jwt';
import { JWTDecoded } from 'did-jwt/lib/JWT';
import forge from 'node-forge';

import { PresentationDefinitionWithLocation } from '../authorization-response';
import { PresentationExchange } from '../authorization-response/PresentationExchange';
Expand Down Expand Up @@ -186,11 +189,15 @@ export class AuthorizationRequest {
throw new Error(`${SIOPErrors.INVALID_REQUEST}, redirect_uri or response_uri is needed`);
}

if (mergedPayload.client_id_scheme === 'verifier_attestation') {
verifiedJwt = await AuthorizationRequest.verifyAttestationJWT(jwt, mergedPayload.client_id);
} else if (mergedPayload.client_id_scheme === 'x509_san_dns') {
await this.checkX509SanDNSScheme(jwt, mergedPayload.client_id);
} else if (mergedPayload.client_id_scheme === 'x509_san_uri') {
throw new Error(SIOPErrors.VERIFICATION_X509_SAN_URI_SCHEME_NOT_IMPLEMENTED_ERROR)
}
await checkWellknownDIDFromRequest(mergedPayload, opts);

// TODO: we need to verify somewhere that if response_mode is direct_post, that the response_uri may be present,
// BUT not both redirect_uri and response_uri. What is the best place to do this?

const presentationDefinitions = await PresentationExchange.findValidPresentationDefinitions(mergedPayload, await this.getSupportedVersion());
return {
...verifiedJwt,
Expand Down Expand Up @@ -248,6 +255,68 @@ export class AuthorizationRequest {
};
}

/**
* Verifies a JWT according to the 'verifier_attestation' client_id_scheme, where the JWT must be
* signed with a private key corresponding to the public key specified within the JWT itself. This method
* ensures that the JWT's 'sub' claim matches the provided clientId, and it extracts and validates the
* public key from the JWT's 'cnf' (confirmation) claim, which must contain a JWK.
*
* @param jwt The JSON Web Token string to be verified. It is expected that this JWT is formatted correctly
* and includes a 'cnf' claim with a JWK representing the public key used for signing the JWT.
* @param clientId The client identifier expected to match the 'sub' claim in the JWT. This is used to
* validate that the JWT is intended for the correct recipient/client.
*/
private static async verifyAttestationJWT(jwt: string, clientId: string): Promise<VerifiedJWT> {
if (!jwt) {
throw new Error(SIOPErrors.NO_JWT);
}
const payload = decodeJWT(jwt);
const sub = payload['sub'];
const cnf = payload['cnf'];

if (sub !== clientId || !cnf || typeof cnf !== 'object' || !cnf['jwk'] || typeof cnf['jwk'] !== 'object') {
throw new Error(SIOPErrors.VERIFICATION_VERIFIER_ATTESTATION_SCHEME_ERROR);
}

return {
jwt,
payload: payload.payload,
issuer: payload['iss'],
jwk: cnf['jwk'],
};
}

/**
* verifying JWTs against X.509 certificates focusing on DNS SAN compliance, which is crucial for environments where certificate-based security is pivotal.
* @param jwt The encoded JWT from which the certificate needs to be extracted.
* @param clientId The DNS name to match against the certificate's SANs.
*/
private async checkX509SanDNSScheme(jwt: string, clientId: string): Promise<void> {
const jwtDecoded: JWTDecoded = decodeJWT(jwt);
const x5c = jwtDecoded.header['x5c'];

if (x5c == null || !Array.isArray(x5c) || x5c.length === 0) {
throw new Error(SIOPErrors.VERIFICATION_X509_SAN_DNS_SCHEME_ERROR);
}

const certificate = x5c[0];
if (!certificate) {
throw new Error(SIOPErrors.VERIFICATION_X509_SAN_DNS_SCHEME_NO_CERTIFICATE_ERROR);
}

const der = forge.util.decode64(certificate);
const asn1 = forge.asn1.fromDer(der);
const cert = forge.pki.certificateFromAsn1(asn1);

const subjectAltNames = cert.getExtension('subjectAltName');
if (!subjectAltNames || !Array.isArray(subjectAltNames['altNames'])) {
throw new Error(SIOPErrors.VERIFICATION_X509_SAN_DNS_ALT_NAMES_ERROR);
}
if (!subjectAltNames || !subjectAltNames['altNames'].some((name: any) => name.value === clientId)) {

Check warning on line 315 in src/authorization-request/AuthorizationRequest.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type

Check warning on line 315 in src/authorization-request/AuthorizationRequest.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
throw new Error(SIOPErrors.VERIFICATION_X509_SAN_DNS_SCHEME_DNS_NAME_MATCH);
}
}

public async containsResponseType(singleType: ResponseType | string): Promise<boolean> {
const responseType: string = await this.getMergedProperty('response_type');
return responseType?.includes(singleType) === true;
Expand Down
8 changes: 4 additions & 4 deletions src/helpers/SIOPSpecVersion.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AuthorizationRequestPayloadVD11Schema, AuthorizationRequestPayloadVID1Schema } from '../schemas';
import { AuthorizationRequestPayloadVD12OID4VPD18Schema } from '../schemas/validation/schemaValidation';
import { AuthorizationRequestPayloadVD13OID4VPD20Schema } from '../schemas/validation/schemaValidation';
import { AuthorizationRequestPayload, ResponseMode, SupportedVersion } from '../types';
import errors from '../types/Errors';

Expand Down Expand Up @@ -34,15 +34,15 @@ export const authorizationRequestVersionDiscovery = (authorizationRequest: Autho
const versions = [];
const authorizationRequestCopy: AuthorizationRequestPayload = JSON.parse(JSON.stringify(authorizationRequest));
// todo: We could use v11 validation for v12 for now, as we do not differentiate in the schema at this point\
const vd12Validation = AuthorizationRequestPayloadVD12OID4VPD18Schema(authorizationRequestCopy);
if (vd12Validation) {
const vd13Validation = AuthorizationRequestPayloadVD13OID4VPD20Schema(authorizationRequestCopy);
if (vd13Validation) {
if (
!authorizationRequestCopy.registration_uri &&
!authorizationRequestCopy.registration &&
!(authorizationRequestCopy.claims && 'vp_token' in authorizationRequestCopy.claims) &&
authorizationRequestCopy.response_mode !== ResponseMode.POST // Post has been replaced by direct post
) {
versions.push(SupportedVersion.SIOPv2_D12_OID4VP_D18);
versions.push(SupportedVersion.SIOPv2_D13_OID4VP_D20);
}
}
const vd11Validation = AuthorizationRequestPayloadVD11Schema(authorizationRequestCopy);
Expand Down
2 changes: 1 addition & 1 deletion src/id-token/Payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const createIDTokenPayload = async (
const rpSupportedVersions = authorizationRequestVersionDiscovery(payload);
const maxRPVersion = rpSupportedVersions.reduce(
(previous, current) => (current.valueOf() > previous.valueOf() ? current : previous),
SupportedVersion.SIOPv2_D12_OID4VP_D18,
SupportedVersion.SIOPv2_D13_OID4VP_D20,
);
if (responseOpts.version && rpSupportedVersions.length > 0 && !rpSupportedVersions.includes(responseOpts.version)) {
throw Error(`RP does not support spec version ${responseOpts.version}, supported versions: ${rpSupportedVersions.toString()}`);
Expand Down
2 changes: 1 addition & 1 deletion src/op/OP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export class OP {
registration: { ...this._createResponseOptions?.registration, issuer },
responseURI,
responseURIType:
this._createResponseOptions.responseURIType ?? (version < SupportedVersion.SIOPv2_D12_OID4VP_D18 && responseURI ? 'redirect_uri' : undefined),
this._createResponseOptions.responseURIType ?? (version < SupportedVersion.SIOPv2_D13_OID4VP_D20 && responseURI ? 'redirect_uri' : undefined),
};
}

Expand Down
Loading

0 comments on commit 9cd97fe

Please sign in to comment.