diff --git a/README.md b/README.md index bc221c3f..6e90b4f3 100644 --- a/README.md +++ b/README.md @@ -52,14 +52,10 @@ expect to keep that to a minimum though. Version 0.3.X has changed the external This library supports: -- [Decentralized Identifiers (DID)](https://www.w3.org/TR/did-core/) method neutral: Resolve DIDs using - DIFs [did-resolver](https://github.com/decentralized-identity/did-resolver) and - Sphereon's [Universal registrar and resolver client](https://github.com/Sphereon-Opensource/did-uni-client) -- Verify and Create/sign Json Web Tokens (JWTs) as used in OpenID Connect using Decentralized Identifiers (DIDs) or JSON Web Keys (JWK) +- Generic methods to verify and create/sign Json Web Tokens (JWTs) as used in OpenID Connect, with adapter for Decentralized Identifiers (DIDs), JSON Web Keys (JWK), x509 certificates - OP class to create Authorization Requests and verify Authorization Responses - RP class to verify Authorization Requests and create Authorization Responses - Verifiable Presentation and Presentation Exchange support on the RP and OP sides, according to the OpenID for Verifiable Presentations (OID4VP) and Presentation Exchange specifications -- [Well-known DID Configuration](https://identity.foundation/.well-known/resources/did-configuration/) support to bind domain names to DIDs. - SIOPv2 specification version discovery with support for the latest [development version (draft 11)](https://openid.net/specs/openid-connect-self-issued-v2-1_0.html), [Implementers Draft 1](https://openid.net/specs/openid-connect-self-issued-v2-1_0-ID1.html) and the [JWT VC Presentation Interop Profile](https://identity.foundation/jwt-vc-presentation-profile/) ## Steps involved @@ -263,45 +259,65 @@ You could also use the actual example keys and DIDs, as they are valid Ethr Rops ### Relying Party and SIOP should have keys and DIDs -Since the library uses DIDs for both the RP and the OP, we expect these -DIDs to be present on both sides, and the respective parties should have access to their private key(s). How DIDs are -created is out of scope of this library, but we provide a [ethereum DID example](ethr-dids-testnet.md) -and [manual eosio DID walk-through](eosio-dids-testnet.md) if you want to test it yourself without having DIDs. +This library does not provide methods for signing and verifying tokens and authorization requests. Verification and Signing functionality must be externally provided. ### Setting up the Relying Party (RP) The Relying Party, typically a web app, but can also be something else, like a mobile app. +The consumer of this library must provide means for creating and verifying JWT to the RP class instance. +This library provides adapters for creating and verifying did, jwk, and x5c protected JWT`s. -We will use an example private key and DID on the Ethereum Ropsten testnet network. Both the actual JWT request and the +Both the actual JWT request and the registration metadata will be sent as part of the Auth Request since we pass them by value instead of by reference where we would have to host the data at the reference URL. The redirect URL means that the OP will need to deliver the -auth response at the URL specified by the RP. Lastly we have enabled the 'ethr' DID method on the RP side for -doing Auth Response checks with Ethereum DIDs in them. Please note that you can add multiple DID methods, and -they have no influence on the DIDs being used to sign, the internal withSignature. We also populated the RP with -a `PresentationDefinition` claim, meaning we expect the OP to send in a Verifiable Presentation that matches our -definition. You can pass where you expect this presentation_definition to end up via the required `location` property. +auth response at the URL specified by the RP. We also populated the RP with a `PresentationDefinition` claim, +meaning we expect the OP to send in a Verifiable Presentation that matches our definition. +You can pass where you expect this presentation_definition to end up via the required `location` property. This is either a top-level vp_token or it becomes part of the id_token. ````typescript // The relying party (web) private key and DID and DID key (public key) -import { SigningAlgo } from '@sphereon/did-auth-siop'; const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello'; -const rpKeys = { - hexPrivateKey: 'a1458fac9ea502099f40be363ad3144d6d509aa5aa3d17158a9e6c3b67eb0397', - did: 'did:ethr:ropsten:0x028360fb95417724cb7dd2ff217b15d6f17fc45e0ffc1b3dce6c2b8dd1e704fa98', - didKey: 'did:ethr:ropsten:0x028360fb95417724cb7dd2ff217b15d6f17fc45e0ffc1b3dce6c2b8dd1e704fa98#controller', - alg: SigningAlgo.ES256K + +function verifyJwtCallback(): VerifyJwtCallback { + return async (jwtVerifier, jwt) => { + if (jwtVerifier.method === 'did') { + // verify didJwt's + } else if (jwtVerifier.method === 'x5c') { + // verify x5c certificate protected jwt's + } else if (jwtVerifier.method === 'jwk') { + // verify jwk certificate protected jwt's + } else if (jwtVerifier.method === 'custom') { + // Only called if based on the jwt the verification method could not be determined + throw new Error(`Unsupported JWT verifier method ${jwtIssuer.method}`) + } + } +} + +function createJwtCallback(): CreateJwtCallback { + return async (jwtIssuer, jwt) => { + if (jwtIssuer.method === 'did') { + // create didJwt + } else if (jwtIssuer.method === 'x5c') { + // create x5c certificate protected jwt + } else if (jwtIssuer.method === 'jwk') { + // create a jwk certificate protected jwt + } else if (jwtIssuer.method === 'custom') { + // Only called if no or a Custom jwtIssuer was passed to the respective methods + throw new Error(`Unsupported JWT issuer method ${jwtIssuer.method}`) + } + } } + const rp = RP.builder() .redirect(EXAMPLE_REDIRECT_URL) .requestBy(PassBy.VALUE) .withPresentationVerification(presentationVerificationCallback) - .addVerifyCallback(verifyCallback) + .withCreateJwtCallback(createJwtCallback) + .withVerifyJwtCallback(verifyJwtCallback) .withRevocationVerification(RevocationVerification.NEVER) - .withInternalSignature(rpKeys.hexPrivateKey, rpKeys.did, rpKeys.didKey, rpKeys.alg) - .addDidMethod("ethr") .withClientMetadata({ idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], @@ -332,27 +348,15 @@ const rp = RP.builder() ### OpenID Provider (OP) The OP, typically a useragent together with a mobile phone in a cross device flow is accessing a protected resource at the RP, or needs to sent -in Verifiable Presentations. In the example below we are expressing that the OP supports the 'ethr' didMethod, we are -passing the signing information, which will never leave the OP's computer and we are configuring to send the JWT as part -of the payload (by value). +in Verifiable Presentations. The consumer of the library must provide means for creating and verifying JWT to the OP class instance. +This library provides adapters for creating and verifying did, jwk, and x5c protected JWT`s. ````typescript -// The OpenID Provider (client) private key and DID and DID key (public key) -import { SigningAlgo } from '@sphereon/did-auth-siop'; - -const opKeys = { - hexPrivateKey: '88a62d50de38dc22f5b4e7cc80d68a0f421ea489dda0e3bd5c165f08ce46e666', - did: 'did:ethr:ropsten:0x03f8b96c88063da2b7f5cc90513560a7ec38b92616fff9c95ae95f46cc692a7c75', - didKey: 'did:ethr:ropsten:0x03f8b96c88063da2b7f5cc90513560a7ec38b92616fff9c95ae95f46cc692a7c75#controller', - alg: SigningAlgo.alg -} - const op = OP.builder() .withExpiresIn(6000) .addDidMethod("ethr") - .addVerifyCallback(verifyCallback) - .addIssuer(ResponseIss.SELF_ISSUED_V2) - .withInternalSignature(opKeys.hexPrivateKey, opKeys.did, opKeys.didKey, opKeys.alg) + .withCreateJwtCallback(createJwtCallback) + .withVerifyJwtCallback(verifyJwtCallback) .withClientMetadata({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -382,12 +386,18 @@ return them in the object that is returned from the method. Next to the nonce we could also pass in claim options, for instance to specify a Presentation Definition. We have already configured the RP itself to have a Presentation Definition, so we can omit it in the request creation, as the RP class will take care of that on every Auth Request creation. +When creating signed objects on the OP and RP side, a jwtIssuer can be specified. +These adapters provide information about how the jwt will be signed later and metadata to set certain fields in the JWT, +This means that the JWT only needs to be signed and not necessarily modified by the consumer of this library. +If the jwtIssuer is omitted the createJwtCallback will be called with method 'custom' indicating that it's up to the consumer +to populate required fields before the JWT is signed. ````typescript const authRequest = await rp.createAuthorizationRequest({ correlationId: '1', nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', state: 'b32f0087fc9816eb813fd11f', + jwtIssuer: { method: 'did', didUrl: 'did:key:v4zagSPkqFJxuNWu#zUC74VEqqhEHQc', alg: SigningAlgo.EDDSA } }); console.log(`nonce: ${authRequest.requestOpts.nonce}, state: ${authRequest.requestOpts.state}`); @@ -433,18 +443,17 @@ console.log(parsedReqURI.jwt); The Auth Request from the RP in the form of a URI or JWT string needs to be verified by the OP. The verifyAuthorizationRequest method of the OP class takes care of this. As input it expects either the URI or the JWT -string together with optional verify options. IF a JWT is supplied it will use the JWT directly, if a URI is provided it -will internally parse the URI and extract/resolve the jwt. The options can contain an optional nonce, which means the +string. IF a JWT is supplied it will use the JWT directly, if a URI is provided it +will internally parse the URI and extract/resolve the JWT before passing it to the provided verifyJwtCallback. +The jwtVerifier in the verifyJwtCallback is augmented with metadata to simplify jwt verification for each adapter. +The options can contain an optional nonce, which means the request will be checked against the supplied nonce, otherwise the supplied nonce is only checked for presence. Normally the OP doesn't know the nonce beforehand, so this option can be left out. -The verified Auth Request object returned again contains the Auth Request payload, the DID -resolution result, including DID document of the RP, the issuer (DID of RP) and the signer (the DID verification method -that signed). The verification method will throw an error if something is of with the JWT, or if the JWT has not been -signed by the DID of the RP. - +The verified Auth Request object returned again contains the Auth Request payload, and the issuer. --- + **NOTE** In the below example we directly access requestURI.encodedUri, in a real world scenario the RP and OP don't have access @@ -777,7 +786,7 @@ export interface VerifiedJWT { } export interface VerifyAuthorizationRequestOpts { - verification: InternalVerification | ExternalVerification; // To use internal verification or external hosted verification + verification: Verification nonce?: string; // If provided the nonce in the request needs to match verifyCallback?: VerifyCallback; } @@ -810,7 +819,6 @@ static async verifyJWT(jwt:string, opts: SIOP.VerifyAuthorizationRequestOpts): P ````typescript const verifyOpts: VerifyAuthorizationRequestOpts = { verification: { - mode: VerificationMode.INTERNAL, resolveOpts: { subjectSyntaxTypesSupported: ['did:ethr'], } @@ -865,7 +873,7 @@ export enum PresentationLocation { } export interface VerifyAuthorizationRequestOpts { - verification: InternalVerification | ExternalVerification; // To use internal verification or external hosted verification + verification: Verification nonce?: string; // If provided the nonce in the request needs to match verifyCallback?: VerifyCallback // Callback function to verify the domain linkage credential } @@ -928,83 +936,6 @@ static async createJWTFromRequestJWT(requestJwt: string, responseOpts: SIOP.Auth did: "did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0", responseMode: ResponseMode.POST, } -const verifyOpts: VerifyAuthorizationRequestOpts = { - verification: { - resolveOpts: { - subjectSyntaxTypesSupported: ['did:ethr:'], - }, - mode: VerificationMode.INTERNAL, - } -} -createJWTFromRequestJWT('ey....', responseOpts, verifyOpts).then(resp => { - console.log(resp.payload.sub); - // output: did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0 - console.log(resp.nonce); - // output: 5c1d29c1-cf7d-4e14-9305-9db46d8c1916 -}); -```` - -### verifyJWT - -Verifies the OPs Auth Response JWT on the RP side as received from the OP/client. Throws an error if the token -is invalid, otherwise returns the Verified JWT - -#### Data Interface - -````typescript -export interface VerifiedAuthorizationResponseWithJWT extends VerifiedJWT { - payload: AuthorizationResponsePayload; // The unsigned Auth Response payload - verifyOpts: VerifyAuthorizationResponseOpts;// The Auth Request payload -} - -export interface AuthorizationResponsePayload extends JWTPayload { - iss: ResponseIss.SELF_ISSUED_V2 | string; // The SIOP V2 spec mentions this is required, but current implementations use the kid/did here - sub: string; // did (or thumbprint of sub_jwk key when type is jkt) - sub_jwk?: JWK; // Sub Json webkey - aud: string; // redirect_uri from request - exp: number; // Expiration time - iat: number; // Issued at time - state: string; // State value - nonce: string; // Nonce - did: string; // DID of the OP - registration?: DiscoveryMetadataPayload; // Registration metadata - registration_uri?: string; // Registration URI if metadata is hosted by the OP - verifiable_presentations?: VerifiablePresentationPayload[]; // Verifiable Presentations as part of the id token - vp_token?: VerifiablePresentationPayload; // Verifiable Presentation (the vp_token) -} - -export interface VerifiedJWT { - payload: Partial; // The JWT payload - didResolutionResult: DIDResolutionResult; // DID resolution result including DID document - issuer: string; // The issuer (did) of the JWT - signer: VerificationMethod; // The matching verification method from the DID that was used to sign - jwt: string; // The JWT -} - -export interface JWTPayload { // A default JWT Payload - iss?: string - sub?: string - aud?: string | string[] - iat?: number - nbf?: number - type?: string; - exp?: number - rexp?: number - jti?: string; - [x: string]: any -} - -export interface VerifyAuthorizationResponseOpts { - verification: InternalVerification | ExternalVerification; // To use internal verification or external hosted verification - nonce?: string; // To verify the response against the supplied nonce - state?: string; // To verify the response against the supplied state - audience: string; // The audience/redirect_uri - claims?: ClaimOpts; // The claims, typically the same values used during request creation - verifyCallback?: VerifyCallback; // Callback function to verify the domain linkage credential - presentationVerificationCallback?: PresentationVerificationCallback; // Callback function to verify the verifiable presentations -} - -static async verifyJWT(jwt:string, verifyOpts: VerifyAuthorizationResponseOpts): Promise ```` #### Usage @@ -1015,12 +946,6 @@ const NONCE = "5c1d29c1-cf7d-4e14-9305-9db46d8c1916"; const verifyOpts: VerifyAuthorizationResponseOpts = { audience: "https://rp.acme.com/siop/jwts", nonce: NONCE, - verification: { - resolveOpts: { - subjectSyntaxTypesSupported: ['did:ethr:'], - }, - mode: VerificationMode.INTERNAL, - } } verifyJWT('ey......', verifyOpts).then(jwt => { @@ -1029,209 +954,6 @@ verifyJWT('ey......', verifyOpts).then(jwt => { }) ```` -## DID resolution - -### Description - -Resolves the DID to a DID document using the DID method provided in didUrl and using -DIFs [did-resolver](https://github.com/decentralized-identity/did-resolver) and -Sphereons [Universal registrar and resolver client](https://github.com/Sphereon-Opensource/did-uni-client). - -This process allows retrieving public keys and verificationMethod material, as well as services provided by a DID -controller. Can be used in both the webapp and mobile applications. Uses the did-uni-client, but could use other DIF -did-resolver drivers as well. The benefit of the uni client is that it can resolve many DID methods. Since the -resolution itself is provided by the mentioned external dependencies above, we suffice with a usage example. - -#### Usage - -```typescript -import {Resolver} from 'did-resolver' -import {getResolver as getUniResolver} from '@sphereon/did-uni-client' - -const resolver = new Resolver(getUniResolver({ - subjectSyntaxTypesSupported: ['did:ethr:'] -})); - -resolver.resolve('did:ethr:0x998D43DA5d9d78500898346baf2d9B1E39Eb0Dda').then(doc => console.log) -``` - -The DidResolution file exposes 2 functions that help with the resolution as well: - -```typescript -import {getResolver, resolveDidDocument} from './helpers/DIDResolution'; - -// combines 2 uni resolvers for ethr and eosio together with the myCustomResolver and return that as a single resolver -const myCustomResolver = new MyCustomResolver(); -getResolver({ - subjectSyntaxTypesSupported: ['did:ethr:', 'did:eosio:'], resolver: myCustomResolver}); - -// Returns a DID document for the specified DID, using the universal resolver client for the ehtr DID method -await resolveDidDocument('did:ethr:0x998D43DA5d9d78500898346baf2d9B1E39Eb0Dda', {subjectSyntaxTypesSupported: ['did:ethr:', 'did:eosio:']}); -``` - -## JWT and DID creation and verification - -Please note that this chapter is about low level JWT functions, which normally aren't used by end users of this library. -Typically, you use the AuthorizationRequest and Response classes (low-level) or the OP and RP classes (high-level). - -### Create JWT - -Creates a signed JWT given a DID which becomes the issuer, a signer function, and a payload over which the withSignature is -created. - -#### Data Interface - -```typescript -export interface JWTPayload { // This is a standard JWT payload described on for instance https://jwt.io - iss?: string - sub?: string - aud?: string | string[] - iat?: number - nbf?: number - exp?: number - rexp?: number - jti?: string; - - [x: string]: any -} - -export interface JWTHeader { // This is a standard JWT header - typ: 'JWT' - alg: string // The JWT signing algorithm to use. Supports: [ES256K, ES256K-R, Ed25519, EdDSA], Defaults to: ES256K - [x: string]: any -} - -export interface JWTOptions { - issuer: string // The DID of the issuer (signer) of JWT - signer: Signer // A signer function, eg: `ES256KSigner` or `EdDSASigner` - expiresIn?: number // optional expiration time - canonicalize?: boolean // optional flag to canonicalize header and payload before signing -} -``` - -#### Usage - -````typescript -const signer = ES256KSigner(process.env.PRIVATE_KEY); -createDidJWT({requested: ['name', 'phone']}, {issuer: 'did:eosio:example', signer}).then(jwt => console.log) -```` - -### Verify JWT - -Verifies the given JWT. If the JWT is valid, the promise returns an object including the JWT, the payload of the JWT, -and the DID Document of the issuer of the JWT, using the resolver mentioned earlier. The checks performed include, -general JWT decoding, DID resolution, Proof purposes - -proof purposes allows restriction of verification methods to the ones specifically listed, otherwise the ' -authentication' verification method of the resolved DID document will be used - -#### Data Interface - -Verify options: - -```typescript -export interface JWTVerifyOptions { - audience?: string // DID of the recipient of the JWT - callbackUrl?: string // callback url in JWT - skewTime?: number // Allow to skey time in the expiration check with this amount - proofPurpose?: ProofPurposeTypes // Restrict to this proof purpose type in the DID resolution -} -``` - -Response: - -```typescript -export interface JWTVerified { - payload: Partial // Standard partial JWT payload, see above - didResolutionResult: DIDResolutionResult // The DID resolution - issuer?: string // The DID that issued the JWT - signer?: VerificationMethod // The verification method that issued the JWT - jwt: string // The JWT itself -} - -export interface VerificationMethod { - id: string // The id of the key - type: string // authentication, assertionMethod etc (see DID spec) - controller: string // The controller of the Verification method - publicKeyBase58?: string // Public key in base58 if any - publicKeyJwk?: JsonWebKey // Public key in JWK if any - publicKeyHex?: string // Public key in hex if any - blockchainAccountId?: string // optional blockchain account id associated with the DID - ethereumAddress?: string // deprecated -} -``` - -#### Usage - -```typescript -verifyDidJWT(jwt, resolver, {audience: '6B2bRWU3F7j3REx3vkJ..'}).then(verifiedJWT => { - const did = verifiedJWT.issuer; // DID of signer - const payload = verifiedJWT.payload; // The JHT payload - const doc = verifiedJWT.didResolutionResult.didDocument; // DID Document of signer - const jwt = verifiedJWT.jwt; // JWS in string format - const signerKeyId = verifiedJWT.signer.id; // ID of key in DID document that signed JWT -... -}); -``` - -### Verify Linked Domain with DID - -Verifies whether a domain linkage credential is valid - -#### Data Interface - -Verify callback: - -```typescript -export declare type VerifyCallback = (args: IVerifyCallbackArgs) => Promise; // The callback function to verify the Domain Linkage Credential -``` - -```typescript -export interface IVerifyCallbackArgs { - credential: DomainLinkageCredential; // The domain linkage credential to be verified - proofFormat?: ProofFormatTypesEnum; // Whether it is a JWT or JsonLD credential -} -``` - -```typescript -export interface IVerifyCredentialResult { - verified: boolean; // The result of the domain linkage credential verification -} -``` - -```typescript -export enum CheckLinkedDomain { - NEVER = 'never', // We don't want to verify Linked domains - IF_PRESENT = 'if_present', // If present, did-auth-siop will check the linked domain, if exist and not valid, throws an exception - ALWAYS = 'always', // We'll always check the linked domains, if not exist or not valid, throws an exception -} -``` - -#### Usage - -```typescript -const verifyCallback = async (args: IVerifyCallbackArgs): Promise => { - const keyPair = await Ed25519VerificationKey2020.from(VC_KEY_PAIR); - const suite = new Ed25519Signature2020({ key: keyPair }); - suite.verificationMethod = keyPair.id; - return await vc.verifyCredential({ credential: args.credential, suite, documentLoader: new DocumentLoader().getLoader() }); -}; -``` - -```typescript -const rp = RP.builder() - .withCheckLinkedDomain(CheckLinkedDomain.ALWAYS) - .addVerifyCallback((args: IVerifyCallbackArgs) => verifyCallback(args)) - ... -``` - -```typescript -const op = OP.builder() - .withCheckLinkedDomain(CheckLinkedDomain.ALWAYS) - .addVerifyCallback((args: IVerifyCallbackArgs) => verifyCallback(args)) - ... -``` - ### Verify Revocation Verifies whether a verifiable credential contained verifiable presentation is revoked @@ -1343,12 +1065,6 @@ Services and objects: [![](./docs/services-class-diagram.svg)](https://mermaid-js.github.io/mermaid-live-editor/edit#eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIFJQIHtcbiAgICA8PHNlcnZpY2U-PlxuICAgIGNyZWF0ZUF1dGhlbnRpY2F0aW9uUmVxdWVzdChvcHRzPykgUHJvbWlzZShBdXRoZW50aWNhdGlvblJlcXVlc3RVUkkpXG4gICAgdmVyaWZ5QXV0aGVudGljYXRpb25SZXNwb25zZUp3dChqd3Q6IHN0cmluZywgb3B0cz8pIFByb21pc2UoVmVyaWZpZWRBdXRoZW50aWNhdGlvblJlc3BvbnNlV2l0aEpXVClcbn1cblJQIC0tPiBBdXRoZW50aWNhdGlvblJlcXVlc3RVUklcblJQIC0tPiBWZXJpZmllZEF1dGhlbnRpY2F0aW9uUmVzcG9uc2VXaXRoSldUXG5SUCAtLT4gQXV0aGVudGljYXRpb25SZXF1ZXN0XG5SUCAtLT4gQXV0aGVudGljYXRpb25SZXNwb25zZVxuXG5jbGFzcyBPUCB7XG4gICAgPDxzZXJ2aWNlPj5cbiAgICBjcmVhdGVBdXRoZW50aWNhdGlvblJlc3BvbnNlKGp3dE9yVXJpOiBzdHJpbmcsIG9wdHM_KSBQcm9taXNlKEF1dGhlbnRpY2F0aW9uUmVzcG9uc2VXaXRoSldUKVxuICAgIHZlcmlmeUF1dGhlbnRpY2F0aW9uUmVxdWVzdChqd3Q6IHN0cmluZywgb3B0cz8pIFByb21pc2UoVmVyaWZpZWRBdXRoZW50aWNhdGlvblJlcXVlc3RXaXRoSldUKVxufVxuT1AgLS0-IEF1dGhlbnRpY2F0aW9uUmVzcG9uc2VXaXRoSldUXG5PUCAtLT4gVmVyaWZpZWRBdXRoZW50aWNhdGlvblJlcXVlc3RXaXRoSldUXG5PUCAtLT4gQXV0aGVudGljYXRpb25SZXF1ZXN0XG5PUCAtLT4gQXV0aGVudGljYXRpb25SZXNwb25zZVxuXG5cbmNsYXNzIEF1dGhlbnRpY2F0aW9uUmVxdWVzdE9wdHMge1xuICA8PGludGVyZmFjZT4-XG4gIHJlZGlyZWN0VXJpOiBzdHJpbmc7XG4gIHJlcXVlc3RCeTogT2JqZWN0Qnk7XG4gIHNpZ25hdHVyZVR5cGU6IEludGVybmFsU2lnbmF0dXJlIHwgRXh0ZXJuYWxTaWduYXR1cmUgfCBOb1NpZ25hdHVyZTtcbiAgcmVzcG9uc2VNb2RlPzogUmVzcG9uc2VNb2RlO1xuICBjbGFpbXM_OiBPaWRjQ2xhaW07XG4gIHJlZ2lzdHJhdGlvbjogUmVxdWVzdFJlZ2lzdHJhdGlvbk9wdHM7XG4gIG5vbmNlPzogc3RyaW5nO1xuICBzdGF0ZT86IHN0cmluZztcbn1cbkF1dGhlbnRpY2F0aW9uUmVxdWVzdE9wdHMgLS0-IFJlc3BvbnNlTW9kZVxuQXV0aGVudGljYXRpb25SZXF1ZXN0T3B0cyAtLT4gUlBSZWdpc3RyYXRpb25NZXRhZGF0YU9wdHNcblxuXG5cbmNsYXNzIFJQUmVnaXN0cmF0aW9uTWV0YWRhdGFPcHRzIHtcbiAgPDxpbnRlcmZhY2U-PlxuICBzdWJqZWN0SWRlbnRpZmllcnNTdXBwb3J0ZWQ6IFN1YmplY3RJZGVudGlmaWVyVHlwZVtdIHwgU3ViamVjdElkZW50aWZpZXJUeXBlO1xuICBkaWRNZXRob2RzU3VwcG9ydGVkPzogc3RyaW5nW10gfCBzdHJpbmc7XG4gIGNyZWRlbnRpYWxGb3JtYXRzU3VwcG9ydGVkOiBDcmVkZW50aWFsRm9ybWF0W10gfCBDcmVkZW50aWFsRm9ybWF0O1xufVxuXG5jbGFzcyBSZXF1ZXN0UmVnaXN0cmF0aW9uT3B0cyB7XG4gIDw8aW50ZXJmYWNlPj5cbiAgcmVnaXN0cmF0aW9uQnk6IFJlZ2lzdHJhdGlvblR5cGU7XG59XG5SZXF1ZXN0UmVnaXN0cmF0aW9uT3B0cyAtLXw-IFJQUmVnaXN0cmF0aW9uTWV0YWRhdGFPcHRzXG5cblxuY2xhc3MgVmVyaWZ5QXV0aGVudGljYXRpb25SZXF1ZXN0T3B0cyB7XG4gIDw8aW50ZXJmYWNlPj5cbiAgdmVyaWZpY2F0aW9uOiBJbnRlcm5hbFZlcmlmaWNhdGlvbiB8IEV4dGVybmFsVmVyaWZpY2F0aW9uO1xuICBub25jZT86IHN0cmluZztcbn1cblxuY2xhc3MgQXV0aGVudGljYXRpb25SZXF1ZXN0IHtcbiAgICA8PHNlcnZpY2U-PlxuICAgIGNyZWF0ZVVSSShvcHRzOiBBdXRoZW50aWNhdGlvblJlcXVlc3RPcHRzKSBQcm9taXNlKEF1dGhlbnRpY2F0aW9uUmVxdWVzdFVSSSlcbiAgICBjcmVhdGVKV1Qob3B0czogQXV0aGVudGljYXRpb25SZXF1ZXN0T3B0cykgUHJvbWlzZShBdXRoZW50aWNhdGlvblJlcXVlc3RXaXRoSldUKTtcbiAgICB2ZXJpZnlKV1Qoand0OiBzdHJpbmcsIG9wdHM6IFZlcmlmeUF1dGhlbnRpY2F0aW9uUmVxdWVzdE9wdHMpIFByb21pc2UoVmVyaWZpZWRBdXRoZW50aWNhdGlvblJlcXVlc3RXaXRoSldUKVxufVxuQXV0aGVudGljYXRpb25SZXF1ZXN0IDwtLSBBdXRoZW50aWNhdGlvblJlcXVlc3RPcHRzXG5BdXRoZW50aWNhdGlvblJlcXVlc3QgPC0tIFZlcmlmeUF1dGhlbnRpY2F0aW9uUmVxdWVzdE9wdHNcbkF1dGhlbnRpY2F0aW9uUmVxdWVzdCAtLT4gQXV0aGVudGljYXRpb25SZXF1ZXN0VVJJXG5BdXRoZW50aWNhdGlvblJlcXVlc3QgLS0-IEF1dGhlbnRpY2F0aW9uUmVxdWVzdFdpdGhKV1RcbkF1dGhlbnRpY2F0aW9uUmVxdWVzdCAtLT4gVmVyaWZpZWRBdXRoZW50aWNhdGlvblJlcXVlc3RXaXRoSldUXG5cbmNsYXNzIEF1dGhlbnRpY2F0aW9uUmVzcG9uc2Uge1xuICA8PGludGVyZmFjZT4-XG4gIGNyZWF0ZUpXVEZyb21SZXF1ZXN0SldUKGp3dDogc3RyaW5nLCByZXNwb25zZU9wdHM6IEF1dGhlbnRpY2F0aW9uUmVzcG9uc2VPcHRzLCB2ZXJpZnlPcHRzOiBWZXJpZnlBdXRoZW50aWNhdGlvblJlcXVlc3RPcHRzKSBQcm9taXNlKEF1dGhlbnRpY2F0aW9uUmVzcG9uc2VXaXRoSldUKVxuICB2ZXJpZnlKV1Qoand0OiBzdHJpbmcsIHZlcmlmeU9wdHM6IFZlcmlmeUF1dGhlbnRpY2F0aW9uUmVzcG9uc2VPcHRzKSBQcm9taXNlKFZlcmlmaWVkQXV0aGVudGljYXRpb25SZXNwb25zZVdpdGhKV1QpXG59XG5BdXRoZW50aWNhdGlvblJlc3BvbnNlIDwtLSBBdXRoZW50aWNhdGlvblJlc3BvbnNlT3B0c1xuQXV0aGVudGljYXRpb25SZXNwb25zZSA8LS0gVmVyaWZ5QXV0aGVudGljYXRpb25SZXF1ZXN0T3B0c1xuQXV0aGVudGljYXRpb25SZXNwb25zZSAtLT4gQXV0aGVudGljYXRpb25SZXNwb25zZVdpdGhKV1RcbkF1dGhlbnRpY2F0aW9uUmVzcG9uc2UgPC0tIFZlcmlmeUF1dGhlbnRpY2F0aW9uUmVzcG9uc2VPcHRzXG5BdXRoZW50aWNhdGlvblJlc3BvbnNlIC0tPiBWZXJpZmllZEF1dGhlbnRpY2F0aW9uUmVzcG9uc2VXaXRoSldUXG5cbmNsYXNzIEF1dGhlbnRpY2F0aW9uUmVzcG9uc2VPcHRzIHtcbiAgPDxpbnRlcmZhY2U-PlxuICBzaWduYXR1cmVUeXBlOiBJbnRlcm5hbFNpZ25hdHVyZSB8IEV4dGVybmFsU2lnbmF0dXJlO1xuICBub25jZT86IHN0cmluZztcbiAgc3RhdGU_OiBzdHJpbmc7XG4gIHJlZ2lzdHJhdGlvbjogUmVzcG9uc2VSZWdpc3RyYXRpb25PcHRzO1xuICByZXNwb25zZU1vZGU_OiBSZXNwb25zZU1vZGU7XG4gIGRpZDogc3RyaW5nO1xuICB2cD86IFZlcmlmaWFibGVQcmVzZW50YXRpb247XG4gIGV4cGlyZXNJbj86IG51bWJlcjtcbn1cbkF1dGhlbnRpY2F0aW9uUmVzcG9uc2VPcHRzIC0tPiBSZXNwb25zZU1vZGVcblxuY2xhc3MgQXV0aGVudGljYXRpb25SZXNwb25zZVdpdGhKV1Qge1xuICA8PGludGVyZmFjZT4-XG4gIGp3dDogc3RyaW5nO1xuICBub25jZTogc3RyaW5nO1xuICBzdGF0ZTogc3RyaW5nO1xuICBwYXlsb2FkOiBBdXRoZW50aWNhdGlvblJlc3BvbnNlUGF5bG9hZDtcbiAgdmVyaWZ5T3B0cz86IFZlcmlmeUF1dGhlbnRpY2F0aW9uUmVxdWVzdE9wdHM7XG4gIHJlc3BvbnNlT3B0czogQXV0aGVudGljYXRpb25SZXNwb25zZU9wdHM7XG59XG5BdXRoZW50aWNhdGlvblJlc3BvbnNlV2l0aEpXVCAtLT4gQXV0aGVudGljYXRpb25SZXNwb25zZVBheWxvYWRcbkF1dGhlbnRpY2F0aW9uUmVzcG9uc2VXaXRoSldUIC0tPiBWZXJpZnlBdXRoZW50aWNhdGlvblJlcXVlc3RPcHRzXG5BdXRoZW50aWNhdGlvblJlc3BvbnNlV2l0aEpXVCAtLT4gQXV0aGVudGljYXRpb25SZXNwb25zZU9wdHNcblxuXG5jbGFzcyBWZXJpZnlBdXRoZW50aWNhdGlvblJlc3BvbnNlT3B0cyB7XG4gIDw8aW50ZXJmYWNlPj5cbiAgdmVyaWZpY2F0aW9uOiBJbnRlcm5hbFZlcmlmaWNhdGlvbiB8IEV4dGVybmFsVmVyaWZpY2F0aW9uO1xuICBub25jZT86IHN0cmluZztcbiAgc3RhdGU_OiBzdHJpbmc7XG4gIGF1ZGllbmNlOiBzdHJpbmc7XG59XG5cbmNsYXNzIFJlc3BvbnNlTW9kZSB7XG4gICAgPDxlbnVtPj5cbn1cblxuIGNsYXNzIFVyaVJlc3BvbnNlIHtcbiAgICA8PGludGVyZmFjZT4-XG4gICAgcmVzcG9uc2VNb2RlPzogUmVzcG9uc2VNb2RlO1xuICAgIGJvZHlFbmNvZGVkPzogc3RyaW5nO1xufVxuVXJpUmVzcG9uc2UgLS0-IFJlc3BvbnNlTW9kZVxuVXJpUmVzcG9uc2UgPHwtLSBTSU9QVVJJXG5cbmNsYXNzIFNJT1BVUkkge1xuICAgIDw8aW50ZXJmYWNlPj5cbiAgICBlbmNvZGVkVXJpOiBzdHJpbmc7XG4gICAgZW5jb2RpbmdGb3JtYXQ6IFVybEVuY29kaW5nRm9ybWF0O1xufVxuU0lPUFVSSSAtLT4gVXJsRW5jb2RpbmdGb3JtYXRcblNJT1BVUkkgPHwtLSBBdXRoZW50aWNhdGlvblJlcXVlc3RVUklcblxuY2xhc3MgQXV0aGVudGljYXRpb25SZXF1ZXN0VVJJIHtcbiAgPDxpbnRlcmZhY2U-PlxuICBqd3Q_OiBzdHJpbmc7IFxuICByZXF1ZXN0T3B0czogQXV0aGVudGljYXRpb25SZXF1ZXN0T3B0cztcbiAgcmVxdWVzdFBheWxvYWQ6IEF1dGhlbnRpY2F0aW9uUmVxdWVzdFBheWxvYWQ7XG59XG5BdXRoZW50aWNhdGlvblJlcXVlc3RVUkkgLS0-IEF1dGhlbnRpY2F0aW9uUmVxdWVzdFBheWxvYWRcblxuY2xhc3MgVXJsRW5jb2RpbmdGb3JtYXQge1xuICAgIDw8ZW51bT4-XG59XG5cbmNsYXNzIFJlc3BvbnNlTW9kZSB7XG4gIDw8ZW51bT4-XG59XG5cbmNsYXNzIEF1dGhlbnRpY2F0aW9uUmVxdWVzdFBheWxvYWQge1xuICAgIDw8aW50ZXJmYWNlPj5cbiAgICBzY29wZTogU2NvcGU7XG4gICAgcmVzcG9uc2VfdHlwZTogUmVzcG9uc2VUeXBlO1xuICAgIGNsaWVudF9pZDogc3RyaW5nO1xuICAgIHJlZGlyZWN0X3VyaTogc3RyaW5nO1xuICAgIHJlc3BvbnNlX21vZGU6IFJlc3BvbnNlTW9kZTtcbiAgICByZXF1ZXN0OiBzdHJpbmc7XG4gICAgcmVxdWVzdF91cmk6IHN0cmluZztcbiAgICBzdGF0ZT86IHN0cmluZztcbiAgICBub25jZTogc3RyaW5nO1xuICAgIGRpZF9kb2M_OiBESUREb2N1bWVudDtcbiAgICBjbGFpbXM_OiBSZXF1ZXN0Q2xhaW1zO1xufVxuQXV0aGVudGljYXRpb25SZXF1ZXN0UGF5bG9hZCAtLXw-IEpXVFBheWxvYWRcblxuY2xhc3MgIEpXVFBheWxvYWQge1xuICBpc3M_OiBzdHJpbmdcbiAgc3ViPzogc3RyaW5nXG4gIGF1ZD86IHN0cmluZyB8IHN0cmluZ1tdXG4gIGlhdD86IG51bWJlclxuICBuYmY_OiBudW1iZXJcbiAgZXhwPzogbnVtYmVyXG4gIHJleHA_OiBudW1iZXJcbiAgW3g6IHN0cmluZ106IGFueVxufVxuXG5cbmNsYXNzIFZlcmlmaWVkQXV0aGVudGljYXRpb25SZXF1ZXN0V2l0aEpXVCB7XG4gIDw8aW50ZXJmYWNlPj5cbiAgcGF5bG9hZDogQXV0aGVudGljYXRpb25SZXF1ZXN0UGF5bG9hZDsgXG4gIHZlcmlmeU9wdHM6IFZlcmlmeUF1dGhlbnRpY2F0aW9uUmVxdWVzdE9wdHM7IFxufVxuVmVyaWZpZWRKV1QgPHwtLSBWZXJpZmllZEF1dGhlbnRpY2F0aW9uUmVxdWVzdFdpdGhKV1RcblZlcmlmaWVkQXV0aGVudGljYXRpb25SZXF1ZXN0V2l0aEpXVCAtLT4gVmVyaWZ5QXV0aGVudGljYXRpb25SZXF1ZXN0T3B0c1xuVmVyaWZpZWRBdXRoZW50aWNhdGlvblJlcXVlc3RXaXRoSldUIC0tPiBBdXRoZW50aWNhdGlvblJlcXVlc3RQYXlsb2FkXG5cbmNsYXNzIFZlcmlmaWVkQXV0aGVudGljYXRpb25SZXNwb25zZVdpdGhKV1Qge1xuICA8PGludGVyZmFjZT4-XG4gIHBheWxvYWQ6IEF1dGhlbnRpY2F0aW9uUmVzcG9uc2VQYXlsb2FkO1xuICB2ZXJpZnlPcHRzOiBWZXJpZnlBdXRoZW50aWNhdGlvblJlc3BvbnNlT3B0cztcbn1cblZlcmlmaWVkQXV0aGVudGljYXRpb25SZXNwb25zZVdpdGhKV1QgLS0-IEF1dGhlbnRpY2F0aW9uUmVzcG9uc2VQYXlsb2FkXG5WZXJpZmllZEF1dGhlbnRpY2F0aW9uUmVzcG9uc2VXaXRoSldUIC0tPiBWZXJpZnlBdXRoZW50aWNhdGlvblJlc3BvbnNlT3B0c1xuVmVyaWZpZWRKV1QgPHwtLSBWZXJpZmllZEF1dGhlbnRpY2F0aW9uUmVzcG9uc2VXaXRoSldUXG5cbmNsYXNzIFZlcmlmaWVkSldUIHtcbiAgPDxpbnRlcmZhY2U-PlxuICBwYXlsb2FkOiBQYXJ0aWFsPEpXVFBheWxvYWQ-O1xuICBkaWRSZXNvbHV0aW9uUmVzdWx0OiBESURSZXNvbHV0aW9uUmVzdWx0O1xuICBpc3N1ZXI6IHN0cmluZztcbiAgc2lnbmVyOiBWZXJpZmljYXRpb25NZXRob2Q7XG4gIGp3dDogc3RyaW5nO1xufVxuXG5cbiIsIm1lcm1haWQiOiJ7XG4gIFwidGhlbWVcIjogXCJkYXJrXCJcbn0iLCJ1cGRhdGVFZGl0b3IiOmZhbHNlLCJhdXRvU3luYyI6ZmFsc2UsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ) -DID JWTs: - -[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBEaWRSZXNvbHV0aW9uT3B0aW9ucyB7XG4gICAgPDxpbnRlcmZhY2U-PlxuICAgIGFjY2VwdD86IHN0cmluZ1xufVxuY2xhc3MgUmVzb2x2YWJsZSB7XG4gICAgPDxpbnRlcmZhY2U-PlxuICAgIHJlc29sdmUoZGlkVXJsOiBzdHJpbmcsIG9wdGlvbnM6IERpZFJlc29sdXRpb25PcHRpb25zKSBQcm9taXNlKERpZFJlc29sdXRpb25SZXN1bHQpXG59XG5EaWRSZXNvbHV0aW9uT3B0aW9ucyA8LS0gUmVzb2x2YWJsZVxuRElEUmVzb2x1dGlvblJlc3VsdCA8LS0gUmVzb2x2YWJsZVxuXG5jbGFzcyAgRElEUmVzb2x1dGlvblJlc3VsdCB7XG4gIGRpZFJlc29sdXRpb25NZXRhZGF0YTogRElEUmVzb2x1dGlvbk1ldGFkYXRhXG4gIGRpZERvY3VtZW50OiBESUREb2N1bWVudCB8IG51bGxcbiAgZGlkRG9jdW1lbnRNZXRhZGF0YTogRElERG9jdW1lbnRNZXRhZGF0YVxufVxuRElERG9jdW1lbnRNZXRhZGF0YSA8LS0gRElEUmVzb2x1dGlvblJlc3VsdFxuRElERG9jdW1lbnQgPC0tIERJRFJlc29sdXRpb25SZXN1bHRcblxuY2xhc3MgRElERG9jdW1lbnRNZXRhZGF0YSB7XG4gIGNyZWF0ZWQ_OiBzdHJpbmdcbiAgdXBkYXRlZD86IHN0cmluZ1xuICBkZWFjdGl2YXRlZD86IGJvb2xlYW5cbiAgdmVyc2lvbklkPzogc3RyaW5nXG4gIG5leHRVcGRhdGU_OiBzdHJpbmdcbiAgbmV4dFZlcnNpb25JZD86IHN0cmluZ1xuICBlcXVpdmFsZW50SWQ_OiBzdHJpbmdcbiAgY2Fub25pY2FsSWQ_OiBzdHJpbmdcbn1cblxuY2xhc3MgRElERG9jdW1lbnQge1xuICAgIDw8aW50ZXJmYWNlPj5cbiAgICAnQGNvbnRleHQnPzogJ2h0dHBzOi8vd3d3LnczLm9yZy9ucy9kaWQvdjEnIHwgc3RyaW5nIHwgc3RyaW5nW11cbiAgICBpZDogc3RyaW5nXG4gICAgYWxzb0tub3duQXM_OiBzdHJpbmdbXVxuICAgIGNvbnRyb2xsZXI_OiBzdHJpbmcgfCBzdHJpbmdbXVxuICAgIHZlcmlmaWNhdGlvbk1ldGhvZD86IFZlcmlmaWNhdGlvbk1ldGhvZFtdXG4gICAgYXV0aGVudGljYXRpb24_OiAoc3RyaW5nIHwgVmVyaWZpY2F0aW9uTWV0aG9kKVtdXG4gICAgYXNzZXJ0aW9uTWV0aG9kPzogKHN0cmluZyB8IFZlcmlmaWNhdGlvbk1ldGhvZClbXVxuICAgIGtleUFncmVlbWVudD86IChzdHJpbmcgfCBWZXJpZmljYXRpb25NZXRob2QpW11cbiAgICBjYXBhYmlsaXR5SW52b2NhdGlvbj86IChzdHJpbmcgfCBWZXJpZmljYXRpb25NZXRob2QpW11cbiAgICBjYXBhYmlsaXR5RGVsZWdhdGlvbj86IChzdHJpbmcgfCBWZXJpZmljYXRpb25NZXRob2QpW11cbiAgICBzZXJ2aWNlPzogU2VydmljZUVuZHBvaW50W11cbn1cblZlcmlmaWNhdGlvbk1ldGhvZCA8LS0gRElERG9jdW1lbnRcblxuY2xhc3MgVmVyaWZpY2F0aW9uTWV0aG9kIHtcbiAgICA8PGludGVyZmFjZT4-XG4gICAgaWQ6IHN0cmluZ1xuICAgIHR5cGU6IHN0cmluZ1xuICAgIGNvbnRyb2xsZXI6IHN0cmluZ1xuICAgIHB1YmxpY0tleUJhc2U1OD86IHN0cmluZ1xuICAgIHB1YmxpY0tleUp3az86IEpzb25XZWJLZXlcbiAgICBwdWJsaWNLZXlIZXg_OiBzdHJpbmdcbiAgICBibG9ja2NoYWluQWNjb3VudElkPzogc3RyaW5nXG4gICAgZXRoZXJldW1BZGRyZXNzPzogc3RyaW5nXG59XG5cbmNsYXNzIEpXVFBheWxvYWQge1xuICAgIDw8aW50ZXJmYWNlPj5cbiAgICBpc3M6IHN0cmluZ1xuICAgIHN1Yj86IHN0cmluZ1xuICAgIGF1ZD86IHN0cmluZyB8IHN0cmluZ1tdXG4gICAgaWF0PzogbnVtYmVyXG4gICAgbmJmPzogbnVtYmVyXG4gICAgZXhwPzogbnVtYmVyXG4gICAgcmV4cD86IG51bWJlclxufVxuY2xhc3MgSldUSGVhZGVyIHsgLy8gVGhpcyBpcyBhIHN0YW5kYXJkIEpXVCBoZWFkZXJcbiAgICB0eXA6ICdKV1QnXG4gICAgYWxnOiBzdHJpbmcgICAvLyBUaGUgSldUIHNpZ25pbmcgYWxnb3JpdGhtIHRvIHVzZS4gU3VwcG9ydHM6IFtFUzI1NkssIEVTMjU2Sy1SLCBFZDI1NTE5LCBFZERTQV0sIERlZmF1bHRzIHRvOiBFUzI1NktcbiAgICBbeDogc3RyaW5nXTogYW55XG59XG5cbmNsYXNzIFZlcmlmaWNhdGlvbk1ldGhvZCB7XG4gIGlkOiBzdHJpbmdcbiAgdHlwZTogc3RyaW5nXG4gIGNvbnRyb2xsZXI6IHN0cmluZ1xuICBwdWJsaWNLZXlCYXNlNTg_OiBzdHJpbmdcbiAgcHVibGljS2V5SndrPzogSnNvbldlYktleVxuICBwdWJsaWNLZXlIZXg_OiBzdHJpbmdcbiAgYmxvY2tjaGFpbkFjY291bnRJZD86IHN0cmluZ1xuICBldGhlcmV1bUFkZHJlc3M_OiBzdHJpbmdcbn1cblxuSnNvbldlYktleSA8fC0tIFZlcmlmaWNhdGlvbk1ldGhvZFxuY2xhc3MgSnNvbldlYktleSB7XG4gIGFsZz86IHN0cmluZ1xuICBjcnY_OiBzdHJpbmdcbiAgZT86IHN0cmluZ1xuICBleHQ_OiBib29sZWFuXG4gIGtleV9vcHM_OiBzdHJpbmdbXVxuICBraWQ_OiBzdHJpbmdcbiAga3R5OiBzdHJpbmdcbiAgbj86IHN0cmluZ1xuICB1c2U_OiBzdHJpbmdcbiAgeD86IHN0cmluZ1xuICB5Pzogc3RyaW5nXG59XG5cblxuY2xhc3MgRGlkSldUIHtcbiAgICA8PHNlcnZpY2U-PlxuICAgIGNyZWF0ZURpZEpXVChwYXlsb2FkOiBKV1RQYXlsb2FkLCBvcHRpb25zOiBKV1RPcHRpb25zLCBoZWFkZXI6IEpXVEpIZWFkZXIpIFByb21pc2Uoc3RyaW5nKVxuICAgIHZlcmlmeURpZEpXVChqd3Q6IHN0cmluZywgcmVzb2x2ZXI6IFJlc29sdmFibGUpIFByb21pc2UoYm9vbGVhbilcbn1cbkpXVFBheWxvYWQgPC0tIERpZEpXVFxuSldUT3B0aW9ucyA8LS0gRGlkSldUXG5KV1RIZWFkZXIgPC0tIERpZEpXVFxuUmVzb2x2YWJsZSA8LS0gRGlkSldUXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlLCJhdXRvU3luYyI6ZmFsc2UsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ)](https://mermaid-js.github.io/mermaid-live-editor/edit##eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBEaWRSZXNvbHV0aW9uT3B0aW9ucyB7XG4gICAgPDxpbnRlcmZhY2U-PlxuICAgIGFjY2VwdD86IHN0cmluZ1xufVxuY2xhc3MgUmVzb2x2YWJsZSB7XG4gICAgPDxpbnRlcmZhY2U-PlxuICAgIHJlc29sdmUoZGlkVXJsOiBzdHJpbmcsIG9wdGlvbnM6IERpZFJlc29sdXRpb25PcHRpb25zKSBQcm9taXNlKERpZFJlc29sdXRpb25SZXN1bHQpXG59XG5EaWRSZXNvbHV0aW9uT3B0aW9ucyA8LS0gUmVzb2x2YWJsZVxuRElEUmVzb2x1dGlvblJlc3VsdCA8LS0gUmVzb2x2YWJsZVxuXG5jbGFzcyAgRElEUmVzb2x1dGlvblJlc3VsdCB7XG4gIGRpZFJlc29sdXRpb25NZXRhZGF0YTogRElEUmVzb2x1dGlvbk1ldGFkYXRhXG4gIGRpZERvY3VtZW50OiBESUREb2N1bWVudCB8IG51bGxcbiAgZGlkRG9jdW1lbnRNZXRhZGF0YTogRElERG9jdW1lbnRNZXRhZGF0YVxufVxuRElERG9jdW1lbnRNZXRhZGF0YSA8LS0gRElEUmVzb2x1dGlvblJlc3VsdFxuRElERG9jdW1lbnQgPC0tIERJRFJlc29sdXRpb25SZXN1bHRcblxuY2xhc3MgRElERG9jdW1lbnRNZXRhZGF0YSB7XG4gIGNyZWF0ZWQ_OiBzdHJpbmdcbiAgdXBkYXRlZD86IHN0cmluZ1xuICBkZWFjdGl2YXRlZD86IGJvb2xlYW5cbiAgdmVyc2lvbklkPzogc3RyaW5nXG4gIG5leHRVcGRhdGU_OiBzdHJpbmdcbiAgbmV4dFZlcnNpb25JZD86IHN0cmluZ1xuICBlcXVpdmFsZW50SWQ_OiBzdHJpbmdcbiAgY2Fub25pY2FsSWQ_OiBzdHJpbmdcbn1cblxuY2xhc3MgRElERG9jdW1lbnQge1xuICAgIDw8aW50ZXJmYWNlPj5cbiAgICAnQGNvbnRleHQnPzogJ2h0dHBzOi8vd3d3LnczLm9yZy9ucy9kaWQvdjEnIHwgc3RyaW5nIHwgc3RyaW5nW11cbiAgICBpZDogc3RyaW5nXG4gICAgYWxzb0tub3duQXM_OiBzdHJpbmdbXVxuICAgIGNvbnRyb2xsZXI_OiBzdHJpbmcgfCBzdHJpbmdbXVxuICAgIHZlcmlmaWNhdGlvbk1ldGhvZD86IFZlcmlmaWNhdGlvbk1ldGhvZFtdXG4gICAgYXV0aGVudGljYXRpb24_OiAoc3RyaW5nIHwgVmVyaWZpY2F0aW9uTWV0aG9kKVtdXG4gICAgYXNzZXJ0aW9uTWV0aG9kPzogKHN0cmluZyB8IFZlcmlmaWNhdGlvbk1ldGhvZClbXVxuICAgIGtleUFncmVlbWVudD86IChzdHJpbmcgfCBWZXJpZmljYXRpb25NZXRob2QpW11cbiAgICBjYXBhYmlsaXR5SW52b2NhdGlvbj86IChzdHJpbmcgfCBWZXJpZmljYXRpb25NZXRob2QpW11cbiAgICBjYXBhYmlsaXR5RGVsZWdhdGlvbj86IChzdHJpbmcgfCBWZXJpZmljYXRpb25NZXRob2QpW11cbiAgICBzZXJ2aWNlPzogU2VydmljZUVuZHBvaW50W11cbn1cblZlcmlmaWNhdGlvbk1ldGhvZCA8LS0gRElERG9jdW1lbnRcblxuY2xhc3MgVmVyaWZpY2F0aW9uTWV0aG9kIHtcbiAgICA8PGludGVyZmFjZT4-XG4gICAgaWQ6IHN0cmluZ1xuICAgIHR5cGU6IHN0cmluZ1xuICAgIGNvbnRyb2xsZXI6IHN0cmluZ1xuICAgIHB1YmxpY0tleUJhc2U1OD86IHN0cmluZ1xuICAgIHB1YmxpY0tleUp3az86IEpzb25XZWJLZXlcbiAgICBwdWJsaWNLZXlIZXg_OiBzdHJpbmdcbiAgICBibG9ja2NoYWluQWNjb3VudElkPzogc3RyaW5nXG4gICAgZXRoZXJldW1BZGRyZXNzPzogc3RyaW5nXG59XG5cbmNsYXNzIEpXVFBheWxvYWQge1xuICAgIDw8aW50ZXJmYWNlPj5cbiAgICBpc3M6IHN0cmluZ1xuICAgIHN1Yj86IHN0cmluZ1xuICAgIGF1ZD86IHN0cmluZyB8IHN0cmluZ1tdXG4gICAgaWF0PzogbnVtYmVyXG4gICAgbmJmPzogbnVtYmVyXG4gICAgZXhwPzogbnVtYmVyXG4gICAgcmV4cD86IG51bWJlclxufVxuY2xhc3MgSldUSGVhZGVyIHsgLy8gVGhpcyBpcyBhIHN0YW5kYXJkIEpXVCBoZWFkZXJcbiAgICB0eXA6ICdKV1QnXG4gICAgYWxnOiBzdHJpbmcgICAvLyBUaGUgSldUIHNpZ25pbmcgYWxnb3JpdGhtIHRvIHVzZS4gU3VwcG9ydHM6IFtFUzI1NkssIEVTMjU2Sy1SLCBFZDI1NTE5LCBFZERTQV0sIERlZmF1bHRzIHRvOiBFUzI1NktcbiAgICBbeDogc3RyaW5nXTogYW55XG59XG5cbmNsYXNzIFZlcmlmaWNhdGlvbk1ldGhvZCB7XG4gIGlkOiBzdHJpbmdcbiAgdHlwZTogc3RyaW5nXG4gIGNvbnRyb2xsZXI6IHN0cmluZ1xuICBwdWJsaWNLZXlCYXNlNTg_OiBzdHJpbmdcbiAgcHVibGljS2V5SndrPzogSnNvbldlYktleVxuICBwdWJsaWNLZXlIZXg_OiBzdHJpbmdcbiAgYmxvY2tjaGFpbkFjY291bnRJZD86IHN0cmluZ1xuICBldGhlcmV1bUFkZHJlc3M_OiBzdHJpbmdcbn1cblxuSnNvbldlYktleSA8fC0tIFZlcmlmaWNhdGlvbk1ldGhvZFxuY2xhc3MgSnNvbldlYktleSB7XG4gIGFsZz86IHN0cmluZ1xuICBjcnY_OiBzdHJpbmdcbiAgZT86IHN0cmluZ1xuICBleHQ_OiBib29sZWFuXG4gIGtleV9vcHM_OiBzdHJpbmdbXVxuICBraWQ_OiBzdHJpbmdcbiAga3R5OiBzdHJpbmdcbiAgbj86IHN0cmluZ1xuICB1c2U_OiBzdHJpbmdcbiAgeD86IHN0cmluZ1xuICB5Pzogc3RyaW5nXG59XG5cblxuY2xhc3MgRGlkSldUIHtcbiAgICA8PHNlcnZpY2U-PlxuICAgIGNyZWF0ZURpZEpXVChwYXlsb2FkOiBKV1RQYXlsb2FkLCBvcHRpb25zOiBKV1RPcHRpb25zLCBoZWFkZXI6IEpXVEpIZWFkZXIpIFByb21pc2Uoc3RyaW5nKVxuICAgIHZlcmlmeURpZEpXVChqd3Q6IHN0cmluZywgcmVzb2x2ZXI6IFJlc29sdmFibGUpIFByb21pc2UoYm9vbGVhbilcbn1cbkpXVFBheWxvYWQgPC0tIERpZEpXVFxuSldUT3B0aW9ucyA8LS0gRGlkSldUXG5KV1RIZWFkZXIgPC0tIERpZEpXVFxuUmVzb2x2YWJsZSA8LS0gRGlkSldUXG4iLCJtZXJtYWlkIjoie1xuICBcInRoZW1lXCI6IFwiZGVmYXVsdFwiXG59IiwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOmZhbHNlLCJ1cGRhdGVEaWFncmFtIjp0cnVlfQ) - - - ## Acknowledgements This library has been partially sponsored by [Gimly](https://www.gimly.io/) as part of the [NGI Ontochain](https://ontochain.ngi.eu/) project. NGI Ontochain has received funding from the European Union’s Horizon 2020 research and innovation programme under grant agreement No 957338 diff --git a/docs/services-class-diagram.md b/docs/services-class-diagram.md index 785b0df9..a2e028e8 100644 --- a/docs/services-class-diagram.md +++ b/docs/services-class-diagram.md @@ -54,7 +54,7 @@ RequestRegistrationOpts --|> RPRegistrationMetadataOpts class VerifyAuthenticationRequestOpts { <> - verification: InternalVerification | ExternalVerification; + verification: Verification nonce?: string; } @@ -110,7 +110,7 @@ AuthenticationResponseWithJWT --> AuthenticationResponseOpts class VerifyAuthenticationResponseOpts { <> - verification: InternalVerification | ExternalVerification; + verification: Verification nonce?: string; state?: string; audience: string; diff --git a/docs/services-class-diagram.svg b/docs/services-class-diagram.svg index 69466101..a4c8112b 100644 --- a/docs/services-class-diagram.svg +++ b/docs/services-class-diagram.svg @@ -1 +1 @@ -
«service»
RP
createAuthenticationRequest(opts?) Promise(AuthenticationRequestURI)
verifyAuthenticationResponseJwt(jwt: string, opts?) Promise(VerifiedAuthenticationResponseWithJWT)
«interface»
AuthenticationRequestURI
jwt?: string;
requestOpts: AuthenticationRequestOpts;
requestPayload: AuthenticationRequestPayload;
«interface»
VerifiedAuthenticationResponseWithJWT
payload: AuthenticationResponsePayload;
verifyOpts: VerifyAuthenticationResponseOpts;
«service»
AuthorizationRequest
createURI(opts: AuthenticationRequestOpts) Promise(AuthenticationRequestURI)
createJWT(opts: AuthenticationRequestOpts)
verifyJWT(jwt: string, opts: VerifyAuthenticationRequestOpts) Promise(VerifiedAuthenticationRequestWithJWT)
«interface»
AuthenticationResponse
createJWTFromRequestJWT(jwt: string, responseOpts: AuthenticationResponseOpts, verifyOpts: VerifyAuthenticationRequestOpts) Promise(AuthenticationResponseWithJWT)
verifyJWT(jwt: string, verifyOpts: VerifyAuthenticationResponseOpts) Promise(VerifiedAuthenticationResponseWithJWT)
«service»
OP
createAuthenticationResponse(jwtOrUri: string, opts?) Promise(AuthenticationResponseWithJWT)
verifyAuthenticationRequest(jwt: string, opts?) Promise(VerifiedAuthenticationRequestWithJWT)
«interface»
AuthenticationResponseWithJWT
jwt: string;
nonce: string;
state: string;
payload: AuthenticationResponsePayload;
verifyOpts?: VerifyAuthenticationRequestOpts;
responseOpts: AuthenticationResponseOpts;
«interface»
VerifiedAuthenticationRequestWithJWT
payload: AuthenticationRequestPayload;
verifyOpts: VerifyAuthenticationRequestOpts;
«interface»
AuthenticationRequestOpts
redirectUri: string;
requestBy: ObjectBy;
signature: InternalSignature | ExternalSignature | NoSignature;
responseMode?: ResponseMode;
claims?: ClaimPayload;
registration: RequestRegistrationOpts;
nonce?: string;
state?: string;
«enum»
ResponseMode
«interface»
RPRegistrationMetadataOpts
subjectIdentifiersSupported: SubjectIdentifierType[] | SubjectIdentifierType;
didMethodsSupported?: string[] | string;
credentialFormatsSupported: CredentialFormat[] | CredentialFormat;
«interface»
RequestRegistrationOpts
registrationBy: RegistrationType;
«interface»
VerifyAuthenticationRequestOpts
verification: InternalVerification | ExternalVerification;
nonce?: string;
AuthenticationRequestWithJWT
«interface»
AuthenticationResponseOpts
signature: InternalSignature | ExternalSignature;
nonce?: string;
state?: string;
registration: ResponseRegistrationOpts;
responseMode?: ResponseMode;
did: string;
vp?: VerifiablePresentation;
expiresIn?: number;
«interface»
VerifyAuthenticationResponseOpts
verification: InternalVerification | ExternalVerification;
nonce?: string;
state?: string;
audience: string;
AuthenticationResponsePayload
«interface»
UriResponse
responseMode?: ResponseMode;
bodyEncoded?: string;
«interface»
SIOPURI
encodedUri: string;
encodingFormat: UrlEncodingFormat;
«enum»
UrlEncodingFormat
«interface»
AuthenticationRequestPayload
scope: Scope;
response_type: ResponseType;
client_id: string;
redirect_uri: string;
response_mode: ResponseMode;
request: string;
request_uri: string;
state?: string;
nonce: string;
did_doc?: DIDDocument;
claims?: RequestClaims;
JWTPayload
iss?: string
sub?: string
aud?: string | string[]
iat?: number
nbf?: number
exp?: number
rexp?: number
[x: string]: any
«interface»
VerifiedJWT
payload: Partial<JWTPayload>;
didResolutionResult: DIDResolutionResult;
issuer: string;
signer: VerificationMethod;
jwt: string;
+
«service»
RP
createAuthenticationRequest(opts?) Promise(AuthenticationRequestURI)
verifyAuthenticationResponseJwt(jwt: string, opts?) Promise(VerifiedAuthenticationResponseWithJWT)
«interface»
AuthenticationRequestURI
jwt?: string;
requestOpts: AuthenticationRequestOpts;
requestPayload: AuthenticationRequestPayload;
«interface»
VerifiedAuthenticationResponseWithJWT
payload: AuthenticationResponsePayload;
verifyOpts: VerifyAuthenticationResponseOpts;
«service»
AuthorizationRequest
createURI(opts: AuthenticationRequestOpts) Promise(AuthenticationRequestURI)
createJWT(opts: AuthenticationRequestOpts)
verifyJWT(jwt: string, opts: VerifyAuthenticationRequestOpts) Promise(VerifiedAuthenticationRequestWithJWT)
«interface»
AuthenticationResponse
createJWTFromRequestJWT(jwt: string, responseOpts: AuthenticationResponseOpts, verifyOpts: VerifyAuthenticationRequestOpts) Promise(AuthenticationResponseWithJWT)
verifyJWT(jwt: string, verifyOpts: VerifyAuthenticationResponseOpts) Promise(VerifiedAuthenticationResponseWithJWT)
«service»
OP
createAuthenticationResponse(jwtOrUri: string, opts?) Promise(AuthenticationResponseWithJWT)
verifyAuthenticationRequest(jwt: string, opts?) Promise(VerifiedAuthenticationRequestWithJWT)
«interface»
AuthenticationResponseWithJWT
jwt: string;
nonce: string;
state: string;
payload: AuthenticationResponsePayload;
verifyOpts?: VerifyAuthenticationRequestOpts;
responseOpts: AuthenticationResponseOpts;
«interface»
VerifiedAuthenticationRequestWithJWT
payload: AuthenticationRequestPayload;
verifyOpts: VerifyAuthenticationRequestOpts;
«interface»
AuthenticationRequestOpts
redirectUri: string;
requestBy: ObjectBy;
signature: InternalSignature | ExternalSignature | NoSignature;
responseMode?: ResponseMode;
claims?: ClaimPayload;
registration: RequestRegistrationOpts;
nonce?: string;
state?: string;
«enum»
ResponseMode
«interface»
RPRegistrationMetadataOpts
subjectIdentifiersSupported: SubjectIdentifierType[] | SubjectIdentifierType;
didMethodsSupported?: string[] | string;
credentialFormatsSupported: CredentialFormat[] | CredentialFormat;
«interface»
RequestRegistrationOpts
registrationBy: RegistrationType;
«interface»
VerifyAuthenticationRequestOpts
verification: Verification;
nonce?: string;
AuthenticationRequestWithJWT
«interface»
AuthenticationResponseOpts
signature: InternalSignature | ExternalSignature;
nonce?: string;
state?: string;
registration: ResponseRegistrationOpts;
responseMode?: ResponseMode;
did: string;
vp?: VerifiablePresentation;
expiresIn?: number;
«interface»
VerifyAuthenticationResponseOpts
verification: Verification;
nonce?: string;
state?: string;
audience: string;
AuthenticationResponsePayload
«interface»
UriResponse
responseMode?: ResponseMode;
bodyEncoded?: string;
«interface»
SIOPURI
encodedUri: string;
encodingFormat: UrlEncodingFormat;
«enum»
UrlEncodingFormat
«interface»
AuthenticationRequestPayload
scope: Scope;
response_type: ResponseType;
client_id: string;
redirect_uri: string;
response_mode: ResponseMode;
request: string;
request_uri: string;
state?: string;
nonce: string;
did_doc?: DIDDocument;
claims?: RequestClaims;
JWTPayload
iss?: string
sub?: string
aud?: string | string[]
iat?: number
nbf?: number
exp?: number
rexp?: number
[x: string]: any
«interface»
VerifiedJWT
payload: Partial<JWTPayload>;
didResolutionResult: DIDResolutionResult;
issuer: string;
signer: VerificationMethod;
jwt: string;
diff --git a/generator/schemaGenerator.ts b/generator/schemaGenerator.ts index 27ba28ad..246fcac4 100644 --- a/generator/schemaGenerator.ts +++ b/generator/schemaGenerator.ts @@ -11,7 +11,7 @@ import { FunctionType, MutableTypeFormatter, SchemaGenerator, - SubTypeFormatter + SubTypeFormatter, } from 'ts-json-schema-generator'; import { Schema } from 'ts-json-schema-generator/dist/src/Schema/Schema'; @@ -26,9 +26,9 @@ class CustomTypeFormatter implements SubTypeFormatter { properties: { isFunction: { type: 'boolean', - const: true - } - } + const: true, + }, + }, }; } @@ -49,59 +49,63 @@ function writeSchema(config: any): Schema { schemaString = correctSchema(schemaString); fs.writeFile(config.outputPath, `export const ${config.schemaId}Obj = ${schemaString};`, (err) => { - if (err) throw err; + if (err) { + throw err; + } }); return schema; } function generateValidationCode(schemas: Schema[]) { - const ajv = new Ajv({ schemas, code: { source: true, lines: true, esm: false }, allowUnionTypes: true, strict: false }); + const ajv = new Ajv({ schemas, code: { source: true, lines: true, esm: false }, allowUnionTypes: true, strict: false }); const moduleCode = standaloneCode(ajv); fs.writeFileSync(path.join(__dirname, '../src/schemas/validation/schemaValidation.js'), moduleCode); } function correctSchema(schemaString: string) { - return schemaString.replace('"SuppliedSignature": {\n' + - ' "type": "object",\n' + - ' "properties": {\n' + - ' "withSignature": {\n' + - ' "properties": {\n' + - ' "isFunction": {\n' + - ' "type": "boolean",\n' + - ' "const": true\n' + - ' }\n' + - ' }\n' + - ' },\n' + - ' "did": {\n' + - ' "type": "string"\n' + - ' },\n' + - ' "kid": {\n' + - ' "type": "string"\n' + - ' }\n' + - ' },\n' + - ' "required": [\n' + - ' "withSignature",\n' + - ' "did",\n' + - ' "kid"\n' + - ' ],\n' + - ' "additionalProperties": false\n' + - ' },', + return schemaString.replace( '"SuppliedSignature": {\n' + - ' "type": "object",\n' + - ' "properties": {\n' + - ' "did": {\n' + - ' "type": "string"\n' + - ' },\n' + - ' "kid": {\n' + - ' "type": "string"\n' + - ' }\n' + - ' },\n' + - ' "required": [\n' + - ' "did",\n' + - ' "kid"\n' + - ' ],\n' + - ' "additionalProperties": true\n' + - ' },'); + ' "type": "object",\n' + + ' "properties": {\n' + + ' "withSignature": {\n' + + ' "properties": {\n' + + ' "isFunction": {\n' + + ' "type": "boolean",\n' + + ' "const": true\n' + + ' }\n' + + ' }\n' + + ' },\n' + + ' "did": {\n' + + ' "type": "string"\n' + + ' },\n' + + ' "kid": {\n' + + ' "type": "string"\n' + + ' }\n' + + ' },\n' + + ' "required": [\n' + + ' "withSignature",\n' + + ' "did",\n' + + ' "kid"\n' + + ' ],\n' + + ' "additionalProperties": false\n' + + ' },', + '"SuppliedSignature": {\n' + + ' "type": "object",\n' + + ' "properties": {\n' + + ' "did": {\n' + + ' "type": "string"\n' + + ' },\n' + + ' "kid": {\n' + + ' "type": "string"\n' + + ' }\n' + + ' },\n' + + ' "required": [\n' + + ' "did",\n' + + ' "kid"\n' + + ' ],\n' + + ' "additionalProperties": true\n' + + ' },', + ); } /* const requestOptsConf = { @@ -114,7 +118,6 @@ const requestOptsConf = { skipTypeCheck: true };*/ - const responseOptsConf = { path: '../src/authorization-response/types.ts', tsconfig: 'tsconfig.json', @@ -122,7 +125,7 @@ const responseOptsConf = { schemaId: 'AuthorizationResponseOptsSchema', outputPath: 'src/schemas/AuthorizationResponseOpts.schema.ts', // outputConstName: 'AuthorizationResponseOptsSchema', - skipTypeCheck: true + skipTypeCheck: true, }; const rPRegistrationMetadataPayload = { @@ -132,7 +135,7 @@ const rPRegistrationMetadataPayload = { schemaId: 'RPRegistrationMetadataPayloadSchema', outputPath: 'src/schemas/RPRegistrationMetadataPayload.schema.ts', // outputConstName: 'RPRegistrationMetadataPayloadSchema', - skipTypeCheck: true + skipTypeCheck: true, }; const discoveryMetadataPayload = { @@ -142,7 +145,7 @@ const discoveryMetadataPayload = { schemaId: 'DiscoveryMetadataPayloadSchema', outputPath: 'src/schemas/DiscoveryMetadataPayload.schema.ts', // outputConstName: 'DiscoveryMetadataPayloadSchema', - skipTypeCheck: true + skipTypeCheck: true, }; const authorizationRequestPayloadVID1 = { @@ -152,7 +155,7 @@ const authorizationRequestPayloadVID1 = { schemaId: 'AuthorizationRequestPayloadVID1Schema', outputPath: 'src/schemas/AuthorizationRequestPayloadVID1.schema.ts', // outputConstName: 'AuthorizationRequestPayloadSchemaVID1', - skipTypeCheck: true + skipTypeCheck: true, }; const authorizationRequestPayloadVD11 = { @@ -162,7 +165,7 @@ const authorizationRequestPayloadVD11 = { schemaId: 'AuthorizationRequestPayloadVD11Schema', outputPath: 'src/schemas/AuthorizationRequestPayloadVD11.schema.ts', // outputConstName: 'AuthorizationRequestPayloadSchemaVD11', - skipTypeCheck: true + skipTypeCheck: true, }; const authorizationRequestPayloadVD12OID4VPD18 = { @@ -172,7 +175,7 @@ const authorizationRequestPayloadVD12OID4VPD18 = { schemaId: 'AuthorizationRequestPayloadVD12OID4VPD18Schema', outputPath: 'src/schemas/AuthorizationRequestPayloadVD12OID4VPD18.schema.ts', // outputConstName: 'AuthorizationRequestPayloadSchemaVD11', - skipTypeCheck: true + skipTypeCheck: true, }; let schemas: Schema[] = [ @@ -182,7 +185,7 @@ let schemas: Schema[] = [ // writeSchema(requestOptsConf), writeSchema(responseOptsConf), writeSchema(rPRegistrationMetadataPayload), - writeSchema(discoveryMetadataPayload) + writeSchema(discoveryMetadataPayload), ]; generateValidationCode(schemas); diff --git a/package.json b/package.json index 181d20d5..592b7465 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", diff --git a/src/authorization-request/AuthorizationRequest.ts b/src/authorization-request/AuthorizationRequest.ts index 4e9b80c0..23ca6445 100644 --- a/src/authorization-request/AuthorizationRequest.ts +++ b/src/authorization-request/AuthorizationRequest.ts @@ -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, @@ -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'; @@ -117,30 +115,19 @@ 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) { + 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); } - requestObjectPayload = verifiedJwt.payload as RequestObjectPayload; if (this.hasRequestObject() && !this.payload.request_uri) { // Put back the request object as that won't be present yet @@ -186,14 +173,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, diff --git a/src/authorization-request/Opts.ts b/src/authorization-request/Opts.ts index a534e598..4b3deb1b 100644 --- a/src/authorization-request/Opts.ts +++ b/src/authorization-request/Opts.ts @@ -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) { @@ -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, diff --git a/src/authorization-request/Payload.ts b/src/authorization-request/Payload.ts index a9c29dcb..85d01b4b 100644 --- a/src/authorization-request/Payload.ts +++ b/src/authorization-request/Payload.ts @@ -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)) { @@ -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 => { - 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); - } - } -}; diff --git a/src/authorization-request/URI.ts b/src/authorization-request/URI.ts index f2d67dc8..b0e29dc4 100644 --- a/src/authorization-request/URI.ts +++ b/src/authorization-request/URI.ts @@ -1,7 +1,6 @@ -import { decodeJWT } from 'did-jwt'; - import { PresentationExchange } from '../authorization-response/PresentationExchange'; import { decodeUriAsJson, encodeJsonAsURI, fetchByReferenceOrUseByValue } from '../helpers'; +import { parseJWT } from '../helpers/jwtUtils'; import { assertValidRequestObjectPayload, RequestObject } from '../request-object'; import { AuthorizationRequestPayload, @@ -43,7 +42,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 ? (parseJWT(requestObjectJwt).payload as RequestObjectPayload) : undefined; if (requestObjectPayload) { assertValidRequestObjectPayload(requestObjectPayload); } @@ -156,10 +155,11 @@ export class URI implements AuthorizationRequestURI { : typeof authorizationRequestPayload === 'string' ? authorizationRequestPayload : 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 ? (parseJWT(requestObjectJwt).payload as RequestObjectPayload) : undefined; if (requestObjectPayload) { // Only used to validate if the request object contains presentation definition(s) diff --git a/src/authorization-request/types.ts b/src/authorization-request/types.ts index 46b6e3fb..a4a8ce66 100644 --- a/src/authorization-request/types.ts +++ b/src/authorization-request/types.ts @@ -3,10 +3,9 @@ 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, @@ -14,7 +13,9 @@ import { SigningAlgo, SubjectType, SupportedVersion, + Verification, } from '../types'; +import { VerifyJwtCallback } from '../types/JwtVerifier'; export interface ClaimPayloadOptsVID1 extends ClaimPayloadCommonOpts { id_token?: IdTokenClaimPayload; @@ -34,6 +35,7 @@ export interface RequestObjectPayloadOpts { 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 @@ -58,7 +60,6 @@ interface AuthorizationRequestCommonOpts { 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; requestObject: RequestObjectOpts; - uriScheme?: Schema | string; // Use a custom scheme for the URI. By default openid:// will be used } @@ -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; } diff --git a/src/authorization-response/AuthorizationResponse.ts b/src/authorization-response/AuthorizationResponse.ts index b6cb6e27..79ff28b2 100644 --- a/src/authorization-response/AuthorizationResponse.ts +++ b/src/authorization-response/AuthorizationResponse.ts @@ -48,7 +48,7 @@ export class AuthorizationResponse { verifyOpts: VerifyAuthorizationRequestOpts, ): Promise { assertValidVerifyAuthorizationRequestOpts(verifyOpts); - await assertValidResponseOpts(responseOpts); + assertValidResponseOpts(responseOpts); if (!requestObject || !requestObject.startsWith('ey')) { throw new Error(SIOPErrors.NO_JWT); } @@ -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({ @@ -79,7 +80,7 @@ export class AuthorizationResponse { responseOpts: AuthorizationResponseOpts, verifyOpts: VerifyAuthorizationRequestOpts, ): Promise { - await assertValidResponseOpts(responseOpts); + assertValidResponseOpts(responseOpts); if (!authorizationRequest) { throw new Error(SIOPErrors.NO_REQUEST); } @@ -92,7 +93,7 @@ export class AuthorizationResponse { responseOpts: AuthorizationResponseOpts, verifyOpts: VerifyAuthorizationRequestOpts, ): Promise { - await assertValidResponseOpts(responseOpts); + assertValidResponseOpts(responseOpts); if (!verifiedAuthorizationRequest) { throw new Error(SIOPErrors.NO_REQUEST); } diff --git a/src/authorization-response/OpenID4VP.ts b/src/authorization-response/OpenID4VP.ts index fb4e6028..e7e13377 100644 --- a/src/authorization-response/OpenID4VP.ts +++ b/src/authorization-response/OpenID4VP.ts @@ -8,10 +8,10 @@ import { W3CVerifiablePresentation, WrappedVerifiablePresentation, } from '@sphereon/ssi-types'; -import { decodeJWT } from 'did-jwt'; import { AuthorizationRequest } from '../authorization-request'; import { verifyRevocation } from '../helpers'; +import { parseJWT } from '../helpers/jwtUtils'; import { AuthorizationResponsePayload, IDTokenPayload, @@ -39,7 +39,9 @@ function extractNonceFromWrappedVerifiablePresentation(wrappedVp: WrappedVerifia // If it doesn't end with ~, it contains a kbJwt if (!wrappedVp.presentation.compactSdJwtVc.endsWith('~')) { const kbJwt = wrappedVp.presentation.compactSdJwtVc.split('~').pop(); - const { payload } = decodeJWT(kbJwt); + + const { payload } = parseJWT(kbJwt); + return payload.nonce; } diff --git a/src/authorization-response/Opts.ts b/src/authorization-response/Opts.ts index b0f732a8..753da683 100644 --- a/src/authorization-response/Opts.ts +++ b/src/authorization-response/Opts.ts @@ -1,17 +1,15 @@ -import { isExternalSignature, isExternalVerification, isInternalSignature, isInternalVerification, isSuppliedSignature, SIOPErrors } from '../types'; +import { SIOPErrors } from '../types'; import { AuthorizationResponseOpts, VerifyAuthorizationResponseOpts } from './types'; -export const assertValidResponseOpts = async (opts: AuthorizationResponseOpts): Promise => { - if (!opts?.signature) { +export const assertValidResponseOpts = (opts: AuthorizationResponseOpts) => { + if (!opts?.createJwtCallback) { throw new Error(SIOPErrors.BAD_PARAMS); - } else if (!(isInternalSignature(opts.signature) || isExternalSignature(opts.signature) || isSuppliedSignature(opts.signature))) { - throw new Error(SIOPErrors.SIGNATURE_OBJECT_TYPE_NOT_SET); } }; export const assertValidVerifyOpts = (opts: VerifyAuthorizationResponseOpts) => { - if (!opts?.verification || (!isExternalVerification(opts.verification) && !isInternalVerification(opts.verification))) { + if (!opts?.verification || !opts.verifyJwtCallback) { throw new Error(SIOPErrors.VERIFY_BAD_PARAMS); } }; diff --git a/src/authorization-response/Payload.ts b/src/authorization-response/Payload.ts index 2bae4670..3635e873 100644 --- a/src/authorization-response/Payload.ts +++ b/src/authorization-response/Payload.ts @@ -31,7 +31,8 @@ export const createResponsePayload = async ( // vp tokens await putPresentationSubmissionInLocation(authorizationRequest, responsePayload, responseOpts, idTokenPayload); if (idTokenPayload) { - responsePayload.id_token = await IDToken.fromIDTokenPayload(idTokenPayload, responseOpts).then((id) => id.jwt()); + const idToken = await IDToken.fromIDTokenPayload(idTokenPayload, responseOpts); + responsePayload.id_token = await idToken.jwt(responseOpts.jwtIssuer); } return responsePayload; diff --git a/src/authorization-response/types.ts b/src/authorization-response/types.ts index 9dfc37ee..f94a8046 100644 --- a/src/authorization-response/types.ts +++ b/src/authorization-response/types.ts @@ -2,20 +2,9 @@ import { IPresentationDefinition, PresentationSignCallBackParams } from '@sphere import { Format } from '@sphereon/pex-models'; import { CompactSdJwtVc, Hasher, PresentationSubmission, W3CVerifiablePresentation } from '@sphereon/ssi-types'; -import { - CheckLinkedDomain, - ExternalSignature, - ExternalVerification, - InternalSignature, - InternalVerification, - NoSignature, - ResponseMode, - ResponseRegistrationOpts, - ResponseURIType, - SuppliedSignature, - SupportedVersion, - VerifiablePresentationWithFormat, -} from '../types'; +import { ResponseMode, ResponseRegistrationOpts, ResponseURIType, SupportedVersion, VerifiablePresentationWithFormat, Verification } from '../types'; +import { CreateJwtCallback, JwtIssuer } from '../types/JwtIssuer'; +import { VerifyJwtCallback } from '../types/JwtVerifier'; import { AuthorizationResponse } from './AuthorizationResponse'; @@ -24,12 +13,10 @@ export interface AuthorizationResponseOpts { responseURI?: string; // This is either the redirect URI or response URI. See also responseURIType. response URI is used when response_mode is `direct_post` responseURIType?: ResponseURIType; registration?: ResponseRegistrationOpts; - checkLinkedDomain?: CheckLinkedDomain; - version?: SupportedVersion; audience?: string; - - signature?: InternalSignature | ExternalSignature | SuppliedSignature | NoSignature; + createJwtCallback: CreateJwtCallback; + jwtIssuer?: JwtIssuer; responseMode?: ResponseMode; // did: string; expiresIn?: number; @@ -98,12 +85,11 @@ export type PresentationSignCallback = (args: PresentationSignCallBackParams) => export interface VerifyAuthorizationResponseOpts { correlationId: string; - verification: InternalVerification | ExternalVerification; + verification: Verification; + verifyJwtCallback: VerifyJwtCallback; hasher?: Hasher; - // didDocument?: DIDDocument; // If not provided the DID document will be resolved from the request nonce?: string; // To verify the response against the supplied nonce state?: string; // To verify the response against the supplied state - presentationDefinitions?: PresentationDefinitionWithLocation | PresentationDefinitionWithLocation[]; // The presentation definitions to match against VPs in the response audience?: string; // The audience/redirect_uri restrictToFormats?: Format; // Further restrict to certain VC formats, not expressed in the presentation definition diff --git a/src/did/DIDResolution.ts b/src/did/DIDResolution.ts deleted file mode 100644 index b3b5f445..00000000 --- a/src/did/DIDResolution.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { getUniResolver, UniResolver } from '@sphereon/did-uni-client'; -import { DIDResolutionOptions, DIDResolutionResult, ParsedDID, Resolvable, Resolver } from 'did-resolver'; - -import { DIDDocument, ResolveOpts, SIOPErrors, SubjectIdentifierType, SubjectSyntaxTypesSupportedValues } from '../types'; - -import { getMethodFromDid, toSIOPRegistrationDidMethod } from './index'; - -export function getResolver(opts: ResolveOpts): Resolvable { - if (opts && typeof opts.resolver === 'object') { - return opts.resolver; - } - if (!opts || !opts.subjectSyntaxTypesSupported) { - if (opts?.noUniversalResolverFallback) { - throw Error(`No subject syntax types nor did methods configured for DID resolution, but fallback to universal resolver has been disabled`); - } - console.log( - `Falling back to universal resolver as no resolve opts have been provided, or no subject syntax types supported are provided. It is wise to fix this`, - ); - return new UniResolver(); - } - - const uniResolvers: { - [p: string]: (did: string, _parsed: ParsedDID, _didResolver: Resolver, _options: DIDResolutionOptions) => Promise; - }[] = []; - if (opts.subjectSyntaxTypesSupported.indexOf(SubjectIdentifierType.DID) === -1) { - const specificDidMethods = opts.subjectSyntaxTypesSupported.filter((sst) => sst.includes('did:')); - if (!specificDidMethods.length) { - throw new Error(SIOPErrors.NO_DID_METHOD_FOUND); - } - for (const didMethod of specificDidMethods) { - const uniResolver = getUniResolver(getMethodFromDid(didMethod), { resolveUrl: opts.resolveUrl }); - uniResolvers.push(uniResolver); - } - return new Resolver(...uniResolvers); - } else { - if (opts?.noUniversalResolverFallback) { - throw Error(`No subject syntax types nor did methods configured for DID resolution, but fallback to universal resolver has been disabled`); - } - console.log( - `Falling back to universal resolver as no resolve opts have been provided, or no subject syntax types supported are provided. It is wise to fix this`, - ); - return new UniResolver(); - } -} - -/** - * This method returns a resolver object in OP/RP - * If the user of this library, configures OP/RP to have a customResolver, we will use that - * If the user of this library configures OP/RP to use a custom resolver for any specific did method, we will use that - * and in the end for the rest of the did methods, configured either with calling `addDidMethod` upon building OP/RP - * (without any resolver configuration) or declaring in the subject_syntax_types_supported of the registration object - * we will use universal resolver from Sphereon's DID Universal Resolver library - * @param customResolver - * @param subjectSyntaxTypesSupported - * @param resolverMap - */ -export function getResolverUnion( - customResolver: Resolvable, - subjectSyntaxTypesSupported: string[] | string, - resolverMap: Map, -): Resolvable { - if (customResolver) { - return customResolver; - } - const fallbackResolver: Resolvable = customResolver ? customResolver : new UniResolver(); - const uniResolvers: { - [p: string]: (did: string, _parsed: ParsedDID, _didResolver: Resolver, _options: DIDResolutionOptions) => Promise; - }[] = []; - const subjectTypes: string[] = []; - if (subjectSyntaxTypesSupported) { - typeof subjectSyntaxTypesSupported === 'string' - ? subjectTypes.push(subjectSyntaxTypesSupported) - : subjectTypes.push(...subjectSyntaxTypesSupported); - } - if (subjectTypes.indexOf(SubjectSyntaxTypesSupportedValues.DID.valueOf()) !== -1) { - return customResolver ? customResolver : new UniResolver(); - } - const specificDidMethods = subjectTypes.filter((sst) => !!sst && sst.startsWith('did:')); - specificDidMethods.forEach((dm) => { - let methodResolver; - if (!resolverMap.has(dm) || resolverMap.get(dm) === null) { - methodResolver = getUniResolver(getMethodFromDid(dm)); - } else { - methodResolver = resolverMap.get(dm); - } - uniResolvers.push(methodResolver); - }); - return subjectTypes.indexOf(SubjectSyntaxTypesSupportedValues.DID.valueOf()) !== -1 - ? new Resolver(...{ fallbackResolver, ...uniResolvers }) - : new Resolver(...uniResolvers); -} - -export function mergeAllDidMethods(subjectSyntaxTypesSupported: string | string[], resolvers: Map): string[] { - if (!Array.isArray(subjectSyntaxTypesSupported)) { - subjectSyntaxTypesSupported = [subjectSyntaxTypesSupported]; - } - const unionSubjectSyntaxTypes = new Set(); - subjectSyntaxTypesSupported.forEach((sst) => unionSubjectSyntaxTypes.add(sst)); - resolvers.forEach((_, didMethod) => unionSubjectSyntaxTypes.add(toSIOPRegistrationDidMethod(didMethod))); - return Array.from(unionSubjectSyntaxTypes) as string[]; -} - -export async function resolveDidDocument(did: string, opts?: ResolveOpts): Promise { - // todo: The accept is only there because did:key used by Veramo requires it. According to the spec it is optional. It should not hurt, but let's test - const result = await getResolver({ ...opts }).resolve(did, { accept: 'application/did+ld+json' }); - if (result?.didResolutionMetadata?.error) { - throw Error(result.didResolutionMetadata.error); - } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - if (!result.didDocument && result.id) { - // todo: This looks like a bug. It seems that sometimes we get back a DID document directly instead of a did resolution results - return result as unknown as DIDDocument; - } - return result.didDocument; -} diff --git a/src/did/DidJWT.ts b/src/did/DidJWT.ts deleted file mode 100644 index 76e203bd..00000000 --- a/src/did/DidJWT.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { - createJWT, - decodeJWT, - EdDSASigner, - ES256KSigner, - ES256Signer, - hexToBytes, - JWTHeader, - JWTOptions, - JWTPayload, - JWTVerifyOptions, - Signer, - verifyJWT, -} from 'did-jwt'; -import { JWTDecoded } from 'did-jwt/lib/JWT'; -import { Resolvable } from 'did-resolver'; - -import { ClaimPayloadCommonOpts } from '../authorization-request'; -import { AuthorizationResponseOpts } from '../authorization-response'; -import { post } from '../helpers'; -import { RequestObjectOpts } from '../request-object'; -import { - DEFAULT_EXPIRATION_TIME, - IDTokenPayload, - isExternalSignature, - isInternalSignature, - isSuppliedSignature, - RequestObjectPayload, - ResponseIss, - SignatureResponse, - SigningAlgo, - SIOPErrors, - SIOPResonse, - VerifiedJWT, -} from '../types'; - -/** - * Verifies given JWT. If the JWT is valid, the promise returns an object including the JWT, the payload of the JWT, - * and the did doc of the issuer of the JWT. - * - * @example - * verifyDidJWT('did:key:example', resolver, {audience: '5A8bRWU3F7j3REx3vkJ...', callbackUrl: 'https://...'}).then(obj => { - * const did = obj.did // DIDres of signer - * const payload = obj.payload - * const doc = obj.doc // DIDres Document of signer - * const JWT = obj.JWT // JWT - * const signerKeyId = obj.signerKeyId // ID of key in DIDres document that signed JWT - * ... - * }) - * - * @param {String} jwt a JSON Web Token to verify - * @param {Resolvable} resolver - * @param {JWTVerifyOptions} [options] Options - * @param {String} options.audience DID of the recipient of the JWT - * @param {String} options.callbackUrl callback url in JWT - * @return {Promise} a promise which resolves with a response object or rejects with an error - */ -export async function verifyDidJWT(jwt: string, resolver: Resolvable, options: JWTVerifyOptions): Promise { - return verifyJWT(jwt, { ...options, resolver }); -} - -/** - * Creates a signed JWT given an address which becomes the issuer, a signer function, and a payload for which the withSignature is over. - * - * @example - * const signer = ES256KSigner(process.env.PRIVATE_KEY) - * createJWT({address: '5A8bRWU3F7j3REx3vkJ...', signer}, {key1: 'value', key2: ..., ... }).then(JWT => { - * ... - * }) - * - * @param {Object} payload payload object - * @param {Object} [options] an unsigned credential object - * @param {String} options.issuer The DID of the issuer (signer) of JWT - * @param {Signer} options.signer a `Signer` function, Please see `ES256KSigner` or `EdDSASigner` - * @param {boolean} options.canonicalize optional flag to canonicalize header and payload before signing - * @param {Object} header optional object to specify or customize the JWT header - * @return {Promise} a promise which resolves with a signed JSON Web Token or rejects with an error - */ -export async function createDidJWT( - payload: Partial, - { issuer, signer, expiresIn, canonicalize }: JWTOptions, - header: Partial, -): Promise { - return createJWT(payload, { issuer, signer, expiresIn, canonicalize }, header); -} - -export async function signIDTokenPayload(payload: IDTokenPayload, opts: AuthorizationResponseOpts) { - if (!payload.sub) { - payload.sub = opts.signature.did; - } - - const issuer = opts.registration.issuer || payload.iss; - if (!issuer || !(issuer.includes(ResponseIss.SELF_ISSUED_V2) || issuer === payload.sub)) { - throw new Error(SIOPErrors.NO_SELFISSUED_ISS); - } - if (!payload.iss) { - payload.iss = issuer; - } - - if (isInternalSignature(opts.signature)) { - return signDidJwtInternal(payload, issuer, opts.signature.hexPrivateKey, opts.signature.alg, opts.signature.kid, opts.signature.customJwtSigner); - } else if (isExternalSignature(opts.signature)) { - return signDidJwtExternal(payload, opts.signature.signatureUri, opts.signature.authZToken, opts.signature.alg, opts.signature.kid); - } else if (isSuppliedSignature(opts.signature)) { - return signDidJwtSupplied(payload, issuer, opts.signature.signature, opts.signature.alg, opts.signature.kid); - } else { - throw new Error(SIOPErrors.BAD_SIGNATURE_PARAMS); - } -} - -export async function signRequestObjectPayload(payload: RequestObjectPayload, opts: RequestObjectOpts) { - let issuer = payload.iss; - if (!issuer) { - issuer = opts.signature.did; - } - if (!issuer) { - throw Error('No issuer supplied to sign the JWT'); - } - if (!payload.iss) { - payload.iss = issuer; - } - if (!payload.sub) { - payload.sub = opts.signature.did; - } - if (isInternalSignature(opts.signature)) { - return signDidJwtInternal(payload, issuer, opts.signature.hexPrivateKey, opts.signature.alg, opts.signature.kid, opts.signature.customJwtSigner); - } else if (isExternalSignature(opts.signature)) { - return signDidJwtExternal(payload, opts.signature.signatureUri, opts.signature.authZToken, opts.signature.alg, opts.signature.kid); - } else if (isSuppliedSignature(opts.signature)) { - return signDidJwtSupplied(payload, issuer, opts.signature.signature, opts.signature.alg, opts.signature.kid); - } else { - throw new Error(SIOPErrors.BAD_SIGNATURE_PARAMS); - } -} - -async function signDidJwtInternal( - payload: IDTokenPayload | RequestObjectPayload, - issuer: string, - hexPrivateKey: string, - alg: SigningAlgo, - kid: string, - customJwtSigner?: Signer, -): Promise { - const signer = determineSigner(alg, hexPrivateKey, customJwtSigner); - const header = { - alg, - kid, - }; - const options = { - issuer, - signer, - expiresIn: DEFAULT_EXPIRATION_TIME, - }; - - return await createDidJWT({ ...payload }, options, header); -} - -async function signDidJwtExternal( - payload: IDTokenPayload | RequestObjectPayload, - signatureUri: string, - authZToken: string, - alg: SigningAlgo, - kid?: string, -): Promise { - const body = { - issuer: payload.iss && payload.iss.includes('did:') ? payload.iss : payload.sub, - payload, - expiresIn: DEFAULT_EXPIRATION_TIME, - alg, - selfIssued: payload.iss.includes(ResponseIss.SELF_ISSUED_V2) ? payload.iss : undefined, - kid, - }; - - const response: SIOPResonse = await post(signatureUri, JSON.stringify(body), { bearerToken: authZToken }); - return response.successBody.jws; -} - -async function signDidJwtSupplied( - payload: IDTokenPayload | RequestObjectPayload, - issuer: string, - signer: Signer, - alg: SigningAlgo, - kid: string, -): Promise { - const header = { - alg, - kid, - }; - const options = { - issuer, - signer, - expiresIn: DEFAULT_EXPIRATION_TIME, - }; - - return await createDidJWT({ ...payload }, options, header); -} - -const determineSigner = (alg: SigningAlgo, hexPrivateKey?: string, customSigner?: Signer): Signer => { - if (customSigner) { - return customSigner; - } else if (!hexPrivateKey) { - throw new Error('no private key provided'); - } - const privateKey = hexToBytes(hexPrivateKey.replace('0x', '')); - switch (alg) { - case SigningAlgo.EDDSA: - return EdDSASigner(privateKey); - case SigningAlgo.ES256: - return ES256Signer(privateKey); - case SigningAlgo.ES256K: - return ES256KSigner(privateKey); - case SigningAlgo.PS256: - throw Error('PS256 is not supported yet. Please provide a custom signer'); - case SigningAlgo.RS256: - throw Error('RS256 is not supported yet. Please provide a custom signer'); - } -}; - -export function getAudience(jwt: string) { - const { payload } = decodeJWT(jwt); - if (!payload) { - throw new Error(SIOPErrors.NO_AUDIENCE); - } else if (!payload.aud) { - return undefined; - } else if (Array.isArray(payload.aud)) { - throw new Error(SIOPErrors.INVALID_AUDIENCE); - } - - return payload.aud; -} - -//TODO To enable automatic registration, it cannot be a did, but HTTPS URL -function assertIssSelfIssuedOrDid(payload: JWTPayload) { - if (!payload.sub || !payload.sub.startsWith('did:') || !payload.iss || !isIssSelfIssued(payload)) { - throw new Error(SIOPErrors.NO_ISS_DID); - } -} - -export function getSubDidFromPayload(payload: JWTPayload, header?: JWTHeader): string { - assertIssSelfIssuedOrDid(payload); - - if (isIssSelfIssued(payload)) { - let did; - if (payload.sub && payload.sub.startsWith('did:')) { - did = payload.sub; - } - if (!did && header && header.kid && header.kid.startsWith('did:')) { - did = header.kid.split('#')[0]; - } - if (did) { - return did; - } - } - return payload.sub; -} - -export function isIssSelfIssued(payload: JWTPayload): boolean { - return payload.iss.includes(ResponseIss.SELF_ISSUED_V1) || payload.iss.includes(ResponseIss.SELF_ISSUED_V2) || payload.iss === payload.sub; -} - -export function getIssuerDidFromJWT(jwt: string): string { - const { payload } = parseJWT(jwt); - return getSubDidFromPayload(payload); -} - -export function parseJWT(jwt: string): JWTDecoded { - const decodedJWT = decodeJWT(jwt); - const { payload, header } = decodedJWT; - if (!payload || !header) { - throw new Error(SIOPErrors.NO_JWT); - } - return decodedJWT; -} - -export function getMethodFromDid(did: string): string { - if (!did) { - throw new Error(SIOPErrors.BAD_PARAMS); - } - const split = did.split(':'); - if (split.length == 1 && did.length > 0) { - return did; - } else if (!did.startsWith('did:') || split.length < 2) { - throw new Error(SIOPErrors.BAD_PARAMS); - } - - return split[1]; -} - -export function getNetworkFromDid(did: string): string { - const network = 'mainnet'; // default - const split = did.split(':'); - if (!did.startsWith('did:') || split.length < 2) { - throw new Error(SIOPErrors.BAD_PARAMS); - } - - if (split.length === 4) { - return split[2]; - } else if (split.length > 4) { - return `${split[2]}:${split[3]}`; - } - return network; -} - -/** - * Since the OIDC SIOP spec incorrectly uses 'did::' and calls that a method, we have to fix it - * @param didOrMethod - */ -export function toSIOPRegistrationDidMethod(didOrMethod: string) { - let prefix = didOrMethod; - if (!didOrMethod.startsWith('did:')) { - prefix = 'did:' + didOrMethod; - } - const split = prefix.split(':'); - return `${split[0]}:${split[1]}`; -} diff --git a/src/did/LinkedDomainValidations.ts b/src/did/LinkedDomainValidations.ts deleted file mode 100644 index ab8af62c..00000000 --- a/src/did/LinkedDomainValidations.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { IDomainLinkageValidation, ValidationStatusEnum, VerifyCallback, WDCErrors, WellKnownDidVerifier } from '@sphereon/wellknown-dids-client'; - -import { CheckLinkedDomain, DIDDocument, ExternalVerification, InternalVerification } from '../types'; - -import { resolveDidDocument } from './DIDResolution'; -import { getMethodFromDid, toSIOPRegistrationDidMethod } from './DidJWT'; - -function getValidationErrorMessages(validationResult: IDomainLinkageValidation): string[] { - const messages = []; - if (validationResult.message) { - messages.push(validationResult.message); - } - if (validationResult?.endpointDescriptors.length) { - for (const endpointDescriptor of validationResult.endpointDescriptors) { - if (endpointDescriptor.message) { - messages.push(endpointDescriptor.message); - } - if (endpointDescriptor.resources) { - for (const resource of endpointDescriptor.resources) { - if (resource.message) { - messages.push(resource.message); - } - } - } - } - } - return messages; -} - -/** - * @param validationErrorMessages - * @return returns false if the messages received from wellknown-dids-client makes this invalid for CheckLinkedDomain.IF_PRESENT plus the message itself - * and true for when we can move on - */ -function checkInvalidMessages(validationErrorMessages: string[]): { status: boolean; message?: string } { - if (!validationErrorMessages || !validationErrorMessages.length) { - return { status: false, message: 'linked domain is invalid.' }; - } - const validMessages: string[] = [ - WDCErrors.PROPERTY_LINKED_DIDS_DOES_NOT_CONTAIN_ANY_DOMAIN_LINK_CREDENTIALS.valueOf(), - WDCErrors.PROPERTY_LINKED_DIDS_NOT_PRESENT.valueOf(), - WDCErrors.PROPERTY_TYPE_NOT_CONTAIN_VALID_LINKED_DOMAIN.valueOf(), - WDCErrors.PROPERTY_SERVICE_NOT_PRESENT.valueOf(), - ]; - for (const validationErrorMessage of validationErrorMessages) { - if (!validMessages.filter((vm) => validationErrorMessage.includes(vm)).pop()) { - return { status: false, message: validationErrorMessage }; - } - } - return { status: true }; -} - -export async function validateLinkedDomainWithDid(did: string, verification: InternalVerification | ExternalVerification) { - const { checkLinkedDomain, resolveOpts, wellknownDIDVerifyCallback } = verification; - if (checkLinkedDomain === CheckLinkedDomain.NEVER) { - return; - } - const didDocument = await resolveDidDocument(did, { - ...resolveOpts, - subjectSyntaxTypesSupported: [toSIOPRegistrationDidMethod(getMethodFromDid(did))], - }); - if (!didDocument) { - throw Error(`Could not resolve DID: ${did}`); - } - if ((!didDocument.service || !didDocument.service.find((s) => s.type === 'LinkedDomains')) && checkLinkedDomain === CheckLinkedDomain.IF_PRESENT) { - // No linked domains in DID document and it was optional. Let's cut it short here. - return; - } - try { - const validationResult = await checkWellKnownDid({ didDocument, verifyCallback: wellknownDIDVerifyCallback }); - if (validationResult.status === ValidationStatusEnum.INVALID) { - const validationErrorMessages = getValidationErrorMessages(validationResult); - const messageCondition: { status: boolean; message?: string } = checkInvalidMessages(validationErrorMessages); - if (checkLinkedDomain === CheckLinkedDomain.ALWAYS || (checkLinkedDomain === CheckLinkedDomain.IF_PRESENT && !messageCondition.status)) { - throw new Error(messageCondition.message ? messageCondition.message : validationErrorMessages[0]); - } - } - } catch (err) { - const messageCondition: { status: boolean; message?: string } = checkInvalidMessages([err.message]); - if (checkLinkedDomain === CheckLinkedDomain.ALWAYS || (checkLinkedDomain === CheckLinkedDomain.IF_PRESENT && !messageCondition.status)) { - throw new Error(err.message); - } - } -} - -interface CheckWellKnownDidArgs { - didDocument: DIDDocument; - verifyCallback: VerifyCallback; -} - -async function checkWellKnownDid(args: CheckWellKnownDidArgs): Promise { - const verifier = new WellKnownDidVerifier({ - verifySignatureCallback: args.verifyCallback, - onlyVerifyServiceDid: false, - }); - return await verifier.verifyDomainLinkage({ didDocument: args.didDocument }); -} diff --git a/src/did/index.ts b/src/did/index.ts deleted file mode 100644 index 8c3866bd..00000000 --- a/src/did/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './DidJWT'; -export * from './DIDResolution'; -export * from './LinkedDomainValidations'; diff --git a/src/helpers/Keys.ts b/src/helpers/Keys.ts index 896325a9..06a0b97e 100644 --- a/src/helpers/Keys.ts +++ b/src/helpers/Keys.ts @@ -82,13 +82,15 @@ export const getThumbprint = async (hexPrivateKey: string, did: string): Promise }; */ +export type DigestAlgorithm = 'sha256' | 'sha384' | 'sha512'; + const check = (value, description) => { if (typeof value !== 'string' || !value) { throw Error(`${description} missing or invalid`); } }; -async function calculateJwkThumbprint(jwk: JWK, digestAlgorithm?: 'sha256' | 'sha384' | 'sha512'): Promise { +export async function calculateJwkThumbprint(jwk: JWK, digestAlgorithm?: DigestAlgorithm): Promise { if (!jwk || typeof jwk !== 'object') { throw new TypeError('JWK must be an object'); } @@ -125,13 +127,24 @@ async function calculateJwkThumbprint(jwk: JWK, digestAlgorithm?: 'sha256' | 'sh return u8a.toString(await digest(algorithm, data), 'base64url'); } -const digest = async (algorithm: 'sha256' | 'sha384' | 'sha512', data: Uint8Array) => { +const digest = async (algorithm: DigestAlgorithm, data: Uint8Array) => { const subtleDigest = `SHA-${algorithm.slice(-3)}`; return new Uint8Array(await crypto.subtle.digest(subtleDigest, data)); }; -export async function calculateJwkThumbprintUri(jwk: JWK, digestAlgorithm?: 'sha256' | 'sha384' | 'sha512'): Promise { - digestAlgorithm !== null && digestAlgorithm !== void 0 ? digestAlgorithm : (digestAlgorithm = 'sha256'); +export async function getDigestAlgorithmFromJwkThumbprintUri(uri: string): Promise { + 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 = `sha${match[1]}` as DigestAlgorithm; + 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: DigestAlgorithm = 'sha256'): Promise { const thumbprint = await calculateJwkThumbprint(jwk, digestAlgorithm); return `urn:ietf:params:oauth:jwk-thumbprint:sha-${digestAlgorithm.slice(-3)}:${thumbprint}`; } diff --git a/src/helpers/jwtUtils.ts b/src/helpers/jwtUtils.ts new file mode 100644 index 00000000..07832118 --- /dev/null +++ b/src/helpers/jwtUtils.ts @@ -0,0 +1,17 @@ +import { jwtDecode } from 'jwt-decode'; + +import { JwtHeader, JwtPayload, SIOPErrors } from '../types'; + +export type JwtType = 'id-token' | 'request-object'; + +export type JwtProtectionMethod = 'did' | 'x5c' | 'jwk' | 'custom'; + +export function parseJWT(jwt: string) { + const header = jwtDecode(jwt, { header: true }); + const payload = jwtDecode(jwt, { header: false }); + + if (!payload || !header) { + throw new Error(SIOPErrors.NO_JWT); + } + return { header, payload }; +} diff --git a/src/id-token/IDToken.ts b/src/id-token/IDToken.ts index 9c8437f6..16fdf4d8 100644 --- a/src/id-token/IDToken.ts +++ b/src/id-token/IDToken.ts @@ -1,23 +1,25 @@ -import { JWTHeader } from 'did-jwt'; - import { AuthorizationResponseOpts, VerifyAuthorizationResponseOpts } from '../authorization-response'; import { assertValidVerifyOpts } from '../authorization-response/Opts'; -import { getResolver, getSubDidFromPayload, parseJWT, signIDTokenPayload, validateLinkedDomainWithDid, verifyDidJWT } from '../did'; +import { parseJWT } from '../helpers/jwtUtils'; import { - CheckLinkedDomain, + getJwtVerifierWithContext, IDTokenJwt, IDTokenPayload, + JWK, + JwtHeader, JWTPayload, ResponseIss, SIOPErrors, VerifiedAuthorizationRequest, VerifiedIDToken, } from '../types'; +import { JwtIssuer, JwtIssuerWithContext } from '../types/JwtIssuer'; +import { calculateJwkThumbprintUri } from './../helpers/Keys'; import { createIDTokenPayload } from './Payload'; export class IDToken { - private _header?: JWTHeader; + private _header?: JwtHeader; private _payload?: IDTokenPayload; private _jwt?: IDTokenJwt; private readonly _responseOpts: AuthorizationResponseOpts; @@ -82,12 +84,50 @@ export class IDToken { return this._payload; } - public async jwt(): Promise { + public async jwt(_jwtIssuer: JwtIssuer): Promise { if (!this._jwt) { if (!this.responseOpts) { - throw Error(SIOPErrors.BAD_SIGNATURE_PARAMS); + throw Error(SIOPErrors.BAD_IDTOKEN_RESPONSE_OPTS); + } + + const jwtIssuer: JwtIssuerWithContext = _jwtIssuer + ? { ..._jwtIssuer, type: 'id-token', authorizationResponseOpts: this.responseOpts } + : { method: 'custom', type: 'id-token', authorizationResponseOpts: this.responseOpts }; + + if (jwtIssuer.method === 'custom') { + this._jwt = await this.responseOpts.createJwtCallback(jwtIssuer, { header: {}, payload: this._payload }); + } else if (jwtIssuer.method === 'did') { + const did = jwtIssuer.didUrl.split('#')[0]; + this._payload.sub = did; + + const issuer = this._responseOpts.registration.issuer || this._payload.iss; + if (!issuer || !(issuer.includes(ResponseIss.SELF_ISSUED_V2) || issuer === this._payload.sub)) { + throw new Error(SIOPErrors.NO_SELF_ISSUED_ISS); + } + if (!this._payload.iss) { + this._payload.iss = issuer; + } + + const header = { kid: jwtIssuer.didUrl, alg: jwtIssuer.alg, typ: 'JWT' }; + 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.x5c, typ: 'JWT' }; + this._jwt = await this._responseOpts.createJwtCallback(jwtIssuer, { header, payload: this._payload }); + } else if (jwtIssuer.method === '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 }); + } else { + throw new Error(`JwtIssuer method '${(jwtIssuer as JwtIssuer).method}' not implemented`); } - this._jwt = await signIDTokenPayload(this._payload, this.responseOpts); + const { header, payload } = this.parseAndVerifyJwt(); this._header = header; this._payload = payload; @@ -95,7 +135,7 @@ export class IDToken { return this._jwt; } - private parseAndVerifyJwt(): { header: JWTHeader; payload: IDTokenPayload } { + private parseAndVerifyJwt(): { header: JwtHeader; payload: IDTokenPayload } { const { header, payload } = parseJWT(this._jwt); this.assertValidResponseJWT({ header, payload }); const idTokenPayload = payload as IDTokenPayload; @@ -111,31 +151,27 @@ export class IDToken { public async verify(verifyOpts: VerifyAuthorizationResponseOpts): Promise { assertValidVerifyOpts(verifyOpts); - const { header, payload } = parseJWT(await this.jwt()); - this.assertValidResponseJWT({ header, payload }); + if (!this._jwt) { + throw new Error(SIOPErrors.NO_JWT); + } - const verifiedJWT = await verifyDidJWT(await this.jwt(), getResolver(verifyOpts.verification.resolveOpts), { - ...verifyOpts.verification.resolveOpts?.jwtVerifyOpts, - audience: verifyOpts.audience ?? verifyOpts.verification.resolveOpts?.jwtVerifyOpts?.audience, - }); + const parsedJwt = parseJWT(this._jwt); + this.assertValidResponseJWT(parsedJwt); - const issuerDid = getSubDidFromPayload(payload); - if (verifyOpts.verification.checkLinkedDomain && verifyOpts.verification.checkLinkedDomain !== CheckLinkedDomain.NEVER) { - await validateLinkedDomainWithDid(issuerDid, verifyOpts.verification); - } else if (!verifyOpts.verification.checkLinkedDomain) { - await validateLinkedDomainWithDid(issuerDid, verifyOpts.verification); + 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); } - const verPayload = verifiedJWT.payload as IDTokenPayload; - this.assertValidResponseJWT({ header, verPayload: verPayload, audience: verifyOpts.audience }); + + const verPayload = parsedJwt.payload as IDTokenPayload; + this.assertValidResponseJWT({ header: parsedJwt.header, verPayload: verPayload, audience: verifyOpts.audience }); // Enforces verifyPresentationCallback function on the RP side, if (!verifyOpts?.verification.presentationVerificationCallback) { throw new Error(SIOPErrors.VERIFIABLE_PRESENTATION_VERIFICATION_FUNCTION_MISSING); } return { - jwt: await this.jwt(), - didResolutionResult: verifiedJWT.didResolutionResult, - signer: verifiedJWT.signer, - issuer: issuerDid, + jwt: this._jwt, payload: { ...verPayload }, verifyOpts, }; @@ -150,13 +186,13 @@ export class IDToken { }; } - private assertValidResponseJWT(opts: { header: JWTHeader; payload?: JWTPayload; verPayload?: IDTokenPayload; audience?: string; nonce?: string }) { + private assertValidResponseJWT(opts: { header: JwtHeader; payload?: JWTPayload; verPayload?: IDTokenPayload; audience?: string; nonce?: string }) { if (!opts.header) { throw new Error(SIOPErrors.BAD_PARAMS); } if (opts.payload) { if (!opts.payload.iss || !(opts.payload.iss.includes(ResponseIss.SELF_ISSUED_V2) || opts.payload.iss.startsWith('did:'))) { - throw new Error(`${SIOPErrors.NO_SELFISSUED_ISS}, got: ${opts.payload.iss}`); + throw new Error(`${SIOPErrors.NO_SELF_ISSUED_ISS}, got: ${opts.payload.iss}`); } } @@ -182,7 +218,7 @@ export class IDToken { } } - get header(): JWTHeader { + get header(): JwtHeader { return this._header; } diff --git a/src/id-token/Payload.ts b/src/id-token/Payload.ts index 1d2fcfee..22a9442b 100644 --- a/src/id-token/Payload.ts +++ b/src/id-token/Payload.ts @@ -1,16 +1,7 @@ import { AuthorizationResponseOpts, mergeOAuth2AndOpenIdInRequestPayload } from '../authorization-response'; import { assertValidResponseOpts } from '../authorization-response/Opts'; import { authorizationRequestVersionDiscovery } from '../helpers/SIOPSpecVersion'; -import { - IDTokenPayload, - isSuppliedSignature, - JWK, - ResponseIss, - SIOPErrors, - SubjectSyntaxTypesSupportedValues, - SupportedVersion, - VerifiedAuthorizationRequest, -} from '../types'; +import { IDTokenPayload, ResponseIss, SIOPErrors, SupportedVersion, VerifiedAuthorizationRequest } from '../types'; export const createIDTokenPayload = async ( verifiedAuthorizationRequest: VerifiedAuthorizationRequest, @@ -24,10 +15,6 @@ export const createIDTokenPayload = async ( } const payload = await mergeOAuth2AndOpenIdInRequestPayload(authorizationRequestPayload, requestObject); - const supportedDidMethods = - verifiedAuthorizationRequest.registrationMetadataPayload?.subject_syntax_types_supported?.filter((sst) => - sst.includes(SubjectSyntaxTypesSupportedValues.DID.valueOf()), - ) ?? []; const state = payload.state; const nonce = payload.nonce; const SEC_IN_MS = 1000; @@ -50,34 +37,10 @@ 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: responseOpts.signature.did, ...(payload.auth_time && { auth_time: payload.auth_time }), nonce, state, // ...(responseOpts.presentationExchange?._vp_token ? { _vp_token: responseOpts.presentationExchange._vp_token } : {}), }; - if (supportedDidMethods.indexOf(SubjectSyntaxTypesSupportedValues.JWK_THUMBPRINT) != -1 && !responseOpts.signature.did) { - const { thumbprint, subJwk } = await createThumbprintAndJWK(responseOpts); - idToken['sub_jwk'] = subJwk; - idToken.sub = thumbprint; - } return idToken; }; - -const createThumbprintAndJWK = async (resOpts: AuthorizationResponseOpts): Promise<{ thumbprint: string; subJwk: JWK }> => { - let thumbprint; - let subJwk; - /* if (isInternalSignature(resOpts.signature)) { - thumbprint = await getThumbprint(resOpts.signature.hexPrivateKey, resOpts.signature.did); - subJwk = getPublicJWKFromHexPrivateKey( - resOpts.signature.hexPrivateKey, - resOpts.signature.kid || `${resOpts.signature.did}#key-1`, - resOpts.signature.did - ); - } else*/ if (isSuppliedSignature(resOpts.signature)) { - // fixme: These are uninitialized. Probably we have to extend the supplied withSignature to provide these. - return { thumbprint, subJwk }; - } else { - throw new Error(SIOPErrors.SIGNATURE_OBJECT_TYPE_NOT_SET); - } -}; diff --git a/src/index.ts b/src/index.ts index 8f9a639c..15b30394 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ import * as RPRegistrationMetadata from './authorization-request/RequestRegistration'; import { PresentationExchange } from './authorization-response/PresentationExchange'; -export * from './did'; export * from './helpers'; export * from './types'; export * from './authorization-request'; @@ -10,5 +9,4 @@ export * from './id-token'; export * from './request-object'; export * from './rp'; export * from './op'; -export { JWTHeader, JWTPayload, JWTOptions, JWTVerifyOptions } from 'did-jwt'; export { PresentationExchange, RPRegistrationMetadata }; diff --git a/src/op/OP.ts b/src/op/OP.ts index c5a67dd5..eee5f257 100644 --- a/src/op/OP.ts +++ b/src/op/OP.ts @@ -17,19 +17,16 @@ import { AuthorizationEvent, AuthorizationEvents, ContentType, - ExternalSignature, - ExternalVerification, - InternalSignature, - InternalVerification, + JwtIssuer, ParsedAuthorizationRequestURI, RegisterEventListener, ResponseIss, ResponseMode, SIOPErrors, SIOPResonse, - SuppliedSignature, SupportedVersion, UrlEncodingFormat, + Verification, VerifiedAuthorizationRequest, } from '../types'; @@ -58,7 +55,7 @@ export class OP { public async verifyAuthorizationRequest( requestJwtOrUri: string | URI, - requestOpts?: { correlationId?: string; verification?: InternalVerification | ExternalVerification }, + requestOpts?: { correlationId?: string; verification?: Verification }, ): Promise { const correlationId = requestOpts?.correlationId || uuidv4(); const authorizationRequest = await AuthorizationRequest.fromUriOrJwt(requestJwtOrUri) @@ -96,13 +93,13 @@ export class OP { public async createAuthorizationResponse( verifiedAuthorizationRequest: VerifiedAuthorizationRequest, - responseOpts?: { + responseOpts: { + jwtIssuer?: JwtIssuer; version?: SupportedVersion; correlationId?: string; audience?: string; issuer?: ResponseIss | string; - signature?: InternalSignature | ExternalSignature | SuppliedSignature; - verification?: InternalVerification | ExternalVerification; + verification?: Verification; presentationExchange?: PresentationExchangeResponseOpts; }, ): Promise { @@ -217,7 +214,6 @@ export class OP { version?: SupportedVersion; issuer?: IIssuerId | ResponseIss; audience?: string; - signature?: InternalSignature | ExternalSignature | SuppliedSignature; presentationExchange?: PresentationExchangeResponseOpts; }): AuthorizationResponseOpts { const version = opts.version ?? this._createResponseOptions.version; @@ -237,10 +233,6 @@ export class OP { return { ...this._createResponseOptions, ...opts, - signature: { - ...this._createResponseOptions?.signature, - ...opts.signature, - }, ...(presentationExchange && { presentationExchange }), registration: { ...this._createResponseOptions?.registration, issuer }, responseURI, @@ -249,12 +241,10 @@ export class OP { }; } - private newVerifyAuthorizationRequestOpts(requestOpts: { - correlationId: string; - verification?: InternalVerification | ExternalVerification; - }): VerifyAuthorizationRequestOpts { + private newVerifyAuthorizationRequestOpts(requestOpts: { correlationId: string; verification?: Verification }): VerifyAuthorizationRequestOpts { const verification: VerifyAuthorizationRequestOpts = { ...this._verifyRequestOptions, + verifyJwtCallback: this._verifyRequestOptions.verifyJwtCallback, ...requestOpts, verification: mergeVerificationOpts(this._verifyRequestOptions, requestOpts), correlationId: requestOpts.correlationId, diff --git a/src/op/OPBuilder.ts b/src/op/OPBuilder.ts index 69d745f4..f3135256 100644 --- a/src/op/OPBuilder.ts +++ b/src/op/OPBuilder.ts @@ -1,55 +1,27 @@ import { EventEmitter } from 'events'; -import { Config, getUniResolver, UniResolver } from '@sphereon/did-uni-client'; import { Hasher, IIssuerId } from '@sphereon/ssi-types'; -import { VerifyCallback } from '@sphereon/wellknown-dids-client'; -import { Signer } from 'did-jwt'; -import { Resolvable, Resolver } from 'did-resolver'; import { PropertyTargets } from '../authorization-request'; import { PresentationSignCallback } from '../authorization-response'; -import { getMethodFromDid } from '../did'; -import { - CheckLinkedDomain, - EcdsaSignature, - ExternalSignature, - InternalSignature, - ResponseIss, - ResponseMode, - ResponseRegistrationOpts, - SigningAlgo, - SubjectSyntaxTypesSupportedValues, - SuppliedSignature, - SupportedVersion, -} from '../types'; +import { ResponseIss, ResponseMode, ResponseRegistrationOpts, SupportedVersion, VerifyJwtCallback } from '../types'; +import { CreateJwtCallback } from '../types/JwtIssuer'; import { OP } from './OP'; export class OPBuilder { expiresIn?: number; issuer?: IIssuerId | ResponseIss; - resolvers: Map = new Map(); responseMode?: ResponseMode = ResponseMode.DIRECT_POST; responseRegistration?: Partial = {}; - customResolver?: Resolvable; - signature?: InternalSignature | ExternalSignature | SuppliedSignature; - checkLinkedDomain?: CheckLinkedDomain; - wellknownDIDVerifyCallback?: VerifyCallback; + createJwtCallback?: CreateJwtCallback; + verifyJwtCallback?: VerifyJwtCallback; presentationSignCallback?: PresentationSignCallback; supportedVersions?: SupportedVersion[]; eventEmitter?: EventEmitter; hasher?: Hasher; - addDidMethod(didMethod: string, opts?: { resolveUrl?: string; baseUrl?: string }): OPBuilder { - const method = didMethod.startsWith('did:') ? getMethodFromDid(didMethod) : didMethod; - if (method === SubjectSyntaxTypesSupportedValues.DID.valueOf()) { - opts ? this.addResolver('', new UniResolver({ ...opts } as Config)) : this.addResolver('', null); - } - opts ? this.addResolver(method, new Resolver(getUniResolver(method, { ...opts }))) : this.addResolver(method, null); - return this; - } - withHasher(hasher: Hasher): OPBuilder { this.hasher = hasher; @@ -61,32 +33,11 @@ export class OPBuilder { return this; } - withCustomResolver(resolver: Resolvable): OPBuilder { - this.customResolver = resolver; - return this; - } - - addResolver(didMethod: string, resolver: Resolvable): OPBuilder { - const qualifiedDidMethod = didMethod.startsWith('did:') ? getMethodFromDid(didMethod) : didMethod; - this.resolvers.set(qualifiedDidMethod, resolver); - return this; - } - - /*withDid(did: string): OPBuilder { - this.did = did; - return this; - } -*/ withExpiresIn(expiresIn: number): OPBuilder { this.expiresIn = expiresIn; return this; } - withCheckLinkedDomain(mode: CheckLinkedDomain): OPBuilder { - this.checkLinkedDomain = mode; - return this; - } - withResponseMode(responseMode: ResponseMode): OPBuilder { this.responseMode = responseMode; return this; @@ -108,29 +59,13 @@ export class OPBuilder { requestObjectSigningAlgValuesSupported?: SigningAlgo[] | SigningAlgo; */ - // Only internal and supplied signatures supported for now - withSignature(signature: InternalSignature | SuppliedSignature): OPBuilder { - this.signature = signature; - return this; - } - - withInternalSignature(hexPrivateKey: string, did: string, kid: string, alg: SigningAlgo, customJwtSigner?: Signer): OPBuilder { - this.withSignature({ hexPrivateKey, did, kid, alg, customJwtSigner }); - return this; - } - - withSuppliedSignature( - signature: (data: string | Uint8Array) => Promise, - did: string, - kid: string, - alg: SigningAlgo, - ): OPBuilder { - this.withSignature({ signature, did, kid, alg }); + withCreateJwtCallback(createJwtCallback: CreateJwtCallback): OPBuilder { + this.createJwtCallback = createJwtCallback; return this; } - withWellknownDIDVerifyCallback(wellknownDIDVerifyCallback: VerifyCallback): OPBuilder { - this.wellknownDIDVerifyCallback = wellknownDIDVerifyCallback; + withVerifyJwtCallback(verifyJwtCallback: VerifyJwtCallback): OPBuilder { + this.verifyJwtCallback = verifyJwtCallback; return this; } diff --git a/src/op/Opts.ts b/src/op/Opts.ts index ddd8615c..07f7b0f4 100644 --- a/src/op/Opts.ts +++ b/src/op/Opts.ts @@ -1,11 +1,8 @@ -import { Resolvable } from 'did-resolver'; - import { VerifyAuthorizationRequestOpts } from '../authorization-request'; import { AuthorizationResponseOpts } from '../authorization-response'; -import { getResolverUnion, mergeAllDidMethods } from '../did'; import { LanguageTagUtils } from '../helpers'; import { AuthorizationResponseOptsSchema } from '../schemas'; -import { InternalVerification, PassBy, ResponseRegistrationOpts, VerificationMode } from '../types'; +import { PassBy, ResponseRegistrationOpts } from '../types'; import { OPBuilder } from './OPBuilder'; @@ -13,13 +10,6 @@ export const createResponseOptsFromBuilderOrExistingOpts = (opts: { builder?: OPBuilder; responseOpts?: AuthorizationResponseOpts; }): AuthorizationResponseOpts => { - if (opts?.builder?.resolvers.size && opts.builder?.responseRegistration?.subject_syntax_types_supported) { - opts.builder.responseRegistration.subject_syntax_types_supported = mergeAllDidMethods( - opts.builder.responseRegistration.subject_syntax_types_supported, - opts.builder.resolvers, - ); - } - let responseOpts: AuthorizationResponseOpts; if (opts.builder) { responseOpts = { @@ -28,7 +18,8 @@ export const createResponseOptsFromBuilderOrExistingOpts = (opts: { ...(opts.builder.responseRegistration as ResponseRegistrationOpts), }, expiresIn: opts.builder.expiresIn, - signature: opts.builder.signature, + jwtIssuer: responseOpts?.jwtIssuer, + createJwtCallback: opts.builder.createJwtCallback, responseMode: opts.builder.responseMode, ...(responseOpts?.version ? { version: responseOpts.version } @@ -69,32 +60,11 @@ export const createVerifyRequestOptsFromBuilderOrExistingOpts = (opts: { builder?: OPBuilder; verifyOpts?: VerifyAuthorizationRequestOpts; }): VerifyAuthorizationRequestOpts => { - if (opts?.builder?.resolvers.size && opts.builder?.responseRegistration) { - opts.builder.responseRegistration.subject_syntax_types_supported = mergeAllDidMethods( - opts.builder.responseRegistration.subject_syntax_types_supported, - opts.builder.resolvers, - ); - } - let resolver: Resolvable; - if (opts.builder) { - resolver = getResolverUnion( - opts.builder.customResolver, - opts.builder.responseRegistration.subject_syntax_types_supported, - opts.builder.resolvers, - ); - } return opts.builder ? { + verifyJwtCallback: opts.builder.verifyJwtCallback, hasher: opts.builder.hasher, - verification: { - mode: VerificationMode.INTERNAL, - checkLinkedDomain: opts.builder.checkLinkedDomain, - wellknownDIDVerifyCallback: opts.builder.wellknownDIDVerifyCallback, - resolveOpts: { - subjectSyntaxTypesSupported: opts.builder.responseRegistration.subject_syntax_types_supported, - resolver: resolver, - }, - } as InternalVerification, + verification: {}, supportedVersions: opts.builder.supportedVersions, correlationId: undefined, } diff --git a/src/request-object/Payload.ts b/src/request-object/Payload.ts index caa89124..038510fc 100644 --- a/src/request-object/Payload.ts +++ b/src/request-object/Payload.ts @@ -20,15 +20,8 @@ export const createRequestObjectPayload = async (opts: CreateAuthorizationReques const registration = await createRequestRegistration(opts.clientMetadata, opts); const claims = createPresentationDefinitionClaimsProperties(payload.claims); - let clientId = payload.client_id; - const metadataKey = opts.version >= SupportedVersion.SIOPv2_D11.valueOf() ? 'client_metadata' : 'registration'; - if (!clientId) { - clientId = registration.payload[metadataKey]?.client_id; - } - if (!clientId && !opts.requestObject.signature.did) { - throw Error('Please provide a clientId for the RP'); - } + const clientId = payload.client_id ?? registration.payload[metadataKey]?.client_id; const now = Math.round(new Date().getTime() / 1000); const validInSec = 120; // todo config/option @@ -41,7 +34,8 @@ export const createRequestObjectPayload = async (opts: CreateAuthorizationReques response_type: payload.response_type ?? ResponseType.ID_TOKEN, scope: payload.scope ?? Scope.OPENID, //TODO implement /.well-known/openid-federation support in the OP side to resolve the client_id (URL) and retrieve the metadata - client_id: clientId ?? opts.requestObject.signature.did, + client_id: clientId, + client_id_scheme: opts.clientMetadata.client_id_scheme, ...(payload.redirect_uri && { redirect_uri: payload.redirect_uri }), ...(payload.response_uri && { response_uri: payload.response_uri }), response_mode: payload.response_mode ?? ResponseMode.DIRECT_POST, diff --git a/src/request-object/RequestObject.ts b/src/request-object/RequestObject.ts index 597217ea..ae66bc2e 100644 --- a/src/request-object/RequestObject.ts +++ b/src/request-object/RequestObject.ts @@ -1,10 +1,8 @@ -import { decodeJWT } from 'did-jwt'; - import { ClaimPayloadCommonOpts, ClaimPayloadOptsVID1, CreateAuthorizationRequestOpts } from '../authorization-request'; import { assertValidAuthorizationRequestOpts } from '../authorization-request/Opts'; -import { signRequestObjectPayload } from '../did'; import { fetchByReferenceOrUseByValue, removeNullUndefined } from '../helpers'; -import { AuthorizationRequestPayload, RequestObjectJwt, RequestObjectPayload, SIOPErrors } from '../types'; +import { parseJWT } from '../helpers/jwtUtils'; +import { AuthorizationRequestPayload, JwtIssuer, JwtIssuerWithContext, RequestObjectJwt, RequestObjectPayload, SIOPErrors } from '../types'; import { assertValidRequestObjectOpts } from './Opts'; import { assertValidRequestObjectPayload, createRequestObjectPayload } from './Payload'; @@ -12,7 +10,7 @@ import { RequestObjectOpts } from './types'; export class RequestObject { private payload: RequestObjectPayload; - private jwt: RequestObjectJwt; + private jwt?: RequestObjectJwt; private readonly opts: RequestObjectOpts; private constructor( @@ -40,11 +38,12 @@ export class RequestObject { */ public static async fromOpts(authorizationRequestOpts: CreateAuthorizationRequestOpts) { assertValidAuthorizationRequestOpts(authorizationRequestOpts); - const signature = authorizationRequestOpts.requestObject.signature; // We copy the signature separately as it can contain a function, which would be removed in the merge function below + const createJwtCallback = authorizationRequestOpts.requestObject.createJwtCallback; // We copy the signature separately as it can contain a function, which would be removed in the merge function below + const jwtIssuer = authorizationRequestOpts.requestObject.jwtIssuer; // We copy the signature separately as it can contain a function, which would be removed in the merge function below const requestObjectOpts = RequestObject.mergeOAuth2AndOpenIdProperties(authorizationRequestOpts); const mergedOpts = { ...authorizationRequestOpts, - requestObject: { ...authorizationRequestOpts.requestObject, ...requestObjectOpts, signature }, + requestObject: { ...authorizationRequestOpts.requestObject, ...requestObjectOpts, createJwtCallback, jwtIssuer }, }; return new RequestObject(mergedOpts, await createRequestObjectPayload(mergedOpts)); } @@ -76,7 +75,38 @@ export class RequestObject { } assertValidRequestObjectPayload(this.payload); - this.jwt = await signRequestObjectPayload(this.payload, this.opts); + const jwtIssuer: JwtIssuerWithContext = this.opts.jwtIssuer + ? { ...this.opts.jwtIssuer, type: 'request-object' } + : { method: 'custom', type: 'request-object' }; + + if (jwtIssuer.method === 'custom') { + this.jwt = await this.opts.createJwtCallback(jwtIssuer, { header: {}, payload: this.payload }); + } else if (jwtIssuer.method === 'did') { + const did = jwtIssuer.didUrl.split('#')[0]; + this.payload.iss = this.payload.iss ?? did; + this.payload.sub = this.payload.sub ?? did; + this.payload.client_id = this.payload.client_id ?? did; + + const header = { kid: jwtIssuer.didUrl, alg: jwtIssuer.alg, typ: 'JWT' }; + this.jwt = await this.opts.createJwtCallback(jwtIssuer, { header, payload: this.payload }); + } else if (jwtIssuer.method === 'x5c') { + this.payload.iss = jwtIssuer.issuer; + this.payload.client_id = jwtIssuer.issuer; + this.payload.redirect_uri = jwtIssuer.issuer; + this.payload.client_id_scheme = jwtIssuer.clientIdScheme; + + const header = { x5c: jwtIssuer.x5c, typ: 'JWT' }; + this.jwt = await this.opts.createJwtCallback(jwtIssuer, { header, payload: this.payload }); + } else if (jwtIssuer.method === 'jwk') { + if (!this.payload.client_id) { + throw new Error('Please provide a client_id for the RP'); + } + + const header = { jwk: jwtIssuer.jwk, typ: 'JWT', alg: jwtIssuer.jwk.alg as string }; + this.jwt = await this.opts.createJwtCallback(jwtIssuer, { header, payload: this.payload }); + } else { + throw new Error(`JwtIssuer method '${(jwtIssuer as JwtIssuer).method}' not implemented`); + } } return this.jwt; } @@ -86,7 +116,7 @@ export class RequestObject { if (!this.jwt) { return undefined; } - this.payload = removeNullUndefined(decodeJWT(this.jwt).payload) as RequestObjectPayload; + this.payload = removeNullUndefined(parseJWT(this.jwt).payload) as RequestObjectPayload; this.removeRequestProperties(); if (this.payload.registration_uri) { delete this.payload.registration; @@ -126,9 +156,13 @@ export class RequestObject { } const isAuthReq = opts['requestObject'] !== undefined; const mergedOpts = JSON.parse(JSON.stringify(opts)); - const signature = opts['requestObject']?.signature?.signature; - if (signature && mergedOpts.requestObject.signature) { - mergedOpts.requestObject.signature.signature = signature; + const createJwtCallback = opts['requestObject']?.createJwtCallback; + if (createJwtCallback) { + mergedOpts.requestObject.createJwtCallback = createJwtCallback; + } + const jwtIssuer = opts['requestObject']?.jwtIssuer; + if (createJwtCallback) { + mergedOpts.requestObject.jwtIssuer = jwtIssuer; } delete mergedOpts?.request?.requestObject; return isAuthReq ? mergedOpts.requestObject : mergedOpts; diff --git a/src/request-object/types.ts b/src/request-object/types.ts index c9588218..ca685845 100644 --- a/src/request-object/types.ts +++ b/src/request-object/types.ts @@ -1,7 +1,9 @@ import { ClaimPayloadCommonOpts, RequestObjectPayloadOpts } from '../authorization-request'; -import { ExternalSignature, InternalSignature, NoSignature, ObjectBy, SuppliedSignature } from '../types'; +import { ObjectBy } from '../types'; +import { CreateJwtCallback, JwtIssuer } from '../types/JwtIssuer'; export interface RequestObjectOpts extends ObjectBy { payload?: RequestObjectPayloadOpts; // for pass by value - signature: InternalSignature | ExternalSignature | SuppliedSignature | NoSignature; // Whether no withSignature is being used, internal (access to private key), or external (hosted using authentication), or supplied (callback supplied) + createJwtCallback: CreateJwtCallback; + jwtIssuer: JwtIssuer; } diff --git a/src/rp/Opts.ts b/src/rp/Opts.ts index 2954744e..7df3b133 100644 --- a/src/rp/Opts.ts +++ b/src/rp/Opts.ts @@ -1,10 +1,7 @@ -import { Resolvable } from 'did-resolver'; - import { CreateAuthorizationRequestOpts, PropertyTarget, PropertyTargets, RequestPropertyWithTargets } from '../authorization-request'; import { VerifyAuthorizationResponseOpts } from '../authorization-response'; -import { getResolverUnion, mergeAllDidMethods } from '../did'; // import { CreateAuthorizationRequestOptsSchema } from '../schemas'; -import { ClientMetadataOpts, InternalVerification, RequestObjectPayload, SIOPErrors, VerificationMode } from '../types'; +import { ClientMetadataOpts, RequestObjectPayload, SIOPErrors, Verification } from '../types'; import { RPBuilder } from './RPBuilder'; @@ -34,7 +31,7 @@ export const createRequestOptsFromBuilderOrExistingOpts = (opts: { builder?: RPB subject_types_supported: opts.builder.clientMetadata?.subjectTypesSupported, request_object_signing_alg_values_supported: opts.builder.clientMetadata?.requestObjectSigningAlgValuesSupported, }, - signature: opts.builder.signature, + createJwtCallback: opts.builder.createJwtCallback, }, clientMetadata: opts.builder.clientMetadata as ClientMetadataOpts, } @@ -50,35 +47,19 @@ export const createRequestOptsFromBuilderOrExistingOpts = (opts: { builder?: RPB }; export const createVerifyResponseOptsFromBuilderOrExistingOpts = (opts: { builder?: RPBuilder; verifyOpts?: VerifyAuthorizationResponseOpts }) => { - if (opts?.builder?.resolvers.size && opts.builder?.clientMetadata) { - opts.builder.clientMetadata.subject_syntax_types_supported = mergeAllDidMethods( - opts.builder.clientMetadata.subject_syntax_types_supported, - opts.builder.resolvers, - ); - } - let resolver: Resolvable; - if (opts.builder) { - resolver = getResolverUnion(opts.builder.customResolver, opts.builder.clientMetadata.subject_syntax_types_supported, opts.builder.resolvers); - } return opts.builder ? { hasher: opts.builder.hasher, + verifyJwtCallback: opts.builder.verifyJwtCallback, verification: { - mode: VerificationMode.INTERNAL, - checkLinkedDomain: opts.builder.checkLinkedDomain, - wellknownDIDVerifyCallback: opts.builder.wellknownDIDVerifyCallback, presentationVerificationCallback: opts.builder.presentationVerificationCallback, - resolveOpts: { - subjectSyntaxTypesSupported: opts.builder.clientMetadata.subject_syntax_types_supported, - resolver: resolver, - }, supportedVersions: opts.builder.supportedVersions, revocationOpts: { revocationVerification: opts.builder.revocationVerification, revocationVerificationCallback: opts.builder.revocationVerificationCallback, }, replayRegistry: opts.builder.sessionManager, - } as InternalVerification, + } as Verification, audience: opts.builder.clientId || opts.builder.clientMetadata?.client_id, } : opts.verifyOpts; diff --git a/src/rp/RP.ts b/src/rp/RP.ts index 00b5fa11..9f4c21b0 100644 --- a/src/rp/RP.ts +++ b/src/rp/RP.ts @@ -15,18 +15,16 @@ import { import { mergeVerificationOpts } from '../authorization-request/Opts'; import { AuthorizationResponse, PresentationDefinitionWithLocation, VerifyAuthorizationResponseOpts } from '../authorization-response'; import { getNonce, getState } from '../helpers'; +import { JwtIssuer, PassBy } from '../types'; import { AuthorizationEvent, AuthorizationEvents, AuthorizationResponsePayload, - CheckLinkedDomain, - ExternalVerification, - InternalVerification, - PassBy, RegisterEventListener, ResponseURIType, SIOPErrors, SupportedVersion, + Verification, VerifiedAuthorizationResponse, } from '../types'; @@ -68,6 +66,7 @@ export class RP { correlationId: string; nonce: string | RequestPropertyWithTargets; state: string | RequestPropertyWithTargets; + jwtIssuer?: JwtIssuer; claims?: ClaimPayloadCommonOpts | RequestPropertyWithTargets; version?: SupportedVersion; requestByReferenceURI?: string; @@ -96,6 +95,7 @@ export class RP { correlationId: string; nonce: string | RequestPropertyWithTargets; state: string | RequestPropertyWithTargets; + jwtIssuer?: JwtIssuer; claims?: ClaimPayloadCommonOpts | RequestPropertyWithTargets; version?: SupportedVersion; requestByReferenceURI?: string; @@ -141,7 +141,7 @@ export class RP { audience?: string; state?: string; nonce?: string; - verification?: InternalVerification | ExternalVerification; + verification?: Verification; presentationDefinitions?: PresentationDefinitionWithLocation | PresentationDefinitionWithLocation[]; }, ): Promise { @@ -199,6 +199,7 @@ export class RP { correlationId: string; nonce: string | RequestPropertyWithTargets; state: string | RequestPropertyWithTargets; + jwtIssuer?: JwtIssuer; claims?: ClaimPayloadCommonOpts | RequestPropertyWithTargets; version?: SupportedVersion; requestByReferenceURI?: string; @@ -254,6 +255,8 @@ export class RP { } const newOpts = { ...this._createRequestOptions, version }; + newOpts.requestObject = { ...newOpts.requestObject, jwtIssuer: opts.jwtIssuer }; + newOpts.requestObject.payload = newOpts.requestObject.payload ?? ({} as RequestObjectPayloadOpts); newOpts.payload = newOpts.payload ?? {}; if (referenceURI) { @@ -301,9 +304,8 @@ export class RP { hasher?: Hasher; state?: string; nonce?: string; - verification?: InternalVerification | ExternalVerification; + verification?: Verification; audience?: string; - checkLinkedDomain?: CheckLinkedDomain; presentationDefinitions?: PresentationDefinitionWithLocation | PresentationDefinitionWithLocation[]; }, ): Promise { @@ -336,15 +338,13 @@ export class RP { state = state ?? reqState; } } + return { ...this._verifyResponseOptions, + verifyJwtCallback: this._verifyResponseOptions.verifyJwtCallback, ...opts, correlationId, - audience: - opts?.audience ?? - this._verifyResponseOptions.audience ?? - this._verifyResponseOptions.verification.resolveOpts.jwtVerifyOpts.audience ?? - this._createRequestOptions.payload.client_id, + audience: opts?.audience ?? this._verifyResponseOptions.audience ?? this._createRequestOptions.payload.client_id, state, nonce, verification: mergeVerificationOpts(this._verifyResponseOptions, opts), diff --git a/src/rp/RPBuilder.ts b/src/rp/RPBuilder.ts index df301eb2..a2c801df 100644 --- a/src/rp/RPBuilder.ts +++ b/src/rp/RPBuilder.ts @@ -1,23 +1,14 @@ import { EventEmitter } from 'events'; -import { Config, getUniResolver, UniResolver } from '@sphereon/did-uni-client'; import { IPresentationDefinition } from '@sphereon/pex'; import { Hasher } from '@sphereon/ssi-types'; -import { VerifyCallback } from '@sphereon/wellknown-dids-client'; -import { Signer } from 'did-jwt'; -import { Resolvable, Resolver } from 'did-resolver'; import { PropertyTarget, PropertyTargets } from '../authorization-request'; import { PresentationVerificationCallback } from '../authorization-response'; -import { getMethodFromDid } from '../did'; +import { CreateJwtCallback, VerifyJwtCallback } from '../types'; import { AuthorizationRequestPayload, - CheckLinkedDomain, ClientMetadataOpts, - EcdsaSignature, - ExternalSignature, - InternalSignature, - NoSignature, ObjectBy, PassBy, RequestObjectPayload, @@ -26,9 +17,6 @@ import { ResponseType, RevocationVerification, RevocationVerificationCallback, - SigningAlgo, - SubjectSyntaxTypesSupportedValues, - SuppliedSignature, SupportedVersion, } from '../types'; @@ -37,12 +25,9 @@ import { RP } from './RP'; import { IRPSessionManager } from './types'; export class RPBuilder { - resolvers: Map = new Map(); - customResolver?: Resolvable; requestObjectBy: ObjectBy; - signature: InternalSignature | ExternalSignature | SuppliedSignature | NoSignature; - checkLinkedDomain?: CheckLinkedDomain; - wellknownDIDVerifyCallback?: VerifyCallback; + createJwtCallback?: CreateJwtCallback; + verifyJwtCallback?: VerifyJwtCallback; revocationVerification?: RevocationVerification; revocationVerificationCallback?: RevocationVerificationCallback; presentationVerificationCallback?: PresentationVerificationCallback; @@ -110,17 +95,6 @@ export class RPBuilder { return this; } - withCustomResolver(resolver: Resolvable): RPBuilder { - this.customResolver = resolver; - return this; - } - - addResolver(didMethod: string, resolver: Resolvable): RPBuilder { - const qualifiedDidMethod = didMethod.startsWith('did:') ? getMethodFromDid(didMethod) : didMethod; - this.resolvers.set(qualifiedDidMethod, resolver); - return this; - } - withAuthorizationEndpoint(authorizationEndpoint: string, targets?: PropertyTargets): RPBuilder { this._authorizationRequestPayload.authorization_endpoint = assignIfAuth( { @@ -139,20 +113,6 @@ export class RPBuilder { return this; } - withCheckLinkedDomain(mode: CheckLinkedDomain): RPBuilder { - this.checkLinkedDomain = mode; - return this; - } - - addDidMethod(didMethod: string, opts?: { resolveUrl?: string; baseUrl?: string }): RPBuilder { - const method = didMethod.startsWith('did:') ? getMethodFromDid(didMethod) : didMethod; - if (method === SubjectSyntaxTypesSupportedValues.DID.valueOf()) { - opts ? this.addResolver('', new UniResolver({ ...opts } as Config)) : this.addResolver('', null); - } - opts ? this.addResolver(method, new Resolver(getUniResolver(method, { ...opts }))) : this.addResolver(method, null); - return this; - } - withRedirectUri(redirectUri: string, targets?: PropertyTargets): RPBuilder { this._authorizationRequestPayload.redirect_uri = assignIfAuth({ propertyValue: redirectUri, targets }, false); this._requestObjectPayload.redirect_uri = assignIfRequestObject({ propertyValue: redirectUri, targets }, true); @@ -222,24 +182,13 @@ export class RPBuilder { return this; } - // Only internal and supplied signatures supported for now - withSignature(signature: InternalSignature | SuppliedSignature): RPBuilder { - this.signature = signature; + withCreateJwtCallback(createJwtCallback: CreateJwtCallback): RPBuilder { + this.createJwtCallback = createJwtCallback; return this; } - withInternalSignature(hexPrivateKey: string, did: string, kid: string, alg: SigningAlgo, customJwtSigner?: Signer): RPBuilder { - this.withSignature({ hexPrivateKey, did, kid, alg, customJwtSigner }); - return this; - } - - withSuppliedSignature( - signature: (data: string | Uint8Array) => Promise, - did: string, - kid: string, - alg: SigningAlgo, - ): RPBuilder { - this.withSignature({ signature, did, kid, alg }); + withVerifyJwtCallback(verifyJwtCallback: VerifyJwtCallback): RPBuilder { + this.verifyJwtCallback = verifyJwtCallback; return this; } @@ -297,11 +246,6 @@ export class RPBuilder { return this; } - withWellknownDIDVerifyCallback(wellknownDIDVerifyCallback: VerifyCallback): RPBuilder { - this.wellknownDIDVerifyCallback = wellknownDIDVerifyCallback; - return this; - } - private initSupportedVersions() { if (!this.supportedVersions) { this.supportedVersions = []; diff --git a/src/schemas/AuthorizationRequestPayloadVD12OID4VPD18.schema.ts b/src/schemas/AuthorizationRequestPayloadVD12OID4VPD18.schema.ts index 9f6f63c3..96cb5f7b 100644 --- a/src/schemas/AuthorizationRequestPayloadVD12OID4VPD18.schema.ts +++ b/src/schemas/AuthorizationRequestPayloadVD12OID4VPD18.schema.ts @@ -1062,7 +1062,9 @@ export const AuthorizationRequestPayloadVD12OID4VPD18SchemaObj = { "pre-registered", "redirect_uri", "entity_id", - "did" + "did", + "x509_san_dns", + "x509_san_uri" ] } } diff --git a/src/schemas/AuthorizationResponseOpts.schema.ts b/src/schemas/AuthorizationResponseOpts.schema.ts index 01533a4e..9b410859 100644 --- a/src/schemas/AuthorizationResponseOpts.schema.ts +++ b/src/schemas/AuthorizationResponseOpts.schema.ts @@ -15,30 +15,17 @@ export const AuthorizationResponseOptsSchemaObj = { "registration": { "$ref": "#/definitions/ResponseRegistrationOpts" }, - "checkLinkedDomain": { - "$ref": "#/definitions/CheckLinkedDomain" - }, "version": { "$ref": "#/definitions/SupportedVersion" }, "audience": { "type": "string" }, - "signature": { - "anyOf": [ - { - "$ref": "#/definitions/InternalSignature" - }, - { - "$ref": "#/definitions/ExternalSignature" - }, - { - "$ref": "#/definitions/SuppliedSignature" - }, - { - "$ref": "#/definitions/NoSignature" - } - ] + "createJwtCallback": { + "$ref": "#/definitions/CreateJwtCallback" + }, + "jwtIssuer": { + "$ref": "#/definitions/JwtIssuer" }, "responseMode": { "$ref": "#/definitions/ResponseMode" @@ -59,6 +46,9 @@ export const AuthorizationResponseOptsSchemaObj = { "$ref": "#/definitions/PresentationExchangeResponseOpts" } }, + "required": [ + "createJwtCallback" + ], "additionalProperties": false }, "ResponseURIType": { @@ -1513,14 +1503,6 @@ export const AuthorizationResponseOptsSchemaObj = { "attester_signed" ] }, - "CheckLinkedDomain": { - "type": "string", - "enum": [ - "never", - "if_present", - "always" - ] - }, "SupportedVersion": { "type": "number", "enum": [ @@ -1530,33 +1512,7 @@ export const AuthorizationResponseOptsSchemaObj = { 71 ] }, - "InternalSignature": { - "type": "object", - "properties": { - "hexPrivateKey": { - "type": "string" - }, - "did": { - "type": "string" - }, - "alg": { - "$ref": "#/definitions/SigningAlgo" - }, - "kid": { - "type": "string" - }, - "customJwtSigner": { - "$ref": "#/definitions/Signer" - } - }, - "required": [ - "hexPrivateKey", - "did", - "alg" - ], - "additionalProperties": false - }, - "Signer": { + "CreateJwtCallback": { "properties": { "isFunction": { "type": "boolean", @@ -1564,82 +1520,196 @@ export const AuthorizationResponseOptsSchemaObj = { } } }, - "ExternalSignature": { - "type": "object", - "properties": { - "signatureUri": { - "type": "string" - }, - "did": { - "type": "string" - }, - "authZToken": { - "type": "string" - }, - "hexPublicKey": { - "type": "string" - }, - "alg": { - "$ref": "#/definitions/SigningAlgo" - }, - "kid": { - "type": "string" - } - }, - "required": [ - "signatureUri", - "did", - "alg" - ], - "additionalProperties": false - }, - "SuppliedSignature": { - "type": "object", - "properties": { - "signature": { + "JwtIssuer": { + "anyOf": [ + { + "type": "object", "properties": { - "isFunction": { - "type": "boolean", - "const": true + "method": { + "type": "string", + "const": "did" + }, + "options": { + "type": "object", + "additionalProperties": {}, + "description": "Additional options for the issuance context" + }, + "didUrl": { + "type": "string" + }, + "alg": { + "$ref": "#/definitions/SigningAlgo" } - } + }, + "required": [ + "alg", + "didUrl", + "method" + ], + "additionalProperties": false }, - "alg": { - "$ref": "#/definitions/SigningAlgo" + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "x5c" + }, + "options": { + "type": "object", + "additionalProperties": {}, + "description": "Additional options for the issuance context" + }, + "x5c": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of base64-encoded certificate strings in the DER-format.\n\nThe certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate." + }, + "issuer": { + "type": "string", + "description": "The issuer jwt\n\nThis value will be used as the iss value of the issue jwt. It is also used as the client_id. And will also be set as the redirect_uri\n\nIt must match an entry in the x5c certificate leaf entry dnsName / uriName" + }, + "clientIdScheme": { + "$ref": "#/definitions/ClientIdScheme" + } + }, + "required": [ + "clientIdScheme", + "issuer", + "method", + "x5c" + ], + "additionalProperties": false }, - "did": { - "type": "string" + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "jwk" + }, + "options": { + "type": "object", + "additionalProperties": {}, + "description": "Additional options for the issuance context" + }, + "jwk": { + "type": "object", + "properties": { + "alg": { + "type": "string" + }, + "crv": { + "type": "string" + }, + "d": { + "type": "string" + }, + "dp": { + "type": "string" + }, + "dq": { + "type": "string" + }, + "e": { + "type": "string" + }, + "ext": { + "type": "boolean" + }, + "k": { + "type": "string" + }, + "key_ops": { + "type": "array", + "items": { + "type": "string" + } + }, + "kty": { + "type": "string" + }, + "n": { + "type": "string" + }, + "oth": { + "type": "array", + "items": { + "type": "object", + "properties": { + "d": { + "type": "string" + }, + "r": { + "type": "string" + }, + "t": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "p": { + "type": "string" + }, + "q": { + "type": "string" + }, + "qi": { + "type": "string" + }, + "use": { + "type": "string" + }, + "x": { + "type": "string" + }, + "y": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "required": [ + "jwk", + "method" + ], + "additionalProperties": false }, - "kid": { - "type": "string" + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "custom" + }, + "options": { + "type": "object", + "additionalProperties": {}, + "description": "Additional options for the issuance context" + } + }, + "required": [ + "method" + ], + "additionalProperties": false } - }, - "required": [ - "signature", - "alg", - "did", - "kid" - ], - "additionalProperties": false + ] }, - "NoSignature": { - "type": "object", - "properties": { - "hexPublicKey": { - "type": "string" - }, - "did": { - "type": "string" - }, - "kid": { - "type": "string" - } - }, - "required": [ - "hexPublicKey", - "did" - ], - "additionalProperties": false + "ClientIdScheme": { + "type": "string", + "enum": [ + "pre-registered", + "redirect_uri", + "entity_id", + "did", + "x509_san_dns", + "x509_san_uri" + ] }, "PresentationExchangeResponseOpts": { "type": "object", diff --git a/src/types/Errors.ts b/src/types/Errors.ts index b9f23bca..30356ac0 100644 --- a/src/types/Errors.ts +++ b/src/types/Errors.ts @@ -3,15 +3,12 @@ enum SIOPErrors { INVALID_REQUEST = 'The request contained invalid or conflicting parameters', AUTH_REQUEST_EXPECTS_VP = 'authentication request expects a verifiable presentation in the response', AUTH_REQUEST_DOESNT_EXPECT_VP = "authentication request doesn't expect a verifiable presentation in the response", - BAD_INTERNAL_VERIFICATION_PARAMS = 'Error: One of the either didUrlResolver or both registry and rpcUrl must be set', BAD_STATE = 'The state in the payload does not match the supplied state', BAD_NONCE = 'The nonce in the payload does not match the supplied nonce', + NO_ALG_SUPPORTED = 'Algorithm not supported.', BAD_PARAMS = 'Wrong parameters provided.', - BAD_SIGNATURE_PARAMS = 'Signature parameters should be internal signature with hexPrivateKey, did, and an optional kid, or external signature parameters with signatureUri, did, and optionals parameters authZToken, hexPublicKey, and kid', - CANT_UNMARSHAL_JWT_VP = "can't unmarshal the presentation object", - + BAD_IDTOKEN_RESPONSE_OPTS = 'Id-token response options are not set.', NO_REQUEST_VERSION = 'No request spec version provided.', - NO_REQUEST = 'No request (payload) provided.', NO_RESPONSE = 'No response (payload) provided.', NO_PRESENTATION_SUBMISSION = 'The VP did not contain a presentation submission. Did you forget to call PresentationExchange.checkSubmissionFrom?', @@ -20,52 +17,28 @@ enum SIOPErrors { COULD_NOT_FIND_VCS_MATCHING_PD = 'Could not find VerifiableCredentials matching presentationDefinition object in the provided VC list', DIDAUTH_REQUEST_PAYLOAD_NOT_CREATED = 'DidAuthRequestPayload not created', DID_METHODS_NOT_SUPORTED = 'DID_METHODS_NOT_SUPPORTED', - EVALUATE_PRSENTATION_EXCHANGE_FAILED = 'Evaluation of presentation definition from the request against the Verifiable Presentation failed.', - ERROR_ON_POST_CALL = 'Error on Post call: ', - ERROR_RETRIEVING_DID_DOCUMENT = 'Error retrieving DID document', - ERROR_RETRIEVING_VERIFICATION_METHOD = 'Error retrieving verification method from did document', - ERROR_VALIDATING_NONCE = 'Error validating nonce.', ERROR_VERIFYING_SIGNATURE = 'Error verifying the DID Auth Token signature.', + ERROR_INVALID_JWT = 'Received an invalid JWT.', EXPIRED = 'The token has expired', INVALID_AUDIENCE = 'Audience is invalid. Should be a string value.', - ISS_DID_NOT_JWKS_URI_DID = ' DID in the jwks_uri does NOT match the DID in the iss claim', - JWK_THUMBPRINT_MISMATCH_SUB = 'JWK computed thumbprint does not match thumbprint included in Response Token sub claim', - LINK_DOMAIN_CANT_BE_VERIFIED = "Can't verify linked domains.", - MALFORMED_SIGNATURE_RESPONSE = 'Response format is malformed', - NO_ALG_SUPPORTED = 'Algorithm not supported.', - NO_ALG_SUPPORTED_YET = 'Algorithm is not supported yet. Only ES256 supported for this version.', NO_AUDIENCE = 'No audience found in JWT payload or not configured', - NO_DID_PAYLOAD = 'payload must contain did field in payload for self-issued tokens', - NO_IDENTIFIERS_URI = 'identifiersUri must be defined to get the publick key', - NO_ISS_DID = 'Token does not have a iss DID', - NO_URI = 'no URI was supplied', NO_JWT = 'no JWT was supplied', - NO_KEY_CURVE_SUPPORTED = 'Key Curve not supported.', NO_NONCE = 'No nonce found in JWT payload', NO_REFERENCE_URI = 'referenceUri must be defined when REFERENCE option is used', REFERENCE_URI_NO_PAYLOAD = 'referenceUri specified, but object to host there is not present', - NO_DID_METHOD_FOUND = 'No did method found.', - NO_SELFISSUED_ISS = 'The Response Token Issuer Claim (iss) MUST start with https://self-isued.me/v2', - NO_SUB_TYPE = 'No or empty sub_type found in JWT payload', + NO_SELF_ISSUED_ISS = 'The Response Token Issuer Claim (iss) MUST start with https://self-isued.me/v2', REGISTRATION_NOT_SET = 'Registration metadata not set.', REQUEST_CLAIMS_PRESENTATION_DEFINITION_BY_REF_AND_VALUE_NON_EXCLUSIVE = "Request claims can't have both 'presentation_definition' and 'presentation_definition_uri'", REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID = 'Presentation definition in the request claims is not valid', REQUEST_OBJECT_TYPE_NOT_SET = 'Request object type is not set.', - RESPONSE_AUD_MISMATCH_REDIRECT_URI = 'The audience (aud) in Response Token does NOT match the redirect_uri value sent in the Authentication Request', - RESPONSE_OPTS_MUST_CONTAIN_VERIFIABLE_CREDENTIALS_AND_HOLDER_DID = "Since the request has a presentation definition, response must contain verifiable credentials and holder's did", RESPONSE_OPTS_PRESENTATIONS_SUBMISSION_IS_NOT_VALID = 'presentation_submission object inside the response opts vp should be valid', RESPONSE_STATUS_UNEXPECTED = 'Received unexpected response status', REG_OBJ_N_REG_URI_CANT_BE_SET_SIMULTANEOUSLY = 'Registration can either be passed by value or passed by reference. Hence, registration object and registration URI can not be set simultaneously', REG_OBJ_MALFORMED = 'The registration object is malformed.', REG_PASS_BY_REFERENCE_INCORRECTLY = 'Request error', REGISTRATION_OBJECT_TYPE_NOT_SET = 'Registration object type is not set.', - SIGNATURE_OBJECT_TYPE_NOT_SET = 'Signature object type is not set.', SIOP_VERSION_NOT_SUPPORTED = 'The SIOP spec version could not inferred from the authentication request payload', - SUB_JWK_NOT_FOUND_OR_NOT_KID = 'Response Token does not contains sub_jwk claim or sub_jwk does not contain kid attribute.', - VERIFIABLE_PRESENTATION_FORMAT_NOT_SUPPORTED = "This type of verifiable presentation isn't supported in this version", NO_VERIFIABLE_PRESENTATION_NO_CREDENTIALS = 'Either no verifiable presentation or no credentials found in the verifiable presentation', - VERIFICATION_METHOD_NOT_SUPPORTED = 'Verification method not supported', - VERIFICATION_METHOD_NO_MATCH = "The verification method from the RP's DID Document does NOT match the kid of the SIOP Request", VERIFY_BAD_PARAMS = 'Verify bad parameters', VERIFIABLE_PRESENTATION_SIGNATURE_NOT_VALID = 'The signature of the verifiable presentation is not valid', VERIFIABLE_PRESENTATION_VERIFICATION_FUNCTION_MISSING = 'The verifiable presentation verification function is missing', diff --git a/src/types/JWT.types.ts b/src/types/JWT.types.ts index cd8a9e27..900aabbf 100644 --- a/src/types/JWT.types.ts +++ b/src/types/JWT.types.ts @@ -1,13 +1,23 @@ -import type { DIDResolutionResult, VerificationMethod } from 'did-resolver'; - +import { JwtHeader as jwtDecodeJwtHeader, JwtPayload as jwtDecodePayload } from 'jwt-decode'; export interface EcdsaSignature { r: string; s: string; recoveryParam?: number | null; } -// Signer interface conforming to the DID-JWT module -export type Signer = (data: string | Uint8Array) => Promise; +export type JwtHeader = jwtDecodeJwtHeader & { + alg?: string; + x5c?: string[]; + kid?: string; + jwk?: JsonWebKey; +} & Record; + +export type JwtPayload = jwtDecodePayload & { + client_id?: string; + nonce?: string; + request_uri?: string; + client_id_scheme?: string; +} & Record; export interface JWTPayload { iss?: string; @@ -26,9 +36,7 @@ export interface JWTPayload { export interface VerifiedJWT { payload: Partial; // The JWT payload - didResolutionResult?: DIDResolutionResult; // DID resolution result including DID document issuer: string; //The issuer (did) of the JWT - signer?: VerificationMethod; // The matching verification method from the DID that was used to sign jwt: string; // The JWT } diff --git a/src/types/JwtIssuer.ts b/src/types/JwtIssuer.ts new file mode 100644 index 00000000..eeb2ec1c --- /dev/null +++ b/src/types/JwtIssuer.ts @@ -0,0 +1,75 @@ +import { AuthorizationResponseOpts } from '../authorization-response'; +import { JwtProtectionMethod, JwtType } from '../helpers/jwtUtils'; + +import { JwtHeader, JwtPayload } from './JWT.types'; +import { ClientIdScheme, SigningAlgo } from './SIOP.types'; + +interface JwtIssuerBase { + method: JwtProtectionMethod; + /** + * Additional options for the issuance context + */ + options?: Record; +} + +interface JwtIssuanceContextBase extends JwtIssuerBase { + type: JwtType; +} + +interface RequestObjectContext extends JwtIssuanceContextBase { + type: 'request-object'; +} + +interface IdTokenContext extends JwtIssuanceContextBase { + type: 'id-token'; + authorizationResponseOpts: AuthorizationResponseOpts; +} + +export type JwtIssuanceContext = RequestObjectContext | IdTokenContext; + +interface JwtIssuerDid extends JwtIssuerBase { + method: 'did'; + + didUrl: string; + alg: SigningAlgo; +} + +interface JwtIssuerX5c extends JwtIssuerBase { + method: 'x5c'; + + /** + * + * Array of base64-encoded certificate strings in the DER-format. + * + * The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. + */ + x5c: Array; + + /** + * The issuer jwt + * + * This value will be used as the iss value of the issue jwt. + * It is also used as the client_id. + * And will also be set as the redirect_uri + * + * It must match an entry in the x5c certificate leaf entry dnsName / uriName + */ + issuer: string; + + clientIdScheme: ClientIdScheme; +} + +interface JwtIssuerJwk extends JwtIssuerBase { + method: 'jwk'; + + jwk: JsonWebKey; +} + +interface JwtIssuerCustom extends JwtIssuerBase { + method: 'custom'; +} + +export type JwtIssuer = JwtIssuerDid | JwtIssuerX5c | JwtIssuerJwk | JwtIssuerCustom; +export type JwtIssuerWithContext = JwtIssuer & JwtIssuanceContext; + +export type CreateJwtCallback = (jwtIssuer: JwtIssuerWithContext, jwt: { header: JwtHeader; payload: JwtPayload }) => Promise; diff --git a/src/types/JwtVerifier.ts b/src/types/JwtVerifier.ts new file mode 100644 index 00000000..59be1242 --- /dev/null +++ b/src/types/JwtVerifier.ts @@ -0,0 +1,93 @@ +import { calculateJwkThumbprintUri, getDigestAlgorithmFromJwkThumbprintUri } from '../helpers'; +import { JwtProtectionMethod, JwtType } from '../helpers/jwtUtils'; + +import SIOPErrors from './Errors'; +import { JWK, JwtHeader, JwtPayload } from './JWT.types'; + +interface JwtVerifierBase { + type: JwtType; + method: JwtProtectionMethod; +} + +interface DidJwtVerifier extends JwtVerifierBase { + method: 'did'; + didUrl: string; +} + +interface X5cJwtVerifier extends JwtVerifierBase { + method: 'x5c'; + + /** + * + * Array of base64-encoded certificate strings in the DER-format. + * + * The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. + */ + x5c: Array; + + /** + * The jwt issuer + */ + issuer: string; +} + +type JwkJwtVerifier = + | (JwtVerifierBase & { + method: 'jwk'; + type: 'id-token'; + + jwk: JsonWebKey; + jwkThumbprint: string; + }) + | (JwtVerifierBase & { + method: 'jwk'; + type: 'request-object'; + + jwk: JsonWebKey; + jwkThumbprint?: never; + }); + +interface CustomJwtVerifier extends JwtVerifierBase { + method: 'custom'; +} + +export type JwtVerifier = DidJwtVerifier | X5cJwtVerifier | CustomJwtVerifier | JwkJwtVerifier; + +export const getJwtVerifierWithContext = async (jwt: { header: JwtHeader; payload: JwtPayload }, type: JwtType): Promise => { + if (jwt.header.kid?.startsWith('did:')) { + if (!jwt.header.kid.includes('#')) { + throw new Error(`${SIOPErrors.ERROR_INVALID_JWT}. '${type}' contains an invalid kid header.`); + } + return { method: 'did', didUrl: jwt.header.kid, type }; + } else if (jwt.header.x5c) { + if (!Array.isArray(jwt.header.x5c) || typeof jwt.header.x5c.some((cert) => typeof cert !== 'string')) { + throw new Error(`${SIOPErrors.ERROR_INVALID_JWT}. '${type}' contains an invalid x5c header.`); + } + return { method: 'x5c', x5c: jwt.header.x5c, issuer: jwt.payload.iss, type }; + } else if (jwt.header.jwk) { + if (typeof jwt.header.jwk !== 'object') { + throw new Error(`${SIOPErrors.ERROR_INVALID_JWT} '${type}' contains an invalid jwk header.`); + } + if (type === 'id-token') { + if (typeof jwt.payload.sub_jwk !== 'string') { + throw new Error(`${SIOPErrors.ERROR_INVALID_JWT} '${type}' is missing the 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(`${SIOPErrors.ERROR_INVALID_JWT} '${type}' contains an invalid sub_jwk claim.`); + } + + return { method: 'jwk', type, jwk: jwt.header.jwk, jwkThumbprint: jwt.payload.sub_jwk }; + } + + return { method: 'jwk', type, jwk: jwt.header.jwk }; + } else { + return { method: 'custom', type }; + } +}; + +export type VerifyJwtCallback = (jwtVerifier: JwtVerifier, jwt: { header: JwtHeader; payload: JwtPayload; raw: string }) => Promise; diff --git a/src/types/SIOP.types.ts b/src/types/SIOP.types.ts index 7acd7024..2f819c26 100644 --- a/src/types/SIOP.types.ts +++ b/src/types/SIOP.types.ts @@ -11,9 +11,6 @@ import { W3CVerifiablePresentation, WrappedVerifiablePresentation, } from '@sphereon/ssi-types'; -import { VerifyCallback as WellknownDIDVerifyCallback } from '@sphereon/wellknown-dids-client'; -import { Signer } from 'did-jwt'; -import { DIDResolutionResult, VerificationMethod } from 'did-resolver'; import { AuthorizationRequest, CreateAuthorizationRequestOpts, PropertyTargets, VerifyAuthorizationRequestOpts } from '../authorization-request'; import { @@ -26,7 +23,7 @@ import { import { RequestObject, RequestObjectOpts } from '../request-object'; import { IRPSessionManager } from '../rp'; -import { EcdsaSignature, JWTPayload, ResolveOpts, VerifiedJWT } from './index'; +import { JWTPayload, VerifiedJWT } from './index'; export const DEFAULT_EXPIRATION_TIME = 10 * 60; // https://openid.net/specs/openid-connect-core-1_0.html#RequestObject @@ -88,7 +85,7 @@ export interface AuthorizationRequestPayloadVD12OID4VPD18 response_uri?: string; // New since OID4VP18 OPTIONAL. The Response URI to which the Wallet MUST send the Authorization Response using an HTTPS POST request as defined by the Response Mode direct_post. The Response URI receives all Authorization Response parameters as defined by the respective Response Type. When the response_uri parameter is present, the redirect_uri Authorization Request parameter MUST NOT be present. If the redirect_uri Authorization Request parameter is present when the Response Mode is direct_post, the Wallet MUST return an invalid_request Authorization Response error. } -export type ClientIdScheme = 'pre-registered' | 'redirect_uri' | 'entity_id' | 'did'; +export type ClientIdScheme = 'pre-registered' | 'redirect_uri' | 'entity_id' | 'did' | 'x509_san_dns' | 'x509_san_uri'; // https://openid.bitbucket.io/connect/openid-connect-self-issued-v2-1_0.html#section-10 export type AuthorizationRequestPayload = @@ -163,10 +160,6 @@ export interface AuthorizationResponsePayload { [x: string]: any; } -export interface PresentationDefinitionPayload { - presentation_definition: PresentationDefinitionV1 | PresentationDefinitionV2; -} - export interface IdTokenClaimPayload { // eslint-disable-next-line @typescript-eslint/no-explicit-any [x: string]: any; @@ -178,6 +171,7 @@ export interface VpTokenClaimPayload { } export interface ClaimPayloadCommon { + // eslint-disable-next-line @typescript-eslint/no-explicit-any [x: string]: any; } @@ -204,19 +198,6 @@ export interface RequestStateInfo { iat: number; } -/** - * - */ -export interface AuthorizationResponseResult { - idToken: string; - nonce: string; - state: string; - idTokenPayload: IDTokenPayload; - responsePayload: AuthorizationResponsePayload; - verifyOpts?: VerifyAuthorizationRequestOpts; - responseOpts: AuthorizationResponseOpts; -} - interface DiscoveryMetadataCommonOpts { //TODO add the check: Mandatory if PassBy.Value authorizationEndpoint?: Schema | string; @@ -399,6 +380,7 @@ export type RPRegistrationMetadataOpts = Partial< | 'clientPurpose' > > & { + client_id_scheme?: ClientIdScheme; // eslint-disable-next-line @typescript-eslint/no-explicit-any [x: string]: any; }; @@ -485,84 +467,19 @@ export enum ResponseContext { OP = 'op', } -export enum CheckLinkedDomain { - NEVER = 'never', // We don't want to verify Linked domains - IF_PRESENT = 'if_present', // If present, did-auth-siop will check the linked domain, if exist and not valid, throws an exception - ALWAYS = 'always', // We'll always check the linked domains, if not exist or not valid, throws an exception -} - -export interface InternalSignature { - hexPrivateKey: string; // hex private key Only secp256k1 format - did: string; - - alg: SigningAlgo; - kid?: string; // Optional: key identifier - - customJwtSigner?: Signer; -} - -export interface SuppliedSignature { - signature: (data: string | Uint8Array) => Promise; - - alg: SigningAlgo; - did: string; - kid: string; -} - -export interface NoSignature { - hexPublicKey: string; // hex public key - did: string; - kid?: string; // Optional: key identifier -} - -export interface ExternalSignature { - signatureUri: string; // url to call to generate a withSignature - did: string; - authZToken?: string; // Optional: bearer token to use to the call - hexPublicKey?: string; // Optional: hex encoded public key to compute JWK key, if not possible from DIDres Document - - alg: SigningAlgo; - kid?: string; // Optional: key identifier. default did#keys-1 -} - -export enum VerificationMode { - INTERNAL, - EXTERNAL, -} - export interface Verification { - checkLinkedDomain?: CheckLinkedDomain; - wellknownDIDVerifyCallback?: WellknownDIDVerifyCallback; presentationVerificationCallback?: PresentationVerificationCallback; - mode: VerificationMode; - resolveOpts: ResolveOpts; revocationOpts?: RevocationOpts; replayRegistry?: IRPSessionManager; } -export type InternalVerification = Verification; - -export interface ExternalVerification extends Verification { - verifyUri: string; // url to call to verify the id_token withSignature - authZToken?: string; // Optional: bearer token to use to the call -} - export interface ResponseClaims { verified_claims?: string; encryption_key?: JsonWebKey; } -export interface DidAuthValidationResponse { - signatureValidation: boolean; - signer: VerificationMethod; - payload: JWTPayload; -} - export interface VerifiedIDToken { jwt: string; - didResolutionResult: DIDResolutionResult; - signer: VerificationMethod; - issuer: string; payload: IDTokenPayload; verifyOpts: VerifyAuthorizationResponseOpts; } @@ -710,18 +627,6 @@ export enum ResponseIss { JWT_VC_PRESENTATION_V1 = 'https://self-issued.me/v2/openid-vc', } -export const isInternalSignature = (object: InternalSignature | ExternalSignature | SuppliedSignature | NoSignature): object is InternalSignature => - 'hexPrivateKey' in object && 'did' in object; - -export const isExternalSignature = (object: InternalSignature | ExternalSignature | SuppliedSignature | NoSignature): object is ExternalSignature => - 'signatureUri' in object && 'did' in object; - -export const isSuppliedSignature = (object: InternalSignature | ExternalSignature | SuppliedSignature | NoSignature): object is SuppliedSignature => - 'signature' in object; - -export const isNoSignature = (object: InternalSignature | ExternalSignature | NoSignature): object is NoSignature => - 'hexPublicKey' in object && 'did' in object; - export const isRequestOpts = (object: CreateAuthorizationRequestOpts | AuthorizationResponseOpts): object is CreateAuthorizationRequestOpts => 'requestBy' in object; @@ -735,11 +640,6 @@ export const isRequestPayload = ( export const isResponsePayload = (object: RequestObjectPayload | IDTokenPayload): object is IDTokenPayload => 'iss' in object && 'aud' in object; -export const isInternalVerification = (object: InternalVerification | ExternalVerification): object is InternalVerification => - object.mode === VerificationMode.INTERNAL; /* && !isExternalVerification(object)*/ -export const isExternalVerification = (object: InternalVerification | ExternalVerification): object is ExternalVerification => - object.mode === VerificationMode.EXTERNAL; /*&& 'verifyUri' in object || 'authZToken' in object*/ - export const isVP = (object: IVerifiablePresentation | IPresentation): object is IVerifiablePresentation => 'presentation' in object; export const isPresentation = (object: IVerifiablePresentation | IPresentation): object is IPresentation => 'presentation_submission' in object; diff --git a/src/types/SSI.types.ts b/src/types/SSI.types.ts deleted file mode 100644 index 9faedda8..00000000 --- a/src/types/SSI.types.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { JWTVerifyOptions } from 'did-jwt'; -import { DIDDocument as DIFDIDDocument, Resolvable } from 'did-resolver'; - -export interface ResolveOpts { - jwtVerifyOpts?: JWTVerifyOptions; - resolver?: Resolvable; - resolveUrl?: string; - - // By default we fallback to the universal resolver for max interop. - noUniversalResolverFallback?: boolean; - subjectSyntaxTypesSupported?: string[]; -} - -/*export interface PublicKey { - id: string; - type: string; - controller: string; - ethereumAddress?: string; - publicKeyBase64?: string; - publicKeyBase58?: string; - publicKeyHex?: string; - publicKeyPem?: string; - publicKeyJwk?: JWK; -}*/ -/*export interface VerificationMethod { - id: string; - type: string; - controller: string; - publicKeyHex?: string; - publicKeyMultibase?: string; - publicKeyBase58?: string; - publicKeyJwk?: JWK; -}*/ -/* -export interface Authentication { - type: string; - publicKey: string; -}*/ -export interface LinkedDataProof { - type: string; - created: string; - creator: string; - nonce: string; - signatureValue: string; -} -/* -export interface ServiceEndpoint { - id: string; - type: string; - serviceEndpoint: string; - description?: string; -} -*/ -export interface DIDDocument extends DIFDIDDocument { - owner?: string; - created?: string; - updated?: string; - proof?: LinkedDataProof; -} diff --git a/src/types/index.ts b/src/types/index.ts index 3ae6982b..d090ea4d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,6 +3,7 @@ import SIOPErrors from './Errors'; export { SIOPErrors }; export * from './JWT.types'; export * from './SIOP.types'; -export * from './SSI.types'; export * from './Events'; export * from './SessionManager'; +export * from './JwtIssuer'; +export * from './JwtVerifier'; diff --git a/test/AuthenticationRequest.request.spec.ts b/test/AuthenticationRequest.request.spec.ts index e047f384..03fc90b1 100644 --- a/test/AuthenticationRequest.request.spec.ts +++ b/test/AuthenticationRequest.request.spec.ts @@ -17,6 +17,7 @@ import { } from '../src'; import SIOPErrors from '../src/types/Errors'; +import { getCreateJwtCallback } from './DidJwtTestUtils'; import { WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; import { VERIFIER_LOGO_FOR_CLIENT, @@ -99,14 +100,19 @@ describe('create Request Uri should', () => { redirect_uri: EXAMPLE_REDIRECT_URL, }, requestObject: { + jwtIssuer: { + method: 'did', + didUrl: KID, + alg: SigningAlgo.ES256, + }, passBy: PassBy.REFERENCE, reference_uri: EXAMPLE_REFERENCE_URL, - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: HEX_KEY, alg: SigningAlgo.ES256, did: DID, kid: KID, - }, + }), payload: { client_id: WELL_KNOWN_OPENID_FEDERATION, scope: 'openid', @@ -162,15 +168,16 @@ describe('create Request Uri should', () => { const opts: CreateAuthorizationRequestOpts = { version: SupportedVersion.SIOPv2_ID1, requestObject: { + jwtIssuer: { method: 'did', didUrl: KID, alg: SigningAlgo.ES256 }, passBy: PassBy.REFERENCE, reference_uri: EXAMPLE_REFERENCE_URL, - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: 'd474ffdb3ea75fbb3f07673e67e52002a3b7eb42767f709f4100acf493c7fc8743017577997b72e7a8b4bce8c32c8e78fd75c1441e95d6aaa888056d1200beb3', did: 'did:key:z6MkixpejjET5qJK4ebN5m3UcdUPmYV4DPSCs1ALH8x2UCfc', kid: 'did:key:z6MkixpejjET5qJK4ebN5m3UcdUPmYV4DPSCs1ALH8x2UCfc#z6MkixpejjET5qJK4ebN5m3UcdUPmYV4DPSCs1ALH8x2UCfc', alg: SigningAlgo.EDDSA, - }, + }), payload: { client_id: WELL_KNOWN_OPENID_FEDERATION, scope: 'test', @@ -215,16 +222,19 @@ describe('create Request Uri should', () => { expect.assertions(3); const opts: CreateAuthorizationRequestOpts = { version: SupportedVersion.SIOPv2_ID1, - requestObject: { passBy: PassBy.VALUE, - - signature: { + jwtIssuer: { + method: 'did', + didUrl: KID, + alg: SigningAlgo.ES256K, + }, + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: HEX_KEY, did: DID, kid: KID, alg: SigningAlgo.ES256K, - }, + }), payload: { client_id: WELL_KNOWN_OPENID_FEDERATION, scope: 'test', @@ -274,15 +284,15 @@ describe('create Request JWT should', () => { payload: { redirect_uri: EXAMPLE_REDIRECT_URL, }, - requestObject: { - passBy: 'other type', - - signature: { + jwtIssuer: { method: 'did', didUrl: KID, alg: SigningAlgo.ES256K }, + passBy: 'other type' as never, + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: HEX_KEY, did: DID, kid: KID, - }, + alg: SigningAlgo.ES256K, + }), }, registration: { idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], @@ -301,18 +311,19 @@ describe('create Request JWT should', () => { it('throw NO_REFERENCE_URI when no referenceUri is passed with REFERENCE requestBy type is set', async () => { expect.assertions(1); const opts = { + version: SupportedVersion.SIOPv2_ID1, payload: { redirect_uri: EXAMPLE_REDIRECT_URL, }, - requestObject: { passBy: PassBy.REFERENCE, - - signature: { + jwtIssuer: { method: 'did', didUrl: KID, alg: SigningAlgo.ES256K }, + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: HEX_KEY, did: DID, kid: KID, - }, + alg: SigningAlgo.ES256K, + }), }, registration: { idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], @@ -328,31 +339,6 @@ describe('create Request JWT should', () => { await expect(RequestObject.fromOpts(opts as never)).rejects.toThrow(SIOPErrors.NO_REFERENCE_URI); }); - it('throw BAD_SIGNATURE_PARAMS when withSignature Type is neither internal nor external', async () => { - expect.assertions(1); - const opts = { - requestObject: { - passBy: PassBy.REFERENCE, - reference_uri: EXAMPLE_REFERENCE_URL, - payload: { - redirect_uri: EXAMPLE_REDIRECT_URL, - }, - signature: { did: 'did:example:123' }, - }, - clientMetadata: { - idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], - subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], - vpFormatsSupported: { - ldp_vc: { - proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], - }, - }, - passBy: PassBy.VALUE, - }, - }; - await expect((await RequestObject.fromOpts(opts as never)).toJwt()).rejects.toThrow(SIOPErrors.BAD_SIGNATURE_PARAMS); - }); - it('throw REGISTRATION_OBJECT_TYPE_NOT_SET when registrationBy type is neither REFERENCE nor VALUE', async () => { expect.assertions(1); const opts = { @@ -427,14 +413,15 @@ describe('create Request JWT should', () => { }, requestObject: { + jwtIssuer: { method: 'did', didUrl: KID, alg: SigningAlgo.ES256K }, passBy: PassBy.REFERENCE, reference_uri: EXAMPLE_REFERENCE_URL, - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: HEX_KEY, did: DID, kid: KID, alg: SigningAlgo.ES256K, - }, + }), payload: { client_id: 'test_client_id', scope: 'test', @@ -549,15 +536,16 @@ describe('create Request JWT should', () => { }, },*/ requestObject: { + jwtIssuer: { method: 'did', didUrl: KID, alg: SigningAlgo.ES256K }, passBy: PassBy.REFERENCE, reference_uri: EXAMPLE_REFERENCE_URL, - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: HEX_KEY, did: DID, kid: KID, alg: SigningAlgo.ES256K, - }, + }), payload: { client_id: WELL_KNOWN_OPENID_FEDERATION, scope: 'test', @@ -642,15 +630,16 @@ describe('create Request JWT should', () => { }, requestObject: { + jwtIssuer: { method: 'did', didUrl: KID, alg: SigningAlgo.ES256K }, passBy: PassBy.REFERENCE, reference_uri: EXAMPLE_REFERENCE_URL, - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: HEX_KEY, did: DID, kid: KID, alg: SigningAlgo.ES256K, - }, + }), payload: { client_id: 'test_client_id', scope: 'test', diff --git a/test/AuthenticationRequest.verify.spec.ts b/test/AuthenticationRequest.verify.spec.ts index ae1bf820..33a8de50 100644 --- a/test/AuthenticationRequest.verify.spec.ts +++ b/test/AuthenticationRequest.verify.spec.ts @@ -1,5 +1,4 @@ import { IProofType } from '@sphereon/ssi-types'; -import { IVerifyCallbackArgs, IVerifyCredentialResult } from '@sphereon/wellknown-dids-client'; import Ajv from 'ajv'; import * as dotenv from 'dotenv'; @@ -11,15 +10,15 @@ import { ResponseType, Scope, SigningAlgo, - SubjectSyntaxTypesSupportedValues, SubjectType, SupportedVersion, - VerificationMode, VerifyAuthorizationRequestOpts, } from '../src'; import { RPRegistrationMetadataPayloadSchemaObj } from '../src/schemas'; import SIOPErrors from '../src/types/Errors'; +import { getCreateJwtCallback, getVerifyJwtCallback } from './DidJwtTestUtils'; +import { getResolver } from './ResolverTestUtils'; import { metadata, mockedGetEnterpriseAuthToken, WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; import { UNIT_TEST_TIMEOUT, @@ -30,9 +29,6 @@ import { VERIFIERZ_PURPOSE_TO_VERIFY_NL, } from './data/mockedData'; -const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello'; -const EXAMPLE_REFERENCE_URL = 'https://rp.acme.com/siop/jwts'; - dotenv.config(); describe('verifyJWT should', () => { @@ -250,28 +246,34 @@ describe('verifyJWT should', () => { it('throw BAD_NONCE when a different nonce is supplied during verification', async () => { expect.assertions(1); + + const mockEntity = await mockedGetEnterpriseAuthToken('COMPANY AA INC'); + const requestOpts: CreateAuthorizationRequestOpts = { version: SupportedVersion.SIOPv2_ID1, - requestObject: { - passBy: PassBy.REFERENCE, - reference_uri: EXAMPLE_REFERENCE_URL, - - signature: { - hexPrivateKey: - 'd474ffdb3ea75fbb3f07673e67e52002a3b7eb42767f709f4100acf493c7fc8743017577997b72e7a8b4bce8c32c8e78fd75c1441e95d6aaa888056d1200beb3', - did: 'did:key:z6MkixpejjET5qJK4ebN5m3UcdUPmYV4DPSCs1ALH8x2UCfc', - kid: 'did:key:z6MkixpejjET5qJK4ebN5m3UcdUPmYV4DPSCs1ALH8x2UCfc#z6MkixpejjET5qJK4ebN5m3UcdUPmYV4DPSCs1ALH8x2UCfc', - alg: SigningAlgo.EDDSA, + jwtIssuer: { + method: 'did', + didUrl: `${mockEntity.did}#controller`, + alg: SigningAlgo.ES256K, }, + passBy: PassBy.REFERENCE, + reference_uri: 'https://my-request.com/here', + createJwtCallback: getCreateJwtCallback({ + hexPrivateKey: mockEntity.hexPrivateKey, + did: mockEntity.did, + kid: `${mockEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }), payload: { - state: 'expected state', client_id: WELL_KNOWN_OPENID_FEDERATION, scope: 'test', response_type: 'id_token', + state: '12345', + nonce: '12345', request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], - redirect_uri: EXAMPLE_REDIRECT_URL, - nonce: 'expected nonce', + authorization_endpoint: '', + redirect_uri: 'https://acme.com/hello', }, }, clientMetadata: { @@ -281,7 +283,7 @@ describe('verifyJWT should', () => { subjectTypesSupported: [SubjectType.PAIRWISE], idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256K], requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256K], - subject_syntax_types_supported: ['did:ethr:', SubjectSyntaxTypesSupportedValues.DID], + subject_syntax_types_supported: ['did:ethr:'], vpFormatsSupported: { ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], @@ -290,47 +292,46 @@ describe('verifyJWT should', () => { passBy: PassBy.VALUE, logo_uri: VERIFIER_LOGO_FOR_CLIENT, clientName: VERIFIER_NAME_FOR_CLIENT, - 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100308', + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100309', clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }, }; - const requestObject = await RequestObject.fromOpts(requestOpts); + const resolver = getResolver('ethr'); const verifyOpts: VerifyAuthorizationRequestOpts = { - verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: { - subjectSyntaxTypesSupported: ['did:key'], - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - wellknownDIDVerifyCallback: async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }), - }, - correlationId: '1234', + verifyJwtCallback: getVerifyJwtCallback(resolver, { checkLinkedDomain: 'if_present' }), + verification: {}, supportedVersions: [SupportedVersion.SIOPv2_ID1], - nonce: 'This nonce is different and should throw error', + correlationId: '1234', + nonce: 'invalid_nonce', }; - // expect.assertions(1); - await expect(AuthorizationRequest.verify(await requestObject.toJwt(), verifyOpts)).rejects.toThrow(SIOPErrors.BAD_NONCE); + const jwt = await requestObject.toJwt(); + await expect(AuthorizationRequest.verify(jwt, verifyOpts)).rejects.toThrow(SIOPErrors.BAD_NONCE); }); + it( 'succeed if a valid JWT is passed', async () => { const mockEntity = await mockedGetEnterpriseAuthToken('COMPANY AA INC'); const requestOpts: CreateAuthorizationRequestOpts = { version: SupportedVersion.SIOPv2_ID1, - requestObject: { + jwtIssuer: { + method: 'did', + didUrl: `${mockEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }, passBy: PassBy.REFERENCE, reference_uri: 'https://my-request.com/here', - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: mockEntity.hexPrivateKey, did: mockEntity.did, kid: `${mockEntity.did}#controller`, alg: SigningAlgo.ES256K, - }, + }), payload: { client_id: WELL_KNOWN_OPENID_FEDERATION, scope: 'test', @@ -365,15 +366,10 @@ describe('verifyJWT should', () => { }; const requestObject = await RequestObject.fromOpts(requestOpts); + const resolver = getResolver('ethr'); const verifyOpts: VerifyAuthorizationRequestOpts = { - verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: { - subjectSyntaxTypesSupported: ['did:ethr'], - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - wellknownDIDVerifyCallback: async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }), - }, + verifyJwtCallback: getVerifyJwtCallback(resolver, { checkLinkedDomain: 'if_present' }), + verification: {}, supportedVersions: [SupportedVersion.SIOPv2_ID1], correlationId: '1234', }; diff --git a/test/AuthenticationResponse.response.spec.ts b/test/AuthenticationResponse.response.spec.ts index f2d9ee1f..414031e1 100644 --- a/test/AuthenticationResponse.response.spec.ts +++ b/test/AuthenticationResponse.response.spec.ts @@ -1,11 +1,9 @@ import { IPresentationDefinition } from '@sphereon/pex'; import { ICredential, IPresentation, IProofType, IVerifiableCredential, IVerifiablePresentation } from '@sphereon/ssi-types'; -import { IVerifyCallbackArgs, IVerifyCredentialResult } from '@sphereon/wellknown-dids-client'; import { AuthorizationResponse, AuthorizationResponseOpts, - CheckLinkedDomain, CreateAuthorizationRequestOpts, PassBy, PresentationExchange, @@ -19,13 +17,14 @@ import { SubjectIdentifierType, SubjectType, SupportedVersion, - VerificationMode, VerifyAuthorizationRequestOpts, VPTokenLocation, } from '../src'; import { createPresentationSubmission } from '../src/authorization-response/OpenID4VP'; import SIOPErrors from '../src/types/Errors'; +import { getCreateJwtCallback, getVerifyJwtCallback } from './DidJwtTestUtils'; +import { getResolver } from './ResolverTestUtils'; import { mockedGetEnterpriseAuthToken, WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; import { UNIT_TEST_TIMEOUT, @@ -50,7 +49,6 @@ const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello'; describe('create JWT from Request JWT should', () => { const responseOpts: AuthorizationResponseOpts = { - checkLinkedDomain: CheckLinkedDomain.NEVER, responseURI: EXAMPLE_REDIRECT_URL, responseURIType: 'redirect_uri', responseMode: ResponseMode.POST, @@ -74,22 +72,19 @@ describe('create JWT from Request JWT should', () => { clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }, - signature: { + createJwtCallback: getCreateJwtCallback({ did: DID, hexPrivateKey: HEX_KEY, kid: KID, alg: SigningAlgo.ES256K, - }, + }), + jwtIssuer: { method: 'did', didUrl: KID, alg: SigningAlgo.ES256K }, }; + + const resolver = getResolver('ethr'); const verifyOpts: VerifyAuthorizationRequestOpts = { - verification: { - resolveOpts: { - subjectSyntaxTypesSupported: ['did:ethr'], - }, - mode: VerificationMode.INTERNAL, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - wellknownDIDVerifyCallback: async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }), - }, + verifyJwtCallback: getVerifyJwtCallback(resolver), + verification: {}, supportedVersions: [SupportedVersion.SIOPv2_ID1], correlationId: '1234', }; @@ -125,13 +120,14 @@ describe('create JWT from Request JWT should', () => { },*/ requestObject: { passBy: PassBy.REFERENCE, + jwtIssuer: { method: 'did', didUrl: `${mockReqEntity.did}#controller`, alg: SigningAlgo.ES256K }, reference_uri: 'https://my-request.com/here', - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: mockReqEntity.hexPrivateKey, did: mockReqEntity.did, kid: `${mockReqEntity.did}#controller`, alg: SigningAlgo.ES256K, - }, + }), payload: { nonce: '12345', state: '12345', @@ -163,7 +159,6 @@ describe('create JWT from Request JWT should', () => { }, }; const responseOpts: AuthorizationResponseOpts = { - checkLinkedDomain: CheckLinkedDomain.NEVER, responseURI: EXAMPLE_REDIRECT_URL, responseURIType: 'redirect_uri', registration: { @@ -187,12 +182,13 @@ describe('create JWT from Request JWT should', () => { clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }, - signature: { + createJwtCallback: getCreateJwtCallback({ did: mockResEntity.did, hexPrivateKey: mockResEntity.hexPrivateKey, kid: `${mockResEntity.did}#controller`, alg: SigningAlgo.ES256K, - }, + }), + jwtIssuer: { method: 'did', didUrl: `${mockResEntity.did}#controller`, alg: SigningAlgo.ES256K }, responseMode: ResponseMode.POST, }; @@ -218,12 +214,13 @@ describe('create JWT from Request JWT should', () => { requestObject: { passBy: PassBy.REFERENCE, reference_uri: 'https://my-request.com/here', - signature: { + jwtIssuer: { method: 'did', didUrl: `${mockReqEntity.did}#controller`, alg: SigningAlgo.ES256K }, + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: mockReqEntity.hexPrivateKey, did: mockReqEntity.did, kid: `${mockReqEntity.did}#controller`, alg: SigningAlgo.ES256K, - }, + }), payload: { client_id: WELL_KNOWN_OPENID_FEDERATION, scope: 'test', @@ -253,7 +250,6 @@ describe('create JWT from Request JWT should', () => { }, }; const responseOpts: AuthorizationResponseOpts = { - checkLinkedDomain: CheckLinkedDomain.NEVER, responseURI: EXAMPLE_REDIRECT_URL, responseURIType: 'redirect_uri', registration: { @@ -277,18 +273,21 @@ describe('create JWT from Request JWT should', () => { clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }, - signature: { + createJwtCallback: getCreateJwtCallback({ did: mockResEntity.did, hexPrivateKey: mockResEntity.hexPrivateKey, kid: `${mockResEntity.did}#controller`, alg: SigningAlgo.ES256K, - }, + }), + jwtIssuer: { method: 'did', didUrl: `${mockResEntity.did}#controller`, alg: SigningAlgo.ES256K }, responseMode: ResponseMode.POST, }; const requestObject = await RequestObject.fromOpts(requestOpts); // console.log(JSON.stringify(await AuthorizationResponse.fromRequestObject(await requestObject.toJwt(), responseOpts, verifyOpts))); - await expect(AuthorizationResponse.fromRequestObject(await requestObject.toJwt(), responseOpts, verifyOpts)).resolves.toBeDefined(); + const jwt = await requestObject.toJwt(); + const response = await AuthorizationResponse.fromRequestObject(jwt, responseOpts, verifyOpts); + await expect(response).toBeDefined(); }, UNIT_TEST_TIMEOUT, ); @@ -341,12 +340,13 @@ describe('create JWT from Request JWT should', () => { requestObject: { passBy: PassBy.REFERENCE, reference_uri: 'https://my-request.com/here', - signature: { + jwtIssuer: { method: 'did', didUrl: mockReqEntity.did, alg: SigningAlgo.ES256K }, + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: mockReqEntity.hexPrivateKey, did: mockReqEntity.did, kid: `${mockReqEntity.did}#controller`, alg: SigningAlgo.ES256K, - }, + }), payload: { client_id: WELL_KNOWN_OPENID_FEDERATION, scope: 'test', @@ -415,7 +415,6 @@ describe('create JWT from Request JWT should', () => { {}, ); const responseOpts: AuthorizationResponseOpts = { - checkLinkedDomain: CheckLinkedDomain.NEVER, responseURI: EXAMPLE_REDIRECT_URL, responseURIType: 'redirect_uri', registration: { @@ -437,12 +436,13 @@ describe('create JWT from Request JWT should', () => { clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }, - signature: { + createJwtCallback: getCreateJwtCallback({ did: mockResEntity.did, hexPrivateKey: mockResEntity.hexPrivateKey, kid: `${mockResEntity.did}#controller`, alg: SigningAlgo.ES256K, - }, + }), + jwtIssuer: { method: 'did', didUrl: `${mockResEntity.did}#controller`, alg: SigningAlgo.ES256K }, presentationExchange: { verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], vpTokenLocation: VPTokenLocation.ID_TOKEN, @@ -457,7 +457,9 @@ describe('create JWT from Request JWT should', () => { /* console.log( JSON.stringify(await AuthenticationResponse.createJWTFromRequestJWT(requestWithJWT.jwt, responseOpts, verifyOpts)) );*/ - await expect(await AuthorizationResponse.fromRequestObject(await requestObject.toJwt(), responseOpts, verifyOpts)).toBeDefined(); + const jwt = await requestObject.toJwt(); + const authorizationRequest = await AuthorizationResponse.fromRequestObject(jwt, responseOpts, verifyOpts); + await expect(authorizationRequest).toBeDefined(); }); it('succeed when valid JWT with PD is passed in for id_token', async () => { @@ -503,14 +505,15 @@ describe('create JWT from Request JWT should', () => { },*/ }, requestObject: { + jwtIssuer: { method: 'did', didUrl: `${mockReqEntity.did}#controller`, alg: SigningAlgo.ES256K }, passBy: PassBy.REFERENCE, reference_uri: 'https://my-request.com/here', - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: mockReqEntity.hexPrivateKey, did: mockReqEntity.did, kid: `${mockReqEntity.did}#controller`, alg: SigningAlgo.ES256K, - }, + }), payload: { client_id: WELL_KNOWN_OPENID_FEDERATION, scope: 'test', @@ -591,7 +594,6 @@ describe('create JWT from Request JWT should', () => { {}, ); const responseOpts: AuthorizationResponseOpts = { - checkLinkedDomain: CheckLinkedDomain.NEVER, responseURI: EXAMPLE_REDIRECT_URL, responseURIType: 'redirect_uri', registration: { @@ -614,12 +616,13 @@ describe('create JWT from Request JWT should', () => { clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }, - signature: { + createJwtCallback: getCreateJwtCallback({ did: mockResEntity.did, hexPrivateKey: mockResEntity.hexPrivateKey, kid: `${mockResEntity.did}#controller`, alg: SigningAlgo.ES256K, - }, + }), + jwtIssuer: { method: 'did', didUrl: `${mockResEntity.did}#controller`, alg: SigningAlgo.ES256K }, presentationExchange: { verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], presentationSubmission: await createPresentationSubmission([verifiablePresentationResult.verifiablePresentation], { @@ -632,6 +635,8 @@ describe('create JWT from Request JWT should', () => { }; const requestObject = await RequestObject.fromOpts(requestOpts); - await expect(AuthorizationResponse.fromRequestObject(await requestObject.toJwt(), responseOpts, verifyOpts)).resolves.toBeDefined(); + const jwt = await requestObject.toJwt(); + const authResponse = AuthorizationResponse.fromRequestObject(jwt, responseOpts, verifyOpts); + await expect(authResponse).toBeDefined(); }); }); diff --git a/test/AuthenticationResponse.verify.spec.ts b/test/AuthenticationResponse.verify.spec.ts index 1eb497f0..f3724878 100644 --- a/test/AuthenticationResponse.verify.spec.ts +++ b/test/AuthenticationResponse.verify.spec.ts @@ -1,8 +1,9 @@ -import { IVerifyCallbackArgs, IVerifyCredentialResult } from '@sphereon/wellknown-dids-client'; - -import { IDToken, VerificationMode, VerifyAuthorizationResponseOpts } from '../src'; +import { IDToken, VerifyAuthorizationResponseOpts } from '../src'; import SIOPErrors from '../src/types/Errors'; +import { getVerifyJwtCallback } from './DidJwtTestUtils'; +import { getResolver } from './ResolverTestUtils'; + // const EXAMPLE_REDIRECT_URL = "https://acme.com/hello"; const DID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0'; @@ -13,14 +14,10 @@ describe('verify JWT from Request JWT should', () => { const verifyOpts: VerifyAuthorizationResponseOpts = { correlationId: '1234', audience: DID, - verification: { - resolveOpts: { - subjectSyntaxTypesSupported: ['did:ethr'], - }, - mode: VerificationMode.INTERNAL, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - wellknownDIDVerifyCallback: async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }), - }, + verifyJwtCallback: getVerifyJwtCallback(getResolver('ethr'), { + checkLinkedDomain: 'if_present', + }), + verification: {}, }; it('throw NO_JWT when no jwt is passed', async () => { diff --git a/test/DidJwtTestUtils.ts b/test/DidJwtTestUtils.ts new file mode 100644 index 00000000..6656b3e4 --- /dev/null +++ b/test/DidJwtTestUtils.ts @@ -0,0 +1,166 @@ +import { VerifyCallback } from '@sphereon/wellknown-dids-client'; +import { createJWT, EdDSASigner, ES256KSigner, ES256Signer, hexToBytes, JWTOptions, JWTVerifyOptions, Signer, verifyJWT } from 'did-jwt'; +import { Resolvable } from 'did-resolver'; + +import { parseJWT } from '../src/helpers/jwtUtils'; +import { DEFAULT_EXPIRATION_TIME, JwtPayload, ResponseIss, SigningAlgo, SIOPErrors, VerifiedJWT, VerifyJwtCallback } from '../src/types'; +import { CreateJwtCallback } from '../src/types/JwtIssuer'; + +import { getResolver } from './ResolverTestUtils'; + +export async function verifyDidJWT(jwt: string, resolver: Resolvable, options: JWTVerifyOptions): Promise { + return verifyJWT(jwt, { ...options, resolver }); +} + +/** + * Creates a signed JWT given an address which becomes the issuer, a signer function, and a payload for which the withSignature is over. + * + * @example + * const signer = ES256KSigner(process.env.PRIVATE_KEY) + * createJWT({address: '5A8bRWU3F7j3REx3vkJ...', signer}, {key1: 'value', key2: ..., ... }).then(JWT => { + * ... + * }) + * + * @param {Object} payload payload object + * @param {Object} [options] an unsigned credential object + * @param {String} options.issuer The DID of the issuer (signer) of JWT + * @param {Signer} options.signer a `Signer` function, Please see `ES256KSigner` or `EdDSASigner` + * @param {boolean} options.canonicalize optional flag to canonicalize header and payload before signing + * @param {Object} header optional object to specify or customize the JWT header + * @return {Promise} a promise which resolves with a signed JSON Web Token or rejects with an error + */ +export async function createDidJWT( + payload: Partial, + { issuer, signer, expiresIn, canonicalize }: JWTOptions, + header: Partial, +): Promise { + return createJWT(payload, { issuer, signer, expiresIn, canonicalize }, header); +} +export interface InternalSignature { + hexPrivateKey: string; // hex private key Only secp256k1 format + did: string; + + alg: SigningAlgo; + kid?: string; // Optional: key identifier + + customJwtSigner?: Signer; +} + +export function getAudience(jwt: string) { + const { payload } = parseJWT(jwt); + if (!payload) { + throw new Error(SIOPErrors.NO_AUDIENCE); + } else if (!payload.aud) { + return undefined; + } else if (Array.isArray(payload.aud)) { + throw new Error(SIOPErrors.INVALID_AUDIENCE); + } + + return payload.aud; +} + +export const internalSignature = (hexPrivateKey: string, did: string, didUrl: string, alg: SigningAlgo) => { + return getCreateJwtCallback({ + hexPrivateKey, + kid: didUrl, + alg, + did, + }); +}; + +export function getCreateJwtCallback(signature: InternalSignature): CreateJwtCallback { + return (jwtIssuer, jwt) => { + if (jwtIssuer.method === 'did') { + const issuer = jwtIssuer.didUrl.split('#')[0]; + return signDidJwtInternal(jwt.payload, issuer, signature.hexPrivateKey, signature.alg, signature.kid, signature.customJwtSigner); + } else if (jwtIssuer.method === 'custom') { + if (jwtIssuer.type === 'request-object') { + const did = signature.did; + jwt.payload.iss = jwt.payload.iss ?? did; + jwt.payload.sub = jwt.payload.sub ?? did; + jwt.payload.client_id = jwt.payload.client_id ?? did; + } + + if (jwtIssuer.type === 'id-token') { + if (!jwt.payload.sub) jwt.payload.sub = signature.did; + + const issuer = jwtIssuer.authorizationResponseOpts.registration.issuer || this._payload.iss; + if (!issuer || !(issuer.includes(ResponseIss.SELF_ISSUED_V2) || issuer === this._payload.sub)) { + throw new Error(SIOPErrors.NO_SELF_ISSUED_ISS); + } + if (!jwt.payload.iss) { + jwt.payload.iss = issuer; + } + return signDidJwtInternal(jwt.payload, issuer, signature.hexPrivateKey, signature.alg, signature.kid, signature.customJwtSigner); + } + + return signDidJwtInternal(jwt.payload, signature.did, signature.hexPrivateKey, signature.alg, signature.kid, signature.customJwtSigner); + } + throw new Error('Not implemented yet'); + }; +} + +export function getVerifyJwtCallback( + resolver?: Resolvable, + verifyOpts?: JWTVerifyOptions & { + checkLinkedDomain: 'never' | 'if_present' | 'always'; + wellknownDIDVerifyCallback?: VerifyCallback; + }, +): VerifyJwtCallback { + return async (jwtVerifier, jwt) => { + resolver = resolver ?? getResolver(['ethr', 'ion']); + const audience = + jwtVerifier.type === 'request-object' + ? verifyOpts?.audience ?? getAudience(jwt.raw) + : jwtVerifier.type === 'id-token' + ? verifyOpts.audience + : undefined; + + await verifyDidJWT(jwt.raw, resolver, { audience, ...verifyOpts }); + // we can always because the verifyDidJWT will throw an error if the JWT is invalid + return true; + }; +} + +async function signDidJwtInternal( + payload: JwtPayload, + issuer: string, + hexPrivateKey: string, + alg: SigningAlgo, + kid: string, + customJwtSigner?: Signer, +): Promise { + const signer = determineSigner(alg, hexPrivateKey, customJwtSigner); + const header = { + alg, + kid, + }; + const options = { + issuer, + signer, + expiresIn: DEFAULT_EXPIRATION_TIME, + }; + + return await createDidJWT({ ...payload }, options, header); +} + +const determineSigner = (alg: SigningAlgo, hexPrivateKey?: string, customSigner?: Signer): Signer => { + if (customSigner) { + return customSigner; + } else if (!hexPrivateKey) { + throw new Error('no private key provided'); + } + const privateKey = hexToBytes(hexPrivateKey.replace('0x', '')); + switch (alg) { + case SigningAlgo.EDDSA: + return EdDSASigner(privateKey); + case SigningAlgo.ES256: + return ES256Signer(privateKey); + case SigningAlgo.ES256K: + return ES256KSigner(privateKey); + case SigningAlgo.PS256: + throw Error('PS256 is not supported yet. Please provide a custom signer'); + case SigningAlgo.RS256: + throw Error('RS256 is not supported yet. Please provide a custom signer'); + } +}; diff --git a/test/IT.spec.ts b/test/IT.spec.ts index 131c1059..108fb454 100644 --- a/test/IT.spec.ts +++ b/test/IT.spec.ts @@ -2,11 +2,10 @@ import { EventEmitter } from 'events'; import { IPresentationDefinition } from '@sphereon/pex'; import { CredentialMapper, IPresentation, IProofType, IVerifiableCredential, W3CVerifiablePresentation } from '@sphereon/ssi-types'; -import { IVerifyCallbackArgs, IVerifyCredentialResult, VerifyCallback, WDCErrors } from '@sphereon/wellknown-dids-client'; +import { WDCErrors } from '@sphereon/wellknown-dids-client'; import nock from 'nock'; import { - CheckLinkedDomain, OP, PassBy, PresentationDefinitionWithLocation, @@ -23,13 +22,14 @@ import { SigningAlgo, SubjectType, SupportedVersion, - VerificationMode, verifyRevocation, VPTokenLocation, } from '../src'; import { InMemoryRPSessionManager } from '../src'; import { checkSIOPSpecVersionSupported } from '../src/helpers/SIOPSpecVersion'; +import { getVerifyJwtCallback, internalSignature } from './DidJwtTestUtils'; +import { getResolver } from './ResolverTestUtils'; import { mockedGetEnterpriseAuthToken, WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; import { UNIT_TEST_TIMEOUT, @@ -61,9 +61,6 @@ const presentationSignCallback: PresentationSignCallback = async (_args) => ({ }, }); -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const verifyCallback: VerifyCallback = async (_args: IVerifyCallbackArgs) => ({ verified: true }); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args: W3CVerifiablePresentation) => ({ verified: true, @@ -153,23 +150,21 @@ describe('RP and OP interaction should', () => { const rpMockEntity = await mockedGetEnterpriseAuthToken('ACME RP'); const opMockEntity = await mockedGetEnterpriseAuthToken('ACME OP'); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const verifyCallback: VerifyCallback = async (_args) => ({ verified: true }); // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => ({ verified: true }); + const resolver = getResolver(['ethr']); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId(rpMockEntity.did) .withScope('test') .withResponseType(ResponseType.ID_TOKEN) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withPresentationVerification(presentationVerificationCallback) - .withWellknownDIDVerifyCallback(verifyCallback) .withRevocationVerification(RevocationVerification.NEVER) .withRequestBy(PassBy.REFERENCE, EXAMPLE_REFERENCE_URL) .withIssuer(ResponseIss.SELF_ISSUED_V2) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, `${rpMockEntity.did}#controller`, SigningAlgo.ES256K) - .addDidMethod('ethr') + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, `${rpMockEntity.did}#controller`, SigningAlgo.ES256K)) .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -191,9 +186,9 @@ describe('RP and OP interaction should', () => { const op = OP.builder() .withPresentationSignCallback(presentationSignCallback) .withExpiresIn(1000) - .withWellknownDIDVerifyCallback(verifyCallback) .withIssuer(ResponseIss.SELF_ISSUED_V2) - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, `${opMockEntity.did}#controller`, SigningAlgo.ES256K) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, `${opMockEntity.did}#controller`, SigningAlgo.ES256K)) .withSupportedVersions(SupportedVersion.SIOPv2_ID1) //FIXME: Move payload options to seperate property .withRegistration({ @@ -227,7 +222,7 @@ describe('RP and OP interaction should', () => { await checkSIOPSpecVersionSupported(requestURI.authorizationRequestPayload, op.verifyRequestOptions.supportedVersions); // The create method also calls the verifyRequest method, so no need to do it manually const verifiedRequest = await op.verifyAuthorizationRequest(requestURI.encodedUri); - const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedRequest); + const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedRequest, {}); nock(EXAMPLE_REDIRECT_URL).post(/.*/).times(3).reply(200, { result: 'ok' }); const response = await op.submitAuthorizationResponse(authenticationResponseWithJWT); @@ -257,22 +252,19 @@ describe('RP and OP interaction should', () => { didKey: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca#controllerKey', }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const verifyCallback = async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }); // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => ({ verified: true }); + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId(rpMockEntity.did) .withScope('test') .withResponseType(ResponseType.ID_TOKEN) .withRedirectUri(EXAMPLE_REDIRECT_URL) - .withWellknownDIDVerifyCallback(verifyCallback) .withPresentationVerification(presentationVerificationCallback) .withRevocationVerification(RevocationVerification.NEVER) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) - .addDidMethod('ethr') + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -293,9 +285,8 @@ describe('RP and OP interaction should', () => { .build(); const op = OP.builder() .withExpiresIn(1000) - .withWellknownDIDVerifyCallback(verifyCallback) - .addDidMethod('ethr') - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -328,10 +319,9 @@ describe('RP and OP interaction should', () => { expect(parsedAuthReqURI.requestObjectJwt).toBeDefined(); const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt, { correlationId: '1234' }); - expect(verifiedAuthReqWithJWT.signer).toBeDefined(); expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did); - const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedAuthReqWithJWT); + const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedAuthReqWithJWT, {}); expect(authenticationResponseWithJWT).toBeDefined(); expect(authenticationResponseWithJWT.correlationId).toEqual('1234'); expect(authenticationResponseWithJWT.response.payload).toBeDefined(); @@ -358,22 +348,19 @@ describe('RP and OP interaction should', () => { didKey: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca#controllerKey', }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const verifyCallback = async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }); // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => ({ verified: true }); + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId(WELL_KNOWN_OPENID_FEDERATION) .withScope('test') .withResponseType([ResponseType.ID_TOKEN, ResponseType.VP_TOKEN]) .withRedirectUri(EXAMPLE_REDIRECT_URL) - .withWellknownDIDVerifyCallback(verifyCallback) .withPresentationVerification(presentationVerificationCallback) .withRevocationVerification(RevocationVerification.NEVER) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) - .addDidMethod('ethr') + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withClientMetadata({ client_id: rpMockEntity.did, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -395,9 +382,8 @@ describe('RP and OP interaction should', () => { .build(); const op = OP.builder() .withExpiresIn(1000) - .withWellknownDIDVerifyCallback(verifyCallback) - .addDidMethod('ethr') - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -433,9 +419,8 @@ describe('RP and OP interaction should', () => { // expect(parsedAuthReqURI.registration).toBeDefined(); const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt); - expect(verifiedAuthReqWithJWT.signer).toBeDefined(); expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did); - await expect(op.createAuthorizationResponse(verifiedAuthReqWithJWT)).rejects.toThrow( + await expect(op.createAuthorizationResponse(verifiedAuthReqWithJWT, {})).rejects.toThrow( Error('authentication request expects a verifiable presentation in the response'), ); @@ -444,133 +429,120 @@ describe('RP and OP interaction should', () => { }); it('succeed when calling with presentation definitions and right verifiable presentation', async () => { - try { - const rpMockEntity = { - hexPrivateKey: '2bbd6a78be9ab2193bcf74aa6d39ab59c1d1e2f7e9ef899a38fb4d94d8aa90e2', - did: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024', - didKey: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024#controllerKey', - }; + const rpMockEntity = { + hexPrivateKey: '2bbd6a78be9ab2193bcf74aa6d39ab59c1d1e2f7e9ef899a38fb4d94d8aa90e2', + did: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024', + didKey: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024#controllerKey', + }; - const opMockEntity = { - hexPrivateKey: '73d24dd0fb69abdc12e7a99d8f9a970fdc8ad90598cc64cff35b584220ace0c8', - did: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca', - didKey: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca#controllerKey', - }; + const opMockEntity = { + hexPrivateKey: '73d24dd0fb69abdc12e7a99d8f9a970fdc8ad90598cc64cff35b584220ace0c8', + did: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca', + didKey: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca#controllerKey', + }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const verifyCallback = async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => ({ verified: true }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => ({ verified: true }); - const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) - .withClientId(rpMockEntity.did) - .withScope('test') - .withResponseType([ResponseType.ID_TOKEN, ResponseType.VP_TOKEN]) - .withRedirectUri(EXAMPLE_REDIRECT_URL) - .withPresentationDefinition({ definition: getPresentationDefinition() }, [ - PropertyTarget.REQUEST_OBJECT, - PropertyTarget.AUTHORIZATION_REQUEST, - ]) - .withPresentationVerification(presentationVerificationCallback) - .withWellknownDIDVerifyCallback(verifyCallback) - .withRevocationVerification(RevocationVerification.NEVER) - .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) - .withAuthorizationEndpoint('www.myauthorizationendpoint.com') - .addDidMethod('ethr') - .withClientMetadata({ - client_id: WELL_KNOWN_OPENID_FEDERATION, - idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], - requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], - responseTypesSupported: [ResponseType.ID_TOKEN], - vpFormatsSupported: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, - scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], - subjectTypesSupported: [SubjectType.PAIRWISE], - subject_syntax_types_supported: ['did', 'did:ethr'], - passBy: PassBy.VALUE, - logo_uri: VERIFIER_LOGO_FOR_CLIENT, - clientName: VERIFIER_NAME_FOR_CLIENT, - 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100322', - clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, - 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, - }) - .withSupportedVersions(SupportedVersion.SIOPv2_ID1) - .build(); - const op = OP.builder() - .withPresentationSignCallback(presentationSignCallback) - .withExpiresIn(1000) - .withWellknownDIDVerifyCallback(verifyCallback) - .addDidMethod('ethr') - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) - .withRegistration({ - authorizationEndpoint: 'www.myauthorizationendpoint.com', - idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], - issuer: ResponseIss.SELF_ISSUED_V2, - requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], - responseTypesSupported: [ResponseType.ID_TOKEN, ResponseType.VP_TOKEN], - vpFormats: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, - scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], - subjectTypesSupported: [SubjectType.PAIRWISE], - subject_syntax_types_supported: [], - passBy: PassBy.VALUE, - logo_uri: VERIFIER_LOGO_FOR_CLIENT, - clientName: VERIFIER_NAME_FOR_CLIENT, - 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100323', - clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, - 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, - }) - .withSupportedVersions(SupportedVersion.SIOPv2_ID1) - .build(); + const resolver = getResolver('ethr'); + const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) + .withClientId(rpMockEntity.did) + .withScope('test') + .withResponseType([ResponseType.ID_TOKEN, ResponseType.VP_TOKEN]) + .withRedirectUri(EXAMPLE_REDIRECT_URL) + .withPresentationDefinition({ definition: getPresentationDefinition() }, [PropertyTarget.REQUEST_OBJECT, PropertyTarget.AUTHORIZATION_REQUEST]) + .withPresentationVerification(presentationVerificationCallback) + .withRevocationVerification(RevocationVerification.NEVER) + .withRequestBy(PassBy.VALUE) + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) + .withAuthorizationEndpoint('www.myauthorizationendpoint.com') + .withClientMetadata({ + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormatsSupported: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did', 'did:ethr'], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100322', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); + const op = OP.builder() + .withPresentationSignCallback(presentationSignCallback) + .withExpiresIn(1000) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) + .withRegistration({ + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], + issuer: ResponseIss.SELF_ISSUED_V2, + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN, ResponseType.VP_TOKEN], + vpFormats: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: [], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100323', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); - const requestURI = await rp.createAuthorizationRequestURI({ - correlationId: '1234', - nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', - state: 'b32f0087fc9816eb813fd11f', - }); + const requestURI = await rp.createAuthorizationRequestURI({ + correlationId: '1234', + nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + state: 'b32f0087fc9816eb813fd11f', + }); - // Let's test the parsing - const parsedAuthReqURI = await op.parseAuthorizationRequestURI(requestURI.encodedUri); - expect(parsedAuthReqURI.authorizationRequestPayload).toBeDefined(); - expect(parsedAuthReqURI.requestObjectJwt).toBeDefined(); - // expect(parsedAuthReqURI.registration).toBeDefined(); + // Let's test the parsing + const parsedAuthReqURI = await op.parseAuthorizationRequestURI(requestURI.encodedUri); + expect(parsedAuthReqURI.authorizationRequestPayload).toBeDefined(); + expect(parsedAuthReqURI.requestObjectJwt).toBeDefined(); + // expect(parsedAuthReqURI.registration).toBeDefined(); - const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt); - expect(verifiedAuthReqWithJWT.signer).toBeDefined(); - expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did); - const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: getVCs() }); - const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( - parsedAuthReqURI.authorizationRequestPayload, - ); - await pex.selectVerifiableCredentialsForSubmission(pd[0].definition); - const verifiablePresentationResult = await pex.createVerifiablePresentation(pd[0].definition, getVCs(), presentationSignCallback, {}); - const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedAuthReqWithJWT, { - presentationExchange: { - verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], - vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, - presentationSubmission: verifiablePresentationResult.presentationSubmission, - /*credentialsAndDefinitions: [ + const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt); + expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did); + const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: getVCs() }); + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( + parsedAuthReqURI.authorizationRequestPayload, + ); + await pex.selectVerifiableCredentialsForSubmission(pd[0].definition); + const verifiablePresentationResult = await pex.createVerifiablePresentation(pd[0].definition, getVCs(), presentationSignCallback, {}); + const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedAuthReqWithJWT, { + presentationExchange: { + verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], + vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, + presentationSubmission: verifiablePresentationResult.presentationSubmission, + /*credentialsAndDefinitions: [ { presentation: vp, format: VerifiablePresentationTypeFormat.LDP_VP, vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, }, ],*/ - }, - }); - expect(authenticationResponseWithJWT.response.payload).toBeDefined(); - expect(authenticationResponseWithJWT.response.idToken).toBeDefined(); + }, + }); + expect(authenticationResponseWithJWT.response.payload).toBeDefined(); + expect(authenticationResponseWithJWT.response.idToken).toBeDefined(); - const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { - /*audience: EXAMPLE_REDIRECT_URL,*/ - presentationDefinitions: [{ definition: pd[0].definition, location: pd[0].location }], - }); + const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { + /*audience: EXAMPLE_REDIRECT_URL,*/ + presentationDefinitions: [{ definition: pd[0].definition, location: pd[0].location }], + }); - expect(verifiedAuthResponseWithJWT.idToken.jwt).toBeDefined(); - expect(verifiedAuthResponseWithJWT.idToken.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); - } catch (error) { - console.error(error); - throw error; - } + expect(verifiedAuthResponseWithJWT.idToken.jwt).toBeDefined(); + expect(verifiedAuthResponseWithJWT.idToken.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); }); it( @@ -588,20 +560,19 @@ describe('RP and OP interaction should', () => { didKey: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca#controllerKey', }; // eslint-disable-next-line @typescript-eslint/no-unused-vars - const verifyCallback = async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => ({ verified: true }); + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId(rpMockEntity.did) .withScope('test') .withResponseType(ResponseType.ID_TOKEN) - .withCheckLinkedDomain(CheckLinkedDomain.ALWAYS) .withPresentationVerification(presentationVerificationCallback) - .withWellknownDIDVerifyCallback(verifyCallback) + .withRedirectUri(EXAMPLE_REDIRECT_URL) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver, { checkLinkedDomain: 'always' })) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, @@ -628,8 +599,9 @@ describe('RP and OP interaction should', () => { const op = OP.builder() .withPresentationSignCallback(presentationSignCallback) .withExpiresIn(1000) - .withWellknownDIDVerifyCallback(verifyCallback) - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -663,7 +635,6 @@ describe('RP and OP interaction should', () => { // expect(parsedAuthReqURI.registration).toBeDefined(); const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt); - expect(verifiedAuthReqWithJWT.signer).toBeDefined(); expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did); const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: getVCs() }); const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( @@ -691,13 +662,6 @@ describe('RP and OP interaction should', () => { // audience: EXAMPLE_REDIRECT_URL, presentationDefinitions: [{ definition: pd[0].definition, location: pd[0].location }], verification: { - mode: VerificationMode.INTERNAL, - verifyUri: '', - resolveOpts: { - subjectSyntaxTypesSupported: ['did', 'did:eth'], - }, - checkLinkedDomain: CheckLinkedDomain.ALWAYS, - wellknownDIDVerifyCallback: verifyCallback, revocationOpts: { revocationVerification: RevocationVerification.ALWAYS, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -724,22 +688,20 @@ describe('RP and OP interaction should', () => { 'did:ion:EiCMvVdXv6iL3W8i4n-LmqUhE614kX4TYxVR5kTY2QGOjg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXkxIiwicHVibGljS2V5SndrIjp7ImNydiI6InNlY3AyNTZrMSIsImt0eSI6IkVDIiwieCI6Ii1MbHNpQVk5b3JmMXpKQlJOV0NuN0RpNUpoYl8tY2xhNlY5R3pHa3FmSFUiLCJ5IjoiRXBIU25GZHQ2ZU5lRkJEZzNVNVFIVDE0TVRsNHZIc0h5NWRpWU9DWEs1TSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiIsImFzc2VydGlvbk1ldGhvZCJdLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFWZXJpZmljYXRpb25LZXkyMDE5In1dLCJzZXJ2aWNlcyI6W3siaWQiOiJsZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHBzOi8vbGR0ZXN0LnNwaGVyZW9uLmNvbSIsInR5cGUiOiJMaW5rZWREb21haW5zIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlBem8wTVVZUW5HNWM0VFJKZVFsNFR5WVRrSmRyeTJoeXlQUlpENzdFQm1CdyJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQUwtaEtrLUVsODNsRVJiZkFDUk1kSWNQVjRXWGJqZ3dsZ1ZDWTNwbDhhMGciLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUItT2NSbTlTNXdhU3QxbU4zSG4zM2RnMzJKN25MOEdBVHpGQ2ZXaWdIXzh3In19#key1', }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const verifyCallback = async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }); // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => ({ verified: true }); + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId(rpMockEntity.did) .withScope('test') .withResponseType([ResponseType.ID_TOKEN, ResponseType.VP_TOKEN]) - .withCheckLinkedDomain(CheckLinkedDomain.ALWAYS) .withPresentationVerification(presentationVerificationCallback) - .withWellknownDIDVerifyCallback(verifyCallback) + .withRevocationVerification(RevocationVerification.NEVER) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, @@ -767,9 +729,10 @@ describe('RP and OP interaction should', () => { .build(); const op = OP.builder() .withPresentationSignCallback(presentationSignCallback) - .withWellknownDIDVerifyCallback(verifyCallback) + .withExpiresIn(1000) - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver, { checkLinkedDomain: 'always' })) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], @@ -808,7 +771,6 @@ describe('RP and OP interaction should', () => { // expect(parsedAuthReqURI.registration).toBeDefined(); const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt); - expect(verifiedAuthReqWithJWT.signer).toBeDefined(); expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did); const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: getVCs() }); const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( @@ -839,7 +801,6 @@ describe('RP and OP interaction should', () => { 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImRpZDprZXk6ejZNa29USHNnTk5yYnk4SnpDTlExaVJMeVc1UVE2UjhYdXU2QUE4aWdHck1WUFVNI3o2TWtvVEhzZ05OcmJ5OEp6Q05RMWlSTHlXNVFRNlI4WHV1NkFBOGlnR3JNVlBVTSJ9.eyJleHAiOjE3NjQ4NzkxMzksImlzcyI6ImRpZDprZXk6b3RoZXIiLCJuYmYiOjE2MDcxMTI3MzksInN1YiI6ImRpZDprZXk6b3RoZXIiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vaWRlbnRpdHkuZm91bmRhdGlvbi8ud2VsbC1rbm93bi9kaWQtY29uZmlndXJhdGlvbi92MSJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDprZXk6b3RoZXIiLCJvcmlnaW4iOiJodHRwczovL2lkZW50aXR5LmZvdW5kYXRpb24ifSwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTEyLTA0VDE0OjEyOjE5LTA2OjAwIiwiaXNzdWFuY2VEYXRlIjoiMjAyMC0xMi0wNFQxNDoxMjoxOS0wNjowMCIsImlzc3VlciI6ImRpZDprZXk6b3RoZXIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiRG9tYWluTGlua2FnZUNyZWRlbnRpYWwiXX19.rRuc-ojuEgyq8p_tBYK7BayuiNTBeXNyAnC14Rnjs-jsnhae4_E1Q12W99K2NGCGBi5KjNsBcZmdNJPxejiKPrjjcB99poFCgTY8tuRzDjVo0lIeBwfx9qqjKHTRTUR8FGM_imlOpVfBF4AHYxjkHvZn6c9lYvatYcDpB2UfH4BNXkdSVrUXy_kYjpMpAdRtyCAnD_isN1YpEHBqBmnfuVUbYcQK5kk6eiokRFDtWruL1OEeJMYPqjuBSd2m-H54tSM84Oic_pg2zXDjjBlXNelat6MPNT2QxmkwJg7oyewQWX2Ot2yyhSp9WyAQWMlQIe2x84R0lADUmZ1TPQchNw', ], }; - expect(rp.verifyResponseOptions.verification.checkLinkedDomain).toBe(CheckLinkedDomain.ALWAYS); nock('https://ldtest.sphereon.com').get('/.well-known/did-configuration.json').times(3).reply(200, DID_CONFIGURATION); const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { presentationDefinitions: [{ definition: pd[0].definition, location: pd[0].location }], @@ -864,24 +825,21 @@ describe('RP and OP interaction should', () => { didKey: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca#controllerKey', }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const verifyCallback = async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }); // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => ({ verified: true }); + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId(rpMockEntity.did) .withScope('test') .withResponseType([ResponseType.ID_TOKEN, ResponseType.VP_TOKEN]) - .withCheckLinkedDomain(CheckLinkedDomain.IF_PRESENT) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver, { checkLinkedDomain: 'if_present' })) .withPresentationVerification(presentationVerificationCallback) .withRevocationVerification(RevocationVerification.NEVER) - .withWellknownDIDVerifyCallback(verifyCallback) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') - .addDidMethod('ethr') .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -906,11 +864,10 @@ describe('RP and OP interaction should', () => { .build(); const op = OP.builder() .withPresentationSignCallback(presentationSignCallback) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) - .withWellknownDIDVerifyCallback(verifyCallback) + .withExpiresIn(1000) - .addDidMethod('ethr') - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver, { checkLinkedDomain: 'never' })) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -944,7 +901,6 @@ describe('RP and OP interaction should', () => { // expect(parsedAuthReqURI.registration).toBeDefined(); const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt); - expect(verifiedAuthReqWithJWT.signer).toBeDefined(); expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did); const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: getVCs() }); const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( @@ -992,26 +948,23 @@ describe('RP and OP interaction should', () => { didKey: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca#controllerKey', }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const verifyCallback = async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }); // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => ({ verified: true }); + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId('test_client_id') .withScope('test') .withResponseType([ResponseType.VP_TOKEN, ResponseType.ID_TOKEN]) .withRevocationVerification(RevocationVerification.ALWAYS) .withPresentationVerification(presentationVerificationCallback) - .withWellknownDIDVerifyCallback(verifyCallback) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) + .withRevocationVerificationCallback(async () => { return { status: RevocationStatus.VALID }; }) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') - .addDidMethod('ion') .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], @@ -1041,11 +994,9 @@ describe('RP and OP interaction should', () => { const op = OP.builder() .withPresentationSignCallback(presentationSignCallback) .withExpiresIn(1000) - .addDidMethod('ethr') - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) .withPresentationSignCallback(presentationSignCallback) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) - .withWellknownDIDVerifyCallback(verifyCallback) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], @@ -1086,7 +1037,6 @@ describe('RP and OP interaction should', () => { // expect(parsedAuthReqURI.registration).toBeDefined(); const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt); //, rp.authRequestOpts - expect(verifiedAuthReqWithJWT.signer).toBeDefined(); expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did); const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: getVCs() }); @@ -1299,22 +1249,19 @@ describe('RP and OP interaction should', () => { didKey: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca#controllerKey', }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const verifyCallback = async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }); // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => ({ verified: true }); + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId('test_client_id') .withScope('test') .withResponseType(ResponseType.ID_TOKEN) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) .withPresentationVerification(presentationVerificationCallback) - .withWellknownDIDVerifyCallback(verifyCallback) .withRevocationVerification(RevocationVerification.NEVER) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, @@ -1340,10 +1287,9 @@ describe('RP and OP interaction should', () => { .build(); const op = OP.builder() .withPresentationSignCallback(presentationSignCallback) - .withWellknownDIDVerifyCallback(verifyCallback) .withExpiresIn(1000) - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], @@ -1380,7 +1326,6 @@ describe('RP and OP interaction should', () => { // expect(parsedAuthReqURI.registration).toBeDefined(); const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt); - expect(verifiedAuthReqWithJWT.signer).toBeDefined(); expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did); const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: getVCs() }); const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( @@ -1426,18 +1371,17 @@ describe('RP and OP interaction should', () => { didKey: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca#controllerKey', }; + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId('test_client_id') .withScope('test') .withResponseType(ResponseType.ID_TOKEN) .withRevocationVerification(RevocationVerification.NEVER) .withPresentationVerification(presentationVerificationCallback) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') - .addDidMethod('ion') .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], @@ -1467,11 +1411,9 @@ describe('RP and OP interaction should', () => { const op = OP.builder() .withPresentationSignCallback(presentationSignCallback) .withExpiresIn(1000) - .addDidMethod('ethr') - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) .withPresentationSignCallback(presentationSignCallback) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) - .withWellknownDIDVerifyCallback(verifyCallback) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], @@ -1559,12 +1501,10 @@ describe('RP and OP interaction should', () => { .withResponseType(ResponseType.ID_TOKEN) .withRevocationVerification(RevocationVerification.NEVER) .withPresentationVerification(presentationVerificationCallback) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') - .addDidMethod('ion') .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], @@ -1640,12 +1580,10 @@ describe('RP and OP interaction should', () => { .withResponseType(ResponseType.ID_TOKEN) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withPresentationVerification(presentationVerificationCallback) - .withWellknownDIDVerifyCallback(verifyCallback) .withRevocationVerification(RevocationVerification.NEVER) .withRequestBy(PassBy.REFERENCE, EXAMPLE_REFERENCE_URL) .withIssuer(ResponseIss.SELF_ISSUED_V2) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, `${rpMockEntity.did}#controller`, SigningAlgo.ES256K) - .addDidMethod('ethr') + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -1693,18 +1631,18 @@ describe('RP and OP interaction should', () => { const eventEmitter = new EventEmitter(); const replayRegistry = new InMemoryRPSessionManager(eventEmitter); + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId(rpMockEntity.did) .withScope('test') .withResponseType(ResponseType.ID_TOKEN) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withPresentationVerification(presentationVerificationCallback) - .withWellknownDIDVerifyCallback(verifyCallback) + .withRevocationVerification(RevocationVerification.NEVER) .withRequestBy(PassBy.REFERENCE, EXAMPLE_REFERENCE_URL) .withIssuer(ResponseIss.SELF_ISSUED_V2) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, `${rpMockEntity.didKey}`, SigningAlgo.ES256K) - .addDidMethod('ethr') + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -1728,9 +1666,9 @@ describe('RP and OP interaction should', () => { const op = OP.builder() .withPresentationSignCallback(presentationSignCallback) .withExpiresIn(1000) - .withWellknownDIDVerifyCallback(verifyCallback) .withIssuer(ResponseIss.SELF_ISSUED_V2) - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, `${opMockEntity.didKey}`, SigningAlgo.ES256K) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) .withSupportedVersions(SupportedVersion.SIOPv2_ID1) //FIXME: Move payload options to seperate property .withRegistration({ @@ -1761,7 +1699,7 @@ describe('RP and OP interaction should', () => { expect(reqStateCreated.status).toBe('created'); nock('https://rp.acme.com').get('/siop/jwts').times(3).reply(200, requestURI.requestObjectJwt); const verifiedRequest = await op.verifyAuthorizationRequest(requestURI.encodedUri); - const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedRequest); + const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedRequest, {}); nock(EXAMPLE_REDIRECT_URL).post(/.*/).times(3).reply(200, { result: 'ok' }); await op.submitAuthorizationResponse(authenticationResponseWithJWT); await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { @@ -1784,18 +1722,17 @@ describe('RP and OP interaction should', () => { const eventEmitter = new EventEmitter(); const replayRegistry = new InMemoryRPSessionManager(eventEmitter); + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId(rpMockEntity.did) .withScope('test') .withResponseType(ResponseType.ID_TOKEN) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withPresentationVerification(presentationVerificationCallback) - .withWellknownDIDVerifyCallback(verifyCallback) .withRevocationVerification(RevocationVerification.NEVER) .withRequestBy(PassBy.REFERENCE, EXAMPLE_REFERENCE_URL) .withIssuer(ResponseIss.SELF_ISSUED_V2) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, `${rpMockEntity.did}#controller`, SigningAlgo.ES256K) - .addDidMethod('ethr') + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -1819,9 +1756,9 @@ describe('RP and OP interaction should', () => { const op = OP.builder() .withPresentationSignCallback(presentationSignCallback) .withExpiresIn(1000) - .withWellknownDIDVerifyCallback(verifyCallback) .withIssuer(ResponseIss.SELF_ISSUED_V2) - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, `${opMockEntity.did}#controller`, SigningAlgo.ES256K) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, `${opMockEntity.did}#controller`, SigningAlgo.ES256K)) .withSupportedVersions(SupportedVersion.SIOPv2_ID1) //FIXME: Move payload options to seperate property .withRegistration({ @@ -1853,7 +1790,7 @@ describe('RP and OP interaction should', () => { nock('https://rp.acme.com').get('/siop/jwts').times(3).reply(200, requestURI.requestObjectJwt); const verifiedRequest = await op.verifyAuthorizationRequest(requestURI.encodedUri); - const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedRequest); + const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedRequest, {}); nock(EXAMPLE_REDIRECT_URL).post(/.*/).reply(200, { result: 'ok' }); await op.submitAuthorizationResponse(authenticationResponseWithJWT); authenticationResponseWithJWT.response.payload.state = 'wrong_value'; diff --git a/test/OP.request.spec.ts b/test/OP.request.spec.ts index a714d936..76502f77 100644 --- a/test/OP.request.spec.ts +++ b/test/OP.request.spec.ts @@ -1,10 +1,8 @@ import { IProofType } from '@sphereon/ssi-types'; -import { IVerifyCallbackArgs, IVerifyCredentialResult } from '@sphereon/wellknown-dids-client'; import nock from 'nock'; import { AuthorizationResponseOpts, - CheckLinkedDomain, CreateAuthorizationRequestOpts, OP, PassBy, @@ -17,10 +15,11 @@ import { SubjectIdentifierType, SubjectType, SupportedVersion, - VerificationMode, VerifyAuthorizationRequestOpts, } from '../src'; +import { getCreateJwtCallback, getVerifyJwtCallback, internalSignature } from './DidJwtTestUtils'; +import { getResolver } from './ResolverTestUtils'; import { mockedGetEnterpriseAuthToken, WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; import { UNIT_TEST_TIMEOUT, @@ -48,8 +47,6 @@ describe('OP OPBuilder should', () => { expect( OP.builder() - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) - .addDidMethod('ethr') .withIssuer(ResponseIss.SELF_ISSUED_V2) .withResponseMode(ResponseMode.POST) .withRegistration({ @@ -61,7 +58,8 @@ describe('OP OPBuilder should', () => { clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }) - .withInternalSignature('myprivatekey', 'did:example:123', 'did:example:123#key', SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature('myprivatekey', 'did:example:123', 'did:example:123#key', SigningAlgo.ES256K)) + .withVerifyJwtCallback(getVerifyJwtCallback(getResolver('ethr'), { checkLinkedDomain: 'never' })) .withExpiresIn(1000) .withSupportedVersions([SupportedVersion.SIOPv2_ID1]) .build(), @@ -71,15 +69,15 @@ describe('OP OPBuilder should', () => { describe('OP should', () => { const responseOpts: AuthorizationResponseOpts = { - checkLinkedDomain: CheckLinkedDomain.NEVER, responseURI: EXAMPLE_REDIRECT_URL, responseURIType: 'redirect_uri', - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: HEX_KEY, did: DID, kid: KID, alg: SigningAlgo.ES256K, - }, + }), + jwtIssuer: { method: 'did', didUrl: KID, alg: SigningAlgo.ES256K }, registration: { authorizationEndpoint: 'www.myauthorizationendpoint.com', responseTypesSupported: [ResponseType.ID_TOKEN], @@ -102,15 +100,10 @@ describe('OP should', () => { expiresIn: 2000, }; + const resolver = getResolver('ethr'); const verifyOpts: VerifyAuthorizationRequestOpts = { - verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: { - subjectSyntaxTypesSupported: ['did:ethr'], - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - wellknownDIDVerifyCallback: async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }), - }, + verifyJwtCallback: getVerifyJwtCallback(resolver), + verification: {}, correlationId: '1234', supportedVersions: [SupportedVersion.SIOPv2_ID1], nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', @@ -135,15 +128,23 @@ describe('OP should', () => { version: SupportedVersion.SIOPv2_ID1, requestObject: { + jwtIssuer: { + method: 'did', + didUrl: `${mockEntity.did}#controller`, + alg: SigningAlgo.ES256K, + options: { + kid: '1234', + }, + }, passBy: PassBy.REFERENCE, reference_uri: EXAMPLE_REFERENCE_URL, - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: mockEntity.hexPrivateKey, did: mockEntity.did, kid: `${mockEntity.did}#controller`, alg: SigningAlgo.ES256K, - }, + }), payload: { redirect_uri: EXAMPLE_REDIRECT_URL, client_id: WELL_KNOWN_OPENID_FEDERATION, @@ -177,6 +178,7 @@ describe('OP should', () => { correlationId: '1234', nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', state: 'b32f0087fc9816eb813fd11f', + jwtIssuer: { method: 'did', didUrl: `${mockEntity.did}#controller`, alg: SigningAlgo.ES256K, options: { kid: '1234' } }, }); nock('https://rp.acme.com').get('/siop/jwts').reply(200, requestURI.requestObjectJwt); @@ -184,11 +186,6 @@ describe('OP should', () => { const verifiedRequest = await OP.fromOpts(responseOpts, verifyOpts).verifyAuthorizationRequest(requestURI.encodedUri); // console.log(JSON.stringify(verifiedRequest)); expect(verifiedRequest.issuer).toMatch(mockEntity.did); - expect(verifiedRequest.signer).toMatchObject({ - id: `${mockEntity.did}#controller`, - type: 'EcdsaSecp256k1RecoveryMethod2020', - controller: `${mockEntity.did}`, - }); expect(verifiedRequest.jwt).toBeDefined(); }, UNIT_TEST_TIMEOUT, @@ -202,12 +199,18 @@ describe('OP should', () => { .withClientId(WELL_KNOWN_OPENID_FEDERATION) .withScope('test') .withResponseType(ResponseType.ID_TOKEN) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') .withRedirectUri(EXAMPLE_REFERENCE_URL) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, `${rpMockEntity.did}#controller`, SigningAlgo.ES256K) - .addDidMethod('ethr') + .withCreateJwtCallback( + getCreateJwtCallback({ + hexPrivateKey: rpMockEntity.hexPrivateKey, + did: rpMockEntity.did, + kid: `${rpMockEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }), + ) .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -230,15 +233,22 @@ describe('OP should', () => { correlationId: '1234', nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', state: 'b32f0087fc9816eb813fd11f', + jwtIssuer: { method: 'did', didUrl: `${rpMockEntity.did}#controller`, alg: SigningAlgo.ES256K }, }); const verifiedRequest = await OP.builder() - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) .withSupportedVersions([SupportedVersion.SIOPv2_ID1]) .withExpiresIn(1000) .withIssuer(ResponseIss.SELF_ISSUED_V2) - .addDidMethod('ethr') - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, `${opMockEntity.did}#controller`, SigningAlgo.ES256K) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver, { checkLinkedDomain: 'never' })) + .withCreateJwtCallback( + getCreateJwtCallback({ + hexPrivateKey: opMockEntity.hexPrivateKey, + did: opMockEntity.did, + kid: `${opMockEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }), + ) .withRegistration({ idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], @@ -259,11 +269,6 @@ describe('OP should', () => { .verifyAuthorizationRequest(requestURI.encodedUri); // console.log(JSON.stringify(verifiedRequest)); expect(verifiedRequest.issuer).toMatch(rpMockEntity.did); - expect(verifiedRequest.signer).toMatchObject({ - id: `${rpMockEntity.did}#controller`, - type: 'EcdsaSecp256k1RecoveryMethod2020', - controller: `${rpMockEntity.did}`, - }); expect(verifiedRequest.jwt).toBeDefined(); }); }); diff --git a/test/RP.request.spec.ts b/test/RP.request.spec.ts index 555372fa..341ba3ea 100644 --- a/test/RP.request.spec.ts +++ b/test/RP.request.spec.ts @@ -1,9 +1,6 @@ -import { getUniResolver } from '@sphereon/did-uni-client'; import { IProofType } from '@sphereon/ssi-types'; -import { Resolver } from 'did-resolver'; import { - CheckLinkedDomain, CreateAuthorizationRequestOpts, PassBy, PropertyTarget, @@ -17,6 +14,8 @@ import { SupportedVersion, } from '../src'; +import { getCreateJwtCallback, getVerifyJwtCallback, internalSignature } from './DidJwtTestUtils'; +import { getResolver } from './ResolverTestUtils'; import { WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; import { VERIFIER_LOGO_FOR_CLIENT, @@ -43,14 +42,13 @@ describe('RP OPBuilder should', () => { it('build an RP when all arguments are set', async () => { expect.assertions(1); + const resolver = getResolver('ethr'); expect( RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId('test_client_id') .withScope('test') .withResponseType(ResponseType.ID_TOKEN) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) - .addDidMethod('factom') - .addResolver('ethr', new Resolver(getUniResolver('ethr'))) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withRedirectUri('https://redirect.me') .withRequestBy(PassBy.VALUE) .withResponseMode(ResponseMode.POST) @@ -64,7 +62,7 @@ describe('RP OPBuilder should', () => { 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }) - .withInternalSignature('myprivatekye', 'did:example:123', 'did:example:123#key', SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature('myprivatekye', 'did:example:123', 'did:example:123#key', SigningAlgo.ES256K)) .withSupportedVersions(SupportedVersion.SIOPv2_ID1) .build(), ).toBeInstanceOf(RP); @@ -88,15 +86,19 @@ describe('RP should', () => { redirect_uri: EXAMPLE_REDIRECT_URL, }, requestObject: { + jwtIssuer: { + method: 'did', + didUrl: KID, + alg: SigningAlgo.ES256K, + }, passBy: PassBy.REFERENCE, reference_uri: EXAMPLE_REFERENCE_URL, - - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: HEX_KEY, did: DID, kid: KID, alg: SigningAlgo.ES256K, - }, + }), }, clientMetadata: { idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], @@ -135,15 +137,20 @@ describe('RP should', () => { redirect_uri: EXAMPLE_REDIRECT_URL, }, requestObject: { + jwtIssuer: { + method: 'did', + didUrl: KID, + alg: SigningAlgo.ES256K, + }, passBy: PassBy.REFERENCE, reference_uri: EXAMPLE_REFERENCE_URL, - signature: { + createJwtCallback: getCreateJwtCallback({ hexPrivateKey: HEX_KEY, did: DID, kid: KID, alg: SigningAlgo.ES256K, - }, + }), }, clientMetadata: { client_id: WELL_KNOWN_OPENID_FEDERATION, @@ -206,6 +213,7 @@ describe('RP should', () => { correlationId: '1234', state: 'b32f0087fc9816eb813fd11f', nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + jwtIssuer: { method: 'did', didUrl: KID, alg: SigningAlgo.ES256K }, }); expect(request.authorizationRequestPayload).toMatchObject(expectedPayloadWithoutRequest); expect(request.encodedUri).toMatch(expectedUri); @@ -253,10 +261,10 @@ describe('RP should', () => { .withClientId(WELL_KNOWN_OPENID_FEDERATION, alltargets) .withScope('test', alltargets) .withResponseType(ResponseType.ID_TOKEN, alltargets) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) + .withVerifyJwtCallback(getVerifyJwtCallback(getResolver('ethr'))) .withRedirectUri(EXAMPLE_REDIRECT_URL, alltargets) .withRequestBy(PassBy.REFERENCE, EXAMPLE_REFERENCE_URL) - .withInternalSignature(HEX_KEY, DID, KID, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(HEX_KEY, DID, KID, SigningAlgo.ES256K)) .withClientMetadata( { client_id: WELL_KNOWN_OPENID_FEDERATION, @@ -276,7 +284,7 @@ describe('RP should', () => { }, scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], subjectTypesSupported: [SubjectType.PAIRWISE], - subject_syntax_types_supported: [], + subject_syntax_types_supported: ['did:ethr'], passBy: PassBy.VALUE, logo_uri: VERIFIER_LOGO_FOR_CLIENT + ' 2022-09-29 01', clientName: VERIFIER_NAME_FOR_CLIENT + ' 2022-09-29 01', @@ -286,7 +294,6 @@ describe('RP should', () => { }, alltargets, ) - .addDidMethod('did:ethr') .withSupportedVersions([SupportedVersion.SIOPv2_D11]) .build() @@ -294,6 +301,7 @@ describe('RP should', () => { correlationId: '1234', state: 'b32f0087fc9816eb813fd11f', nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + jwtIssuer: { method: 'did', didUrl: KID, alg: SigningAlgo.ES256K }, }); expect(request.authorizationRequestPayload).toMatchObject(expectedPayloadWithoutRequest); expect(request.encodedUri).toMatch(expectedUri); diff --git a/test/ResolverTestUtils.ts b/test/ResolverTestUtils.ts new file mode 100644 index 00000000..38f1a0e3 --- /dev/null +++ b/test/ResolverTestUtils.ts @@ -0,0 +1,46 @@ +import { getUniResolver } from '@sphereon/did-uni-client'; +import { DIDResolutionResult, ParsedDID, Resolvable, Resolver } from 'did-resolver'; +import { DIDDocument as DIFDIDDocument } from 'did-resolver'; +import { DIDResolutionOptions } from 'did-resolver/src/resolver'; + +import { SIOPErrors } from '../src'; + +export interface DIDDocument extends DIFDIDDocument { + owner?: string; + created?: string; + updated?: string; + proof?: LinkedDataProof; +} + +export interface LinkedDataProof { + type: string; + created: string; + creator: string; + nonce: string; + signatureValue: string; +} + +export function getResolver(methods: string | string[]): Resolvable { + function getMethodFromDid(did: string): string { + if (!did) { + throw new Error(SIOPErrors.BAD_PARAMS); + } + const split = did.split(':'); + if (split.length == 1 && did.length > 0) { + return did; + } else if (!did.startsWith('did:') || split.length < 2) { + throw new Error(SIOPErrors.BAD_PARAMS); + } + + return split[1]; + } + + const uniResolvers: { + [p: string]: (did: string, _parsed: ParsedDID, _didResolver: Resolver, _options: DIDResolutionOptions) => Promise; + }[] = []; + for (const didMethod of typeof methods === 'string' ? [methods] : methods) { + const uniResolver = getUniResolver(getMethodFromDid(didMethod)); + uniResolvers.push(uniResolver); + } + return new Resolver(...uniResolvers); +} diff --git a/test/SdJwt.spec.ts b/test/SdJwt.spec.ts index 8a0f3879..e785b52e 100644 --- a/test/SdJwt.spec.ts +++ b/test/SdJwt.spec.ts @@ -2,7 +2,6 @@ import { createHash } from 'node:crypto'; import { IPresentationDefinition, SdJwtDecodedVerifiableCredentialWithKbJwtInput } from '@sphereon/pex'; import { OriginalVerifiableCredential } from '@sphereon/ssi-types'; -import { IVerifyCallbackArgs, IVerifyCredentialResult } from '@sphereon/wellknown-dids-client'; import { OP, @@ -23,6 +22,8 @@ import { VPTokenLocation, } from '../src'; +import { getVerifyJwtCallback, internalSignature } from './DidJwtTestUtils'; +import { getResolver } from './ResolverTestUtils'; import { WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; import { VERIFIER_LOGO_FOR_CLIENT, @@ -131,14 +132,12 @@ describe('RP and OP interaction should', () => { didKey: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca#controllerKey', }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const verifyCallback = async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => { return { verified: true }; }; + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withClientId(rpMockEntity.did) .withScope('test') @@ -147,12 +146,11 @@ describe('RP and OP interaction should', () => { .withRedirectUri(EXAMPLE_REDIRECT_URL) .withPresentationDefinition({ definition: getPresentationDefinition() }, [PropertyTarget.REQUEST_OBJECT, PropertyTarget.AUTHORIZATION_REQUEST]) .withPresentationVerification(presentationVerificationCallback) - .withWellknownDIDVerifyCallback(verifyCallback) .withRevocationVerification(RevocationVerification.NEVER) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') - .addDidMethod('ethr') + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -171,13 +169,13 @@ describe('RP and OP interaction should', () => { }) .withSupportedVersions(SupportedVersion.SIOPv2_ID1) .build(); + const op = OP.builder() .withPresentationSignCallback(presentationSignCallback) .withExpiresIn(1000) .withHasher(hasher) - .withWellknownDIDVerifyCallback(verifyCallback) - .addDidMethod('ethr') - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -210,7 +208,6 @@ describe('RP and OP interaction should', () => { expect(parsedAuthReqURI.requestObjectJwt).toBeDefined(); const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt); - expect(verifiedAuthReqWithJWT.signer).toBeDefined(); expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did); const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], @@ -257,14 +254,12 @@ describe('RP and OP interaction should', () => { didKey: 'did:ethr:goerli:0x03a1370d4dd249eabb23245aeb4aec988fbca598ff83db59144d89b3835371daca#controllerKey', }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const verifyCallback = async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => { return { verified: true }; }; + const resolver = getResolver('ethr'); const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_D12_OID4VP_D18, }) @@ -274,12 +269,11 @@ describe('RP and OP interaction should', () => { .withRedirectUri(EXAMPLE_REDIRECT_URL) .withPresentationDefinition({ definition: getPresentationDefinition() }, [PropertyTarget.REQUEST_OBJECT, PropertyTarget.AUTHORIZATION_REQUEST]) .withPresentationVerification(presentationVerificationCallback) - .withWellknownDIDVerifyCallback(verifyCallback) .withRevocationVerification(RevocationVerification.NEVER) .withRequestBy(PassBy.VALUE) - .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, rpMockEntity.didKey, SigningAlgo.ES256K)) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') - .addDidMethod('ethr') .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -301,9 +295,8 @@ describe('RP and OP interaction should', () => { .withPresentationSignCallback(presentationSignCallback) .withExpiresIn(1000) .withHasher(hasher) - .withWellknownDIDVerifyCallback(verifyCallback) - .addDidMethod('ethr') - .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K)) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -328,6 +321,7 @@ describe('RP and OP interaction should', () => { correlationId: '1234', nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', state: 'b32f0087fc9816eb813fd11f', + jwtIssuer: { method: 'did', alg: SigningAlgo.ES256K, didUrl: rpMockEntity.didKey }, }); // Let's test the parsing @@ -336,7 +330,6 @@ describe('RP and OP interaction should', () => { expect(parsedAuthReqURI.requestObjectJwt).toBeDefined(); const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt); - expect(verifiedAuthReqWithJWT.signer).toBeDefined(); expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did); const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], @@ -353,6 +346,11 @@ describe('RP and OP interaction should', () => { }, }); const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedAuthReqWithJWT, { + jwtIssuer: { + method: 'did', + alg: SigningAlgo.ES256K, + didUrl: opMockEntity.didKey, + }, presentationExchange: { verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, diff --git a/test/TestUtils.ts b/test/TestUtils.ts index e4d7b8d0..8f29b278 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -2,10 +2,8 @@ import crypto from 'crypto'; import { IProofType } from '@sphereon/ssi-types'; import base58 from 'bs58'; -import { DIDDocument } from 'did-resolver'; import { ethers } from 'ethers'; -import { exportJWK, importJWK, JWK, JWTPayload, SignJWT } from 'jose'; -import jwt_decode from 'jwt-decode'; +import { exportJWK, importJWK, JWK, SignJWT } from 'jose'; import moment from 'moment'; import { v4 as uuidv4 } from 'uuid'; @@ -13,6 +11,7 @@ import { assertValidMetadata, base64ToHexString, DiscoveryMetadataPayload, + JwtPayload, KeyCurve, KeyType, ResponseIss, @@ -23,8 +22,10 @@ import { SubjectSyntaxTypesSupportedValues, SubjectType, } from '../src'; +import { parseJWT } from '../src/helpers/jwtUtils'; import SIOPErrors from '../src/types/Errors'; +import { DIDDocument } from './ResolverTestUtils'; import { DID_DOCUMENT_PUBKEY_B58, DID_DOCUMENT_PUBKEY_JWK, @@ -70,7 +71,7 @@ function getEthWallet(key: JWK): ethers.Wallet { export const prefixWith0x = (key: string): string => (key.startsWith('0x') ? key : `0x${key}`); -export interface IEnterpriseAuthZToken extends JWTPayload { +export interface IEnterpriseAuthZToken extends JwtPayload { sub?: string; did: string; aud: string; @@ -133,7 +134,7 @@ const mockedEntityAuthNToken = async ( }; const privateKey = await importJWK(jwk, SigningAlgo.ES256K); - const jwt = await new SignJWT(payload as unknown as JWTPayload) + const jwt = await new SignJWT(payload as unknown as JwtPayload) .setProtectedHeader({ alg: 'ES256K', typ: 'JWT', @@ -150,19 +151,19 @@ export async function mockedGetEnterpriseAuthToken(enterpriseName?: string): Pro hexPublicKey: string; }> { const testAuth = await mockedEntityAuthNToken(enterpriseName); - const payload = jwt_decode(testAuth.jwt); + const { payload } = parseJWT(testAuth.jwt); const inputPayload: IEnterpriseAuthZToken = { did: testAuth.did, - aud: (payload as JWTPayload)?.iss ? (payload as JWTPayload).iss : 'Test Entity', + aud: (payload as JwtPayload)?.iss ? (payload as JwtPayload).iss : 'Test Entity', nonce: (payload as IEnterpriseAuthZToken).nonce, }; const testApiPayload = { ...inputPayload, ...{ - sub: (payload as JWTPayload).iss, // Should be the id of the app that is requesting the token + sub: (payload as JwtPayload).iss, // Should be the id of the app that is requesting the token iat: moment().unix(), exp: moment().add(15, 'minutes').unix(), aud: 'test', @@ -218,14 +219,6 @@ export const getParsedDidDocument = (didKey: DidKey): DIDDocument => { return didDocJwk; }; -/* -export const resolveDidKey = async (did: string): Promise => { - return (await DidKeyDriver.get(did, { - accept: 'application/did+ld+json', - })) as DIDResolutionResult; -}; -*/ - export const WELL_KNOWN_OPENID_FEDERATION = 'https://www.example.com/.well-known/openid-federation'; export const metadata: { opMetadata: DiscoveryMetadataPayload; diff --git a/test/data/mockedData.ts b/test/data/mockedData.ts index 173eee0d..cd89e753 100644 --- a/test/data/mockedData.ts +++ b/test/data/mockedData.ts @@ -1,14 +1,8 @@ import { ServiceTypesEnum } from '@sphereon/wellknown-dids-client'; -import { JWTHeader } from 'did-jwt'; -import { DIDDocument } from 'did-resolver'; -export const UNIT_TEST_TIMEOUT = 90000; +import { DIDDocument } from '../ResolverTestUtils'; -export const DIDAUTH_HEADER: JWTHeader = { - typ: 'JWT', - alg: 'ES256K', - kid: 'did:ethr:0x416e6e6162656c2e4c65652e452d412d506f652e#key1', -}; +export const UNIT_TEST_TIMEOUT = 90000; export const VERIFIER_LOGO_FOR_CLIENT = 'https://sphereon.com/content/themes/sphereon/assets/favicons/safari-pinned-tab.svg'; @@ -99,7 +93,7 @@ export const DID_KEY_DOCUMENT = { serviceEndpoint: DID_KEY_ORIGIN, }, ], -}; +} as DIDDocument; export const VC_KEY_PAIR = { type: 'Ed25519VerificationKey2020', diff --git a/test/e2e/EBSI.spec.ts b/test/e2e/EBSI.spec.ts index 07307a31..0058c799 100644 --- a/test/e2e/EBSI.spec.ts +++ b/test/e2e/EBSI.spec.ts @@ -7,7 +7,8 @@ import { Resolver } from 'did-resolver'; import { importJWK, JWK, SignJWT } from 'jose'; import { v4 as uuidv4 } from 'uuid'; -import { CheckLinkedDomain, OP, SigningAlgo } from '../../src'; +import { OP, SigningAlgo } from '../../src'; +import { getCreateJwtCallback, getVerifyJwtCallback } from '../DidJwtTestUtils'; const ID_TOKEN_REQUEST_URL = 'https://api-conformance.ebsi.eu/conformance/v3/auth-mock/id_token_request'; @@ -23,7 +24,7 @@ export const jwk: JWK = { const hexPrivateKey = '47dc6ae067aa011f8574d2da7cf8c326538af08b85e6779d192a9893291c9a0a'; const nonce = uuidv4(); -export const generateDid = (_opts?: { seed?: Uint8Array }) => { +export const generateDid = () => { const did = EbsiWallet.createDid('NATURAL_PERSON', jwk); return did; }; @@ -32,7 +33,7 @@ const keyResolver = getKeyResolver(); const didStr = generateDid(); const kid = `${didStr}#${parseDid(didStr).id}`; -console.log(kid); + describe('EBSI SIOPv2 should', () => { async function testWithOp() { const did = await generateDid(/*{ seed: u8a.fromString(hexPrivateKey, 'base16') }*/); @@ -45,11 +46,11 @@ describe('EBSI SIOPv2 should', () => { const correlationId = 'test'; + const resolver = new Resolver(keyResolver); const op: OP = OP.builder() - .addResolver('key', new Resolver(keyResolver)) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) .withPresentationSignCallback(presentationSignCalback) - .withSignature({ alg: SigningAlgo.ES256, kid, did: didStr, hexPrivateKey }) + .withCreateJwtCallback(getCreateJwtCallback({ alg: SigningAlgo.ES256, kid, did: didStr, hexPrivateKey })) + .withVerifyJwtCallback(getVerifyJwtCallback(resolver, { checkLinkedDomain: 'never' })) .build(); const verifiedAuthRequest = await op.verifyAuthorizationRequest(authRequestURL, { correlationId }); @@ -58,6 +59,11 @@ describe('EBSI SIOPv2 should', () => { const authResponse = await op.createAuthorizationResponse(verifiedAuthRequest, { issuer: didStr, correlationId, + jwtIssuer: { + method: 'did', + didUrl: kid, + alg: SigningAlgo.ES256, + }, }); expect(authResponse).toBeDefined(); diff --git a/test/e2e/mattr.launchpad.spec.ts b/test/e2e/mattr.launchpad.spec.ts index ded7ab68..09b15476 100644 --- a/test/e2e/mattr.launchpad.spec.ts +++ b/test/e2e/mattr.launchpad.spec.ts @@ -1,24 +1,21 @@ import { PresentationSignCallBackParams, PresentationSubmissionLocation } from '@sphereon/pex'; import { W3CVerifiablePresentation } from '@sphereon/ssi-types'; import * as ed25519 from '@transmute/did-key-ed25519'; -import { resolve as didKeyResolve } from '@transmute/did-key-ed25519'; import { fetch } from 'cross-fetch'; import { DIDResolutionResult } from 'did-resolver'; -import { DIDResolutionOptions } from 'did-resolver/src/resolver'; import { importJWK, JWK, SignJWT } from 'jose'; import * as u8a from 'uint8arrays'; import { AuthorizationRequest, AuthorizationResponse, - CheckLinkedDomain, OP, PresentationDefinitionWithLocation, PresentationExchange, SigningAlgo, SupportedVersion, - VerificationMode, } from '../../src'; +import { getCreateJwtCallback, getVerifyJwtCallback } from '../DidJwtTestUtils'; export interface InitiateOfferRequest { types: string[]; @@ -49,7 +46,7 @@ const hexPrivateKey = '913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a8 const didStr = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`; const kid = `${didStr}#z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`; -console.log(kid); + export const generateDid = async (opts?: { seed?: Uint8Array }) => { const { didDocument, keys } = await ed25519.generate( { @@ -63,12 +60,16 @@ export const generateDid = async (opts?: { seed?: Uint8Array }) => { return { keys, didDocument }; }; -const resolve = async (didUrl: string, options?: DIDResolutionOptions): Promise => { +const resolve = async (didUrl: string): Promise => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return await didKeyResolve(didUrl, options); }; +const getResolver = () => { + return { resolve }; +}; + describe('OID4VCI-Client using Mattr issuer should', () => { async function testWithOp(format: string | string[]) { const did = await generateDid({ seed: u8a.fromString(hexPrivateKey, 'base16') }); @@ -84,10 +85,9 @@ describe('OID4VCI-Client using Mattr issuer should', () => { const correlationId = 'test'; const op: OP = OP.builder() - .addResolver('key', { resolve }) - .withCheckLinkedDomain(CheckLinkedDomain.NEVER) .withPresentationSignCallback(presentationSignCalback) - .withSignature({ alg: SigningAlgo.EDDSA, kid, did: didStr, hexPrivateKey }) + .withCreateJwtCallback(getCreateJwtCallback({ alg: SigningAlgo.EDDSA, kid, did: didStr, hexPrivateKey })) + .withVerifyJwtCallback(getVerifyJwtCallback(getResolver(), { checkLinkedDomain: 'never' })) .build(); const verifiedAuthRequest = await op.verifyAuthorizationRequest(authorizeRequestUri, { correlationId }); @@ -112,13 +112,17 @@ describe('OID4VCI-Client using Mattr issuer should', () => { presentationSubmission: verifiablePresentationResult.presentationSubmission, }, correlationId, + jwtIssuer: { + method: 'did', + didUrl: kid, + alg: SigningAlgo.EDDSA, + }, }); expect(authResponse).toBeDefined(); expect(authResponse.response.payload).toBeDefined(); expect(authResponse.response.payload.presentation_submission).toBeDefined(); expect(authResponse.response.payload.vp_token).toBeDefined(); - console.log(JSON.stringify(authResponse)); const result = await op.submitAuthorizationResponse(authResponse); expect(result.status).toEqual(200); @@ -139,10 +143,8 @@ describe('OID4VCI-Client using Mattr issuer should', () => { const verifiedAuthRequest = await AuthorizationRequest.verify(authorizeRequestUri, { correlationId, - verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: {}, - }, + verifyJwtCallback: getVerifyJwtCallback(getResolver()), + verification: {}, }); expect(verifiedAuthRequest).toBeDefined(); expect(verifiedAuthRequest.presentationDefinitions).toHaveLength(1); @@ -161,20 +163,35 @@ describe('OID4VCI-Client using Mattr issuer should', () => { const authResponse = await AuthorizationResponse.fromVerifiedAuthorizationRequest( verifiedAuthRequest, { + jwtIssuer: { + method: 'did', + didUrl: kid, + alg: SigningAlgo.EDDSA, + }, presentationExchange: { verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], presentationSubmission: verifiablePresentationResult.presentationSubmission, }, - signature: { hexPrivateKey: '913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5', did: didStr, kid, alg: SigningAlgo.EDDSA }, + createJwtCallback: getCreateJwtCallback({ + hexPrivateKey: '913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5', + did: didStr, + kid, + alg: SigningAlgo.EDDSA, + }), + }, + { + correlationId, + verifyJwtCallback: getVerifyJwtCallback(getResolver()), + verification: {}, + nonce, + state, }, - { correlationId, verification: { mode: VerificationMode.INTERNAL, resolveOpts: {} }, nonce, state }, ); expect(authResponse).toBeDefined(); expect(authResponse.payload).toBeDefined(); expect(authResponse.payload.presentation_submission).toBeDefined(); expect(authResponse.payload.vp_token).toBeDefined(); - console.log(JSON.stringify(authResponse)); } it( @@ -214,18 +231,13 @@ describe('Mattr OID4VP v18 credential offer', () => { test('should verify using request directly', async () => { const offer = await getOffer('OpenBadgeCredential'); const authorizationRequest = await AuthorizationRequest.fromUriOrJwt(offer.authorizeRequestUri); - console.log(JSON.stringify(authorizationRequest.payload, null, 2)); - console.log(JSON.stringify(await authorizationRequest.getSupportedVersionsFromPayload())); const verification = await authorizationRequest.verify({ + verifyJwtCallback: getVerifyJwtCallback(getResolver()), correlationId: 'test', - verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: {}, - }, + verification: {}, }); - console.log(JSON.stringify(verification)); expect(verification).toBeDefined(); expect(verification.versions).toEqual([SupportedVersion.SIOPv2_D12_OID4VP_D18]); diff --git a/test/functions/DidJWT.spec.ts b/test/functions/DidJWT.spec.ts deleted file mode 100644 index 400e04f2..00000000 --- a/test/functions/DidJWT.spec.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { JWTDecoded } from 'did-jwt/lib/JWT'; - -import { - getIssuerDidFromJWT, - getMethodFromDid, - getNetworkFromDid, - getSubDidFromPayload, - isEd25519DidKeyMethod, - isIssSelfIssued, - parseJWT, - SIOPErrors, - toSIOPRegistrationDidMethod, -} from '../../src'; - -const validJWT = - 'eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZXRocjoweDk3NTgzNmREM0Y1RTk4QzE5RjBmM2I4N0Y5OWFGMzA1MDAyNkREQzIjY29udHJvbGxlciIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzIyNzE4MDMuMjEyLCJleHAiOjE2MzIyNzI0MDMuMjEyLCJpc3MiOiJodHRwczovL3NlbGYtaXNzdWVkLm1lL3YyIiwic3ViIjoiZGlkOmV0aHI6MHg5NzU4MzZkRDNGNUU5OEMxOUYwZjNiODdGOTlhRjMwNTAwMjZEREMyIiwiYXVkIjoiaHR0cHM6Ly9hY21lLmNvbS9oZWxsbyIsImRpZCI6ImRpZDpldGhyOjB4OTc1ODM2ZEQzRjVFOThDMTlGMGYzYjg3Rjk5YUYzMDUwMDI2RERDMiIsInN1Yl90eXBlIjoiZGlkIiwic3ViX2p3ayI6eyJraWQiOiJkaWQ6ZXRocjoweDk3NTgzNmREM0Y1RTk4QzE5RjBmM2I4N0Y5OWFGMzA1MDAyNkREQzIjY29udHJvbGxlciIsImt0eSI6IkVDIiwiY3J2Ijoic2VjcDI1NmsxIiwieCI6IkloUXVEek5BY1dvczVXeDd4U1NHMks2Zkp6MnBobU1nbUZ4UE1xaEU4XzgiLCJ5IjoiOTlreGpCMVgzaUtkRXZkbVFDbllqVm5PWEJyc2VwRGdlMFJrek1aUDN1TSJ9LCJzdGF0ZSI6ImQ2NzkzYjQ2YWIyMzdkMzczYWRkNzQwMCIsIm5vbmNlIjoiU1JXSzltSVpFd1F6S3dsZlZoMkE5SV9weUtBT0tnNDAtWDJqbk5aZEN0byIsInJlZ2lzdHJhdGlvbiI6eyJpc3N1ZXIiOiJodHRwczovL3NlbGYtaXNzdWVkLm1lL3YyIiwicmVzcG9uc2VfdHlwZXNfc3VwcG9ydGVkIjoiaWRfdG9rZW4iLCJhdXRob3JpemF0aW9uX2VuZHBvaW50Ijoib3BlbmlkOiIsInNjb3Blc19zdXBwb3J0ZWQiOiJvcGVuaWQiLCJpZF90b2tlbl9zaWduaW5nX2FsZ192YWx1ZXNfc3VwcG9ydGVkIjpbIkVTMjU2SyIsIkVkRFNBIl0sInJlcXVlc3Rfb2JqZWN0X3NpZ25pbmdfYWxnX3ZhbHVlc19zdXBwb3J0ZWQiOlsiRVMyNTZLIiwiRWREU0EiXSwic3ViamVjdF90eXBlc19zdXBwb3J0ZWQiOiJwYWlyd2lzZSJ9fQ.coLQr2hQuMwEfYUd3HdFt-ixhsaicc37cC9cwmQ2U5hfxRhAb871s9G1GAo3qhsa9v3t0G1bTX2J9WhLaC5J_Q'; - -describe('DidJWT ', () => { - it('getIssuerDidFromPayload: should pass if issuer is correct', async function () { - const decoded: JWTDecoded = parseJWT(validJWT); - const result = getSubDidFromPayload(decoded.payload); - expect(result).toBeDefined(); - }); - - it('isIssSelfIssued: should pass if fails', async function () { - const decoded = parseJWT(validJWT); - decoded.payload.iss = 'https://3rd-party-issued.me/v2'; - const result = isIssSelfIssued(decoded.payload); - expect(result).toBe(false); - }); - - it('isIssSelfIssued: should pass if isIssSelfIssued', async function () { - const decoded = parseJWT(validJWT); - const result = isIssSelfIssued(decoded.payload); - expect(result).toBe(true); - }); - - it('getIssuerDidFromJWT: should pass if returns correct result', async function () { - const result = getIssuerDidFromJWT(validJWT); - expect(result).toBe('did:ethr:0x975836dD3F5E98C19F0f3b87F99aF3050026DDC2'); - }); - - it('getIssuerDidFromJWT: should pass if throws NO_ISS_DID', async function () { - let err = undefined; - try { - getIssuerDidFromJWT( - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', - ); - } catch (e) { - err = e; - } - expect(err?.message).toContain(SIOPErrors.NO_ISS_DID); - }); - - it('parseJWT: should pass if method throws Incorrect format JWT', async function () { - let err = undefined; - try { - parseJWT('eysfsdfsdfsd'); - } catch (e) { - err = e; - } - expect(err?.message).toContain('Incorrect format JWT'); - }); - - it('parseJWT: should pass if method returns with correct decoded ', async function () { - const result = parseJWT( - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', - ); - expect(result.payload.name).toBe('John Doe'); - }); - - it('getMethodFromDid: should pass if throws BAD_PARAMS on calling with null', async function () { - let err = undefined; - try { - getMethodFromDid(null); - } catch (e) { - err = e; - } - expect(err?.message).toBe(SIOPErrors.BAD_PARAMS); - }); - - it('getMethodFromDid: should pass if throws BAD_PARAMS on calling with less than 3 segments', async function () { - let err = undefined; - try { - getMethodFromDid('pid:123'); - } catch (e) { - err = e; - } - expect(err?.message).toBe(SIOPErrors.BAD_PARAMS); - }); - - it('getMethodFromDid: should pass if idx #1 is returned', async function () { - const result = getMethodFromDid('did:ethr:0x226e2e2223333c2e4c65652e452d412d50611111'); - expect(result).toBe('ethr'); - }); - - it('getNetworkFromDid: should pass if throws BAD_PARAMS', async function () { - try { - getNetworkFromDid('pid:ethr:0x226e2e2223333c2e4c65652e452d412d50611111'); - } catch (e) { - expect(e.message).toBe(SIOPErrors.BAD_PARAMS); - } - }); - - it('getNetworkFromDid: should pass if idx #2 is returned', async function () { - const result = getNetworkFromDid('did:ethr:method:0x226e2e2223333c2e4c65652e452d412d50611111'); - expect(result).toBe('method'); - }); - - it('getNetworkFromDid: should pass if idx #2 and #3 is returned', async function () { - const result = getNetworkFromDid('did:ethr:method1:method2:0x226e2e2223333c2e4c65652e452d412d50611111'); - expect(result).toBe('method1:method2'); - }); - - it('getNetworkFromDid: should pass if network is mainnet', async function () { - const result = getNetworkFromDid('did:ethr:0x226e2e2223333c2e4c65652e452d412d50611111'); - expect(result).toBe('mainnet'); - }); - - it('toSIOPRegistrationDidMethod: should pass if fails', async function () { - const result = toSIOPRegistrationDidMethod('pid:ethr:0x226e2e2223333c2e4c65652e452d412d50611111'); - expect(result).toBe('did:pid'); - }); - - it('toSIOPRegistrationDidMethod: should pass if', async function () { - const result = toSIOPRegistrationDidMethod('did:ethr:0x226e2e2223333c2e4c65652e452d412d50611111'); - expect(result).toBe('did:ethr'); - }); - - it('is ED25519 Did key', async function () { - const result = isEd25519DidKeyMethod('did:key:z6MkwVRpJ1AHXrb3z1Ao59a87MB6NqvUiseQ9XnVDf7RFE3K'); - expect(result).toBe(true); - }); -}); diff --git a/test/functions/LinkedDomainValidations.spec.ts b/test/functions/LinkedDomainValidations.spec.ts deleted file mode 100644 index 81f477c4..00000000 --- a/test/functions/LinkedDomainValidations.spec.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { Ed25519Signature2020 } from '@digitalcredentials/ed25519-signature-2020'; -import { Ed25519VerificationKey2020 } from '@digitalcredentials/ed25519-verification-key-2020'; -import * as vc from '@digitalcredentials/vc'; -import { - DomainLinkageCredential, - IIssueCallbackArgs, - IVerifyCallbackArgs, - IVerifyCredentialResult, - ProofFormatTypesEnum, - WellKnownDidIssuer, -} from '@sphereon/wellknown-dids-client'; -import nock from 'nock'; - -import { CheckLinkedDomain, validateLinkedDomainWithDid, VerificationMode } from '../../src'; -import * as didResolution from '../../src/did/DIDResolution'; -import { DocumentLoader } from '../DocumentLoader'; -import { DID_ION_DOCUMENT, DID_ION_ORIGIN, DID_KEY, DID_KEY_DOCUMENT, DID_KEY_ORIGIN, VC_KEY_PAIR } from '../data/mockedData'; - -jest.setTimeout(300000); - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const verifyCallbackTruthy = async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }); - -let config; -const verify = async (args: IVerifyCallbackArgs): Promise => { - const keyPair = await Ed25519VerificationKey2020.from(VC_KEY_PAIR); - const suite = new Ed25519Signature2020({ key: keyPair }); - suite.verificationMethod = keyPair.id; - return await vc.verifyCredential({ credential: args.credential, suite, documentLoader: new DocumentLoader().getLoader() }); -}; - -afterEach(() => { - jest.clearAllMocks(); -}); - -beforeAll(async () => { - nock.cleanAll(); - // for whatever reason cloudflare sometimes has issues when running the test - nock('https://www.w3.org') - .get('/2018/credentials/v1') - .times(10) - .reply(200, { - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - VerifiableCredential: { - '@id': 'https://www.w3.org/2018/credentials#VerifiableCredential', - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - cred: 'https://www.w3.org/2018/credentials#', - sec: 'https://w3id.org/security#', - xsd: 'http://www.w3.org/2001/XMLSchema#', - - credentialSchema: { - '@id': 'cred:credentialSchema', - '@type': '@id', - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - cred: 'https://www.w3.org/2018/credentials#', - - JsonSchemaValidator2018: 'cred:JsonSchemaValidator2018', - }, - }, - credentialStatus: { '@id': 'cred:credentialStatus', '@type': '@id' }, - credentialSubject: { '@id': 'cred:credentialSubject', '@type': '@id' }, - evidence: { '@id': 'cred:evidence', '@type': '@id' }, - expirationDate: { '@id': 'cred:expirationDate', '@type': 'xsd:dateTime' }, - holder: { '@id': 'cred:holder', '@type': '@id' }, - issued: { '@id': 'cred:issued', '@type': 'xsd:dateTime' }, - issuer: { '@id': 'cred:issuer', '@type': '@id' }, - issuanceDate: { '@id': 'cred:issuanceDate', '@type': 'xsd:dateTime' }, - proof: { '@id': 'sec:proof', '@type': '@id', '@container': '@graph' }, - refreshService: { - '@id': 'cred:refreshService', - '@type': '@id', - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - cred: 'https://www.w3.org/2018/credentials#', - - ManualRefreshService2018: 'cred:ManualRefreshService2018', - }, - }, - termsOfUse: { '@id': 'cred:termsOfUse', '@type': '@id' }, - validFrom: { '@id': 'cred:validFrom', '@type': 'xsd:dateTime' }, - validUntil: { '@id': 'cred:validUntil', '@type': 'xsd:dateTime' }, - }, - }, - - VerifiablePresentation: { - '@id': 'https://www.w3.org/2018/credentials#VerifiablePresentation', - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - cred: 'https://www.w3.org/2018/credentials#', - sec: 'https://w3id.org/security#', - - holder: { '@id': 'cred:holder', '@type': '@id' }, - proof: { '@id': 'sec:proof', '@type': '@id', '@container': '@graph' }, - verifiableCredential: { '@id': 'cred:verifiableCredential', '@type': '@id', '@container': '@graph' }, - }, - }, - - EcdsaSecp256k1Signature2019: { - '@id': 'https://w3id.org/security#EcdsaSecp256k1Signature2019', - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - sec: 'https://w3id.org/security#', - xsd: 'http://www.w3.org/2001/XMLSchema#', - - challenge: 'sec:challenge', - created: { '@id': 'http://purl.org/dc/terms/created', '@type': 'xsd:dateTime' }, - domain: 'sec:domain', - expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, - jws: 'sec:jws', - nonce: 'sec:nonce', - proofPurpose: { - '@id': 'sec:proofPurpose', - '@type': '@vocab', - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - sec: 'https://w3id.org/security#', - - assertionMethod: { '@id': 'sec:assertionMethod', '@type': '@id', '@container': '@set' }, - authentication: { '@id': 'sec:authenticationMethod', '@type': '@id', '@container': '@set' }, - }, - }, - proofValue: 'sec:proofValue', - verificationMethod: { '@id': 'sec:verificationMethod', '@type': '@id' }, - }, - }, - - EcdsaSecp256r1Signature2019: { - '@id': 'https://w3id.org/security#EcdsaSecp256r1Signature2019', - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - sec: 'https://w3id.org/security#', - xsd: 'http://www.w3.org/2001/XMLSchema#', - - challenge: 'sec:challenge', - created: { '@id': 'http://purl.org/dc/terms/created', '@type': 'xsd:dateTime' }, - domain: 'sec:domain', - expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, - jws: 'sec:jws', - nonce: 'sec:nonce', - proofPurpose: { - '@id': 'sec:proofPurpose', - '@type': '@vocab', - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - sec: 'https://w3id.org/security#', - - assertionMethod: { '@id': 'sec:assertionMethod', '@type': '@id', '@container': '@set' }, - authentication: { '@id': 'sec:authenticationMethod', '@type': '@id', '@container': '@set' }, - }, - }, - proofValue: 'sec:proofValue', - verificationMethod: { '@id': 'sec:verificationMethod', '@type': '@id' }, - }, - }, - - Ed25519Signature2018: { - '@id': 'https://w3id.org/security#Ed25519Signature2018', - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - sec: 'https://w3id.org/security#', - xsd: 'http://www.w3.org/2001/XMLSchema#', - - challenge: 'sec:challenge', - created: { '@id': 'http://purl.org/dc/terms/created', '@type': 'xsd:dateTime' }, - domain: 'sec:domain', - expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, - jws: 'sec:jws', - nonce: 'sec:nonce', - proofPurpose: { - '@id': 'sec:proofPurpose', - '@type': '@vocab', - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - sec: 'https://w3id.org/security#', - - assertionMethod: { '@id': 'sec:assertionMethod', '@type': '@id', '@container': '@set' }, - authentication: { '@id': 'sec:authenticationMethod', '@type': '@id', '@container': '@set' }, - }, - }, - proofValue: 'sec:proofValue', - verificationMethod: { '@id': 'sec:verificationMethod', '@type': '@id' }, - }, - }, - - RsaSignature2018: { - '@id': 'https://w3id.org/security#RsaSignature2018', - '@context': { - '@version': 1.1, - '@protected': true, - - challenge: 'sec:challenge', - created: { '@id': 'http://purl.org/dc/terms/created', '@type': 'xsd:dateTime' }, - domain: 'sec:domain', - expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, - jws: 'sec:jws', - nonce: 'sec:nonce', - proofPurpose: { - '@id': 'sec:proofPurpose', - '@type': '@vocab', - '@context': { - '@version': 1.1, - '@protected': true, - - id: '@id', - type: '@type', - - sec: 'https://w3id.org/security#', - - assertionMethod: { '@id': 'sec:assertionMethod', '@type': '@id', '@container': '@set' }, - authentication: { '@id': 'sec:authenticationMethod', '@type': '@id', '@container': '@set' }, - }, - }, - proofValue: 'sec:proofValue', - verificationMethod: { '@id': 'sec:verificationMethod', '@type': '@id' }, - }, - }, - - proof: { '@id': 'https://w3id.org/security#proof', '@type': '@id', '@container': '@graph' }, - }, - }); - const issueCallback = async (args: IIssueCallbackArgs): Promise => { - const keyPair = await Ed25519VerificationKey2020.from(VC_KEY_PAIR); - const suite = new Ed25519Signature2020({ key: keyPair }); - suite.verificationMethod = keyPair.id; - const documentLoader = new DocumentLoader(); - return await vc.issue({ credential: args.credential, suite, documentLoader: documentLoader.getLoader() }); - }; - - const issuer: WellKnownDidIssuer = new WellKnownDidIssuer({ - issueCallback: (args: IIssueCallbackArgs) => issueCallback(args), - }); - const args = { - issuances: [ - { - did: DID_KEY, - origin: 'https://example.com', - issuanceDate: new Date().toISOString(), - expirationDate: new Date(new Date().getFullYear() + 10, new Date().getMonth(), new Date().getDay()).toISOString(), - options: { proofFormat: ProofFormatTypesEnum.JSON_LD }, - }, - ], - }; - config = await issuer.issueDidConfigurationResource(args); -}); - -describe('validateLinkedDomainWithDid', () => { - it('should succeed with key did and CheckLinkedDomain.ALWAYS', async () => { - const DID_CONFIGURATION = { ...config }; - nock(DID_KEY_ORIGIN).get('/.well-known/did-configuration.json').times(1).reply(200, DID_CONFIGURATION); - // Needed to verify the credential - nock(DID_KEY_ORIGIN).get('/1234').times(1).reply(200, DID_KEY_DOCUMENT); - jest.spyOn(didResolution, 'resolveDidDocument').mockResolvedValue(Promise.resolve(DID_KEY_DOCUMENT as never)); - await expect( - validateLinkedDomainWithDid(DID_KEY, { - wellknownDIDVerifyCallback: (args: IVerifyCallbackArgs) => verify(args), - checkLinkedDomain: CheckLinkedDomain.ALWAYS, - resolveOpts: {}, - mode: VerificationMode.INTERNAL, - }), - ).resolves.not.toThrow(); - }); - it('should succeed with ion did and CheckLinkedDomain.ALWAYS', async () => { - const did = - 'did:ion:EiCMvVdXv6iL3W8i4n-LmqUhE614kX4TYxVR5kTY2QGOjg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXkxIiwicHVibGljS2V5SndrIjp7ImNydiI6InNlY3AyNTZrMSIsImt0eSI6IkVDIiwieCI6Ii1MbHNpQVk5b3JmMXpKQlJOV0NuN0RpNUpoYl8tY2xhNlY5R3pHa3FmSFUiLCJ5IjoiRXBIU25GZHQ2ZU5lRkJEZzNVNVFIVDE0TVRsNHZIc0h5NWRpWU9DWEs1TSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiIsImFzc2VydGlvbk1ldGhvZCJdLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFWZXJpZmljYXRpb25LZXkyMDE5In1dLCJzZXJ2aWNlcyI6W3siaWQiOiJsZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHBzOi8vbGR0ZXN0LnNwaGVyZW9uLmNvbSIsInR5cGUiOiJMaW5rZWREb21haW5zIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlBem8wTVVZUW5HNWM0VFJKZVFsNFR5WVRrSmRyeTJoeXlQUlpENzdFQm1CdyJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQUwtaEtrLUVsODNsRVJiZkFDUk1kSWNQVjRXWGJqZ3dsZ1ZDWTNwbDhhMGciLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUItT2NSbTlTNXdhU3QxbU4zSG4zM2RnMzJKN25MOEdBVHpGQ2ZXaWdIXzh3In19'; - const DID_CONFIGURATION = { - '@context': 'https://identity.foundation/.well-known/did-configuration/v1', - linked_dids: [ - 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImRpZDprZXk6ejZNa29USHNnTk5yYnk4SnpDTlExaVJMeVc1UVE2UjhYdXU2QUE4aWdHck1WUFVNI3o2TWtvVEhzZ05OcmJ5OEp6Q05RMWlSTHlXNVFRNlI4WHV1NkFBOGlnR3JNVlBVTSJ9.eyJleHAiOjE3NjQ4NzkxMzksImlzcyI6ImRpZDprZXk6ejZNa29USHNnTk5yYnk4SnpDTlExaVJMeVc1UVE2UjhYdXU2QUE4aWdHck1WUFVNIiwibmJmIjoxNjA3MTEyNzM5LCJzdWIiOiJkaWQ6a2V5Ono2TWtvVEhzZ05OcmJ5OEp6Q05RMWlSTHlXNVFRNlI4WHV1NkFBOGlnR3JNVlBVTSIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9pZGVudGl0eS5mb3VuZGF0aW9uLy53ZWxsLWtub3duL2RpZC1jb25maWd1cmF0aW9uL3YxIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rb1RIc2dOTnJieThKekNOUTFpUkx5VzVRUTZSOFh1dTZBQThpZ0dyTVZQVU0iLCJvcmlnaW4iOiJodHRwczovL2lkZW50aXR5LmZvdW5kYXRpb24ifSwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTEyLTA0VDE0OjEyOjE5LTA2OjAwIiwiaXNzdWFuY2VEYXRlIjoiMjAyMC0xMi0wNFQxNDoxMjoxOS0wNjowMCIsImlzc3VlciI6ImRpZDprZXk6ejZNa29USHNnTk5yYnk4SnpDTlExaVJMeVc1UVE2UjhYdXU2QUE4aWdHck1WUFVNIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkRvbWFpbkxpbmthZ2VDcmVkZW50aWFsIl19fQ.YZnpPMAW3GdaPXC2YKoJ7Igt1OaVZKq09XZBkptyhxTAyHTkX2Ewtew-JKHKQjyDyabY3HAy1LUPoIQX0jrU0J82pIYT3k2o7nNTdLbxlgb49FcDn4czntt5SbY0m1XwrMaKEvV0bHQsYPxNTqjYsyySccgPfmvN9IT8gRS-M9a6MZQxuB3oEMrVOQ5Vco0bvTODXAdCTHibAk1FlvKz0r1vO5QMhtW4OlRrVTI7ibquf9Nim_ch0KeMMThFjsBDKetuDF71nUcL5sf7PCFErvl8ZVw3UK4NkZ6iM-XIRsLL6rXP2SnDUVovcldhxd_pyKEYviMHBOgBdoNP6fOgRQ', - 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImRpZDprZXk6ejZNa29USHNnTk5yYnk4SnpDTlExaVJMeVc1UVE2UjhYdXU2QUE4aWdHck1WUFVNI3o2TWtvVEhzZ05OcmJ5OEp6Q05RMWlSTHlXNVFRNlI4WHV1NkFBOGlnR3JNVlBVTSJ9.eyJleHAiOjE3NjQ4NzkxMzksImlzcyI6ImRpZDprZXk6b3RoZXIiLCJuYmYiOjE2MDcxMTI3MzksInN1YiI6ImRpZDprZXk6b3RoZXIiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vaWRlbnRpdHkuZm91bmRhdGlvbi8ud2VsbC1rbm93bi9kaWQtY29uZmlndXJhdGlvbi92MSJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDprZXk6b3RoZXIiLCJvcmlnaW4iOiJodHRwczovL2lkZW50aXR5LmZvdW5kYXRpb24ifSwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTEyLTA0VDE0OjEyOjE5LTA2OjAwIiwiaXNzdWFuY2VEYXRlIjoiMjAyMC0xMi0wNFQxNDoxMjoxOS0wNjowMCIsImlzc3VlciI6ImRpZDprZXk6b3RoZXIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiRG9tYWluTGlua2FnZUNyZWRlbnRpYWwiXX19.rRuc-ojuEgyq8p_tBYK7BayuiNTBeXNyAnC14Rnjs-jsnhae4_E1Q12W99K2NGCGBi5KjNsBcZmdNJPxejiKPrjjcB99poFCgTY8tuRzDjVo0lIeBwfx9qqjKHTRTUR8FGM_imlOpVfBF4AHYxjkHvZn6c9lYvatYcDpB2UfH4BNXkdSVrUXy_kYjpMpAdRtyCAnD_isN1YpEHBqBmnfuVUbYcQK5kk6eiokRFDtWruL1OEeJMYPqjuBSd2m-H54tSM84Oic_pg2zXDjjBlXNelat6MPNT2QxmkwJg7oyewQWX2Ot2yyhSp9WyAQWMlQIe2x84R0lADUmZ1TPQchNw', - ], - }; - nock(DID_ION_ORIGIN).get('/.well-known/did-configuration.json').times(1).reply(200, DID_CONFIGURATION); - jest.spyOn(didResolution, 'resolveDidDocument').mockResolvedValue(Promise.resolve(DID_ION_DOCUMENT as never)); - await expect( - validateLinkedDomainWithDid(did, { - wellknownDIDVerifyCallback: verifyCallbackTruthy, - checkLinkedDomain: CheckLinkedDomain.ALWAYS, - resolveOpts: {}, - mode: VerificationMode.INTERNAL, - }), - ).resolves.not.toThrow(); - }); - - it('should fail with ion did and CheckLinkedDomain.ALWAYS', async () => { - const did = - 'did:ion:EiCMvVdXv6iL3W8i4n-LmqUhE614kX4TYxVR5kTY2QGOjg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXkxIiwicHVibGljS2V5SndrIjp7ImNydiI6InNlY3AyNTZrMSIsImt0eSI6IkVDIiwieCI6Ii1MbHNpQVk5b3JmMXpKQlJOV0NuN0RpNUpoYl8tY2xhNlY5R3pHa3FmSFUiLCJ5IjoiRXBIU25GZHQ2ZU5lRkJEZzNVNVFIVDE0TVRsNHZIc0h5NWRpWU9DWEs1TSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiIsImFzc2VydGlvbk1ldGhvZCJdLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFWZXJpZmljYXRpb25LZXkyMDE5In1dLCJzZXJ2aWNlcyI6W3siaWQiOiJsZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHBzOi8vbGR0ZXN0LnNwaGVyZW9uLmNvbSIsInR5cGUiOiJMaW5rZWREb21haW5zIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlBem8wTVVZUW5HNWM0VFJKZVFsNFR5WVRrSmRyeTJoeXlQUlpENzdFQm1CdyJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQUwtaEtrLUVsODNsRVJiZkFDUk1kSWNQVjRXWGJqZ3dsZ1ZDWTNwbDhhMGciLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUItT2NSbTlTNXdhU3QxbU4zSG4zM2RnMzJKN25MOEdBVHpGQ2ZXaWdIXzh3In19'; - await expect( - validateLinkedDomainWithDid(did, { - wellknownDIDVerifyCallback: verifyCallbackTruthy, - checkLinkedDomain: CheckLinkedDomain.ALWAYS, - resolveOpts: {}, - mode: VerificationMode.INTERNAL, - }), - ).rejects.toThrow(); - }); - - it('should fail with ion did and CheckLinkedDomain.IF_PRESENT', async () => { - const did = - 'did:ion:EiCMvVdXv6iL3W8i4n-LmqUhE614kX4TYxVR5kTY2QGOjg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXkxIiwicHVibGljS2V5SndrIjp7ImNydiI6InNlY3AyNTZrMSIsImt0eSI6IkVDIiwieCI6Ii1MbHNpQVk5b3JmMXpKQlJOV0NuN0RpNUpoYl8tY2xhNlY5R3pHa3FmSFUiLCJ5IjoiRXBIU25GZHQ2ZU5lRkJEZzNVNVFIVDE0TVRsNHZIc0h5NWRpWU9DWEs1TSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiIsImFzc2VydGlvbk1ldGhvZCJdLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFWZXJpZmljYXRpb25LZXkyMDE5In1dLCJzZXJ2aWNlcyI6W3siaWQiOiJsZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHBzOi8vbGR0ZXN0LnNwaGVyZW9uLmNvbSIsInR5cGUiOiJMaW5rZWREb21haW5zIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlBem8wTVVZUW5HNWM0VFJKZVFsNFR5WVRrSmRyeTJoeXlQUlpENzdFQm1CdyJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQUwtaEtrLUVsODNsRVJiZkFDUk1kSWNQVjRXWGJqZ3dsZ1ZDWTNwbDhhMGciLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUItT2NSbTlTNXdhU3QxbU4zSG4zM2RnMzJKN25MOEdBVHpGQ2ZXaWdIXzh3In19'; - await expect( - validateLinkedDomainWithDid(did, { - wellknownDIDVerifyCallback: verifyCallbackTruthy, - checkLinkedDomain: CheckLinkedDomain.IF_PRESENT, - resolveOpts: {}, - mode: VerificationMode.INTERNAL, - }), - ).rejects.toThrow(); - }); -}); diff --git a/test/interop/EBSI/EBSI.spec.ts b/test/interop/EBSI/EBSI.spec.ts index 890e0014..ee9aa0a3 100644 --- a/test/interop/EBSI/EBSI.spec.ts +++ b/test/interop/EBSI/EBSI.spec.ts @@ -1,7 +1,8 @@ -import { IVerifyCallbackArgs, IVerifyCredentialResult } from '@sphereon/wellknown-dids-client'; import nock from 'nock'; -import { AuthorizationResponseOpts, OP, SupportedVersion, VerificationMode, VerifyAuthorizationRequestOpts } from '../../../src'; +import { AuthorizationResponseOpts, OP, SupportedVersion, VerifyAuthorizationRequestOpts } from '../../../src'; +import { getVerifyJwtCallback } from '../../DidJwtTestUtils'; +import { getResolver } from '../../ResolverTestUtils'; import { UNIT_TEST_TIMEOUT } from '../../data/mockedData'; const SIOP_URI = @@ -10,6 +11,9 @@ const JWT = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkZMeEkzTE04bUZDRkNEMUg0VmpacVd0MVBmaWQyaThBQ1lpRHZFelo5VU0ifQ.eyJzdGF0ZSI6IjNmM2E2NzNhLTc4MzUtNDJmMS1hMDNlLWIxODZmZDA0MmRjYyIsImNsaWVudF9pZCI6Imh0dHBzOi8vY29uZm9ybWFuY2UtdGVzdC5lYnNpLmV1L2NvbmZvcm1hbmNlL3YzL2F1dGgtbW9jayIsInJlZGlyZWN0X3VyaSI6Imh0dHBzOi8vY29uZm9ybWFuY2UtdGVzdC5lYnNpLmV1L2NvbmZvcm1hbmNlL3YzL2F1dGgtbW9jay9kaXJlY3RfcG9zdCIsInJlc3BvbnNlX3R5cGUiOiJpZF90b2tlbiIsInJlc3BvbnNlX21vZGUiOiJkaXJlY3RfcG9zdCIsInNjb3BlIjoib3BlbmlkIiwibm9uY2UiOiIzYTUwZWZmYS00NTA1LTQyY2UtODcwOC0wYzRhYjMyMzc4ZGQiLCJpc3MiOiJodHRwczovL2NvbmZvcm1hbmNlLXRlc3QuZWJzaS5ldS9jb25mb3JtYW5jZS92My9hdXRoLW1vY2siLCJhdWQiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnFTWlpGakc0dFZnS2hFd0twcm9qcUxCM0MyWXBqNEg3M1N0Z2pNa1NYZzJtUXh1V0xmenVSMTJRc052Z1FXenJ6S1NmN1lSQk5yUlhLNzF2ZnExMkJieXhUTEZFWkJXZm5IcWV6QlZHUWlOTGZxZXV5d1pIZ3N0TUNjUzQ0VFhmYjIifQ.h0nQfHq2sck4PizIleqlTTPPjYPgEH8OPKK0ug7r_O7N4qEghfILnL07cs5y1gARIH7hJLNNvI7qXEerl-SdDw'; describe('EBSI', () => { const responseOpts: AuthorizationResponseOpts = { + createJwtCallback: () => { + throw new Error('Not implemented'); + }, /*checkLinkedDomain: CheckLinkedDomain.NEVER, responseURI: EXAMPLE_REDIRECT_URL, responseURIType: 'redirect_uri', @@ -42,14 +46,8 @@ describe('EBSI', () => { }; const verifyOpts: VerifyAuthorizationRequestOpts = { - verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: { - subjectSyntaxTypesSupported: ['did:ebsi'], - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - wellknownDIDVerifyCallback: async (_args: IVerifyCallbackArgs): Promise => ({ verified: true }), - }, + verifyJwtCallback: getVerifyJwtCallback(getResolver('ebsi')), + verification: {}, correlationId: '1234', supportedVersions: [SupportedVersion.SIOPv2_D12_OID4VP_D18], }; @@ -60,7 +58,6 @@ describe('EBSI', () => { const op = OP.fromOpts(responseOpts, verifyOpts); const verifiedRequest = await op.verifyAuthorizationRequest(SIOP_URI); - console.log(JSON.stringify(verifiedRequest, null, 2)); expect(verifiedRequest.issuer).toMatch('https://conformance-test.ebsi.eu/conformance/v3/auth-mock'); expect(verifiedRequest.jwt).toBeDefined(); }, diff --git a/test/regressions/ClientIdIsObject.spec.ts b/test/regressions/ClientIdIsObject.spec.ts index 2333afce..b08efbdb 100644 --- a/test/regressions/ClientIdIsObject.spec.ts +++ b/test/regressions/ClientIdIsObject.spec.ts @@ -1,4 +1,6 @@ import { PassBy, ResponseType, RevocationVerification, RP, Scope, SigningAlgo, SubjectType, SupportedVersion } from '../../src'; +import { parseJWT } from '../../src/helpers/jwtUtils'; +import { internalSignature } from '../DidJwtTestUtils'; const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello'; // const EXAMPLE_REFERENCE_URL = 'https://rp.acme.com/siop/jwts'; @@ -11,9 +13,7 @@ const rp = RP.builder() .withRedirectUri(EXAMPLE_REDIRECT_URL) .withRequestByValue() .withRevocationVerification(RevocationVerification.NEVER) - .withInternalSignature(HEX_KEY, DID, KID, SigningAlgo.ES256K) - .addDidMethod('ethr') - .addDidMethod('key') + .withCreateJwtCallback(internalSignature(HEX_KEY, DID, KID, SigningAlgo.ES256K)) .withSupportedVersions([SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1]) .withClientMetadata({ idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], @@ -52,7 +52,8 @@ describe('Creating an AuthRequest with an RP from builder', () => { state: 'b32f0087fc9816eb813fd11f', }); - const requestObjectPayload = await authRequest.requestObject.getPayload(); - await expect(requestObjectPayload.client_id).toEqual(DID); + const requestObjectJwt = await authRequest.requestObject.toJwt(); + const { payload } = parseJWT(requestObjectJwt); + await expect(payload.client_id).toEqual(DID); }); }); diff --git a/test/spec-compliance/jwtVCPresentationProfile.spec.ts b/test/spec-compliance/jwtVCPresentationProfile.spec.ts index 603db343..0f62c0d0 100644 --- a/test/spec-compliance/jwtVCPresentationProfile.spec.ts +++ b/test/spec-compliance/jwtVCPresentationProfile.spec.ts @@ -1,6 +1,5 @@ import { PresentationSignCallBackParams } from '@sphereon/pex'; import { IProofType } from '@sphereon/ssi-types'; -import { VerifyCallback } from '@sphereon/wellknown-dids-client'; import * as jose from 'jose'; import { KeyLike } from 'jose'; import nock from 'nock'; @@ -9,7 +8,6 @@ import * as u8a from 'uint8arrays'; import { AuthorizationRequest, AuthorizationResponse, - CheckLinkedDomain, IDToken, OP, PassBy, @@ -24,9 +22,10 @@ import { RP, SigningAlgo, SupportedVersion, - VerificationMode, VPTokenLocation, } from '../../src'; +import { getVerifyJwtCallback, internalSignature } from '../DidJwtTestUtils'; +import { getResolver } from '../ResolverTestUtils'; let rp: RP; let op: OP; @@ -34,6 +33,17 @@ let op: OP; afterEach(() => { nock.cleanAll(); }); + +const presentationVerificationCallback: PresentationVerificationCallback = async () => ({ verified: true }); +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const presentationSignCallback: PresentationSignCallback = async (_args: PresentationSignCallBackParams) => + TestVectors.authorizationResponsePayload.vp_token; + +const verifyJwtCallback = getVerifyJwtCallback(getResolver('ion'), { + policies: { exp: false, iat: false, aud: false, nbf: false }, + checkLinkedDomain: 'if_present', +}); + beforeEach(async () => { await TestVectors.init(); @@ -67,25 +77,17 @@ beforeEach(async () => { ) .withRedirectUri('https://example.com/siop-response', PropertyTarget.REQUEST_OBJECT) .withRequestBy(PassBy.REFERENCE, TestVectors.request_uri) - .addDidMethod('ion') - .withInternalSignature(TestVectors.verifierHexPrivateKey, TestVectors.verifierDID, TestVectors.verifierKID, SigningAlgo.EDDSA) + .withCreateJwtCallback(internalSignature(TestVectors.verifierHexPrivateKey, TestVectors.verifierDID, TestVectors.verifierKID, SigningAlgo.EDDSA)) + .withVerifyJwtCallback(verifyJwtCallback) .build(); op = OP.builder() - .addDidMethod('ion') - .withInternalSignature(TestVectors.holderHexPrivateKey, TestVectors.holderDID, TestVectors.holderKID, SigningAlgo.EDDSA) - .withCheckLinkedDomain(CheckLinkedDomain.IF_PRESENT) + .withCreateJwtCallback(internalSignature(TestVectors.holderHexPrivateKey, TestVectors.holderDID, TestVectors.holderKID, SigningAlgo.EDDSA)) + .withVerifyJwtCallback(verifyJwtCallback) .addSupportedVersion(SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1) .build(); }); -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const verifyCallback: VerifyCallback = async (_args) => ({ verified: true }); -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => ({ verified: true }); -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const presentationSignCallback: PresentationSignCallback = async (_args: PresentationSignCallBackParams) => - TestVectors.authorizationResponsePayload.vp_token; describe('RP using test vectors', () => { it('should create matching auth request and URI', async () => { const authRequest = await createAuthRequest(); @@ -160,15 +162,9 @@ describe('RP using test vectors', () => { correlationId: '1234', audience: 'did:ion:EiBWe9RtHT7VZ-Juff8OnnJAyFJtCokcYHx1CQkFtpl7pw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkNfT1VKeEg2aUljQzZYZE5oN0ptQy1USFhBVmFYbnZ1OU9FRVo4dHE5TkkiLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUNYTkJqSWZMVGZOV0NHMFQ2M2VaYmJEZFZoSmJUTjgtSmZlaUx4dW1oZW53In0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlCZVZ5RXBDb0NPeXJ6VDhDSHlvQW1acU1CT1o0VTZqcm1sdUt1SjlxS0pkZyIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQnhkcHlyamlVSFZ1akNRWTBKMkhBUFFYZnNwWFBKYWluV21mV3RNcFhneFEifX0', + verifyJwtCallback: verifyJwtCallback, verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: { - // disable the JWT verifications, as the test vectors are expired - jwtVerifyOpts: { policies: { exp: false, iat: false, aud: false, nbf: false } }, - }, - checkLinkedDomain: CheckLinkedDomain.IF_PRESENT, presentationVerificationCallback, - wellknownDIDVerifyCallback: verifyCallback, }, }), ).toBeTruthy(); @@ -181,18 +177,12 @@ describe('RP using test vectors', () => { expect(await authorizationResponse.idToken.payload()).toEqual(TestVectors.idTokenPayload); expect( await authorizationResponse.idToken.verify({ + verifyJwtCallback: verifyJwtCallback, correlationId: '1234', audience: 'did:ion:EiBWe9RtHT7VZ-Juff8OnnJAyFJtCokcYHx1CQkFtpl7pw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkNfT1VKeEg2aUljQzZYZE5oN0ptQy1USFhBVmFYbnZ1OU9FRVo4dHE5TkkiLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUNYTkJqSWZMVGZOV0NHMFQ2M2VaYmJEZFZoSmJUTjgtSmZlaUx4dW1oZW53In0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlCZVZ5RXBDb0NPeXJ6VDhDSHlvQW1acU1CT1o0VTZqcm1sdUt1SjlxS0pkZyIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQnhkcHlyamlVSFZ1akNRWTBKMkhBUFFYZnNwWFBKYWluV21mV3RNcFhneFEifX0', verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: { - // disable the JWT verifications, as the test vectors are expired - jwtVerifyOpts: { policies: { exp: false, iat: false, aud: false, nbf: false } }, - }, - checkLinkedDomain: CheckLinkedDomain.NEVER, presentationVerificationCallback, - wellknownDIDVerifyCallback: verifyCallback, revocationOpts: { revocationVerification: RevocationVerification.NEVER, }, @@ -205,17 +195,11 @@ describe('RP using test vectors', () => { const verified = await authorizationResponse.verify({ correlationId: '1234', + verifyJwtCallback: verifyJwtCallback, audience: 'did:ion:EiBWe9RtHT7VZ-Juff8OnnJAyFJtCokcYHx1CQkFtpl7pw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkNfT1VKeEg2aUljQzZYZE5oN0ptQy1USFhBVmFYbnZ1OU9FRVo4dHE5TkkiLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUNYTkJqSWZMVGZOV0NHMFQ2M2VaYmJEZFZoSmJUTjgtSmZlaUx4dW1oZW53In0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlCZVZ5RXBDb0NPeXJ6VDhDSHlvQW1acU1CT1o0VTZqcm1sdUt1SjlxS0pkZyIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQnhkcHlyamlVSFZ1akNRWTBKMkhBUFFYZnNwWFBKYWluV21mV3RNcFhneFEifX0', verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: { - // disable the JWT verifications, as the test vectors are expired - jwtVerifyOpts: { policies: { exp: false, iat: false, aud: false, nbf: false } }, - }, - checkLinkedDomain: CheckLinkedDomain.NEVER, presentationVerificationCallback, - wellknownDIDVerifyCallback: verifyCallback, revocationOpts: { revocationVerification: RevocationVerification.NEVER, }, @@ -223,7 +207,6 @@ describe('RP using test vectors', () => { presentationDefinitions, }); expect(verified).toBeDefined(); - // console.log(verified); }); }); @@ -232,17 +215,11 @@ describe('OP using test vectors', () => { nock('https://example').get('/service/api/v1/presentation-request/649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9').reply(200, TestVectors.requestObjectJwt); // expect.assertions(1); const result = await op.verifyAuthorizationRequest(TestVectors.auth_request, { - verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: { - // disable the JWT verifications, as the test vectors are expired - jwtVerifyOpts: { policies: { exp: false, iat: false, aud: false, nbf: false } }, - }, - }, + verification: {}, }); expect(result).toBeDefined(); - console.log(JSON.stringify(result, null, 2)); }); + it('should use test vector auth response', async () => { const authorizationResponse = await AuthorizationResponse.fromPayload(TestVectors.authorizationResponsePayload); @@ -279,20 +256,10 @@ describe('OP using test vectors', () => { audience: 'did:ion:EiBWe9RtHT7VZ-Juff8OnnJAyFJtCokcYHx1CQkFtpl7pw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkNfT1VKeEg2aUljQzZYZE5oN0ptQy1USFhBVmFYbnZ1OU9FRVo4dHE5TkkiLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUNYTkJqSWZMVGZOV0NHMFQ2M2VaYmJEZFZoSmJUTjgtSmZlaUx4dW1oZW53In0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlCZVZ5RXBDb0NPeXJ6VDhDSHlvQW1acU1CT1o0VTZqcm1sdUt1SjlxS0pkZyIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQnhkcHlyamlVSFZ1akNRWTBKMkhBUFFYZnNwWFBKYWluV21mV3RNcFhneFEifX0', verification: { - mode: VerificationMode.INTERNAL, revocationOpts: { revocationVerification: RevocationVerification.NEVER, }, - resolveOpts: { - jwtVerifyOpts: { - policies: { - // disable expiration check - exp: false, - }, - }, - }, presentationVerificationCallback, - wellknownDIDVerifyCallback: verifyCallback, }, presentationDefinitions: [ { @@ -315,13 +282,7 @@ describe('OP using test vectors', () => { .times(1) .reply(200, TestVectors.requestObjectJwt); const result = await op.verifyAuthorizationRequest(TestVectors.auth_request, { - verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: { - // disable the JWT verifications, as the test vectors are expired - jwtVerifyOpts: { policies: { exp: false, iat: false, aud: false, nbf: false } }, - }, - }, + verification: {}, }); const presentationExchange = new PresentationExchange({ allDIDs: [TestVectors.holderDID], @@ -339,21 +300,13 @@ describe('OP using test vectors', () => { }, }, ); - const response = await op.createAuthorizationResponse(result, { + await op.createAuthorizationResponse(result, { presentationExchange: { verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], presentationSubmission: TestVectors.presentation_submission, vpTokenLocation: VPTokenLocation.ID_TOKEN, }, - verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: { - // disable the JWT verifications, as the test vectors are expired - jwtVerifyOpts: { policies: { exp: false, iat: false, aud: false, nbf: false } }, - }, - }, }); - console.log(JSON.stringify(response, null, 2)); }); }); @@ -362,6 +315,7 @@ async function createAuthRequest(): Promise { correlationId: '1234', nonce: { propertyValue: '40252afc-6a82-4a2e-905f-e41f122ef575', targets: PropertyTarget.REQUEST_OBJECT }, state: { propertyValue: '649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9', targets: PropertyTarget.REQUEST_OBJECT }, + jwtIssuer: { method: 'did', alg: SigningAlgo.EDDSA, didUrl: TestVectors.verifierKID }, claims: { propertyValue: { vp_token: { diff --git a/yarn.lock b/yarn.lock index 797dc73c..fdad44a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3090,6 +3090,13 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/jwt-decode@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/jwt-decode/-/jwt-decode-3.1.0.tgz#eb1b24f436962b8857beaf8addfd542de56670a2" + integrity sha512-tthwik7TKkou3mVnBnvVuHnHElbjtdbM63pdBCbZTirCt3WAdM73Y79mOri7+ljsS99ZVwUFZHLMxJuJnv/z1w== + dependencies: + jwt-decode "*" + "@types/language-tags@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@types/language-tags/-/language-tags-1.0.4.tgz#c622209605b919c41cbf5a78c2fb58dbc3d6f029" @@ -6089,6 +6096,11 @@ jsonld@^8.0.0: lru-cache "^6.0.0" rdf-canonize "^3.4.0" +jwt-decode@*, jwt-decode@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b" + integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA== + jwt-decode@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"