Skip to content

Commit

Permalink
fix: things
Browse files Browse the repository at this point in the history
  • Loading branch information
auer-martin committed Sep 30, 2024
1 parent 2a9ce37 commit d76efe4
Show file tree
Hide file tree
Showing 15 changed files with 343 additions and 286 deletions.
1 change: 1 addition & 0 deletions packages/jarm/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './jarm-auth-response-create/index.js';
export * from './jarm-auth-response-send/index.js';
export * from './jarm-auth-response/index.js';
export * from './metadata/index.js';
1 change: 1 addition & 0 deletions packages/jarm/src/jarm-auth-response-create/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './jarm-auth-response-create.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { PickDeep } from '@protokoll/core';
import * as v from 'valibot';

import type { JoseContext } from '@protokoll/jose';
import {
JoseJweEncryptCompact,
JoseJweEncryptJwt,
JoseJwsSignJwt,
} from '@protokoll/jose';

import { vJarmAuthResponseEncrypted as vJarmEncryptedOnlyAuthResponse } from '../jarm-auth-response/v-jarm-auth-response-encrypted.js';
import { vJarmAuthResponse } from '../jarm-auth-response/v-jarm-auth-response.js';

export namespace JarmAuthResponseCreate {
export const vInput = v.variant('type', [
v.object({
type: v.literal('signed'),
authResponse: vJarmAuthResponse,
jwsSignJwtInput: v.omit(JoseJwsSignJwt.vInput, ['payload']),
}),
v.object({
type: v.literal('encrypted'),
authResponse: vJarmEncryptedOnlyAuthResponse,
jweEncryptJwtInput: v.omit(JoseJweEncryptJwt.vInput, ['payload']),
}),
v.object({
type: v.literal('signed encrypted'),
authResponse: vJarmAuthResponse,
jwsSignJwtInput: v.omit(JoseJwsSignJwt.vInput, ['payload']),
jweEncryptCompactInput: v.omit(JoseJweEncryptCompact.vInput, [
'plaintext',
]),
}),
]);
export type Input = v.InferOutput<typeof vInput>;

export const vOut = v.object({
authResponse: v.string(),
});

export type Out = v.InferOutput<typeof vOut>;

export type Context = PickDeep<
JoseContext,
'jose.jwe.encryptJwt' | 'jose.jws.signJwt' | 'jose.jwe.encryptCompact'
>;
}

export const jarmAuthResponseCreate = async (
input: JarmAuthResponseCreate.Input,
ctx: JarmAuthResponseCreate.Context
): Promise<JarmAuthResponseCreate.Out> => {
const { type, authResponse } = input;
if (input.type === 'encrypted') {
const { jwe } = await ctx.jose.jwe.encryptJwt({
...input.jweEncryptJwtInput,
payload: authResponse,
});
return { authResponse: jwe };
} else if (type === 'signed') {
const { jws } = await ctx.jose.jws.signJwt({
...input.jwsSignJwtInput,
payload: authResponse,
});
return { authResponse: jws };
} else {
const { jws } = await ctx.jose.jws.signJwt({
...input.jwsSignJwtInput,
payload: authResponse,
});
const { jwe } = await ctx.jose.jwe.encryptCompact({
...input.jweEncryptCompactInput,
plaintext: jws,
});

return { authResponse: jwe };
}
};

This file was deleted.

127 changes: 32 additions & 95 deletions packages/jarm/src/jarm-auth-response-send/jarm-auth-response-send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,114 +5,51 @@ import {
} from '@protokoll/core';
import * as v from 'valibot';

import {
vJoseJweEncryptCompactInput,
vJoseJweEncryptJwtInput,
vJoseJwsSignJwtInput,
vJwe,
vJws,
} from '@protokoll/jose';
import { vJwe, vJws } from '@protokoll/jose';

import { JarmError } from '../e-jarm.js';
import { vJarmAuthResponse } from '../jarm-auth-response/v-jarm-auth-response';
import { vJarmEncrytedOnlyAuthResponse as vJarmEncryptedOnlyAuthResponse } from '../jarm-auth-response/v-jarm-direct-post-jwt-auth-response.js';
import {
getJarmDefaultResponseMode,
validateResponseMode,
vJarmResponseMode,
vOpenid4vpJarmResponseMode,
} from '../v-response-mode-registry.js';
import { vResponseType } from '../v-response-type-registry.js';
import type { JarmAuthResponseCreateContext } from './c-jarm-auth-response-send.js';

export const vJarmAuthResponseCreateInput = v.variant('type', [
v.object({
type: v.literal('signed'),
authResponse: vJarmAuthResponse,
jwsSignJwtInput: v.omit(vJoseJwsSignJwtInput, ['payload']),
}),
v.object({
type: v.literal('encrypted'),
authResponse: vJarmEncryptedOnlyAuthResponse,
jweEncryptJwtInput: v.omit(vJoseJweEncryptJwtInput, ['payload']),
}),
v.object({
type: v.literal('signed encrypted'),
authResponse: vJarmAuthResponse,
jwsSignJwtInput: v.omit(vJoseJwsSignJwtInput, ['payload']),
jweEncryptCompactInput: v.omit(vJoseJweEncryptCompactInput, ['plaintext']),
}),
]);

export type JarmAuthResponseCreateInput = v.InferOutput<
typeof vJarmAuthResponseCreateInput
>;

export const jarmAuthResponseCreate = async (
input: JarmAuthResponseCreateInput,
ctx: JarmAuthResponseCreateContext
) => {
const { type, authResponse } = input;
if (input.type === 'encrypted') {
const { jwe } = await ctx.jose.jwe.encryptJwt({
...input.jweEncryptJwtInput,
payload: authResponse,
});
return { authResponse: jwe };
} else if (type === 'signed') {
const { jws } = await ctx.jose.jws.signJwt({
...input.jwsSignJwtInput,
payload: authResponse,
});
return { authResponse: jws };
} else {
const { jws } = await ctx.jose.jws.signJwt({
...input.jwsSignJwtInput,
payload: authResponse,
});
const { jwe } = await ctx.jose.jwe.encryptCompact({
...input.jweEncryptCompactInput,
plaintext: jws,
});

return { authResponse: jwe };
}
};

export const vJarmAuthResponseSendInput = v.object({
authRequest: v.intersect([
v.object({
response_mode: v.optional(
v.union([vJarmResponseMode, vOpenid4vpJarmResponseMode])
),
response_type: vResponseType,
}),
v.union([
v.looseObject({
response_uri: v.string(),
redirect_uri: v.optional(v.never()),
}),
v.looseObject({
redirect_uri: v.string(),
response_uri: v.optional(v.never()),
export namespace JarmAuthResponseSend {
export const vInput = v.object({
authRequest: v.intersect([
v.object({
response_mode: v.optional(
v.union([vJarmResponseMode, vOpenid4vpJarmResponseMode])
),
response_type: vResponseType,
}),
v.union([
v.object({
response_uri: v.string(),
redirect_uri: v.optional(v.never()),
}),
v.object({
response_uri: v.optional(v.never()),
redirect_uri: v.string(),
}),
]),
]),
]),
authResponse: v.union([vJwe, vJws]),
});
export type JarmAuthResponseSendInput = v.InferOutput<
typeof vJarmAuthResponseSendInput
>;
authResponse: v.union([vJwe, vJws]),
});
export type Input = v.InferOutput<typeof vInput>;

export type Out = Response;
}

export const jarmAuthResponseSend = async (
input: JarmAuthResponseSendInput
): Promise<Response> => {
input: JarmAuthResponseSend.Input
): Promise<JarmAuthResponseSend.Out> => {
const { authRequest, authResponse } = input;

const responseEndpoint = authRequest.response_uri
? new URL(authRequest.response_uri)
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
new URL(authRequest.redirect_uri!);
const responseEndpoint = authRequest.response_uri ?? authRequest.redirect_uri;
const responseEndpointUrl = new URL(responseEndpoint);

const responseMode =
authRequest.response_mode && authRequest.response_mode !== 'jwt'
Expand All @@ -126,11 +63,11 @@ export const jarmAuthResponseSend = async (

switch (responseMode) {
case 'direct_post.jwt':
return handleDirectPostJwt(responseEndpoint, authResponse);
return handleDirectPostJwt(responseEndpointUrl, authResponse);
case 'query.jwt':
return handleQueryJwt(responseEndpoint, authResponse);
return handleQueryJwt(responseEndpointUrl, authResponse);
case 'fragment.jwt':
return handleFragmentJwt(responseEndpoint, authResponse);
return handleFragmentJwt(responseEndpointUrl, authResponse);
case 'form_post.jwt':
return NOT_IMPLEMENTED({
message: 'form_post.jwt',
Expand Down
5 changes: 2 additions & 3 deletions packages/jarm/src/jarm-auth-response/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './c-jarm-auth-response.js';
export * from './jarm-auth-response.js';
export * from './jarm-auth-response-encrypted.js';
export * from './v-jarm-auth-response-encrypted.js';
export * from './v-jarm-auth-response.js';
export * from './v-jarm-direct-post-jwt-auth-response.js';
Original file line number Diff line number Diff line change
@@ -1,32 +1,57 @@
import * as v from 'valibot';

import type { JoseContext } from '@protokoll/jose';
import {
decodeJwt,
decodeProtectedHeader,
isJwe,
isJws,
} from '@protokoll/jose';

import type { MaybePromise, PickDeep } from '@protokoll/core';
import {
JarmAuthResponseValidationError,
JarmReceivedErrorResponse,
} from '../e-jarm.js';
import type { JarmDirectPostJwtResponse } from '../index.js';
import type {
AuthRequest,
JarmDirectPostJwtAuthResponseValidationContext,
} from './c-jarm-auth-response.js';
import { vJarmAuthResponseError } from './v-jarm-auth-response.js';
import type { JarmAuthResponse, JarmAuthResponseEncrypted } from '../index.js';
import type { OAuthAuthRequestGetParamsOut } from '../v-auth-request.js';
import { vAuthRequest } from '../v-auth-request.js';
import {
jarmAuthResponseEncryptionOnlyValidate,
vJarmEncrytedOnlyAuthResponse,
} from './v-jarm-direct-post-jwt-auth-response.js';

export interface JarmDirectPostJwtAuthResponseValidation {
/**
* The JARM response parameter conveyed either as url query param, fragment param, or application/x-www-form-urlencoded in the body of a post request
*/
response: string;
jarmAuthResponseEncryptedValidate,
vJarmAuthResponseEncrypted,
vJarmAuthResponseEncrypted as vJarmEncryptedOnlyAuthResponse,
} from './v-jarm-auth-response-encrypted.js';
import { vJarmAuthResponseError } from './v-jarm-auth-response.js';

export namespace JarmAuthResponseEncryptedHandle {
export const vInput = v.object({
/**
* The JARM response parameter conveyed either as url query param, fragment param, or application/x-www-form-urlencoded in the body of a post request
*/
response: v.string(),
});
export type Input = v.InferOutput<typeof vInput>;

export const vOut = v.object({
authRequest: vAuthRequest,
authResponse: vJarmAuthResponseEncrypted,
type: v.picklist(['signed encrypted', 'encrypted', 'signed']),
});
export type Out = v.InferOutput<typeof vOut>;

export interface Context
extends PickDeep<
JoseContext,
'jose.jwe.decryptCompact' | 'jose.jws.verifyJwt'
> {
openid4vp: {
authRequest: {
get: (
input: JarmAuthResponse | JarmAuthResponseEncrypted
) => MaybePromise<OAuthAuthRequestGetParamsOut>;
};
};
}
}

const parseJarmAuthResponse = <
Expand All @@ -48,7 +73,7 @@ const parseJarmAuthResponse = <

const decryptJarmAuthResponse = async (
input: { response: string },
ctx: JarmDirectPostJwtAuthResponseValidationContext
ctx: JarmAuthResponseEncryptedHandle.Context
) => {
const { response } = input;

Expand All @@ -72,10 +97,10 @@ const decryptJarmAuthResponse = async (
* * The decryption key should be resolvable using the the protected header's 'kid' field
* * The signature verification jwk should be resolvable using the jws protected header's 'kid' field and the payload's 'iss' field.
*/
export const jarmAuthResponseDirectPostJwtValidate = async (
input: JarmDirectPostJwtAuthResponseValidation,
ctx: JarmDirectPostJwtAuthResponseValidationContext
) => {
export const jarmAuthResponseEncryptedHandle = async (
input: JarmAuthResponseEncryptedHandle.Input,
ctx: JarmAuthResponseEncryptedHandle.Context
): Promise<JarmAuthResponseEncryptedHandle.Out> => {
const { response } = input;

const responseIsEncrypted = isJwe(response);
Expand All @@ -91,14 +116,13 @@ export const jarmAuthResponseDirectPostJwtValidate = async (
});
}

let authResponse: JarmDirectPostJwtResponse;
let authRequest: AuthRequest;
let authResponse: JarmAuthResponseEncrypted;
let authRequest: v.InferOutput<typeof vAuthRequest>;

if (responseIsSigned) {
const jwsProtectedHeader = decodeProtectedHeader(decryptedResponse);
const jwsPayload = decodeJwt(decryptedResponse);

const schema = v.required(vJarmEncrytedOnlyAuthResponse, [
const schema = v.required(vJarmEncryptedOnlyAuthResponse, [
'iss',
'aud',
'exp',
Expand All @@ -120,16 +144,13 @@ export const jarmAuthResponseDirectPostJwtValidate = async (
} else {
const jsonResponse: unknown = JSON.parse(decryptedResponse);
authResponse = parseJarmAuthResponse(
vJarmEncrytedOnlyAuthResponse,
vJarmEncryptedOnlyAuthResponse,
jsonResponse
);
({ authRequest } = await ctx.openid4vp.authRequest.get(authResponse));
}

jarmAuthResponseEncryptionOnlyValidate({
authRequest: authRequest,
authResponse: authResponse,
});
jarmAuthResponseEncryptedValidate({ authRequest, authResponse });

let type: 'signed encrypted' | 'encrypted' | 'signed';
if (responseIsSigned && responseIsEncrypted) type = 'signed encrypted';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AuthRequest } from './c-jarm-auth-response.js';
import type { AuthRequest } from '../v-auth-request';

// ISO-compliant driving licence — Part 7: Mobile driving licence (mDL) add-on functions
export const ISO_MDL_7_EPHEMERAL_MDOC_PUBLIC_KEY_JWK = {
Expand Down
Loading

0 comments on commit d76efe4

Please sign in to comment.