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

feat: remove did-resolver and did-jwt dependencies #82

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
394 changes: 60 additions & 334 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/services-class-diagram.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ RequestRegistrationOpts --|> RPRegistrationMetadataOpts

class VerifyAuthenticationRequestOpts {
<<interface>>
verification: InternalVerification | ExternalVerification;
verification: Verification
nonce?: string;
}

Expand Down Expand Up @@ -110,7 +110,7 @@ AuthenticationResponseWithJWT --> AuthenticationResponseOpts

class VerifyAuthenticationResponseOpts {
<<interface>>
verification: InternalVerification | ExternalVerification;
verification: Verification
nonce?: string;
state?: string;
audience: string;
Expand Down
2 changes: 1 addition & 1 deletion docs/services-class-diagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@
"@sphereon/ssi-types": "0.22.0",
"@sphereon/wellknown-dids-client": "^0.1.3",
"cross-fetch": "^4.0.0",
"did-jwt": "6.11.6",
"did-resolver": "^4.1.0",
"jwt-decode": "^4.0.0",
"events": "^3.3.0",
"language-tags": "^1.0.9",
"multiformats": "^12.1.3",
Expand All @@ -49,6 +48,8 @@
"@babel/core": "^7.23.9",
"@babel/plugin-transform-runtime": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"did-jwt": "6.11.6",
"did-resolver": "^4.1.0",
"@cef-ebsi/ebsi-did-resolver": "^3.2.0",
"@cef-ebsi/key-did-resolver": "^1.1.0",
"@cef-ebsi/oauth2-auth": "^3.0.0",
Expand All @@ -63,6 +64,7 @@
"@transmute/ed25519-key-pair": "0.7.0-unstable.82",
"@transmute/ed25519-signature-2018": "^0.7.0-unstable.82",
"@types/jest": "^29.5.11",
"@types/jwt-decode": "^3.1.0",
"@types/language-tags": "^1.0.4",
"@types/qs": "^6.9.11",
"@types/sha.js": "^2.4.4",
Expand All @@ -73,7 +75,6 @@
"bs58": "^5.0.0",
"codecov": "^3.8.3",
"cspell": "^6.26.3",
"did-resolver": "^4.1.0",
"dotenv": "^16.3.1",
"eslint": "^8.34.0",
"eslint-config-prettier": "^8.6.0",
Expand All @@ -84,7 +85,6 @@
"jest-junit": "^16.0.0",
"jest-resolver-enhanced": "^1.1.0",
"jose": "^4.15.5",
"jwt-decode": "^3.1.2",
"moment": "^2.30.1",
"nock": "^13.5.4",
"npm-run-all": "^4.1.5",
Expand Down
45 changes: 15 additions & 30 deletions src/authorization-request/AuthorizationRequest.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { JWTVerifyOptions } from 'did-jwt';

import { PresentationDefinitionWithLocation } from '../authorization-response';
import { PresentationExchange } from '../authorization-response/PresentationExchange';
import { getAudience, getResolver, parseJWT, verifyDidJWT } from '../did';
import { fetchByReferenceOrUseByValue, removeNullUndefined } from '../helpers';
import { authorizationRequestVersionDiscovery } from '../helpers/SIOPSpecVersion';
import { parseJWT } from '../helpers/jwtUtils';
import { RequestObject } from '../request-object';
import {
AuthorizationRequestPayload,
getJwtVerifierWithContext,
PassBy,
RequestObjectJwt,
RequestObjectPayload,
Expand All @@ -19,11 +18,10 @@ import {
SIOPErrors,
SupportedVersion,
VerifiedAuthorizationRequest,
VerifiedJWT,
} from '../types';

import { assertValidAuthorizationRequestOpts, assertValidVerifyAuthorizationRequestOpts } from './Opts';
import { assertValidRPRegistrationMedataPayload, checkWellknownDIDFromRequest, createAuthorizationRequestPayload } from './Payload';
import { assertValidRPRegistrationMedataPayload, createAuthorizationRequestPayload } from './Payload';
import { URI } from './URI';
import { CreateAuthorizationRequestOpts, VerifyAuthorizationRequestOpts } from './types';

Expand Down Expand Up @@ -117,30 +115,17 @@ export class AuthorizationRequest {
assertValidVerifyAuthorizationRequestOpts(opts);

let requestObjectPayload: RequestObjectPayload;
let verifiedJwt: VerifiedJWT;

const jwt = await this.requestObjectJwt();
if (jwt) {
const parsedJWT = parseJWT(jwt);
const payload = parsedJWT.payload;
const audience = getAudience(jwt);
const resolver = getResolver(opts.verification.resolveOpts);
const options: JWTVerifyOptions = {
...opts.verification?.resolveOpts?.jwtVerifyOpts,
resolver,
audience,
};

if (payload.client_id?.startsWith('http') && payload.iss.startsWith('http') && payload.iss === payload.client_id) {
console.error(`FIXME: The client_id and iss are not DIDs. We do not verify the signature in this case yet! ${payload.iss}`);
verifiedJwt = { payload, jwt, issuer: payload.iss };
} else {
verifiedJwt = await verifyDidJWT(jwt, resolver, options);
}
if (!verifiedJwt || !verifiedJwt.payload) {
throw Error(SIOPErrors.ERROR_VERIFYING_SIGNATURE);
}
requestObjectPayload = verifiedJwt.payload as RequestObjectPayload;
const parsedJwt = jwt ? parseJWT(jwt) : undefined;

if (parsedJwt) {
requestObjectPayload = parsedJwt.payload as RequestObjectPayload;

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 Expand Up @@ -186,14 +171,14 @@ export class AuthorizationRequest {
throw new Error(`${SIOPErrors.INVALID_REQUEST}, redirect_uri or response_uri is needed`);
}

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,
jwt,
payload: parsedJwt?.payload,
issuer: parsedJwt?.payload.iss,
responseURIType,
responseURI,
clientIdScheme: mergedPayload.client_id_scheme,
Expand Down
26 changes: 4 additions & 22 deletions src/authorization-request/Opts.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { assertValidRequestObjectOpts } from '../request-object/Opts';
import { ExternalVerification, InternalVerification, isExternalVerification, isInternalVerification, SIOPErrors } from '../types';
import { SIOPErrors, Verification } from '../types';

import { assertValidRequestRegistrationOpts } from './RequestRegistration';
import { CreateAuthorizationRequestOpts, VerifyAuthorizationRequestOpts } from './types';

export const assertValidVerifyAuthorizationRequestOpts = (opts: VerifyAuthorizationRequestOpts) => {
if (!opts || !opts.verification || (!isExternalVerification(opts.verification) && !isInternalVerification(opts.verification))) {
if (!opts || !opts.verification || !opts.verifyJwtCallback) {
throw new Error(SIOPErrors.VERIFY_BAD_PARAMS);
}
if (!opts.correlationId) {
Expand All @@ -23,39 +23,21 @@ export const assertValidAuthorizationRequestOpts = (opts: CreateAuthorizationReq

export const mergeVerificationOpts = (
classOpts: {
verification?: InternalVerification | ExternalVerification;
verification?: Verification;
},
requestOpts: {
correlationId: string;
verification?: InternalVerification | ExternalVerification;
verification?: Verification;
},
) => {
const resolver = requestOpts.verification?.resolveOpts?.resolver ?? classOpts.verification?.resolveOpts?.resolver;
const wellknownDIDVerifyCallback = requestOpts.verification?.wellknownDIDVerifyCallback ?? classOpts.verification?.wellknownDIDVerifyCallback;
const presentationVerificationCallback =
requestOpts.verification?.presentationVerificationCallback ?? classOpts.verification?.presentationVerificationCallback;
const replayRegistry = requestOpts.verification?.replayRegistry ?? classOpts.verification?.replayRegistry;
return {
...classOpts.verification,
...requestOpts.verification,
...(wellknownDIDVerifyCallback && { wellknownDIDVerifyCallback }),
...(presentationVerificationCallback && { presentationVerificationCallback }),
...(replayRegistry && { replayRegistry }),
resolveOpts: {
...classOpts.verification?.resolveOpts,
...requestOpts.verification?.resolveOpts,
...(resolver && { resolver }),
jwtVerifyOpts: {
...classOpts.verification?.resolveOpts?.jwtVerifyOpts,
...requestOpts.verification?.resolveOpts?.jwtVerifyOpts,
...(resolver && { resolver }),
policies: {
...classOpts.verification?.resolveOpts?.jwtVerifyOpts?.policies,
...requestOpts.verification?.resolveOpts?.jwtVerifyOpts?.policies,
aud: false, // todo: check why we are setting this. Probably needs a PR upstream in DID-JWT
},
},
},
revocationOpts: {
...classOpts.verification?.revocationOpts,
...requestOpts.verification?.revocationOpts,
Expand Down
18 changes: 1 addition & 17 deletions src/authorization-request/Payload.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import { PEX } from '@sphereon/pex';

import { validateLinkedDomainWithDid } from '../did';
import { getNonce, removeNullUndefined } from '../helpers';
import { RequestObject } from '../request-object';
import { isTarget, isTargetOrNoTargets } from '../rp/Opts';
import { RPRegistrationMetadataPayloadSchema } from '../schemas';
import {
AuthorizationRequestPayload,
CheckLinkedDomain,
ClaimPayloadVID1,
ClientMetadataOpts,
PassBy,
RequestObjectPayload,
RPRegistrationMetadataPayload,
SIOPErrors,
SupportedVersion,
} from '../types';

import { createRequestRegistration } from './RequestRegistration';
import { ClaimPayloadOptsVID1, CreateAuthorizationRequestOpts, PropertyTarget, VerifyAuthorizationRequestOpts } from './types';
import { ClaimPayloadOptsVID1, CreateAuthorizationRequestOpts, PropertyTarget } from './types';

export const createPresentationDefinitionClaimsProperties = (opts: ClaimPayloadOptsVID1): ClaimPayloadVID1 => {
if (!opts || !opts.vp_token || (!opts.vp_token.presentation_definition && !opts.vp_token.presentation_definition_uri)) {
Expand Down Expand Up @@ -87,16 +84,3 @@ export const assertValidRPRegistrationMedataPayload = (regObj: RPRegistrationMet
throw new Error(`${SIOPErrors.VERIFY_BAD_PARAMS}`);
}
};

export const checkWellknownDIDFromRequest = async (
authorizationRequestPayload: RequestObjectPayload,
opts: VerifyAuthorizationRequestOpts,
): Promise<void> => {
if (authorizationRequestPayload.client_id.startsWith('did:')) {
if (opts.verification.checkLinkedDomain && opts.verification.checkLinkedDomain != CheckLinkedDomain.NEVER) {
await validateLinkedDomainWithDid(authorizationRequestPayload.client_id, opts.verification);
} else if (!opts.verification.checkLinkedDomain && opts.verification.wellknownDIDVerifyCallback) {
await validateLinkedDomainWithDid(authorizationRequestPayload.client_id, opts.verification);
}
}
};
15 changes: 7 additions & 8 deletions src/authorization-request/URI.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { decodeJWT } from 'did-jwt';
import { jwtDecode } from 'jwt-decode';

import { PresentationExchange } from '../authorization-response/PresentationExchange';
import { decodeUriAsJson, encodeJsonAsURI, fetchByReferenceOrUseByValue } from '../helpers';
import { assertValidRequestObjectPayload, RequestObject } from '../request-object';
import {
AuthorizationRequestPayload,
AuthorizationRequestURI,
JwtPayload,
ObjectBy,
PassBy,
RequestObjectJwt,
Expand Down Expand Up @@ -43,7 +44,7 @@ export class URI implements AuthorizationRequestURI {
throw Error(SIOPErrors.BAD_PARAMS);
}
const { scheme, requestObjectJwt, authorizationRequestPayload, registrationMetadata } = await URI.parseAndResolve(uri);
const requestObjectPayload = requestObjectJwt ? (decodeJWT(requestObjectJwt).payload as RequestObjectPayload) : undefined;
const requestObjectPayload = requestObjectJwt ? (jwtDecode(requestObjectJwt, { header: false }) as RequestObjectPayload) : undefined;
if (requestObjectPayload) {
assertValidRequestObjectPayload(requestObjectPayload);
}
Expand Down Expand Up @@ -151,15 +152,13 @@ export class URI implements AuthorizationRequestURI {
}

const isJwt = typeof authorizationRequestPayload === 'string';
const requestObjectJwt = requestObject
? await requestObject.toJwt()
: typeof authorizationRequestPayload === 'string'
? authorizationRequestPayload
: authorizationRequestPayload.request;
const requestObjectJwt = requestObject ? await requestObject.toJwt() : authorizationRequestPayload.request;
if (isJwt && (!requestObjectJwt || !requestObjectJwt.startsWith('ey'))) {
throw Error(SIOPErrors.NO_JWT);
}
const requestObjectPayload: RequestObjectPayload = requestObjectJwt ? (decodeJWT(requestObjectJwt).payload as RequestObjectPayload) : undefined;
const requestObjectPayload: RequestObjectPayload = requestObjectJwt
? (jwtDecode<JwtPayload>(requestObjectJwt, { header: false }) as RequestObjectPayload)
: undefined;

if (requestObjectPayload) {
// Only used to validate if the request object contains presentation definition(s)
Expand Down
14 changes: 6 additions & 8 deletions src/authorization-request/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import { Hasher } from '@sphereon/ssi-types';
import { PresentationDefinitionPayloadOpts } from '../authorization-response';
import { RequestObjectOpts } from '../request-object';
import {
ClientIdScheme,
ClientMetadataOpts,
ExternalVerification,
IdTokenClaimPayload,
InternalVerification,
ResponseMode,
ResponseType,
Schema,
Scope,
SigningAlgo,
SubjectType,
SupportedVersion,
Verification,
} from '../types';
import { VerifyJwtCallback } from '../types/JwtVerifier';

export interface ClaimPayloadOptsVID1 extends ClaimPayloadCommonOpts {
id_token?: IdTokenClaimPayload;
Expand All @@ -34,6 +35,7 @@ export interface RequestObjectPayloadOpts<CT extends ClaimPayloadCommonOpts> {
scope: string; // from openid-connect-self-issued-v2-1_0-ID1
response_type: string; // from openid-connect-self-issued-v2-1_0-ID1
client_id: string; // from openid-connect-self-issued-v2-1_0-ID1
client_id_scheme: ClientIdScheme;
redirect_uri?: string; // from openid-connect-self-issued-v2-1_0-ID1
response_uri?: string; // from openid-connect-self-issued-v2-1_0-D18 // either response uri or redirect uri
id_token_hint?: string; // from openid-connect-self-issued-v2-1_0-ID1
Expand All @@ -58,7 +60,6 @@ interface AuthorizationRequestCommonOpts<CT extends ClaimPayloadCommonOpts> {
clientMetadata?: ClientMetadataOpts; // this maps to 'registration' for older SIOPv2 specs! OPTIONAL. This parameter is used by the RP to provide information about itself to a Self-Issued OP that would normally be provided to an OP during Dynamic RP Registration, as specified in {#rp-registration-parameter}.
payload?: AuthorizationRequestPayloadOpts<CT>;
requestObject: RequestObjectOpts<CT>;

uriScheme?: Schema | string; // Use a custom scheme for the URI. By default openid:// will be used
}

Expand All @@ -72,14 +73,11 @@ export type CreateAuthorizationRequestOpts = AuthorizationRequestOptsVID1 | Auth

export interface VerifyAuthorizationRequestOpts {
correlationId: string;

verification: InternalVerification | ExternalVerification; // To use internal verification or external hosted verification
// didDocument?: DIDDocument; // If not provided the DID document will be resolved from the request
verification: Verification;
verifyJwtCallback: VerifyJwtCallback;
nonce?: string; // If provided the nonce in the request needs to match
state?: string; // If provided the state in the request needs to match

supportedVersions?: SupportedVersion[];

hasher?: Hasher;
}

Expand Down
9 changes: 5 additions & 4 deletions src/authorization-response/AuthorizationResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class AuthorizationResponse {
verifyOpts: VerifyAuthorizationRequestOpts,
): Promise<AuthorizationResponse> {
assertValidVerifyAuthorizationRequestOpts(verifyOpts);
await assertValidResponseOpts(responseOpts);
assertValidResponseOpts(responseOpts);
if (!requestObject || !requestObject.startsWith('ey')) {
throw new Error(SIOPErrors.NO_JWT);
}
Expand All @@ -63,8 +63,9 @@ export class AuthorizationResponse {
if (!authorizationResponsePayload) {
throw new Error(SIOPErrors.NO_RESPONSE);
}

if (responseOpts) {
await assertValidResponseOpts(responseOpts);
assertValidResponseOpts(responseOpts);
}
const idToken = authorizationResponsePayload.id_token ? await IDToken.fromIDToken(authorizationResponsePayload.id_token) : undefined;
return new AuthorizationResponse({
Expand All @@ -79,7 +80,7 @@ export class AuthorizationResponse {
responseOpts: AuthorizationResponseOpts,
verifyOpts: VerifyAuthorizationRequestOpts,
): Promise<AuthorizationResponse> {
await assertValidResponseOpts(responseOpts);
assertValidResponseOpts(responseOpts);
if (!authorizationRequest) {
throw new Error(SIOPErrors.NO_REQUEST);
}
Expand All @@ -92,7 +93,7 @@ export class AuthorizationResponse {
responseOpts: AuthorizationResponseOpts,
verifyOpts: VerifyAuthorizationRequestOpts,
): Promise<AuthorizationResponse> {
await assertValidResponseOpts(responseOpts);
assertValidResponseOpts(responseOpts);
if (!verifiedAuthorizationRequest) {
throw new Error(SIOPErrors.NO_REQUEST);
}
Expand Down
Loading
Loading