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

Commit

Permalink
some final nits
Browse files Browse the repository at this point in the history
  • Loading branch information
auer-martin committed Jun 29, 2024
1 parent dde625a commit 720fba7
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 44 deletions.
16 changes: 4 additions & 12 deletions src/authorization-request/AuthorizationRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,10 @@ export class AuthorizationRequest {
if (parsedJwt) {
requestObjectPayload = parsedJwt.payload as RequestObjectPayload;

if (
requestObjectPayload.client_id?.startsWith('http') &&
requestObjectPayload.iss.startsWith('http') &&
requestObjectPayload.iss === requestObjectPayload.client_id
) {
console.error(`FIXME: The client_id and iss are not DIDs. We do not verify the signature in this case yet! ${requestObjectPayload.iss}`);
} else {
const jwtVerifier = getJwtVerifierWithContext(parsedJwt, 'request-object');
const result = await opts.verifyJwtCallback(jwtVerifier, { ...parsedJwt, raw: jwt });

if (!result) throw Error(SIOPErrors.ERROR_VERIFYING_SIGNATURE);
}
const jwtVerifier = await getJwtVerifierWithContext(parsedJwt, 'request-object');
const result = await opts.verifyJwtCallback(jwtVerifier, { ...parsedJwt, raw: jwt });

if (!result) throw Error(SIOPErrors.ERROR_VERIFYING_SIGNATURE);

if (this.hasRequestObject() && !this.payload.request_uri) {
// Put back the request object as that won't be present yet
Expand Down
17 changes: 14 additions & 3 deletions src/helpers/Keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const check = (value, description) => {
}
};

async function calculateJwkThumbprint(jwk: JWK, digestAlgorithm?: 'sha256' | 'sha384' | 'sha512'): Promise<string> {
export async function calculateJwkThumbprint(jwk: JWK, digestAlgorithm?: 'sha256' | 'sha384' | 'sha512'): Promise<string> {
if (!jwk || typeof jwk !== 'object') {
throw new TypeError('JWK must be an object');
}
Expand Down Expand Up @@ -130,8 +130,19 @@ const digest = async (algorithm: 'sha256' | 'sha384' | 'sha512', data: Uint8Arra
return new Uint8Array(await crypto.subtle.digest(subtleDigest, data));
};

export async function calculateJwkThumbprintUri(jwk: JWK, digestAlgorithm?: 'sha256' | 'sha384' | 'sha512'): Promise<string> {
digestAlgorithm !== null && digestAlgorithm !== void 0 ? digestAlgorithm : (digestAlgorithm = 'sha256');
export async function getDigestAlgorithmFromJwkThumbprintUri(uri: string): Promise<'sha256' | 'sha384' | 'sha512'> {
const match = uri.match(/^urn:ietf:params:oauth:jwk-thumbprint:sha-(\w+):/);
if (!match) {
throw new Error(`Invalid JWK thumbprint URI structure ${uri}`);
}
const algorithm = match[1] as 'sha256' | 'sha384' | 'sha512';
if (algorithm !== 'sha256' && algorithm !== 'sha384' && algorithm !== 'sha512') {
throw new Error(`Invalid JWK thumbprint URI digest algorithm ${uri}`);
}
return algorithm;
}

export async function calculateJwkThumbprintUri(jwk: JWK, digestAlgorithm: 'sha256' | 'sha384' | 'sha512' = 'sha256'): Promise<string> {
const thumbprint = await calculateJwkThumbprint(jwk, digestAlgorithm);
return `urn:ietf:params:oauth:jwk-thumbprint:sha-${digestAlgorithm.slice(-3)}:${thumbprint}`;
}
11 changes: 8 additions & 3 deletions src/id-token/IDToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getJwtVerifierWithContext,
IDTokenJwt,
IDTokenPayload,
JWK,
JwtHeader,
JWTPayload,
ResponseIss,
Expand All @@ -14,6 +15,7 @@ import {
} from '../types';
import { JwtIssuer, JwtIssuerWithContext } from '../types/JwtIssuer';

import { calculateJwkThumbprintUri } from './../helpers/Keys';
import { createIDTokenPayload } from './Payload';

export class IDToken {
Expand Down Expand Up @@ -112,12 +114,15 @@ export class IDToken {
this._jwt = await this.responseOpts.createJwtCallback({ ...jwtIssuer, type: 'id-token' }, { header, payload: this._payload });
} else if (jwtIssuer.method === 'x5c') {
this._payload.iss = jwtIssuer.issuer;
this._payload.sub = jwtIssuer.issuer;

const header = { x5c: jwtIssuer.chain, typ: 'JWT' };
this._jwt = await this._responseOpts.createJwtCallback(jwtIssuer, { header, payload: this._payload });
} else if (jwtIssuer.method === 'jwk') {
this._payload.sub = jwtIssuer.jwkThumbprint;
this._payload['_sub_jwk'] = jwtIssuer.jwk;
const jwkThumbprintUri = await calculateJwkThumbprintUri(jwtIssuer.jwk as JWK);
this._payload.sub = jwkThumbprintUri;
this._payload.iss = jwkThumbprintUri;
this._payload.sub_jwk = jwtIssuer.jwk;

const header = { jwk: jwtIssuer.jwk, alg: jwtIssuer.jwk.alg, typ: 'JWT' };
this._jwt = await this._responseOpts.createJwtCallback(jwtIssuer, { header, payload: this._payload });
Expand Down Expand Up @@ -151,7 +156,7 @@ export class IDToken {
const parsedJwt = parseJWT(this._jwt);
this.assertValidResponseJWT(parsedJwt);

const jwtVerifier = getJwtVerifierWithContext(parsedJwt, 'request-object');
const jwtVerifier = await getJwtVerifierWithContext(parsedJwt, 'request-object');
const verificationResult = await verifyOpts.verifyJwtCallback(jwtVerifier, { ...parsedJwt, raw: this._jwt });
if (!verificationResult) throw Error(SIOPErrors.ERROR_VERIFYING_SIGNATURE);

Expand Down
18 changes: 1 addition & 17 deletions src/id-token/Payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,6 @@ export const createIDTokenPayload = async (
}
const opVersion = responseOpts.version ?? maxRPVersion;

const jwtIssuer = responseOpts.jwtIssuer;
let sub: string | undefined;

if (!jwtIssuer) {
sub = undefined;
} else if (jwtIssuer.method === 'did') {
const did = jwtIssuer.didUrl.split('#')[0];
sub = did;
} else if (jwtIssuer.method === 'x5c') {
sub = jwtIssuer.issuer;
} else if (jwtIssuer.method === 'jwk') {
sub = jwtIssuer.jwkThumbprint;
} else {
throw new Error(`JwtIssuer method '${jwtIssuer.method}' not implemented`);
}

const idToken: IDTokenPayload = {
// fixme: ID11 does not use this static value anymore
iss:
Expand All @@ -53,7 +37,7 @@ export const createIDTokenPayload = async (
aud: responseOpts.audience || payload.client_id,
iat: Math.round(Date.now() / SEC_IN_MS - 60 * SEC_IN_MS),
exp: Math.round(Date.now() / SEC_IN_MS + (responseOpts.expiresIn || 600)),
sub,
sub: undefined,
...(payload.auth_time && { auth_time: payload.auth_time }),
nonce,
state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1068,4 +1068,4 @@ export const AuthorizationRequestPayloadVD12OID4VPD18SchemaObj = {
]
}
}
};
};
2 changes: 0 additions & 2 deletions src/types/JwtIssuer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ interface JwtIssuerJwk {
method: 'jwk';

jwk: JsonWebKey;
//TODO: calculate
jwkThumbprint: string;
}

interface JwtIssuerCustom extends Record<string, unknown> {
Expand Down
19 changes: 13 additions & 6 deletions src/types/JwtVerifier.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { JwtHeader, JwtPayload } from './JWT.types';
import { calculateJwkThumbprintUri, getDigestAlgorithmFromJwkThumbprintUri } from '../helpers';

import { JWK, JwtHeader, JwtPayload } from './JWT.types';

export type JwtVerificationContext = { type: 'id-token' } | { type: 'request-object' };

Expand Down Expand Up @@ -29,7 +31,6 @@ interface JwkJwtVerifier {
method: 'jwk';

jwk: JsonWebKey;
//TODO: calculate
jwkThumbprint: string;
}

Expand All @@ -41,10 +42,10 @@ export type JwtVerifier = DidJwtVerifier | X5cJwtVerifier | CustomJwtVerifier |

export type JwtVerifierWithContext = JwtVerifier & JwtVerificationContext;

export const getJwtVerifierWithContext = (
export const getJwtVerifierWithContext = async (
jwt: { header: JwtHeader; payload: JwtPayload },
type: JwtVerifierWithContext['type'],
): JwtVerifierWithContext => {
): Promise<JwtVerifierWithContext> => {
if (jwt.header.kid?.startsWith('did:')) {
if (!jwt.header.kid.includes('#')) throw new Error('TODO');
return { method: 'did', didUrl: jwt.header.kid, type };
Expand All @@ -53,8 +54,14 @@ export const getJwtVerifierWithContext = (
return { method: 'x5c', chain: jwt.header.x5c, issuer: jwt.payload.iss, type };
} else if (jwt.header.jwk) {
if (typeof jwt.header.jwk !== 'object') throw new Error('TODO');
// todo: !!
return { method: 'jwk', type, jwk: jwt.header.jwk, jwkThumbprint: '' };
if (typeof jwt.payload.sub_jwk !== 'string') throw new Error('Invalid JWT. Missing sub_jwk claim.');

const jwkThumbPrintUri = jwt.payload.sub_jwk;
const digestAlgorithm = await getDigestAlgorithmFromJwkThumbprintUri(jwkThumbPrintUri);
const selfComputedJwkThumbPrintUri = await calculateJwkThumbprintUri(jwt.header.jwk as JWK, digestAlgorithm);

if (selfComputedJwkThumbPrintUri !== jwkThumbPrintUri) throw new Error('Invalid JWT. Thumbprint mismatch.');
return { method: 'jwk', type, jwk: jwt.header.jwk, jwkThumbprint: jwt.payload.sub_jwk };
} else {
return { method: 'custom', type };
}
Expand Down

0 comments on commit 720fba7

Please sign in to comment.