Skip to content

Commit

Permalink
feat: validate jarm metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
auer-martin committed Sep 29, 2024
1 parent 4cb9259 commit b09da53
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 76 deletions.
5 changes: 3 additions & 2 deletions packages/jarm/lib/metadata/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './v-jarm-client-metadata-params.js';
export * from './v-jarm-server-metadata-params.js';
export * from './v-jarm-client-metadata.js';
export * from './v-jarm-server-metadata.js';
export * from './jarm-validate-metadata.js';
90 changes: 90 additions & 0 deletions packages/jarm/lib/metadata/jarm-validate-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as v from 'valibot';

import {
vJarmClientMetadata,
vJarmClientMetadataEncrypt,
vJarmClientMetadataSign,
vJarmClientMetadataSignEncrypt,
} from '../metadata/v-jarm-client-metadata.js';
import { vJarmServerMetadata } from '../metadata/v-jarm-server-metadata.js';
import { assertValueSupported } from '../utils.js';

export const vJarmAuthResponseValidateMetadataInput = v.object({
client_metadata: vJarmClientMetadata,
server_metadata: v.partial(vJarmServerMetadata),
});
export type JarmMetadataValidate = v.InferInput<typeof vJarmAuthResponseValidateMetadataInput>;

export const vJarmMetadataValidateOut = v.variant('type', [
v.object({
type: v.literal('signed'),
client_metadata: vJarmClientMetadataSign,
}),
v.object({
type: v.literal('encrypted'),
client_metadata: vJarmClientMetadataEncrypt,
}),
v.object({
type: v.literal('signed encrypted'),
client_metadata: vJarmClientMetadataSignEncrypt,
}),
]);

export const jarmMetadataValidate = (vJarmMetadataValidate: JarmMetadataValidate): v.InferOutput<typeof vJarmMetadataValidateOut> => {
const { client_metadata, server_metadata } = vJarmMetadataValidate;

assertValueSupported({
supported: server_metadata.authorization_signing_alg_values_supported ?? [],
actual: client_metadata.authorization_signed_response_alg,
required: !!client_metadata.authorization_signed_response_alg,
error: new Error('Invalid authorization_signed_response_alg'),
});

assertValueSupported({
supported: server_metadata.authorization_encryption_alg_values_supported ?? [],
actual: client_metadata.authorization_encrypted_response_alg,
required: !!client_metadata.authorization_encrypted_response_alg,
error: new Error('Invalid authorization_encrypted_response_alg'),
});

assertValueSupported({
supported: server_metadata.authorization_encryption_enc_values_supported ?? [],
actual: client_metadata.authorization_encrypted_response_enc,
required: !!client_metadata.authorization_encrypted_response_enc,
error: new Error('Invalid authorization_encrypted_response_enc'),
});

if (
client_metadata.authorization_signed_response_alg &&
client_metadata.authorization_encrypted_response_alg &&
client_metadata.authorization_encrypted_response_enc
) {
return {
type: 'signed encrypted',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
client_metadata: client_metadata as any,
};
} else if (
client_metadata.authorization_signed_response_alg &&
!client_metadata.authorization_encrypted_response_alg &&
!client_metadata.authorization_encrypted_response_enc
) {
return {
type: 'signed',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
client_metadata: client_metadata as any,
};
} else if (
!client_metadata.authorization_signed_response_alg &&
client_metadata.authorization_encrypted_response_alg &&
client_metadata.authorization_encrypted_response_enc
) {
return {
type: 'encrypted',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
client_metadata: client_metadata as any,
};
} else {
throw new Error(`Invalid jarm client_metadata combination`);
}
};
43 changes: 0 additions & 43 deletions packages/jarm/lib/metadata/v-jarm-client-metadata-params.ts

This file was deleted.

42 changes: 42 additions & 0 deletions packages/jarm/lib/metadata/v-jarm-client-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as v from 'valibot';

export const vJarmClientMetadataSign = v.object({
authorization_signed_response_alg: v.pipe(
v.optional(v.string()), // @default 'RS256' This makes no sense with openid4vp if just encrypted can be specified
v.description(
'JWA. If this is specified, the response will be signed using JWS and the configured algorithm. The algorithm none is not allowed.',
),
),

authorization_encrypted_response_alg: v.optional(v.never()),
authorization_encrypted_response_enc: v.optional(v.never()),
});

export const vJarmClientMetadataEncrypt = v.object({
authorization_signed_response_alg: v.optional(v.never()),
authorization_encrypted_response_alg: v.pipe(
v.string(),
v.description(
'JWE alg algorithm JWA. If both signing and encryption are requested, the response will be signed then encrypted with the provided algorithm.',
),
),

authorization_encrypted_response_enc: v.pipe(
v.optional(v.string(), 'A128CBC-HS256'),
v.description(
'JWE enc algorithm JWA. If both signing and encryption are requested, the response will be signed then encrypted with the provided algorithm.',
),
),
});

export const vJarmClientMetadataSignEncrypt = v.object({
...v.pick(vJarmClientMetadataSign, ['authorization_signed_response_alg']).entries,
...v.pick(vJarmClientMetadataEncrypt, ['authorization_encrypted_response_alg', 'authorization_encrypted_response_enc']).entries,
});

/**
* Clients may register their public encryption keys using the jwks_uri or jwks metadata parameters.
*/
export const vJarmClientMetadata = v.union([vJarmClientMetadataSign, vJarmClientMetadataEncrypt, vJarmClientMetadataSignEncrypt]);

export type JarmClientMetadata = v.InferInput<typeof vJarmClientMetadata>;
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,27 @@ import * as v from 'valibot';
/**
* Authorization servers SHOULD publish the supported algorithms for signing and encrypting the JWT of an authorization response by utilizing OAuth 2.0 Authorization Server Metadata [RFC8414] parameters.
*/
export const vJarmServerMetadataParams = v.object({
export const vJarmServerMetadata = v.object({
authorization_signing_alg_values_supported: v.pipe(
v.array(v.string()),
v.description(
'JSON array containing a list of the JWS [RFC7515] signing algorithms (alg values) JWA [RFC7518] supported by the authorization endpoint to sign the response.'
)
'JSON array containing a list of the JWS [RFC7515] signing algorithms (alg values) JWA [RFC7518] supported by the authorization endpoint to sign the response.',
),
),

authorization_encryption_alg_values_supported: v.pipe(
v.array(v.string()),
v.description(
'JSON array containing a list of the JWE [RFC7516] encryption algorithms (alg values) JWA [RFC7518] supported by the authorization endpoint to encrypt the response.'
)
'JSON array containing a list of the JWE [RFC7516] encryption algorithms (alg values) JWA [RFC7518] supported by the authorization endpoint to encrypt the response.',
),
),

authorization_encryption_enc_values_supported: v.pipe(
v.array(v.string()),
v.description(
'JSON array containing a list of the JWE [RFC7516] encryption algorithms (enc values) JWA [RFC7518] supported by the authorization endpoint to encrypt the response.'
)
'JSON array containing a list of the JWE [RFC7516] encryption algorithms (enc values) JWA [RFC7518] supported by the authorization endpoint to encrypt the response.',
),
),
});

export type JarmServerMetadataParams = v.InferInput<
typeof vJarmServerMetadataParams
>;
export type JarmServerMetadata = v.InferInput<typeof vJarmServerMetadata>;
15 changes: 15 additions & 0 deletions packages/jarm/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,18 @@ export function appendFragmentParams(input: { url: URL; fragments: Record<string

return url;
}

interface AssertValueSupported<T> {
supported: T[];
actual: T;
error: Error;
required: boolean;
}

export function assertValueSupported<T>(input: AssertValueSupported<T>): T | undefined {
const { required, error, supported, actual } = input;
const intersection = supported.find((value) => value === actual);

if (required && !intersection) throw error;
return intersection;
}
18 changes: 4 additions & 14 deletions packages/jarm/lib/v-response-type-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,9 @@ import * as v from 'valibot';
export const oAuthResponseTypes = v.picklist(['code', 'token']);

// NOTE: MAKE SURE THAT THE RESPONSE TYPES ARE SORTED CORRECTLY
export const oAuthMRTEPResponseTypes = v.picklist([
'none',
'id_token',
'code token',
'code id_token',
'id_token token',
'code id_token token',
]);
export const oAuthMRTEPResponseTypes = v.picklist(['none', 'id_token', 'code token', 'code id_token', 'id_token token', 'code id_token token']);

export const openid4vpResponseTypes = v.picklist([
'vp_token',
'id_token vp_token',
]);
export const openid4vpResponseTypes = v.picklist(['vp_token', 'id_token vp_token']);

export const vTransformedResponseTypes = v.picklist([
...openid4vpResponseTypes.options,
Expand All @@ -25,8 +15,8 @@ export const vTransformedResponseTypes = v.picklist([

export const vResponseType = v.pipe(
v.string(),
v.transform(val => val.split(' ').sort().join(' ')),
vTransformedResponseTypes
v.transform((val) => val.split(' ').sort().join(' ')),
vTransformedResponseTypes,
);

export type ResponseType = v.InferInput<typeof vTransformedResponseTypes>;
Expand Down
6 changes: 5 additions & 1 deletion packages/siop-oid4vp/lib/op/OP.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EventEmitter } from 'events'

import { jarmAuthResponseSend } from '@sphereon/jarm'
import { jarmAuthResponseSend, JarmClientMetadata, jarmMetadataValidate, JarmServerMetadata } from '@sphereon/jarm'
import { JwtIssuer, uuidv4 } from '@sphereon/oid4vc-common'
import { IIssuerId } from '@sphereon/ssi-types'

Expand Down Expand Up @@ -361,4 +361,8 @@ export class OP {
get verifyRequestOptions(): Partial<VerifyAuthorizationRequestOpts> {
return this._verifyRequestOptions
}

public static validateJarmMetadata(input: { client_metadata: JarmClientMetadata; server_metadata: Partial<JarmServerMetadata> }) {
return jarmMetadataValidate(input)
}
}
6 changes: 2 additions & 4 deletions packages/siop-oid4vp/lib/rp/RP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
AuthorizationEvent,
AuthorizationEvents,
AuthorizationResponsePayload,
DecryptCompact,
PassBy,
RegisterEventListener,
RequestObjectPayload,
Expand Down Expand Up @@ -143,10 +144,7 @@ export class RP {
static async processJarmAuthorizationResponse(
response: string,
opts: {
decryptCompact: (input: {
jwk: { kid: string }
jwe: string
}) => Promise<{ plaintext: string; protectedHeader: Record<string, unknown> & { alg: string; enc: string } }>
decryptCompact: DecryptCompact
getAuthRequestPayload: (input: JarmDirectPostJwtResponseParams | JarmAuthResponseParams) => Promise<{ authRequestParams: RequestObjectPayload }>
},
) {
Expand Down
5 changes: 5 additions & 0 deletions packages/siop-oid4vp/lib/types/JWT.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,8 @@ export interface JWK {
}

// export declare type ECCurve = 'P-256' | 'secp256k1' | 'P-384' | 'P-521';

export type DecryptCompact = (input: {
jwk: { kid: string }
jwe: string
}) => Promise<{ plaintext: string; protectedHeader: Record<string, unknown> & { alg: string; enc: string } }>
6 changes: 4 additions & 2 deletions packages/siop-oid4vp/lib/types/SIOP.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// noinspection JSUnusedGlobalSymbols
import { JarmClientMetadataParams } from '@sphereon/jarm'
import { JarmClientMetadata } from '@sphereon/jarm'
import { SigningAlgo } from '@sphereon/oid4vc-common'
import { Format, PresentationDefinitionV1, PresentationDefinitionV2 } from '@sphereon/pex-models'
import {
Expand Down Expand Up @@ -376,7 +376,7 @@ export type DiscoveryMetadataPayload = DiscoveryMetadataPayloadVID1 | JWT_VCDisc
export type DiscoveryMetadataOpts = (JWT_VCDiscoveryMetadataOpts | DiscoveryMetadataOptsVID1 | DiscoveryMetadataOptsVD11) &
DiscoveryMetadataCommonOpts

export type ClientMetadataOpts = RPRegistrationMetadataOpts & ClientMetadataProperties & JarmClientMetadataParams & JwksMetadataParams
export type ClientMetadataOpts = RPRegistrationMetadataOpts & ClientMetadataProperties & JarmClientMetadata & JwksMetadataParams

export type ResponseRegistrationOpts = DiscoveryMetadataOpts & ClientMetadataProperties

Expand Down Expand Up @@ -712,3 +712,5 @@ export enum ContentType {
FORM_URL_ENCODED = 'application/x-www-form-urlencoded',
UTF_8 = 'UTF-8',
}

export { JarmClientMetadata }

0 comments on commit b09da53

Please sign in to comment.