diff --git a/lerna.json b/lerna.json index 2ea92685..5f4bfcc1 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "0.8.1", + "version": "0.10.0", "npmClient": "pnpm", "command": { "publish": { diff --git a/package.json b/package.json index 165930c2..2fd5164f 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,12 @@ "publish:unstable": "lerna publish --conventional-prerelease --force-publish --canary --no-git-tag-version --include-merged-tags --preid unstable --pre-dist-tag unstable --yes --registry https://registry.npmjs.org" }, "engines": { - "node": ">=16" + "node": ">=18" }, "resolutions": { - "node-fetch": "2.6.12" + "node-fetch": "2.6.12", + "isomorphic-webcrypto": "npm:@sphereon/isomorphic-webcrypto@^2.4.0-unstable.4", + "eslint>strip-ansi": "6.0.1" }, "prettier": { "endOfLine": "auto", @@ -43,7 +45,7 @@ "prettier": "^3.0.1", "rimraf": "^5.0.1", "ts-jest": "^29.1.1", - "typescript": "4.9.5" + "typescript": "^5.3.3" }, "keywords": [ "Sphereon", diff --git a/packages/callback-example/lib/IssuerCallback.ts b/packages/callback-example/lib/IssuerCallback.ts index 6e634b38..0ba60b76 100644 --- a/packages/callback-example/lib/IssuerCallback.ts +++ b/packages/callback-example/lib/IssuerCallback.ts @@ -3,7 +3,7 @@ import { Ed25519Signature2020 } from '@digitalcredentials/ed25519-signature-2020 import { Ed25519VerificationKey2020 } from '@digitalcredentials/ed25519-verification-key-2020' import { securityLoader } from '@digitalcredentials/security-document-loader' import vc from '@digitalcredentials/vc' -import { CredentialRequestV1_0_11 } from '@sphereon/oid4vci-common' +import { CredentialRequestV1_0_11 } from '@sphereon/oid4vc-common' import { CredentialIssuanceInput } from '@sphereon/oid4vci-issuer' import { W3CVerifiableCredential } from '@sphereon/ssi-types' diff --git a/packages/callback-example/lib/__tests__/issuerCallback.spec.ts b/packages/callback-example/lib/__tests__/issuerCallback.spec.ts index 9ec8f93a..e6157721 100644 --- a/packages/callback-example/lib/__tests__/issuerCallback.spec.ts +++ b/packages/callback-example/lib/__tests__/issuerCallback.spec.ts @@ -1,6 +1,5 @@ import { KeyObject } from 'crypto' -import { CredentialRequestClient, CredentialRequestClientBuilder, ProofOfPossessionBuilder } from '@sphereon/oid4vci-client' import { Alg, CNonceState, @@ -11,8 +10,9 @@ import { JwtVerifyResult, OpenId4VCIVersion, ProofOfPossession, -} from '@sphereon/oid4vci-common' -import { CredentialOfferSession } from '@sphereon/oid4vci-common/dist' +} from '@sphereon/oid4vc-common' +import { CredentialOfferSession } from '@sphereon/oid4vc-common/dist' +import { CredentialRequestClient, CredentialRequestClientBuilder, ProofOfPossessionBuilder } from '@sphereon/oid4vci-client' import { CredentialSupportedBuilderV1_11, VcIssuer, VcIssuerBuilder } from '@sphereon/oid4vci-issuer' import { MemoryStates } from '@sphereon/oid4vci-issuer' import { CredentialDataSupplierResult } from '@sphereon/oid4vci-issuer/dist/types' diff --git a/packages/callback-example/package.json b/packages/callback-example/package.json index 8c814fb8..63a78292 100644 --- a/packages/callback-example/package.json +++ b/packages/callback-example/package.json @@ -16,9 +16,9 @@ "@digitalcredentials/security-document-loader": "^1.0.0", "@digitalcredentials/vc": "^5.0.0", "@sphereon/oid4vci-client": "workspace:*", - "@sphereon/oid4vci-common": "workspace:*", + "@sphereon/oid4vc-common": "workspace:*", "@sphereon/oid4vci-issuer": "workspace:*", - "@sphereon/ssi-types": "0.17.6-unstable.69", + "@sphereon/ssi-types": "0.18.1", "jose": "^4.10.0" }, "devDependencies": { diff --git a/packages/client/lib/AccessTokenClient.ts b/packages/client/lib/AccessTokenClient.ts index d085089d..01984f26 100644 --- a/packages/client/lib/AccessTokenClient.ts +++ b/packages/client/lib/AccessTokenClient.ts @@ -14,7 +14,7 @@ import { TokenErrorResponse, toUniformCredentialOfferRequest, UniformCredentialOfferPayload, -} from '@sphereon/oid4vci-common'; +} from '@sphereon/oid4vc-common'; import { ObjectUtils } from '@sphereon/ssi-types'; import Debug from 'debug'; diff --git a/packages/client/lib/AuthorizationDetailsBuilder.ts b/packages/client/lib/AuthorizationDetailsBuilder.ts index 9b7de3d1..ce34f9c7 100644 --- a/packages/client/lib/AuthorizationDetailsBuilder.ts +++ b/packages/client/lib/AuthorizationDetailsBuilder.ts @@ -1,4 +1,4 @@ -import { AuthorizationDetails, AuthorizationDetailsJwtVcJson, OID4VCICredentialFormat } from '@sphereon/oid4vci-common'; +import { AuthorizationDetails, AuthorizationDetailsJwtVcJson, OID4VCICredentialFormat } from '@sphereon/oid4vc-common'; //todo: refactor this builder to be able to create ldp details as well export class AuthorizationDetailsBuilder { diff --git a/packages/client/lib/CredentialOfferClient.ts b/packages/client/lib/CredentialOfferClient.ts index 0ad4e294..6884a69f 100644 --- a/packages/client/lib/CredentialOfferClient.ts +++ b/packages/client/lib/CredentialOfferClient.ts @@ -7,7 +7,7 @@ import { determineSpecVersionFromURI, OpenId4VCIVersion, toUniformCredentialOfferRequest, -} from '@sphereon/oid4vci-common'; +} from '@sphereon/oid4vc-common'; import Debug from 'debug'; import { convertJsonToURI, convertURIToJsonObject } from './functions'; diff --git a/packages/client/lib/CredentialRequestClient.ts b/packages/client/lib/CredentialRequestClient.ts index 479b1b14..c86e9ddd 100644 --- a/packages/client/lib/CredentialRequestClient.ts +++ b/packages/client/lib/CredentialRequestClient.ts @@ -10,7 +10,7 @@ import { ProofOfPossession, UniformCredentialRequest, URL_NOT_VALID, -} from '@sphereon/oid4vci-common'; +} from '@sphereon/oid4vc-common'; import { CredentialFormat } from '@sphereon/ssi-types'; import Debug from 'debug'; diff --git a/packages/client/lib/CredentialRequestClientBuilder.ts b/packages/client/lib/CredentialRequestClientBuilder.ts index a73205df..8ef863cf 100644 --- a/packages/client/lib/CredentialRequestClientBuilder.ts +++ b/packages/client/lib/CredentialRequestClientBuilder.ts @@ -10,7 +10,7 @@ import { OID4VCICredentialFormat, OpenId4VCIVersion, UniformCredentialOfferRequest, -} from '@sphereon/oid4vci-common'; +} from '@sphereon/oid4vc-common'; import { CredentialFormat } from '@sphereon/ssi-types'; import { CredentialOfferClient } from './CredentialOfferClient'; diff --git a/packages/client/lib/MetadataClient.ts b/packages/client/lib/MetadataClient.ts index 1e58786e..0d9f19e8 100644 --- a/packages/client/lib/MetadataClient.ts +++ b/packages/client/lib/MetadataClient.ts @@ -8,7 +8,7 @@ import { getIssuerFromCredentialOfferPayload, OpenIDResponse, WellKnownEndpoints, -} from '@sphereon/oid4vci-common'; +} from '@sphereon/oid4vc-common'; import Debug from 'debug'; import { getJson } from './functions'; diff --git a/packages/client/lib/OpenID4VCIClient.ts b/packages/client/lib/OpenID4VCIClient.ts index b94de01f..a1b37eda 100644 --- a/packages/client/lib/OpenID4VCIClient.ts +++ b/packages/client/lib/OpenID4VCIClient.ts @@ -19,7 +19,7 @@ import { ProofOfPossessionCallbacks, PushedAuthorizationResponse, ResponseType, -} from '@sphereon/oid4vci-common'; +} from '@sphereon/oid4vc-common'; import { CredentialFormat } from '@sphereon/ssi-types'; import Debug from 'debug'; diff --git a/packages/client/lib/ProofOfPossessionBuilder.ts b/packages/client/lib/ProofOfPossessionBuilder.ts index 8aef307f..85d5e852 100644 --- a/packages/client/lib/ProofOfPossessionBuilder.ts +++ b/packages/client/lib/ProofOfPossessionBuilder.ts @@ -10,7 +10,7 @@ import { ProofOfPossession, ProofOfPossessionCallbacks, Typ, -} from '@sphereon/oid4vci-common'; +} from '@sphereon/oid4vc-common'; import { createProofOfPossession } from './functions'; diff --git a/packages/client/lib/__tests__/AccessTokenClient.spec.ts b/packages/client/lib/__tests__/AccessTokenClient.spec.ts index 045b2148..39e3e69e 100644 --- a/packages/client/lib/__tests__/AccessTokenClient.spec.ts +++ b/packages/client/lib/__tests__/AccessTokenClient.spec.ts @@ -1,4 +1,4 @@ -import { AccessTokenRequest, AccessTokenResponse, GrantTypes, OpenIDResponse, WellKnownEndpoints } from '@sphereon/oid4vci-common'; +import { AccessTokenRequest, AccessTokenResponse, GrantTypes, OpenIDResponse, WellKnownEndpoints } from '@sphereon/oid4vc-common'; import nock from 'nock'; import { AccessTokenClient } from '../AccessTokenClient'; diff --git a/packages/client/lib/__tests__/AuthorizationDetailsBuilder.spec.ts b/packages/client/lib/__tests__/AuthorizationDetailsBuilder.spec.ts index 4d4c161e..a91e9157 100644 --- a/packages/client/lib/__tests__/AuthorizationDetailsBuilder.spec.ts +++ b/packages/client/lib/__tests__/AuthorizationDetailsBuilder.spec.ts @@ -1,4 +1,4 @@ -import { OID4VCICredentialFormat } from '@sphereon/oid4vci-common'; +import { OID4VCICredentialFormat } from '@sphereon/oid4vc-common'; import { AuthorizationDetailsBuilder } from '../AuthorizationDetailsBuilder'; diff --git a/packages/client/lib/__tests__/AuthzFlowType.spec.ts b/packages/client/lib/__tests__/AuthzFlowType.spec.ts index 3ecb058b..776e8a95 100644 --- a/packages/client/lib/__tests__/AuthzFlowType.spec.ts +++ b/packages/client/lib/__tests__/AuthzFlowType.spec.ts @@ -1,4 +1,4 @@ -import { AuthzFlowType, CredentialOfferPayload } from '@sphereon/oid4vci-common'; +import { AuthzFlowType, CredentialOfferPayload } from '@sphereon/oid4vc-common'; //todo: this file is just testing v9, we probably want to add v11 tests here as well describe('Authorization Flow Type determination', () => { diff --git a/packages/client/lib/__tests__/CredentialRequestClient.spec.ts b/packages/client/lib/__tests__/CredentialRequestClient.spec.ts index de8d05e5..7784db7b 100644 --- a/packages/client/lib/__tests__/CredentialRequestClient.spec.ts +++ b/packages/client/lib/__tests__/CredentialRequestClient.spec.ts @@ -10,7 +10,7 @@ import { ProofOfPossession, URL_NOT_VALID, WellKnownEndpoints, -} from '@sphereon/oid4vci-common'; +} from '@sphereon/oid4vc-common'; import * as jose from 'jose'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts b/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts index 41aa35c1..175c4a14 100644 --- a/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts +++ b/packages/client/lib/__tests__/CredentialRequestClientBuilder.spec.ts @@ -8,7 +8,7 @@ import { OpenId4VCIVersion, ProofOfPossession, UniformCredentialRequest, -} from '@sphereon/oid4vci-common'; +} from '@sphereon/oid4vc-common'; import * as jose from 'jose'; import { CredentialRequestClientBuilder, ProofOfPossessionBuilder } from '..'; diff --git a/packages/client/lib/__tests__/EBSIE2E.spec.test.ts b/packages/client/lib/__tests__/EBSIE2E.spec.test.ts index 1d32a9dd..9e759461 100644 --- a/packages/client/lib/__tests__/EBSIE2E.spec.test.ts +++ b/packages/client/lib/__tests__/EBSIE2E.spec.test.ts @@ -1,4 +1,4 @@ -import { Alg, CodeChallengeMethod, Jwt } from '@sphereon/oid4vci-common'; +import { Alg, CodeChallengeMethod, Jwt } from '@sphereon/oid4vc-common'; import { toJwk } from '@sphereon/ssi-sdk-ext.key-utils'; import { CredentialMapper } from '@sphereon/ssi-types'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/client/lib/__tests__/IT.spec.ts b/packages/client/lib/__tests__/IT.spec.ts index 00ca84ea..d808f2c7 100644 --- a/packages/client/lib/__tests__/IT.spec.ts +++ b/packages/client/lib/__tests__/IT.spec.ts @@ -6,7 +6,7 @@ import { OpenId4VCIVersion, ProofOfPossession, WellKnownEndpoints, -} from '@sphereon/oid4vci-common'; +} from '@sphereon/oid4vc-common'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import nock from 'nock'; diff --git a/packages/client/lib/__tests__/IssuanceInitiation.spec.ts b/packages/client/lib/__tests__/IssuanceInitiation.spec.ts index f28e1f02..73eb1b7d 100644 --- a/packages/client/lib/__tests__/IssuanceInitiation.spec.ts +++ b/packages/client/lib/__tests__/IssuanceInitiation.spec.ts @@ -1,4 +1,4 @@ -import { OpenId4VCIVersion } from '@sphereon/oid4vci-common'; +import { OpenId4VCIVersion } from '@sphereon/oid4vc-common'; import { CredentialOfferClient } from '../CredentialOfferClient'; diff --git a/packages/client/lib/__tests__/JsonURIConversions.spec.ts b/packages/client/lib/__tests__/JsonURIConversions.spec.ts index 24bf611e..653439f9 100644 --- a/packages/client/lib/__tests__/JsonURIConversions.spec.ts +++ b/packages/client/lib/__tests__/JsonURIConversions.spec.ts @@ -1,4 +1,4 @@ -import { convertJsonToURI, convertURIToJsonObject, OpenId4VCIVersion } from '@sphereon/oid4vci-common'; +import { convertJsonToURI, convertURIToJsonObject, OpenId4VCIVersion } from '@sphereon/oid4vc-common'; describe('JSON To URI v8', () => { it('should parse an object into open-id-URI with a single credential_type', () => { diff --git a/packages/client/lib/__tests__/MattrE2E.spec.test.ts b/packages/client/lib/__tests__/MattrE2E.spec.test.ts index 770e3a5b..0fde5b0e 100644 --- a/packages/client/lib/__tests__/MattrE2E.spec.test.ts +++ b/packages/client/lib/__tests__/MattrE2E.spec.test.ts @@ -1,4 +1,4 @@ -import { Alg, Jwt } from '@sphereon/oid4vci-common'; +import { Alg, Jwt } from '@sphereon/oid4vc-common'; import { CredentialMapper } from '@sphereon/ssi-types'; import { fetch } from 'cross-fetch'; import { importJWK, JWK, SignJWT } from 'jose'; diff --git a/packages/client/lib/__tests__/MetadataClient.spec.ts b/packages/client/lib/__tests__/MetadataClient.spec.ts index f1afbd7d..c0bba8a6 100644 --- a/packages/client/lib/__tests__/MetadataClient.spec.ts +++ b/packages/client/lib/__tests__/MetadataClient.spec.ts @@ -1,4 +1,4 @@ -import { getIssuerFromCredentialOfferPayload, WellKnownEndpoints } from '@sphereon/oid4vci-common'; +import { getIssuerFromCredentialOfferPayload, WellKnownEndpoints } from '@sphereon/oid4vc-common'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import nock from 'nock'; diff --git a/packages/client/lib/__tests__/MetadataMocks.ts b/packages/client/lib/__tests__/MetadataMocks.ts index 825dad97..c909747a 100644 --- a/packages/client/lib/__tests__/MetadataMocks.ts +++ b/packages/client/lib/__tests__/MetadataMocks.ts @@ -1,4 +1,4 @@ -import { AuthzFlowType, CredentialOfferRequestWithBaseUrl } from '@sphereon/oid4vci-common'; +import { AuthzFlowType, CredentialOfferRequestWithBaseUrl } from '@sphereon/oid4vc-common'; export const IDENTIPROOF_ISSUER_URL = 'https://issuer.research.identiproof.io'; export const IDENTIPROOF_AS_URL = 'https://auth.research.identiproof.io'; diff --git a/packages/client/lib/__tests__/OpenID4VCIClient.spec.ts b/packages/client/lib/__tests__/OpenID4VCIClient.spec.ts index 97a575db..22b8b857 100644 --- a/packages/client/lib/__tests__/OpenID4VCIClient.spec.ts +++ b/packages/client/lib/__tests__/OpenID4VCIClient.spec.ts @@ -1,4 +1,4 @@ -import { CodeChallengeMethod, WellKnownEndpoints } from '@sphereon/oid4vci-common'; +import { CodeChallengeMethod, WellKnownEndpoints } from '@sphereon/oid4vc-common'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import nock from 'nock'; diff --git a/packages/client/lib/__tests__/OpenID4VCIClientPAR.spec.ts b/packages/client/lib/__tests__/OpenID4VCIClientPAR.spec.ts index bad3da3c..05b39ded 100644 --- a/packages/client/lib/__tests__/OpenID4VCIClientPAR.spec.ts +++ b/packages/client/lib/__tests__/OpenID4VCIClientPAR.spec.ts @@ -1,4 +1,4 @@ -import { CodeChallengeMethod, WellKnownEndpoints } from '@sphereon/oid4vci-common'; +import { CodeChallengeMethod, WellKnownEndpoints } from '@sphereon/oid4vc-common'; import nock from 'nock'; import { OpenID4VCIClient } from '../OpenID4VCIClient'; diff --git a/packages/client/lib/__tests__/ProofOfPossessionBuilder.spec.ts b/packages/client/lib/__tests__/ProofOfPossessionBuilder.spec.ts index a4ef65d3..97db98ac 100644 --- a/packages/client/lib/__tests__/ProofOfPossessionBuilder.spec.ts +++ b/packages/client/lib/__tests__/ProofOfPossessionBuilder.spec.ts @@ -1,6 +1,6 @@ import { KeyObject } from 'crypto'; -import { Alg, JWS_NOT_VALID, Jwt, NO_JWT_PROVIDED, OpenId4VCIVersion, PROOF_CANT_BE_CONSTRUCTED, ProofOfPossession } from '@sphereon/oid4vci-common'; +import { Alg, JWS_NOT_VALID, Jwt, NO_JWT_PROVIDED, OpenId4VCIVersion, PROOF_CANT_BE_CONSTRUCTED, ProofOfPossession } from '@sphereon/oid4vc-common'; import * as jose from 'jose'; import { ProofOfPossessionBuilder } from '..'; diff --git a/packages/client/lib/__tests__/SdJwt.spec.ts b/packages/client/lib/__tests__/SdJwt.spec.ts index 061532e9..5e7ab9cf 100644 --- a/packages/client/lib/__tests__/SdJwt.spec.ts +++ b/packages/client/lib/__tests__/SdJwt.spec.ts @@ -1,4 +1,4 @@ -import { AccessTokenRequest, CredentialRequestV1_0_11, CredentialSupportedSdJwtVc } from '@sphereon/oid4vci-common'; +import { AccessTokenRequest, CredentialRequestV1_0_11, CredentialSupportedSdJwtVc } from '@sphereon/oid4vc-common'; import nock from 'nock'; import { OpenID4VCIClient } from '..'; diff --git a/packages/client/lib/__tests__/SphereonE2E.spec.test.ts b/packages/client/lib/__tests__/SphereonE2E.spec.test.ts index 175899d2..0a9ccaf3 100644 --- a/packages/client/lib/__tests__/SphereonE2E.spec.test.ts +++ b/packages/client/lib/__tests__/SphereonE2E.spec.test.ts @@ -1,6 +1,6 @@ import * as crypto from 'crypto'; -import { Alg, Jwt, ProofOfPossessionCallbacks } from '@sphereon/oid4vci-common'; +import { Alg, Jwt, ProofOfPossessionCallbacks } from '@sphereon/oid4vc-common'; import { CredentialMapper } from '@sphereon/ssi-types'; import * as didts from '@transmute/did-key.js'; import { fetch } from 'cross-fetch'; diff --git a/packages/client/lib/__tests__/data/VciDataFixtures.ts b/packages/client/lib/__tests__/data/VciDataFixtures.ts index 81653cd5..81a7638b 100644 --- a/packages/client/lib/__tests__/data/VciDataFixtures.ts +++ b/packages/client/lib/__tests__/data/VciDataFixtures.ts @@ -1,4 +1,4 @@ -import { CredentialSupportedFormatV1_0_08, IssuerCredentialSubjectDisplay, IssuerMetadataV1_0_08 } from '@sphereon/oid4vci-common'; +import { CredentialSupportedFormatV1_0_08, IssuerCredentialSubjectDisplay, IssuerMetadataV1_0_08 } from '@sphereon/oid4vc-common'; import { ICredentialStatus, W3CVerifiableCredential } from '@sphereon/ssi-types'; export function getMockData(issuerName: string): IssuerMockData | null { diff --git a/packages/client/lib/functions/ProofUtil.ts b/packages/client/lib/functions/ProofUtil.ts index cbfed4be..b3ff7548 100644 --- a/packages/client/lib/functions/ProofUtil.ts +++ b/packages/client/lib/functions/ProofUtil.ts @@ -9,7 +9,7 @@ import { ProofOfPossession, ProofOfPossessionCallbacks, Typ, -} from '@sphereon/oid4vci-common'; +} from '@sphereon/oid4vc-common'; import Debug from 'debug'; const debug = Debug('sphereon:openid4vci:token'); diff --git a/packages/client/lib/functions/index.ts b/packages/client/lib/functions/index.ts index 43dcddcf..d64fb75e 100644 --- a/packages/client/lib/functions/index.ts +++ b/packages/client/lib/functions/index.ts @@ -1,3 +1,3 @@ -export * from '@sphereon/oid4vci-common/dist/functions/Encoding'; -export * from '@sphereon/oid4vci-common/dist/functions/HttpUtils'; +export * from '@sphereon/oid4vc-common/dist/functions/Encoding'; +export * from '@sphereon/oid4vc-common/dist/functions/HttpUtils'; export * from './ProofUtil'; diff --git a/packages/client/package.json b/packages/client/package.json index f8658199..5f6aa5cd 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -15,8 +15,8 @@ "build": "tsc" }, "dependencies": { - "@sphereon/oid4vci-common": "workspace:*", - "@sphereon/ssi-types": "0.17.6-unstable.69", + "@sphereon/oid4vc-common": "workspace:*", + "@sphereon/ssi-types": "0.18.1", "cross-fetch": "^3.1.8", "debug": "^4.3.4" }, diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index 0cc7c10a..45a05e76 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -5,11 +5,11 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ## [0.8.1](https://github.com/Sphereon-Opensource/OID4VCI/compare/v0.7.3...v0.8.1) (2023-10-14) -**Note:** Version bump only for package @sphereon/oid4vci-common +**Note:** Version bump only for package @sphereon/oid4vc-common ## [0.7.3](https://github.com/Sphereon-Opensource/OID4VCI/compare/v0.7.2...v0.7.3) (2023-09-30) -**Note:** Version bump only for package @sphereon/oid4vci-common +**Note:** Version bump only for package @sphereon/oid4vc-common ## [0.7.2](https://github.com/Sphereon-Opensource/OID4VCI/compare/v0.7.1...v0.7.2) (2023-09-28) diff --git a/packages/common/lib/types/Authorization.types.ts b/packages/common/lib/types/Authorization.types.ts index 7f5ed8a1..14c16a47 100644 --- a/packages/common/lib/types/Authorization.types.ts +++ b/packages/common/lib/types/Authorization.types.ts @@ -156,6 +156,8 @@ export enum Encoding { export enum ResponseType { AUTH_CODE = 'code', + ID_TOKEN = 'id_token', + VP_TOKEN = 'vp_token', } export enum CodeChallengeMethod { diff --git a/packages/common/package.json b/packages/common/package.json index a08104fd..f8632d92 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,7 +1,7 @@ { - "name": "@sphereon/oid4vci-common", + "name": "@sphereon/oid4vc-common", "version": "0.8.1", - "description": "OpenID 4 Verifiable Credential Issuance Common Types", + "description": "OpenID 4 Verifiable Credentials Common Types", "source": "lib/index.ts", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -10,16 +10,16 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/ssi-types": "0.17.6-unstable.69", + "@sphereon/ssi-types": "0.18.1", "cross-fetch": "^3.1.8", "jwt-decode": "^3.1.2" }, "devDependencies": { "@types/jest": "^29.5.3", - "typescript": "5.0.4" + "typescript": "5.3.3" }, "engines": { - "node": ">=16" + "node": ">=18" }, "files": [ "lib/**/*", diff --git a/packages/issuer-rest/lib/IssuerTokenEndpoint.ts b/packages/issuer-rest/lib/IssuerTokenEndpoint.ts index 6274d2c2..84cf3d63 100644 --- a/packages/issuer-rest/lib/IssuerTokenEndpoint.ts +++ b/packages/issuer-rest/lib/IssuerTokenEndpoint.ts @@ -1,4 +1,4 @@ -import { GrantTypes, PRE_AUTHORIZED_CODE_REQUIRED_ERROR, TokenError, TokenErrorResponse } from '@sphereon/oid4vci-common' +import { GrantTypes, PRE_AUTHORIZED_CODE_REQUIRED_ERROR, TokenError, TokenErrorResponse } from '@sphereon/oid4vc-common' import { assertValidAccessTokenRequest, createAccessTokenResponse, ITokenEndpointOpts, VcIssuer } from '@sphereon/oid4vci-issuer' import { sendErrorResponse } from '@sphereon/ssi-express-support' import { NextFunction, Request, Response } from 'express' diff --git a/packages/issuer-rest/lib/OID4VCIServer.ts b/packages/issuer-rest/lib/OID4VCIServer.ts index 3b230036..bb878669 100644 --- a/packages/issuer-rest/lib/OID4VCIServer.ts +++ b/packages/issuer-rest/lib/OID4VCIServer.ts @@ -7,7 +7,7 @@ import { IssuerCredentialSubjectDisplay, OID4VCICredentialFormat, QRCodeOpts, -} from '@sphereon/oid4vci-common' +} from '@sphereon/oid4vc-common' import { CredentialSupportedBuilderV1_11, ITokenEndpointOpts, VcIssuer, VcIssuerBuilder } from '@sphereon/oid4vci-issuer' import { ExpressSupport, HasEndpointOpts, ISingleEndpointOpts } from '@sphereon/ssi-express-support' import express, { Express } from 'express' diff --git a/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts b/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts index c0ef72d5..7e410200 100644 --- a/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts +++ b/packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts @@ -1,7 +1,6 @@ import { KeyObject } from 'crypto' import * as didKeyDriver from '@digitalcredentials/did-method-key' -import { OpenID4VCIClient } from '@sphereon/oid4vci-client' import { AccessTokenResponse, Alg, @@ -12,7 +11,8 @@ import { JWTHeader, JWTPayload, OpenId4VCIVersion, -} from '@sphereon/oid4vci-common' +} from '@sphereon/oid4vc-common' +import { OpenID4VCIClient } from '@sphereon/oid4vci-client' import { VcIssuer } from '@sphereon/oid4vci-issuer/dist/VcIssuer' import { CredentialSupportedBuilderV1_11, VcIssuerBuilder } from '@sphereon/oid4vci-issuer/dist/builder' import { MemoryStates } from '@sphereon/oid4vci-issuer/dist/state-manager' diff --git a/packages/issuer-rest/lib/__tests__/IssuerTokenServer.spec.ts b/packages/issuer-rest/lib/__tests__/IssuerTokenServer.spec.ts index fde3285a..fa7b1adb 100644 --- a/packages/issuer-rest/lib/__tests__/IssuerTokenServer.spec.ts +++ b/packages/issuer-rest/lib/__tests__/IssuerTokenServer.spec.ts @@ -9,7 +9,7 @@ import { Jwt, STATE_MISSING_ERROR, URIState, -} from '@sphereon/oid4vci-common' +} from '@sphereon/oid4vc-common' import { VcIssuer } from '@sphereon/oid4vci-issuer' import { MemoryStates } from '@sphereon/oid4vci-issuer/dist/state-manager' import { ExpressBuilder, ExpressSupport } from '@sphereon/ssi-express-support' diff --git a/packages/issuer-rest/lib/oid4vci-api-functions.ts b/packages/issuer-rest/lib/oid4vci-api-functions.ts index 6d3afac1..3e5213c8 100644 --- a/packages/issuer-rest/lib/oid4vci-api-functions.ts +++ b/packages/issuer-rest/lib/oid4vci-api-functions.ts @@ -12,8 +12,8 @@ import { IssueStatusResponse, JWT_SIGNER_CALLBACK_REQUIRED_ERROR, TokenErrorResponse, -} from '@sphereon/oid4vci-common' -import { adjustUrl, trimBoth, trimEnd, trimStart } from '@sphereon/oid4vci-common/dist/functions/HttpUtils' +} from '@sphereon/oid4vc-common' +import { adjustUrl, trimBoth, trimEnd, trimStart } from '@sphereon/oid4vc-common/dist/functions/HttpUtils' import { ITokenEndpointOpts, VcIssuer } from '@sphereon/oid4vci-issuer' import { env, ISingleEndpointOpts, sendErrorResponse } from '@sphereon/ssi-express-support' import { CredentialFormat } from '@sphereon/ssi-types' diff --git a/packages/issuer-rest/package.json b/packages/issuer-rest/package.json index 02270a8a..3939271f 100644 --- a/packages/issuer-rest/package.json +++ b/packages/issuer-rest/package.json @@ -11,10 +11,10 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/oid4vci-common": "workspace:*", + "@sphereon/oid4vc-common": "workspace:*", "@sphereon/oid4vci-issuer": "workspace:*", - "@sphereon/ssi-express-support": "0.17.6-unstable.69", - "@sphereon/ssi-types": "0.17.6-unstable.69", + "@sphereon/ssi-express-support": "0.18.1", + "@sphereon/ssi-types": "0.18.1", "body-parser": "^1.20.2", "cookie-parser": "^1.4.6", "cors": "^2.8.5", @@ -42,7 +42,7 @@ "ts-jest": "^29.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "files": [ "lib/**/*", diff --git a/packages/issuer/lib/VcIssuer.ts b/packages/issuer/lib/VcIssuer.ts index a2447deb..9771e8f3 100644 --- a/packages/issuer/lib/VcIssuer.ts +++ b/packages/issuer/lib/VcIssuer.ts @@ -34,7 +34,7 @@ import { TYP_ERROR, UniformCredentialRequest, URIState, -} from '@sphereon/oid4vci-common' +} from '@sphereon/oid4vc-common' import { CredentialMapper, W3CVerifiableCredential } from '@sphereon/ssi-types' import { v4 } from 'uuid' diff --git a/packages/issuer/lib/__tests__/CredentialOfferUtils.spec.ts b/packages/issuer/lib/__tests__/CredentialOfferUtils.spec.ts index ed5b4fe0..6f1d0439 100644 --- a/packages/issuer/lib/__tests__/CredentialOfferUtils.spec.ts +++ b/packages/issuer/lib/__tests__/CredentialOfferUtils.spec.ts @@ -1,4 +1,4 @@ -import { CredentialOfferPayloadV1_0_11 } from '@sphereon/oid4vci-common' +import { CredentialOfferPayloadV1_0_11 } from '@sphereon/oid4vc-common' import { createCredentialOfferURI } from '../index' diff --git a/packages/issuer/lib/__tests__/MemoryCNonceStateManager.spec.ts b/packages/issuer/lib/__tests__/MemoryCNonceStateManager.spec.ts index bacc16d4..925ee709 100644 --- a/packages/issuer/lib/__tests__/MemoryCNonceStateManager.spec.ts +++ b/packages/issuer/lib/__tests__/MemoryCNonceStateManager.spec.ts @@ -1,4 +1,4 @@ -import { CNonceState, IStateManager, STATE_MISSING_ERROR } from '@sphereon/oid4vci-common' +import { CNonceState, IStateManager, STATE_MISSING_ERROR } from '@sphereon/oid4vc-common' import { v4 } from 'uuid' import { MemoryStates } from '../state-manager' diff --git a/packages/issuer/lib/__tests__/MemoryCredentialOfferStateManager.spec.ts b/packages/issuer/lib/__tests__/MemoryCredentialOfferStateManager.spec.ts index 68c13c9d..5b96fdc6 100644 --- a/packages/issuer/lib/__tests__/MemoryCredentialOfferStateManager.spec.ts +++ b/packages/issuer/lib/__tests__/MemoryCredentialOfferStateManager.spec.ts @@ -1,4 +1,4 @@ -import { CredentialOfferSession, IStateManager, STATE_MISSING_ERROR } from '@sphereon/oid4vci-common' +import { CredentialOfferSession, IStateManager, STATE_MISSING_ERROR } from '@sphereon/oid4vc-common' import { CredentialOfferStateBuilder, MemoryStates } from '../state-manager' diff --git a/packages/issuer/lib/__tests__/VcIssuer.spec.ts b/packages/issuer/lib/__tests__/VcIssuer.spec.ts index 98af5fb4..10d89960 100644 --- a/packages/issuer/lib/__tests__/VcIssuer.spec.ts +++ b/packages/issuer/lib/__tests__/VcIssuer.spec.ts @@ -1,4 +1,3 @@ -import { OpenID4VCIClient } from '@sphereon/oid4vci-client' import { Alg, CredentialOfferSession, @@ -6,7 +5,8 @@ import { IssuerCredentialSubjectDisplay, IssueStatus, STATE_MISSING_ERROR, -} from '@sphereon/oid4vci-common' +} from '@sphereon/oid4vc-common' +import { OpenID4VCIClient } from '@sphereon/oid4vci-client' import { IProofPurpose, IProofType } from '@sphereon/ssi-types' import { DIDDocument } from 'did-resolver' diff --git a/packages/issuer/lib/__tests__/VcIssuerBuilder.spec.ts b/packages/issuer/lib/__tests__/VcIssuerBuilder.spec.ts index 8cce7787..4cdd9ab9 100644 --- a/packages/issuer/lib/__tests__/VcIssuerBuilder.spec.ts +++ b/packages/issuer/lib/__tests__/VcIssuerBuilder.spec.ts @@ -1,4 +1,4 @@ -import { CredentialSupported, IssuerCredentialSubjectDisplay, IssueStatus, TokenErrorResponse } from '@sphereon/oid4vci-common' +import { CredentialSupported, IssuerCredentialSubjectDisplay, IssueStatus, TokenErrorResponse } from '@sphereon/oid4vc-common' import { v4 } from 'uuid' import { CredentialSupportedBuilderV1_11, VcIssuerBuilder } from '../index' diff --git a/packages/issuer/lib/builder/CredentialSupportedBuilderV1_11.ts b/packages/issuer/lib/builder/CredentialSupportedBuilderV1_11.ts index ab2ecafa..1be2b470 100644 --- a/packages/issuer/lib/builder/CredentialSupportedBuilderV1_11.ts +++ b/packages/issuer/lib/builder/CredentialSupportedBuilderV1_11.ts @@ -7,7 +7,7 @@ import { IssuerCredentialSubjectDisplay, OID4VCICredentialFormat, TokenErrorResponse, -} from '@sphereon/oid4vci-common' +} from '@sphereon/oid4vc-common' export class CredentialSupportedBuilderV1_11 { format?: OID4VCICredentialFormat diff --git a/packages/issuer/lib/builder/DisplayBuilder.ts b/packages/issuer/lib/builder/DisplayBuilder.ts index afbbde55..0a7e55e6 100644 --- a/packages/issuer/lib/builder/DisplayBuilder.ts +++ b/packages/issuer/lib/builder/DisplayBuilder.ts @@ -1,4 +1,4 @@ -import { ImageInfo, MetadataDisplay } from '@sphereon/oid4vci-common' +import { ImageInfo, MetadataDisplay } from '@sphereon/oid4vc-common' export class DisplayBuilder { name?: string diff --git a/packages/issuer/lib/builder/IssuerMetadataBuilderV1_11.ts b/packages/issuer/lib/builder/IssuerMetadataBuilderV1_11.ts index 30f28060..086d444d 100644 --- a/packages/issuer/lib/builder/IssuerMetadataBuilderV1_11.ts +++ b/packages/issuer/lib/builder/IssuerMetadataBuilderV1_11.ts @@ -1,4 +1,4 @@ -import { CredentialIssuerMetadata, CredentialSupported, MetadataDisplay } from '@sphereon/oid4vci-common' +import { CredentialIssuerMetadata, CredentialSupported, MetadataDisplay } from '@sphereon/oid4vc-common' import { CredentialSupportedBuilderV1_11 } from './CredentialSupportedBuilderV1_11' import { DisplayBuilder } from './DisplayBuilder' diff --git a/packages/issuer/lib/builder/VcIssuerBuilder.ts b/packages/issuer/lib/builder/VcIssuerBuilder.ts index 7b30f874..c94f61ed 100644 --- a/packages/issuer/lib/builder/VcIssuerBuilder.ts +++ b/packages/issuer/lib/builder/VcIssuerBuilder.ts @@ -8,7 +8,7 @@ import { MetadataDisplay, TokenErrorResponse, URIState, -} from '@sphereon/oid4vci-common' +} from '@sphereon/oid4vc-common' import { VcIssuer } from '../VcIssuer' import { MemoryStates } from '../state-manager' diff --git a/packages/issuer/lib/functions/CredentialOfferUtils.ts b/packages/issuer/lib/functions/CredentialOfferUtils.ts index 137e4277..140f6d3c 100644 --- a/packages/issuer/lib/functions/CredentialOfferUtils.ts +++ b/packages/issuer/lib/functions/CredentialOfferUtils.ts @@ -7,7 +7,7 @@ import { Grant, PIN_VALIDATION_ERROR, UniformCredentialOffer, -} from '@sphereon/oid4vci-common' +} from '@sphereon/oid4vc-common' import { v4 as uuidv4 } from 'uuid' export function createCredentialOfferObject( diff --git a/packages/issuer/lib/state-manager/CredentialOfferStateBuilder.ts b/packages/issuer/lib/state-manager/CredentialOfferStateBuilder.ts index bfbdeb74..77d808ac 100644 --- a/packages/issuer/lib/state-manager/CredentialOfferStateBuilder.ts +++ b/packages/issuer/lib/state-manager/CredentialOfferStateBuilder.ts @@ -1,4 +1,4 @@ -import { AssertedUniformCredentialOffer, CredentialOfferSession } from '@sphereon/oid4vci-common' +import { AssertedUniformCredentialOffer, CredentialOfferSession } from '@sphereon/oid4vc-common' export class CredentialOfferStateBuilder { private readonly credentialOfferState: Partial diff --git a/packages/issuer/lib/state-manager/LookupStateManager.ts b/packages/issuer/lib/state-manager/LookupStateManager.ts index 975caf97..fc6d036a 100644 --- a/packages/issuer/lib/state-manager/LookupStateManager.ts +++ b/packages/issuer/lib/state-manager/LookupStateManager.ts @@ -1,7 +1,7 @@ // noinspection ES6MissingAwait -import { IStateManager } from '@sphereon/oid4vci-common' -import { StateType } from '@sphereon/oid4vci-common/dist/types/StateManager.types' +import { IStateManager } from '@sphereon/oid4vc-common' +import { StateType } from '@sphereon/oid4vc-common/dist/types/StateManager.types' export class LookupStateManager implements IStateManager { constructor( diff --git a/packages/issuer/lib/state-manager/MemoryStates.ts b/packages/issuer/lib/state-manager/MemoryStates.ts index 63e681db..28995f74 100644 --- a/packages/issuer/lib/state-manager/MemoryStates.ts +++ b/packages/issuer/lib/state-manager/MemoryStates.ts @@ -1,5 +1,5 @@ -import { IStateManager, STATE_MISSING_ERROR } from '@sphereon/oid4vci-common' -import { StateType } from '@sphereon/oid4vci-common/dist/types/StateManager.types' +import { IStateManager, STATE_MISSING_ERROR } from '@sphereon/oid4vc-common' +import { StateType } from '@sphereon/oid4vc-common/dist/types/StateManager.types' export class MemoryStates implements IStateManager { private readonly expiresInMS: number diff --git a/packages/issuer/lib/tokens/index.ts b/packages/issuer/lib/tokens/index.ts index 4d9c3cc6..0b7509ff 100644 --- a/packages/issuer/lib/tokens/index.ts +++ b/packages/issuer/lib/tokens/index.ts @@ -20,7 +20,7 @@ import { UNSUPPORTED_GRANT_TYPE_ERROR, USER_PIN_NOT_REQUIRED_ERROR, USER_PIN_REQUIRED_ERROR, -} from '@sphereon/oid4vci-common' +} from '@sphereon/oid4vc-common' import { v4 } from 'uuid' import { isPreAuthorizedCodeExpired } from '../functions' diff --git a/packages/issuer/lib/types/index.ts b/packages/issuer/lib/types/index.ts index 438797ca..1868a1ef 100644 --- a/packages/issuer/lib/types/index.ts +++ b/packages/issuer/lib/types/index.ts @@ -6,7 +6,7 @@ import { JwtVerifyResult, OID4VCICredentialFormat, UniformCredentialRequest, -} from '@sphereon/oid4vci-common' +} from '@sphereon/oid4vc-common' import { ICredential, SdJwtDecodedVerifiableCredentialPayload, SdJwtDisclosureFrame, W3CVerifiableCredential } from '@sphereon/ssi-types' export type CredentialSignerCallback = (opts: { diff --git a/packages/issuer/package.json b/packages/issuer/package.json index 61fae484..3d784528 100644 --- a/packages/issuer/package.json +++ b/packages/issuer/package.json @@ -10,8 +10,8 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/oid4vci-common": "workspace:*", - "@sphereon/ssi-types": "0.17.6-unstable.69", + "@sphereon/oid4vc-common": "workspace:*", + "@sphereon/ssi-types": "0.18.1", "uuid": "^9.0.0" }, "peerDependencies": { @@ -30,7 +30,7 @@ "did-resolver": "^4.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "files": [ "lib/**/*", diff --git a/packages/siopv2/.gitignore b/packages/siopv2/.gitignore new file mode 100644 index 00000000..31c8671d --- /dev/null +++ b/packages/siopv2/.gitignore @@ -0,0 +1,10 @@ +.vscode/* +.idea/* +*.iml +.nyc_output +build +dist +node_modules +coverage +*.log +/src/schemas/validation/schemaValidation.js diff --git a/packages/siopv2/.prettierignore b/packages/siopv2/.prettierignore new file mode 100644 index 00000000..b0d3e691 --- /dev/null +++ b/packages/siopv2/.prettierignore @@ -0,0 +1,6 @@ +# Ignore artifacts: +dist +coverage + +# Ignore all schema files: +src/schemas diff --git a/packages/siopv2/CHANGELOG.md b/packages/siopv2/CHANGELOG.md new file mode 100644 index 00000000..de150bed --- /dev/null +++ b/packages/siopv2/CHANGELOG.md @@ -0,0 +1,198 @@ +# Release Notes + +The DID Auth SIOP typescript library is still in an alpha state at this point. Please note that the interfaces might +still change a bit as the software still is in active development. + +## 0.4.2 - 2023-10-01 + +Fixed an issue with did:key resolution used in Veramo + +- Fixed: + - Fixed an issue with did:key resolution from Veramo. The driver requires a mediaType which according to the spec is + optional. We now always set it as it doesn't hurt to begin with. + +## 0.4.1 - 2023-10-01 + +Fixed not being able to configure the resolver for well-known DIDs + +- Fixed: + - Well-known DIDs did not use a configured DID resolver and thus always used the universal resolver, which has + issues quite often. + +## 0.4.0 - 2023-09-28 + +- Fixed: + + - Claims are not required in the auth request + - State is not required in payloads + - We didn't handle merging of verification options present on an object and passed in as argument nicely + +- Updated: + + - Updated to another JSONPath implementation for improved security `@astronautlabs/jsonpath` + - Better error handling and logging in the session manager + - Allow for numbers in the scheme thus supporting openid4vp:// + +- Added: + - Allow to pass additional claims as verified data in the authorization response. Which can be handy in case you + want to extract data from a VP and pass that to the app that uses this library + +## v0.3.1 - 2023-05-17 + +Bugfix release, fixing RPBuilder export and a client_id bug when not explicitly provided to the RP. + +- Fixed: + - Changed RPBuilder default export to a named export + - Fix #54. The client_id took the whole registration object, instead of the client_id in case it was not provided + explicitly +- Updated: + - SSI-types have been updated to the latest version. + +## v0.3.0 - 2023-04-30 + +This release contains many breaking changes. Sorry for these, but this library still is in active development, as +reflected by the major version still being 0. +A lot of code has been refactored. Now certain classes have state, instead of passing around objects between static +methods. + +- Added: + - Allow to restrict selecting VCs against Formats not communicated in a presentation definition. For instance useful + for filtering against a OID4VP RP, which signals support for certain Formats, but uses a definition which does not + include this information + - Allow to restrict selecting VCs against DID methods not communicated in a presentation definition. For instance + useful + for filtering against a OID4VP RP, which signals support for certain DID methods, but uses a definition which does + not + include this information + - Allow passing in submission data separately from a VP. Again useful in a OID4VP situation, where presentation + submission objects can be transferred next to the VP instead if in the VP + - A simple session/state manager for the RP side. This allows to find back definitions for responses coming back in. + As this is a library the only implementation is an in memory implementation. It is left up to implementers to + create their persistent implementations + - Added support for new version of the spec + - Support for JWT VC Presentation Profile + - Support for DID domain linkage +- Removed: + - Several dependencies have been removed or moved to development dependencies. Mainly the cryptographic libraries + have + been removed +- Changed: + - Requests and responses now contain state and can be instantiated from scratch/options or from an actual payload + - Schema's for AJV are now compiled at build time, instead of at runtime. +- Fixed: + - JSON-LD contexts where not always fetched correctly (Github for instance) + - Signature callback function was not always working after creating copies of data + - React-native not playing nicely with AJV schema's + - JWT VCs/VPs were not always handled correctly + - Submission data contained several errors + - Holder was sometimes missing from the VP + - Too many other fixes to list + +## v0.2.14 - 2022-10-27 + +- Updated: + - Updated some dependencies + +## v0.2.13 - 2022-08-15 + +- Updated: + - Updated some dependencies + +## v0.2.12 - 2022-07-07 + +- Fixed: + - We did not check the proper claims in an AuthResponse to determine the key type, resulting in an invalid JWT + header + - Removed some remnants of the DID-jwt fork + +## v0.2.11 - 2022-07-01 + +- Updated: + - Update to PEX 1.1.2 + - Update several other deps +- Fixed: + - Only throw a PEX error in case PEX itself has flagged the submission to be in error + - Use nonce from request in response if available + - Remove DID-JWT fork as the current version supports SIOPv2 iss values + +## v0.2.10 - 2022-02-25 + +- Added: + - Add default resolver support to builder + +## v0.2.9 - 2022-02-23 + +- Fixed: + - Remove did-jwt dependency, since we use an internal fork for the time being anyway + +## v0.2.7 - 2022-02-11 + +- Fixed: + - Revert back to commonjs + +## v0.2.6 - 2022-02-10 + +- Added: + - Supplied withSignature support. Allowing to integrate withSignature callbacks, next to supplying private keys or + using external custodial signing with authn/authz + +## v0.2.5 - 2022-01-26 + +- Updated: + - Update @sphereon/pex to the latest stable version v1.0.2 + - Moved did-key dep to dev dependency and changed to @digitalcredentials/did-method-key + +## v0.2.4 - 2022-01-13 + +- Updated: + - Update @sphereon/pex to latest stable version v1.0.1 + +## v0.2.3 - 2021-12-10 + +- Fixed: + + - Check nonce and did support first before verifying JWT + +- Updated: + - Updated PEX dependency that fixed a JSON-path bug impacting us + +## v0.2.2 - 2021-11-29 + +- Updated: + - Updated dependencies + +## v0.2.1 - 2021-11-28 + +- Updated: + - Presentation Exchange updated to latest PEX version 0.5.x. The eventual Presentation is not a VP yet (proof will + be in next minor release) + - Update Uni Resolver client to latest version 0.3.3 + +## v0.2.0 - 2021-10-06 + +- Added: + + - Presentation Exchange support [OpenID Connect for Verifiable + Presentations(https://openid.net/specs/openid-connect-4-verifiable-presentations-1_0.html) + +- Fixed: + - Many bug fixes (see git history) + +## v0.1.1 - 2021-09-29 + +- Fixed: + - Packaging fix for the did-jwt fork we include for now + +## v0.1.0 - 2021-09-29 + +This is the first Alpha release of the DID Auth SIOP typescript library. Please note that the interfaces might still +change a bit as the software still is in active development. + +- Alpha release: + + - Low level Auth Request and Response service classes + - High Level OP and RP role service classes + - Support for most of [SIOPv2](https://openid.net/specs/openid-connect-self-issued-v2-1_0.html) + +- Planned for Beta: + - [Support for OpenID Connect for Verifiable Presentations](https://openid.net/specs/openid-connect-4-verifiable-presentations-1_0.html) diff --git a/packages/siopv2/LICENSE b/packages/siopv2/LICENSE new file mode 100644 index 00000000..9dd8af7d --- /dev/null +++ b/packages/siopv2/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/packages/siopv2/README.md b/packages/siopv2/README.md new file mode 100644 index 00000000..b88d3b2f --- /dev/null +++ b/packages/siopv2/README.md @@ -0,0 +1,1366 @@ + +

+
+ Sphereon +
Self Issued OpenID Provider (SIOPv2)
+with OpenID4VP support
+
+

+
+ +[![CI](https://github.com/Sphereon-Opensource/SIOP-OpenID4VP/actions/workflows/main.yml/badge.svg)](https://github.com/Sphereon-Opensource/SIOP-OpenID4VP/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/Sphereon-Opensource/SIOP-OpenID4VP/branch/develop/graph/badge.svg?token=9P1JGUYA35)](https://codecov.io/gh/Sphereon-Opensource/SIOP-OpenID4VP) [![NPM Version](https://img.shields.io/npm/v/@sphereon/did-auth-siop.svg)](https://npm.im/@sphereon/did-auth-siop) + +An OpenID authentication library conforming to +the [Self Issued OpenID Provider v2 (SIOPv2)](https://openid.net/specs/openid-connect-self-issued-v2-1_0.html) +and [OpenID for Verifiable Presentations (OpenID4VP)](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) +as specified in the OpenID Connect working group. + +## Introduction + +[SIOP v2](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) is an OpenID specification to allow End-users to act as OpenID Providers (OPs) themselves. Using +Self-Issued OPs, End-users can authenticate themselves and present claims directly to a Relying Party (RP), +typically a webapp, without involving a third-party Identity Provider. This makes the interactions fully self sovereign, as +it doesn't depend on any third parties and strictly happens peer 2 peer, yet still using well known constructs from the OpenID protocol. + +Next to the user acting as an OpenID Provider, this library also has support for Verifiable Presentations using +the [Presentation Exchange](https://identity.foundation/presentation-exchange/) provided by +our [PEX](https://github.com/Sphereon-Opensource/pex) library. This means that the Relying Party can express submission +requirements in the form of Presentation Definitions, defining the Verifiable Credentials(s) types it would like to receive from the User/OP. +The OP then checks whether it has the credentials to support the Presentation Definition. Only if that is the case it will send the relevant (parts of +the) credentials as a Verifiable Presentation in the Authorization Response destined for the Webapp/Relying Party. The +relying party in turn checks validity of the Verifiable Presentation(s) as well as the match with the submission +requirements. Only if everything is verified successfully the RP serves the protected page(s). This means that the +authentication can be extended with claims about the authenticating entity, but it can also be used to easily consume +credentials from supporting applications, without having to setup DIDComm connections for instance. These credentials can either be self-asserted or from trusted 3rd party issuer. + +The term Self-Issued comes from the fact that the End-users (OP) issue self-signed ID Tokens to prove validity of the +identifiers and claims. This is a trust model different from regular OpenID Connect where the OP is run by the +third party who issues ID Tokens on behalf of the End-user to the Relying Party upon the End-user's consent. This means +the End-User is in control about his/her data instead of the 3rd party OP. + +Demo: https://vimeo.com/630104529 and a more stripped down demo: https://youtu.be/cqoKuQWPj-s + +## Active Development + +_IMPORTANT:_ + +- _This software still is in an early development stage. As such you should expect breaking changes in APIs, we + expect to keep that to a minimum though. Version 0.3.X has changed the external API, especially for Requests, Responses and slightly for the RP/OP classes._ +- _The name of the package also changed from [@sphereon/did-auth-siop](https://www.npmjs.com/package/@sphereon/did-auth-siop) to [@sphereon/siopv2-oid4vp](https://www.npmjs.com/package/@sphereon/SIOP-OpenID4VP), to better reflect specification name changes_ + +## Functionality + +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) +- 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 + +Flow diagram: + +![Flow diagram](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/Sphereon-Opensource/did-auth-siop/develop/docs/auth-flow.puml) + +1. Client (OP) initiates an Auth request by POST-ing to an endpoint, like for instance `/did-siop/v1/authentications` or + clicking a Login button and scanning a QR code +2. Web (RP) receives the request and access the RP object which creates the Auth Request as JWT, signs it and + returns the response as an OpenID Connect URI + + 1. JWT example: + + ```json + // JWT Header + { + "alg": "ES256K", + "kid": "did:ethr:0xcBe71d18b5F1259faA9fEE8f9a5FAbe2372BE8c9#controller", + "typ": "JWT" + } + + // JWT Payload + { + "iat": 1632336634, + "exp": 1632337234, + "response_type": "id_token", + "scope": "openid", + "client_id": "did:ethr:0xcBe71d18b5F1259faA9fEE8f9a5FAbe2372BE8c9", + "redirect_uri": "https://acme.com/siop/v1/sessions", + "iss": "did:ethr:0xcBe71d18b5F1259faA9fEE8f9a5FAbe2372BE8c9", + "response_mode": "post", + "claims": ..., + "nonce": "qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg", + "state": "b32f0087fc9816eb813fd11f", + "registration": { + "did_methods_supported": [ + "did:ethr:", + "did:web:" + ], + "subject_identifiers_supported": "did" + } + } + ``` + + 2. The Signed JWT, including the JWS follows the following scheme (JWS Compact + Serialization, https://datatracker.ietf.org/doc/html/rfc7515#section-7.1): + + `BASE64URL(UTF8(JWT Protected Header)) || '.' || BASE64URL(JWT Payload) || '.' || BASE64URL(JWS Signature)` + + 3. Create the URI containing the JWT: + + ``` + openid://?response_type=id_token + &scope=openid + &client_id=did%3Aethr%3A0xBC9484414c1DcA4Aa85BadBBd8a36E3973934444 + &redirect_uri=https%3A%2F%2Frp.acme.com%2Fsiop%2Fjwts + &iss=did%3Aethr%3A0xBC9484414c1DcA4Aa85BadBBd8a36E3973934444 + &response_mode=post + &claims=... + &state=af0ifjsldkj + &nonce=qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg&state=b32f0087fc9816eb813fd11f + ®istration=%5Bobject%20Object%5D + &request= + ``` + + 4. `claims` param can be either a `vp_token` or an `id_token`: + + ```json + // vp_token example + { + "id_token": { + "email": null + }, + "vp_token": { + "presentation_definition": { + "input_descriptors": [ + { + "schema": [ + { + "uri": "https://www.w3.org/2018/credentials/examples/v1/IDCardCredential" + } + ], + "constraints": { + "limit_disclosure": "required", + "fields": [ + { + "path": [ + "$.vc.credentialSubject.given_name" + ] + } + ] + } + } + ] + } + } + } + // id_token example + { + "userinfo": { + "verifiable_presentations": [ + "presentation_definition": { + "input_descriptors": [ + { + "schema": [ + { + "uri": "https://did.itsourweb.org:3000/smartcredential/Ontario-Health-Insurance-Plan" + } + ] + } + ] + } + } + }, + "id_token": { + "auth_time": { + "essential": true + } + } + } + ``` + +3. Web receives the Auth Request URI Object from RP +4. Web sends the Auth Request URI in the response body to the client +5. Client uses the OP instance to create an Auth response +6. OP verifies the auth request, including checks on whether the RP DID method and key types are supported, next to + whether the OP can satisfy the RPs requested Verifiable Credentials +7. Presentation Exchange process in case the RP had presentation definition(s) in the claims (see Presentation + Exchange chapter) +8. OP creates the auth response object as follows: + + 1. Create an ID token as shown below: + + ```json + // JWT encoded ID Token + // JWT Header + { + "alg": "ES256K", + "kid": "did:ethr:0x998D43DA5d9d78500898346baf2d9B1E39Eb0Dda#keys-1", + "typ": "JWT" + } + // JWT Payload + { + "iat": 1632343857.084, + "exp": 1632344857.084, + "iss": "https://self-issued.me/v2", + "sub": "did:ethr:0x998D43DA5d9d78500898346baf2d9B1E39Eb0Dda", + "aud": "https://acme.com/siop/v1/sessions", + "did": "did:ethr:0x998D43DA5d9d78500898346baf2d9B1E39Eb0Dda", + "sub_type": "did", + "sub_jwk": { + "kid": "did:ethr:0x998D43DA5d9d78500898346baf2d9B1E39Eb0Dda#key-1", + "kty": "EC", + "crv": "secp256k1", + "x": "a4IvJILPHe3ddGPi9qvAyXY9qMTEHvQw5DpQYOJVA0c", + "y": "IKOy0JfBF8FOlsOJaC41xiKuGc2-_iqTI01jWHYIyJU" + }, + "nonce": "qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg", + "state": "b32f0087fc9816eb813fd11f", + "registration": { + "issuer": "https://self-issued.me/v2", + "response_types_supported": "id_token", + "authorization_endpoint": "openid:", + "scopes_supported": "openid", + "id_token_signing_alg_values_supported": [ + "ES256K", + "EdDSA" + ], + "request_object_signing_alg_values_supported": [ + "ES256K", + "EdDSA" + ], + "subject_types_supported": "pairwise" + } + } + ``` + + 2. Sign the ID token using the DID key (kid) using JWS scheme (JWS Compact + Serialization, https://datatracker.ietf.org/doc/html/rfc7515#section-7.1) and send it to the RP: + + `BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload) || '.' || BASE64URL(JWS Signature)` + +9. OP returns the Auth response and jwt object to the client +10. Client does a HTTP POST to redirect_uri from the request (and the aud in the + response): https://acme.com/siop/v1/sessions using "application/x-www-form-urlencoded" +11. Web receives the ID token (auth response) and uses the RP's verify method +12. RP performs the validation of the token, including withSignature validation, expiration and Verifiable Presentations if + any. It returns the Verified Auth Response to WEB +13. WEB returns a 200 response to Client with a redirect to another page (logged in or confirmation of VP receipt etc). +14. From that moment on Client can use the Auth Response as bearer token as long as it is valid + +## OP and RP setup and interactions + +This chapter is a walk-through for using the library using the high-level OP and RP classes. To keep +it simple, the examples work without hosting partial request/response related objects using HTTP endpoints. They are passed by value, inlined in the respective payloads versus passed by reference. + +--- + +**NOTE** + +The examples use Ethereum (ethr) DIDs, but these could be other DIDs as well. The creation of DIDs is out of scope. We +provide an [ethereum DID example](ethr-dids-testnet.md), if you want to test it yourself without having DIDs currently. +You could also use the actual example keys and DIDs, as they are valid Ethr Ropsten testnet keys. + +--- + +### 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. + +### Setting up the Relying Party (RP) + +The Relying Party, typically a web app, but can also be something else, like a mobile app. + +We will use an example private key and DID on the Ethereum Ropsten testnet network. 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. +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, +}; +const rp = RP.builder() + .redirect(EXAMPLE_REDIRECT_URL) + .requestBy(PassBy.VALUE) + .withPresentationVerification(presentationVerificationCallback) + .addVerifyCallback(verifyCallback) + .withRevocationVerification(RevocationVerification.NEVER) + .withInternalSignature(rpKeys.hexPrivateKey, rpKeys.did, rpKeys.didKey, rpKeys.alg) + .addDidMethod('ethr') + .withClientMetadata({ + 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], + subjectSyntaxTypesSupported: ['did', 'did:ethr'], + passBY: PassBy.VALUE, + }) + .addPresentationDefinitionClaim({ + definition: { + input_descriptors: [ + { + schema: [ + { + uri: 'https://did.itsourweb.org:3000/smartcredential/Ontario-Health-Insurance-Plan', + }, + ], + }, + ], + }, + location: PresentationLocation.VP_TOKEN, // Toplevel vp_token response expected. This also can be ID_TOKEN + }) + .build(); +``` + +### 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). + +```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) + .withClientMetadata({ + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], + issuer: ResponseIss.SELF_ISSUED_V2, + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormats: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subjectSyntaxTypesSupported: ['did:ethr'], + passBy: PassBy.VALUE, + }) + .build(); +``` + +### RP creates the Auth Request + +The Relying Party creates the Auth Request. This could have been triggered by the OP accessing a URL, or clicking a button +for instance. The Created SIOP V2 Auth Request could also be displayed as a QR code for cross-device flows. In the below text we are +leaving the transport out of scope. + +Given we already have configured the RP itself, all we need to provide is a nonce and state for this request. These will +be communicated throughout the process. The RP definitely needs to keep track of these values for later usage. If no +nonce and state are provided then the createAuthorizationRequest method will automatically provide values for these and +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. + +```typescript +const authRequest = await rp.createAuthorizationRequest({ + correlationId: '1', + nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + state: 'b32f0087fc9816eb813fd11f', +}); + +console.log(`nonce: ${authRequest.requestOpts.nonce}, state: ${authRequest.requestOpts.state}`); +// nonce: qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg, state: b32f0087fc9816eb813fd11f + +console.log(await authRequest.uri().then((uri) => uri.encodedUri)); +// openid://?response_type=id_token&scope=openid&client_id=did.......&jwt=ey.......... +``` + +#### Optional: OP Auth Request Payload parsing access + +The OP class has a method that both parses the Auth Request URI as it was created by the RP, but it als +resolves both the JWT and the Registration values from the Auth Request Payload. Both values can be either +passed by value in the Auth Request, meaning they are present in the request, or passed by reference, meaning +they are hosted by the OP. In the latter case the values have to be retrieved from an https endpoint. The parseAuthorizationRequestURI takes +care of both values and returns the Auth Request Payload for easy access, the resolved signed JWT as well as +the resolved registration metadata of the RP. Please note that the Auth Request Payload that is also returned +is the original payload from the URI, so it will not contain the resolved JWT nor Registration if the OP passed one of +them by reference instead of value. Only the direct access to jwt and registration in the Parsed Auth Request +URI are guaranteed to be resolved. + +--- + +**NOTE** + +Please note that the parsing also automatically happens when calling the verifyAuthorizationRequest method with a URI +as input argument. This method allows for manual parsing if needed. + +--- + +```typescript +const parsedReqURI = op.parseAuthorizationRequestURI(reqURI.encodedUri); + +console.log(parsedReqURI.requestPayload.request); +// ey....... , but could be empty if the OP would have passed the request by reference usiing request_uri! + +console.log(parsedReqURI.jwt); +// ey....... , always resolved even if the OP would have passed the request by reference! +``` + +#### OP Auth Request verification + +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 +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. + +--- + +**NOTE** + +In the below example we directly access requestURI.encodedUri, in a real world scenario the RP and OP don't have access +to shared objects. Normally you would have received the openid:// URI as a string, which you can also directly pass into +the verifyAuthorizationRequest or parse methods of the OP class. The method accepts both a JWT or an openid:// URI as +input + +--- + +```typescript +const verifiedReq = op.verifyAuthorizationRequest(reqURI.encodedUri); // When an HTTP endpoint is used this would be the uri found in the body +// const verifiedReq = op.verifyAuthorizationRequest(parsedReqURI.jwt); // If we have parsed the URI using the above optional parsing + +console.log(`RP DID: ${verifiedReq.issuer}`); +// RP DID: did:ethr:ropsten:0x028360fb95417724cb7dd2ff217b15d6f17fc45e0ffc1b3dce6c2b8dd1e704fa98 +``` + +### OP Presentation Exchange + +The Verified Request object created in the previous step contains a `presentationDefinitions` array property in case the +OP wants to receive a Verifiable Presentation according to +the [OpenID Connect for Verifiable Presentations (OIDC4VP)](https://openid.net/specs/openid-connect-4-verifiable-presentations-1_0.html) +specification. If this is the case we need to select credentials and create a Verifiable Presentation. If the OP doesn't +need to receive a Verifiable Presentation, meaning the presentationDefinitions property is undefined or empty, you can +continue to the next chapter and create the Auth Response immediately. + +See the below sub flow for Presentation Exchange to explain the process: + +![PE Flow diagram](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/Sphereon-Opensource/did-auth-siop/develop/docs/presentation-exchange.puml) + +#### Create PresentationExchange object + +If the `presentationDefinitions` array property is present it means the op.verifyAuthorizationRequest already has +established that the Presentation Definition(s) itself were valid and present. It has populated the +presentationDefinitions array for you. If the definition was not valid, the verify method would have thrown an error, +which means you should never continue the authentication flow! + +Now we have to create a `PresentationExchange` object and pass in both the available Verifiable Credentials (typically +from your wallet) and the holder DID. + +--- + +**NOTE** + +The verifiable credentials you pass in to the PresentationExchange methods do not get sent to the RP. Only the +submissionFrom method creates a VP, which you should manually add as an option to the createAuthorizationResponse +method. + +--- + +```typescript +import { PresentationExchange } from './PresentationExchange'; +import { PresentationDefinition } from '@sphereon/pe-models'; + +const verifiableCredentials: VerifiableCredential[] = [VC1, VC2, VC3]; // This typically comes from your wallet +const presentationDefs: PresentationDefinition[] = verifiedReq.presentationDefinitions; + +if (presentationDefs) { + const pex = new PresentationExchange({ + did: op.authResponseOpts.did, + allVerifiableCredentials: verifiableCredentials, + }); +} +``` + +#### Filter Credentials that match the Presentation Definition + +Now we need to filter the VCs from all the available VCs to an array that matches the Presentation Definition(s) from +the RP. If the OP, or rather the PresentationExchange instance doesn't have all credentials to satisfy the Presentation +Definition from the OP, the method will throw an error. Do not try to authenticate in that case! + +The selectVerifiableCredentialsForSubmission method returns the filtered VCs. These VCs can satisfy the submission +requirements from the Presentation Definition. You have to do a manual selection yourself (see note below). + +--- + +**NOTE** + +You can have multiple VCs that match a single definition. That can be because the OP uses a definition that wants to +receive multiple different VCs as part of the Verifiable Presentation, but it can also be that you have multiple VCs +that match a single constraint from a single definition. Lastly there can be multiple definitions. You always have to do +a final manual selection of VCs from your application (outside of the scope of this library). + +--- + +```typescript +// We are only checking the first definition to not make the example too complex +const checked = await pex.selectVerifiableCredentialsForSubmission(presentationDefs[0]); +// Has errors if the Presentation Definition has requirements we cannot satisfy. +if (checked.errors) { + // error handling here +} +const matches: SubmissionRequirementMatch = checked.matches; + +// Returns the filtered credentials that do match +``` + +#### Application specific selection and approval + +The previous step has filtered the VCs for you into the matches constant. But the user really has to acknowledge that +he/she will be sending in a VP containing the VCs. As mentioned above the selected VCs might still need more filtering +by the user. This part is out of the scope of this library as it is application specific. For more info also see +the [PEX library](https://github.com/Sphereon-Opensource/pex). + +In the code examples we will use 'userSelectedCredentials' as variable for the outcome of this process. + +```typescript +// Your application process here, resulting in: +import { IVerifiableCredential } from '@sphereon/pex'; + +const userSelectedCredentials: VerifiableCredential[]; // Your selected credentials +``` + +#### Create the Verifiable Presentation from the user selected VCs + +Now that we have the final selection of VCs, the Presentation Exchange class will create the Verifiable Presentation for +you. You can optionally sign the Verifiable Presentation, which is out of the scope of this library. As long as the VP +contains VCs which as subject has the same DID as the OP, the RP can know that the VPs are valid, simply by the fact +that withSignature of the resulting Auth Response is signed by the private key belonging to the OP and the VP. + +--- + +**NOTE** + +We do not support signed selective disclosure yet. The VP will only contain attributes that are requested if the +Presentation Definition wanted to limit disclosure. You need BBS+ signatures for instance to sign a VP with selective +disclosure. Unsigned selective disclosure is possible, where the RP relies on the Auth Response being signed +as long as the VP subject DIDs match the OP DID. + +--- + +```typescript +// We are only creating a presentation out of the first definition to keep the example simple +const verifiablePresentation = await pex.submissionFrom(presentationDefs[0], userSelectedCredentials); + +// Optionally sign the verifiable presentation here (outside of SIOP library scope) +``` + +#### End of Presentation Exchange + +Once the VP is returned it means we have gone through the Presentation Exchange process as defined +in [OpenID Connect for Verifiable Presentations (OIDC4VP)](https://openid.net/specs/openid-connect-4-verifiable-presentations-1_0.html) +. We can now continue to the regular flow of creating the Auth Response below, all we have to do is pass the +VP in as an option. + +### OP creates the Auth Response using the Verified Request + +Using the Verified Request object we got back from the op.verifyAuthorizationRequest method, we can now start to create +the Auth Response. If we were in the Presentation Exchange flow because the request contained a Presentation +Definition we now need to pass in the Verifiable Presentations using the vp option. If there was no Presentation +Definition, do not supply a Verifiable Presentation! The method will check for these constraints. + +```typescript +import { PresentationLocation, VerifiablePresentationTypeFormat } from './SIOP.types'; + +// Example with Verifiabl Presentation in linked data proof format and as part of the vp_token +const vpOpt = { + format: VerifiablePresentationTypeFormat.LDP_VP, + presentation: verifiablePresentation, + location: PresentationLocation.VP_TOKEN, +}; + +const authRespWithJWT = await op.createAuthorizationResponse(verifiedReq, { vp: [vpOpt] }); + +// Without Verifiable Presentation +// const authRespWithJWT = await op.createAuthorizationResponse(verifiedReq); +``` + +### OP submits the Auth Response to the RP + +We are now ready to submit the Auth Response to the RP. The OP class has the submitAuthorizationResponse +method which accepts the response object. It will automatically submit to the correct location as specified by the RP in +its request. It expects a response in the 200 range. You get access to the HTTP response from the fetch API as a return +value. + +```typescript +// Example with Verifiable Presentation +const response = await op.submitAuthorizationResponse(authRespWithJWT); +``` + +### RP verifies the Auth Response + +```typescript +const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponseJwt(authRespWithJWT.jwt, { + audience: EXAMPLE_REDIRECT_URL, +}); + +expect(verifiedAuthResponseWithJWT.jwt).toBeDefined(); +expect(verifiedAuthResponseWithJWT.payload.state).toMatch('b32f0087fc9816eb813fd11f'); +expect(verifiedAuthResponseWithJWT.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); +``` + +## AuthorizationRequest class + +In the previous chapter we have seen the highlevel OP and RP classes. These classes use the Auth Request and +Response objects explained in this chapter and the next chapter. If you want you can do most interactions using these +classes at a lower level. This however means you will not get automatic resolution of values passed by reference like +for instance request and registration data. + +### createURI + +Create a signed URL encoded URI with a signed SIOP Auth Request + +#### Data Interface + +```typescript +interface AuthorizationRequestURI extends SIOPURI { + jwt?: string; // The JWT when requestBy was set to mode Reference, undefined if the mode is Value + requestOpts: AuthorizationRequestOpts; // The supplied request opts as passed in to the method + requestPayload: AuthorizationRequestPayload; // The json payload that ends up signed in the JWT +} + +export type SIOPURI = { + encodedUri: string; // The encode JWT as URI + encodingFormat: UrlEncodingFormat; // The encoding format used +}; + +// https://openid.net/specs/openid-connect-self-issued-v2-1_0.html#section-8 +export interface AuthorizationRequestOpts { + authorizationEndpoint?: string; + redirectUri: string; // The redirect URI + requestBy: ObjectBy; // Whether the request is returned by value in the URI or retrieved by reference at the provided URL + signature: InternalSignature | ExternalSignature | NoSignature; // Whether no withSignature is being used, internal (access to private key), or external (hosted using authentication) + checkLinkedDomain?: CheckLinkedDomain; // determines how we'll handle the linked domains for this RP + responseMode?: ResponseMode; // How the URI should be returned. This is not being used by the library itself, allows an implementor to make a decision + responseContext?: ResponseContext; // Defines the context of these opts. Either RP side or OP side + responseTypesSupported?: ResponseType[]; + claims?: ClaimOpts; // The claims, uncluding presentation definitions + registration: RequestRegistrationOpts; // Registration metadata options + nonce?: string; // An optional nonce, will be generated if not provided + state?: string; // An optional state, will be generated if not provided + scopesSupported?: Scope[]; + subjectTypesSupported?: SubjectType[]; + requestObjectSigningAlgValuesSupported?: SigningAlgo[]; + revocationVerificationCallback?: RevocationVerificationCallback; + // slint-disable-next-line @typescript-eslint/no-explicit-any + // [x: string]: any; +} + +static async createURI(opts: SIOP.AuthorizationRequestOpts): Promise +``` + +#### Usage + +```typescript +const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello'; +const EXAMPLE_REFERENCE_URL = 'https://rp.acme.com/siop/jwts'; +const HEX_KEY = 'f857544a9d1097e242ff0b287a7e6e90f19cf973efe2317f2a4678739664420f'; +const DID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0'; +const KID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0#keys-1'; + +const opts: AuthorizationRequestOpts = { + checkLinkedDomain: CheckLinkedDomain.NEVER, + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + redirectUri: EXAMPLE_REDIRECT_URL, + requestBy: { + type: PassBy.VALUE, + }, + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + }, + registration: { + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectSyntaxTypesSupported: ['did:ethr:', SubjectIdentifierType.DID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + registrationBy: { + type: PassBy.VALUE, + }, + }, +}; + +AuthorizationRequest.createURI(opts).then((uri) => console.log(uri.encodedUri)); + +// Output: +// openid:// +// ?response_type=id_token +// &scope=openid +// &client_id=did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0 +// &redirect_uri=https://acme.com/hello&iss=did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0 +// &response_mode=post +// &response_context=rp +// &nonce=HxhBU9jBRVP51Z6J0eQ5AxeKoWK9ChApWRrumIqnixc +// &state=cbde3cdc5389f3be94063be3 +// ®istration={ +// "id_token_signing_alg_values_supported":["EdDSA","ES256"], +// "request_object_signing_alg_values_supported":["EdDSA","ES256"], +// "response_types_supported":["id_token"], +// "scopes_supported":["openid did_authn","openid"], +// "subject_types_supported":["pairwise"], +// "subject_syntax_types_supported":["did:ethr:","did"], +// "vp_formats":{ +// "ldp_vc":{ +// "proof_type":["EcdsaSecp256k1Signature2019","EcdsaSecp256k1Signature2019"] +// } +// } +// } +// &request=eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZXRocjoweDAxMDZhMmU5ODViMUUxRGU5QjVkZGI0YUY2ZEM5ZTkyOEY0ZTk5RDAja2V5cy0xIiwidHlwIjoiSldUIn0.eyJpYXQiOjE2NjQ0Mzk3MzMsImV4cCI6MTY2NDQ0MDMzMywicmVzcG9uc2VfdHlwZSI6ImlkX3Rva2VuIiwic2NvcGUiOiJvcGVuaWQiLCJjbGllbnRfaWQiOiJkaWQ6ZXRocjoweDAxMDZhMmU5ODViMUUxRGU5QjVkZGI0YUY2ZEM5ZTkyOEY0ZTk5RDAiLCJyZWRpcmVjdF91cmkiOiJodHRwczovL2FjbWUuY29tL2hlbGxvIiwiaXNzIjoiZGlkOmV0aHI6MHgwMTA2YTJlOTg1YjFFMURlOUI1ZGRiNGFGNmRDOWU5MjhGNGU5OUQwIiwicmVzcG9uc2VfbW9kZSI6InBvc3QiLCJyZXNwb25zZV9jb250ZXh0IjoicnAiLCJub25jZSI6Ikh4aEJVOWpCUlZQNTFaNkowZVE1QXhlS29XSzlDaEFwV1JydW1JcW5peGMiLCJzdGF0ZSI6ImNiZGUzY2RjNTM4OWYzYmU5NDA2M2JlMyIsInJlZ2lzdHJhdGlvbiI6eyJpZF90b2tlbl9zaWduaW5nX2FsZ192YWx1ZXNfc3VwcG9ydGVkIjpbIkVkRFNBIiwiRVMyNTYiXSwicmVxdWVzdF9vYmplY3Rfc2lnbmluZ19hbGdfdmFsdWVzX3N1cHBvcnRlZCI6WyJFZERTQSIsIkVTMjU2Il0sInJlc3BvbnNlX3R5cGVzX3N1cHBvcnRlZCI6WyJpZF90b2tlbiJdLCJzY29wZXNfc3VwcG9ydGVkIjpbIm9wZW5pZCBkaWRfYXV0aG4iLCJvcGVuaWQiXSwic3ViamVjdF90eXBlc19zdXBwb3J0ZWQiOlsicGFpcndpc2UiXSwic3ViamVjdF9zeW50YXhfdHlwZXNfc3VwcG9ydGVkIjpbImRpZDpldGhyOiIsImRpZCJdLCJ2cF9mb3JtYXRzIjp7ImxkcF92YyI6eyJwcm9vZl90eXBlIjpbIkVjZHNhU2VjcDI1NmsxU2lnbmF0dXJlMjAxOSIsIkVjZHNhU2VjcDI1NmsxU2lnbmF0dXJlMjAxOSJdfX19fQ.owSdQP3ZfOyHryCIO86zB5qenzd5l2AUcEZhA3TvlUWNDJyhhzIgZmBgzV4OMilczr2AJss5HGqxHPmBRTaHcQ +``` + +### verifyJWT + +Verifies a SIOP Auth Request JWT. Throws an error if the verifation fails. Returns the verified JWT and +metadata if the verification succeeds + +#### Data Interface + +```typescript +export interface VerifiedAuthorizationRequestWithJWT extends VerifiedJWT { + payload: AuthorizationRequestPayload; // The unsigned Auth Request payload + presentationDefinitions?: PresentationDefinitionWithLocation[]; // The optional presentation definition objects that the RP requests + verifyOpts: VerifyAuthorizationRequestOpts; // The verification options for the Auth Request +} + +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 VerifyAuthorizationRequestOpts { + verification: InternalVerification | ExternalVerification; // To use internal verification or external hosted verification + nonce?: string; // If provided the nonce in the request needs to match + verifyCallback?: VerifyCallback; +} + +export interface DIDResolutionResult { + didResolutionMetadata: DIDResolutionMetadata // Did resolver metadata + didDocument: DIDDocument // The DID document + didDocumentMetadata: DIDDocumentMetadata // DID document metadata +} + +export interface DIDDocument { // Standard DID Document, see DID spec for explanation + '@context'?: 'https://www.w3.org/ns/did/v1' | string | string[] + id: string + alsoKnownAs?: string[] + controller?: string | string[] + verificationMethod?: VerificationMethod[] + authentication?: (string | VerificationMethod)[] + assertionMethod?: (string | VerificationMethod)[] + keyAgreement?: (string | VerificationMethod)[] + capabilityInvocation?: (string | VerificationMethod)[] + capabilityDelegation?: (string | VerificationMethod)[] + service?: ServiceEndpoint[] +} + +static async verifyJWT(jwt:string, opts: SIOP.VerifyAuthorizationRequestOpts): Promise +``` + +#### Usage + +```typescript +const verifyOpts: VerifyAuthorizationRequestOpts = { + verification: { + mode: VerificationMode.INTERNAL, + resolveOpts: { + subjectSyntaxTypesSupported: ['did:ethr'], + }, + }, +}; +const jwt = 'ey..........'; // JWT created by RP +AuthorizationRequest.verifyJWT(jwt).then((req) => { + console.log(`issuer: ${req.issuer}`); + console.log(JSON.stringify(req.signer)); +}); +// issuer: "did:ethr:0x56C4b92D4a6083Fcee825893A29023cDdfff5c66" +// "signer": { +// "id": "did:ethr:0x56C4b92D4a6083Fcee825893A29023cDdfff5c66#controller", +// "type": "EcdsaSecp256k1RecoveryMethod2020", +// "controller": "did:ethr:0x56C4b92D4a6083Fcee825893A29023cDdfff5c66", +// "blockchainAccountId": "0x56C4b92D4a6083Fcee825893A29023cDdfff5c66@eip155:1" +// } +``` + +## AuthorizationResponse class + +### createJwtFromRequestJWT + +Creates an AuthorizationResponse object from the OP side, using the AuthorizationRequest of the RP and its +verification as input together with settings from the OP. The Auth Response contains the ID token as well as +optional Verifiable Presentations conforming to the Submission Requirements sent by the RP. + +#### Data interface + +```typescript +export interface AuthorizationResponseOpts { + redirectUri?: string; // It's typically comes from the request opts as a measure to prevent hijacking. + registration: ResponseRegistrationOpts; // Registration options + checkLinkedDomain?: CheckLinkedDomain; // When the link domain should be checked + presentationVerificationCallback?: PresentationVerificationCallback; // Callback function to verify the presentations + signature: InternalSignature | ExternalSignature; // Using an internal/private key withSignature, or hosted withSignature + nonce?: string; // Allows to override the nonce, otherwise the nonce of the request will be used + state?: string; // Allows to override the state, otherwise the state of the request will be used + responseMode?: ResponseMode; // Response mode should be form in case a mobile device is being used together with a browser + did: string; // The DID of the OP + vp?: VerifiablePresentationResponseOpts[]; // Verifiable Presentations with location and format + expiresIn?: number; // Expiration +} + +export interface VerifiablePresentationResponseOpts extends VerifiablePresentationPayload { + location: PresentationLocation; +} + +export enum PresentationLocation { + VP_TOKEN = 'vp_token', // VP will be the toplevel vp_token + ID_TOKEN = 'id_token', // VP will be part of the id_token in the verifiable_presentations location +} + +export interface VerifyAuthorizationRequestOpts { + verification: InternalVerification | ExternalVerification; // To use internal verification or external hosted verification + nonce?: string; // If provided the nonce in the request needs to match + verifyCallback?: VerifyCallback // Callback function to verify the domain linkage credential +} + +export interface AuthorizationResponsePayload extends JWTPayload { + iss: ResponseIss.SELF_ISSUED_V2 | string; // The SIOP V2 spec mentions this is required + sub: string; // did (or thumbprint of sub_jwk key when type is jkt) + sub_jwk?: JWK; // JWK containing DID key if subtype is did, or thumbprint if it is JKT + aud: string; // redirect_uri from request + exp: number; // expiration time + iat: number; // issued at + state: string; // The state which should match the AuthRequest state + nonce: string; // The nonce which should match the AuthRequest nonce + did: string; // The DID of the OP + registration?: DiscoveryMetadataPayload; // The registration metadata from the OP + registration_uri?: string; // The URI of the registration metadata if it is returned by reference/URL + verifiable_presentations?: VerifiablePresentationPayload[]; // Verifiable Presentations + vp_token?: VerifiablePresentationPayload; +} + +export interface AuthorizationResponseWithJWT { + jwt: string; // The signed Response JWT + nonce: string; // The nonce which should match the nonce from the request + state: string; // The state which should match the state from the request + payload: AuthorizationResponsePayload; // The unsigned payload object + verifyOpts?: VerifyAuthorizationRequestOpts;// The Auth Request verification parameters that were used + responseOpts: AuthorizationResponseOpts; // The Auth Response options used during generation of the Response +} + +static async createJWTFromRequestJWT(requestJwt: string, responseOpts: SIOP.AuthorizationResponseOpts, verifyOpts: SIOP.VerifyAuthorizationRequestOpts): Promise +``` + +#### Usage + +```typescript +const responseOpts: AuthorizationResponseOpts = { + checkLinkedDomain: CheckLinkedDomain.NEVER, + redirectUri: 'https://acme.com/hello', + registration: { + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + issuer: ResponseIss.SELF_ISSUED_V2, + responseTypesSupported: [ResponseType.ID_TOKEN], + subjectSyntaxTypesSupported: ['did:ethr:'], + vpFormats: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + registrationBy: { + type: PassBy.REFERENCE, + referenceUri: 'https://rp.acme.com/siop/jwts', + }, + }, + signature: { + did: 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0', + hexPrivateKey: 'f857544a9d1097e242ff0b287a7e6e90f19cf973efe2317f2a4678739664420f', + kid: 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0#controller', + }, + 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 + +```typescript +const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello'; +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) => { + console.log(`nonce: ${jwt.payload.nonce}`); + // output: nonce: 5c1d29c1-cf7d-4e14-9305-9db46d8c1916 +}); +``` + +## 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 + +#### Data Interface + +```typescript +export type RevocationVerificationCallback = ( + vc: W3CVerifiableCredential, // The Verifiable Credential to be checked + type: VerifiableCredentialTypeFormat, // Whether it is a LDP or JWT Verifiable Credential +) => Promise; +``` + +```typescript +export interface IRevocationVerificationStatus { + status: RevocationStatus; // Valid or invalid + error?: string; +} +``` + +```typescript +export enum RevocationVerification { + NEVER = 'never', // We don't want to verify revocation + IF_PRESENT = 'if_present', // If credentialStatus is present, did-auth-siop will verify revocation. If present and not valid an exception is thrown + ALWAYS = 'always', // We'll always check the revocation, if not present or not valid, throws an exception +} +``` + +#### Usage + +```typescript + const verifyRevocation = async ( + vc: W3CVerifiableCredential, + type: VerifiableCredentialTypeFormat +):Promise => { + // Logic to verify the credential status + ... + return { status, error } +}; +``` + +```typescript +import { verifyRevocation } from './Revocation'; + +const rp = RP.builder() + .withRevocationVerification(RevocationVerification.ALWAYS) + .withRevocationVerificationCallback((vc, type) => verifyRevocation(vc, type)); +``` + +### Verify Presentation Callback + +The callback function to verify the verifiable presentation + +#### Data interface + +```typescript +export type PresentationVerificationCallback = (args: IVerifiablePresentation) => Promise; +``` + +```typescript +export type IVerifiablePresentation = IPresentation & IHasProof; +``` + +```typescript +export type PresentationVerificationResult = { verified: boolean }; +``` + +#### Usage + +JsonLD + +```typescript +import { PresentationVerificationResult } from './SIOP.types'; + +const verifyPresentation = async (vp: IVerifiablePresentation): Promise => { + const keyPair = await Ed25519VerificationKey2020.from(VC_KEY_PAIR); + const suite = new Ed25519Signature2020({ key: keyPair }); + suite.verificationMethod = keyPair.id; + // If the credentials are not verified individually by the library, + // it needs to be implemented. In this example, the library does it. + const { verified } = await vc.verify({ presentation: vp, suite, challenge: 'challenge', documentLoader: new DocumentLoader().getLoader() }); + return Promise.resolve({ verified }); +}; +``` + +or + +JWT + +```typescript +import { IVerifiablePresentation } from '@sphereon/ssi-types'; + +const verifyPresentation = async (vp: IVerifiablePresentation): Promise => { + // If the credentials are not verified individually by the library, + // it needs to be implemented. In this example, the library does it. + await verifyCredentialJWT(jwtVc, getResolver({ subjectSyntaxTypesSupported: ['did:key:'] })); + return Promise.resolve({ verified: true }); +}; +``` + +```typescript +const rp = RP.builder() + .withPresentationVerification((args) => verifyPresentation(args)) + ... +``` + +## Class and Flow diagram of the interactions + +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 + +
Gimly ONTOCHAIN Logo European Union Flag
diff --git a/packages/siopv2/docs/auth-flow.md b/packages/siopv2/docs/auth-flow.md new file mode 100644 index 00000000..4fd2d14c --- /dev/null +++ b/packages/siopv2/docs/auth-flow.md @@ -0,0 +1 @@ +![Diagram](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/Sphereon-Opensource/did-auth-siop/develop/docs/auth-flow-diagram.puml) diff --git a/packages/siopv2/docs/auth-flow.puml b/packages/siopv2/docs/auth-flow.puml new file mode 100644 index 00000000..41b56e4d --- /dev/null +++ b/packages/siopv2/docs/auth-flow.puml @@ -0,0 +1,54 @@ +@startuml +header SIOP flow diagram +title +DID OpenID SIOP Flow +end title + +autonumber + +participant "Client\n(OP)" as CLIENT order 0 +participant "OP\nclass" as OP order 1 #White +participant "RP\nclass" as RP order 2 #White +participant "Web component\n(RP)" as WEB order 3 + +activate WEB +CLIENT -> WEB: HTTPS POST + +WEB -> RP: CreateAuthRequest\n(Request Opts) +activate RP +RP -> WEB: Return\n +deactivate RP +WEB -> CLIENT: 302: Redirect (can include VC request), optionally displays a QR +deactivate WEB + +activate CLIENT +CLIENT-> OP: create Auth Response Process\n(Auth Request,Response Opts, Verify Opts) +activate OP +OP -> OP: verifyAuthRequest\n(Auth Request, Verify Opts) +OP -> OP: Presentation Exchange process (see below) +OP -> OP: createAuthResponse\n(Auth Request, Response Opts) +OP-> CLIENT: Return\n +deactivate OP +CLIENT-> WEB: HTTPS POST (can include Verifiable Credentials) +deactivate CLIENT + + +activate WEB +WEB -> RP: Verify\n(Auth Response, Verify Opts) +activate RP + +RP -> WEB: Return\n +deactivate RP +WEB -> CLIENT: 200 +deactivate WEB + + +== Protected resources == + +CLIENT-> WEB: HTTPS POST \n/protected-resources + +activate WEB +WEB-> WEB: Verify\n +WEB-> CLIENT: 200: +deactivate WEB +@enduml diff --git a/packages/siopv2/docs/didjwt-class-diagram.md b/packages/siopv2/docs/didjwt-class-diagram.md new file mode 100644 index 00000000..d233e7c0 --- /dev/null +++ b/packages/siopv2/docs/didjwt-class-diagram.md @@ -0,0 +1,107 @@ +```mermaid +classDiagram +class DidResolutionOptions { + <> + accept?: string +} +class Resolvable { + <> + resolve(didUrl: string, options: DidResolutionOptions) Promise(DidResolutionResult) +} +DidResolutionOptions --> Resolvable +DIDResolutionResult <-- Resolvable + +class DIDResolutionResult { + <> + didResolutionMetadata: DIDResolutionMetadata + didDocument: DIDDocument | null + didDocumentMetadata: DIDDocumentMetadata +} +DIDDocumentMetadata <-- DIDResolutionResult +DIDDocument <-- DIDResolutionResult + +class DIDDocumentMetadata { + <> + created?: string + updated?: string + deactivated?: boolean + versionId?: string + nextUpdate?: string + nextVersionId?: string + equivalentId?: string + canonicalId?: string +} + +class DIDDocument { + <> + '@context'?: 'https://www.w3.org/ns/did/v1' | string | string[] + id: string + alsoKnownAs?: string[] + controller?: string | string[] + verificationMethod?: VerificationMethod[] + authentication?: (string | VerificationMethod)[] + assertionMethod?: (string | VerificationMethod)[] + keyAgreement?: (string | VerificationMethod)[] + capabilityInvocation?: (string | VerificationMethod)[] + capabilityDelegation?: (string | VerificationMethod)[] + service?: ServiceEndpoint[] +} +VerificationMethod <-- DIDDocument + +class VerificationMethod { + <> + id: string + type: string + controller: string + publicKeyBase58?: string + publicKeyJwk?: JsonWebKey + publicKeyHex?: string + blockchainAccountId?: string + ethereumAddress?: string +} + +class JWTPayload { + <> + iss: string + sub?: string + aud?: string | string[] + iat?: number + nbf?: number + exp?: number + rexp?: number +} +class 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 +} + +JsonWebKey <|-- VerificationMethod +class JsonWebKey { + <> + alg?: string + crv?: string + e?: string + ext?: boolean + key_ops?: string[] + kid?: string + kty: string + n?: string + use?: string + x?: string + y?: string +} + + +class DidJWT { + <> + createDidJWT(payload: JWTPayload, options: JWTOptions, header: JWTJHeader) Promise(string) + verifyDidJWT(JWT: string, resolver: Resolvable) Promise(boolean) +} +JWTPayload --> DidJWT +JWTOptions --> DidJWT +JWTHeader --> DidJWT +Resolvable <-- DidJWT + +``` diff --git a/packages/siopv2/docs/eosio-dids-testnet.md b/packages/siopv2/docs/eosio-dids-testnet.md new file mode 100644 index 00000000..fab9b3f5 --- /dev/null +++ b/packages/siopv2/docs/eosio-dids-testnet.md @@ -0,0 +1,110 @@ +# EOSIO DID creation Walk-through + +--- + +#WARNING + +**DO NOT USE THIS WALK-THROUGH** + +_Currently eosio uses DIDs with a different Verfication Method then all other DIDs (verifiable conditions). This library cannot access/use the public keys in that method yet, so the EOSIO DIDs cannot be used for authentication currently. +The document is still here, because Gimly hopes to make changes to the DID driver, so that we can support EOSIO DIDs as well_ + +--- + +Although this library simply expects DIDs to be present, we provide an example how to create DIDs on the EOSIO Junle testnet. There are 75+ DID methods and creation of DIDs typically happens from code and varies quite a bit. You can use your prefered DID method of choice. + +## Relying Party and SIOP should have keys and DIDs + +Since the library uses DIDs for both the Relying Party and the Self-Issued OpenID Provider, we expect these DIDs to be present on both sides, If you do not have DIDs this walk-through will result in EOSIO dids for the Relying Party and the OpenID Provider respectively. If you use another DID method the creation might vary, and hopefully is a bit more automated. + +### Generate EOS keypairs + +Go to: [Jungle3.0 - EOS Test Network Monitor - create keys](https://monitor3.jungletestnet.io/#createKey). You will see a popup/modal that give you a public EOS key and a private EOS key. + +- Save these values for the RP, for example: + - Public Key: EOS6kKhHvCuWkJDAoNb35qxHnyGCmFQpe1eBYBj9W18iKEQ82vsKZ + - Private key: 5JoQQVRYuXfEMBMjY9T96bvsHGfwaXMygnwFNA1enLA5coWQKSi +- Repeat for the OP, for example: + - Public Key: EOS8ZcT5JhRUuLwdQt6j4f2b8opJH1guPrQefTpo9Fqd4fLbKCpyw + - Private key: 5Japr2nKKCzfZQHXupqm9hWmhMnifsuePRKgCHHwW4cQsLs4wvu + +### Create EOS accounts + +Go to : [Jungle3.0 - EOS Test Network Monitor - create account](https://monitor3.jungletestnet.io/#account). You will see a popup/modal in which you have to specify an account name and submit an owner and active public key. Lastly the reCaptcha needs to be checked after which the Create button can be used. Important: Only submit the public keys, never submit private keys! Use the public keys from the above respective steps. + +- Create an account for the RP: + - Account name example: sioprptest11 (needs to be unique and exactly 12 character and only allows a-z and 1-5!) + - Owner Public Key: EOS6kKhHvCuWkJDAoNb35qxHnyGCmFQpe1eBYBj9W18iKEQ82vsKZ (RP key from above step) + - Active Public Key: EOS6kKhHvCuWkJDAoNb35qxHnyGCmFQpe1eBYBj9W18iKEQ82vsKZ (RP key from above step) + +You will see debug output and red text, which might look like an error at first, but actually this means it succeeded + +- Create an account for the OP: + - Account name example: siopoptest11 (needs to be unique and exactly 12 character and only allows a-z and 1-5!) + - Owner Public Key: EOS8ZcT5JhRUuLwdQt6j4f2b8opJH1guPrQefTpo9Fqd4fLbKCpyw (RP key from above step) + - Active Public Key: EOS8ZcT5JhRUuLwdQt6j4f2b8opJH1guPrQefTpo9Fqd4fLbKCpyw (RP key from above step) + +You will see debug output and red text, which might look like an error at first, but actually this means it succeeded + +### Add balances using a Faucet + +Go to : [Jungle3.0 - EOS Test Network Monitor - faucet](https://monitor3.jungletestnet.io/#faucet) + +- For the RP: + - Fill in the “account name”, eg "sioprptest11", confirm you are not a robot and click “send coins“. + - This should result in a balance of 100 EOS and 100 JUNGLE +- For the OP: + - Fill in the “account name”, eg "siopoptest11", confirm you are not a robot and click “send coins“. + - This should result in a balance of 100 EOS and 100 JUNGLE + +### Increase CPU usage limit + +To execute transactions we need the correct CPU limit. +Go to [Jungle3.0 - EOS Test Network Monitor - powerup](https://monitor3.jungletestnet.io/#powerup) + +- For the RP: + - Fill in the “account name”, eg "sioprptest11", confirm you are not a robot and click “send coins“ (Don't be alarmed by the button reading 'Send Coins'. That is a mistake in the site as it should read Powerup) + - This should result in a transaction with a powerup +- For the OP: + - Fill in the “account name”, eg "siopoptest11", confirm you are not a robot and click “send coins“ (Don't be alarmed by the button reading 'Send Coins'. That is a mistake in the site as it should read Powerup) + - This should result in a transaction with a powerup + +### Test resolution of the DIDs. + +- Go to : https://dev.uniresolver.io +- In the did-url input box past the below dids and click on the Resolve button. You should get back results: + - did:eosio:eos:testnet:jungle:, eg: + - did:eosio:eos:testnet:jungle:sioprptest11 + - did:eosio:eos:testnet:jungle:siopoptest11 + +### Install/use eosio-did typescript library if you want to create additional DIDs + +We want to use the [Gimly-Blockchain/eosio-did](https://github.com/Gimly-Blockchain/eosio-did) typescript library to create eosio did’s. We expect the user to know its way around a development IDE and have npm installed on the computer. + +- git checkout https://github.com/Gimly-Blockchain/eosio-did.git +- cd eosio-git +- npm install +- This project has a test to create those did’s, “create.test.ts“. This test requires that a jungleTestKeys.json is present in the root of the project. The content looks like: + +```json +{ + "name": "[ACCOUNT_NAME]", + "private": "[PRIV_KEY]", + "public": "[PUB_KEY]" +} +``` + +For the RP: + +- Create a rp.json in the root of the project: + +```json +{ + "name": "sioprptest11", + "private": "5JoQQVRYuXfEMBMjY9T96bvsHGfwaXMygnwFNA1enLA5coWQKSi", + "public": "EOS6kKhHvCuWkJDAoNb35qxHnyGCmFQpe1eBYBj9W18iKEQ82vsKZ" +} +``` + +- Adjust line 6 of create-test.ts to read `const jungleTestKeys = require('../rp.json');` +- Execute the test diff --git a/packages/siopv2/docs/gimly-logo.png b/packages/siopv2/docs/gimly-logo.png new file mode 100644 index 00000000..6504e9c5 Binary files /dev/null and b/packages/siopv2/docs/gimly-logo.png differ diff --git a/packages/siopv2/docs/presentation-exchange.puml b/packages/siopv2/docs/presentation-exchange.puml new file mode 100644 index 00000000..03979d80 --- /dev/null +++ b/packages/siopv2/docs/presentation-exchange.puml @@ -0,0 +1,38 @@ +@startuml +header Presentation Exchange flow diagram +title +Presentation Exchange Flow +end title + +autonumber + +participant "Client\n(OP)" as CLIENT order 0 +participant "OP\n<>" as OP order 1 #White +participant "Presentation Exchange\n<>" as PE order 2 #Gray + +activate OP +OP -> OP: verifyAuthRequest\n(Auth Request, Verify Opts) +== START: Presentation Definition from RP is present == + +OP -> CLIENT: if presentationDefinition is present +deactivate OP +activate CLIENT +CLIENT -> PE: Construct PE with DID and Verifiable Credentials +deactivate CLIENT + +activate PE +PE -> PE: selectVerifiableCredentialsForSubmission(Presentation Definition) +PE -> CLIENT: Return matching VCs or an error +activate CLIENT +CLIENT -> CLIENT: Show UI to confirm and optionally subselect VCs from matches\n(NOTE: Not in scope of this library) +CLIENT -> PE: selected VCs +deactivate CLIENT +PE -> PE: submissionFrom(Presentation Definition, selected VCs) +PE -> OP: Return Verifiable Presentation (VP) +deactivate PE +== END: Presentation Definition from RP is present == +activate OP +OP -> OP: createAuthResponse(Verified Auth request, opts and VP) + + +@enduml diff --git a/packages/siopv2/docs/services-class-diagram.md b/packages/siopv2/docs/services-class-diagram.md new file mode 100644 index 00000000..3051bd93 --- /dev/null +++ b/packages/siopv2/docs/services-class-diagram.md @@ -0,0 +1,209 @@ +```mermaid +classDiagram + +class RP { + <> + createAuthenticationRequest(opts?) Promise(AuthenticationRequestURI) + verifyAuthenticationResponseJwt(jwt: string, opts?) Promise(VerifiedAuthenticationResponseWithJWT) +} +RP --> AuthenticationRequestURI +RP --> VerifiedAuthenticationResponseWithJWT +RP --> AuthorizationRequest +RP --> AuthenticationResponse + +class OP { + <> + createAuthenticationResponse(jwtOrUri: string, opts?) Promise(AuthenticationResponseWithJWT) + verifyAuthenticationRequest(jwt: string, opts?) Promise(VerifiedAuthenticationRequestWithJWT) +} +OP --> AuthenticationResponseWithJWT +OP --> VerifiedAuthenticationRequestWithJWT +OP --> AuthorizationRequest +OP --> AuthenticationResponse + + +class AuthenticationRequestOpts { + <> + redirectUri: string; + requestBy: ObjectBy; + signature: InternalSignature | ExternalSignature | NoSignature; + responseMode?: ResponseMode; + claims?: ClaimPayload; + registration: RequestRegistrationOpts; + nonce?: string; + state?: string; +} +AuthenticationRequestOpts --> ResponseMode +AuthenticationRequestOpts --> RPRegistrationMetadataOpts + + + +class RPRegistrationMetadataOpts { + <> + subjectIdentifiersSupported: SubjectIdentifierType[] | SubjectIdentifierType; + didMethodsSupported?: string[] | string; + credentialFormatsSupported: CredentialFormat[] | CredentialFormat; +} + +class RequestRegistrationOpts { + <> + registrationBy: RegistrationType; +} +RequestRegistrationOpts --|> RPRegistrationMetadataOpts + + +class VerifyAuthenticationRequestOpts { + <> + verification: InternalVerification | ExternalVerification; + nonce?: string; +} + +class AuthorizationRequest { + <> + createURI(opts: AuthenticationRequestOpts) Promise(AuthenticationRequestURI) + createJWT(opts: AuthenticationRequestOpts) Promise(AuthenticationRequestWithJWT); + verifyJWT(jwt: string, opts: VerifyAuthenticationRequestOpts) Promise(VerifiedAuthenticationRequestWithJWT) +} +AuthorizationRequest <-- AuthenticationRequestOpts +AuthorizationRequest <-- VerifyAuthenticationRequestOpts +AuthorizationRequest --> AuthenticationRequestURI +AuthorizationRequest --> AuthenticationRequestWithJWT +AuthorizationRequest --> VerifiedAuthenticationRequestWithJWT + +class AuthenticationResponse { + <> + createJWTFromRequestJWT(jwt: string, responseOpts: AuthenticationResponseOpts, verifyOpts: VerifyAuthenticationRequestOpts) Promise(AuthenticationResponseWithJWT) + verifyJWT(jwt: string, verifyOpts: VerifyAuthenticationResponseOpts) Promise(VerifiedAuthenticationResponseWithJWT) +} +AuthenticationResponse <-- AuthenticationResponseOpts +AuthenticationResponse <-- VerifyAuthenticationRequestOpts +AuthenticationResponse --> AuthenticationResponseWithJWT +AuthenticationResponse <-- VerifyAuthenticationResponseOpts +AuthenticationResponse --> VerifiedAuthenticationResponseWithJWT + +class AuthenticationResponseOpts { + <> + signature: InternalSignature | ExternalSignature; + nonce?: string; + state?: string; + registration: ResponseRegistrationOpts; + responseMode?: ResponseMode; + did: string; + vp?: VerifiablePresentation; + expiresIn?: number; +} +AuthenticationResponseOpts --> ResponseMode + +class AuthenticationResponseWithJWT { + <> + jwt: string; + nonce: string; + state: string; + payload: AuthenticationResponsePayload; + verifyOpts?: VerifyAuthenticationRequestOpts; + responseOpts: AuthenticationResponseOpts; +} +AuthenticationResponseWithJWT --> AuthenticationResponsePayload +AuthenticationResponseWithJWT --> VerifyAuthenticationRequestOpts +AuthenticationResponseWithJWT --> AuthenticationResponseOpts + + +class VerifyAuthenticationResponseOpts { + <> + verification: InternalVerification | ExternalVerification; + nonce?: string; + state?: string; + audience: string; +} + +class ResponseMode { + <> +} + + class UriResponse { + <> + responseMode?: ResponseMode; + bodyEncoded?: string; +} +UriResponse --> ResponseMode +UriResponse <|-- SIOPURI + +class SIOPURI { + <> + encodedUri: string; + encodingFormat: UrlEncodingFormat; +} +SIOPURI --> UrlEncodingFormat +SIOPURI <|-- AuthenticationRequestURI + +class AuthenticationRequestURI { + <> + jwt?: string; + requestOpts: AuthenticationRequestOpts; + requestPayload: AuthenticationRequestPayload; +} +AuthenticationRequestURI --> AuthenticationRequestPayload + +class UrlEncodingFormat { + <> +} + +class ResponseMode { + <> +} + +class 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; +} +AuthenticationRequestPayload --|> JWTPayload + +class JWTPayload { + iss?: string + sub?: string + aud?: string | string[] + iat?: number + nbf?: number + exp?: number + rexp?: number + [x: string]: any +} + + +class VerifiedAuthenticationRequestWithJWT { + <> + payload: AuthenticationRequestPayload; + verifyOpts: VerifyAuthenticationRequestOpts; +} +VerifiedJWT <|-- VerifiedAuthenticationRequestWithJWT +VerifiedAuthenticationRequestWithJWT --> VerifyAuthenticationRequestOpts +VerifiedAuthenticationRequestWithJWT --> AuthenticationRequestPayload + +class VerifiedAuthenticationResponseWithJWT { + <> + payload: AuthenticationResponsePayload; + verifyOpts: VerifyAuthenticationResponseOpts; +} +VerifiedAuthenticationResponseWithJWT --> AuthenticationResponsePayload +VerifiedAuthenticationResponseWithJWT --> VerifyAuthenticationResponseOpts +VerifiedJWT <|-- VerifiedAuthenticationResponseWithJWT + +class VerifiedJWT { + <> + payload: Partial; + didResolutionResult: DIDResolutionResult; + issuer: string; + signer: VerificationMethod; + jwt: string; +} +``` diff --git a/packages/siopv2/docs/services-class-diagram.svg b/packages/siopv2/docs/services-class-diagram.svg new file mode 100644 index 00000000..69466101 --- /dev/null +++ b/packages/siopv2/docs/services-class-diagram.svg @@ -0,0 +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;
diff --git a/packages/siopv2/docs/walk-through.md b/packages/siopv2/docs/walk-through.md new file mode 100644 index 00000000..e69de29b diff --git a/packages/siopv2/generator/schemaGenerator.ts b/packages/siopv2/generator/schemaGenerator.ts new file mode 100644 index 00000000..0826bd7a --- /dev/null +++ b/packages/siopv2/generator/schemaGenerator.ts @@ -0,0 +1,190 @@ +import fs from 'fs'; +import path from 'path'; + +import Ajv from 'ajv'; +import standaloneCode from 'ajv/dist/standalone'; +import { + BaseType, + createFormatter, + createParser, + createProgram, + Definition, + FunctionType, + MutableTypeFormatter, + SchemaGenerator, + SubTypeFormatter, +} from 'ts-json-schema-generator'; +import { Schema } from 'ts-json-schema-generator/dist/src/Schema/Schema'; + +class CustomTypeFormatter implements SubTypeFormatter { + public supportsType(type: FunctionType): boolean { + return type instanceof FunctionType; + } + + public getDefinition(): Definition { + // Return a custom schema for the function property. + return { + properties: { + isFunction: { + type: 'boolean', + const: true, + }, + }, + }; + } + + public getChildren(): BaseType[] { + return []; + } +} + +function writeSchema(config: any): Schema { + const formatter = createFormatter(config, (fmt: MutableTypeFormatter) => { + fmt.addTypeFormatter(new CustomTypeFormatter()); + }); + + const program = createProgram(config); + const schema = new SchemaGenerator(program, createParser(program, config), formatter, config).createSchema(config.type); + + let schemaString = JSON.stringify(schema, null, 2); + schemaString = correctSchema(schemaString); + + fs.writeFile(config.outputPath, `export const ${config.schemaId}Obj = ${schemaString};`, (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 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' + + ' },', + '"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 = { + path: '../src/authorization-request/types.ts', + tsconfig: 'tsconfig.json', + type: 'CreateAuthorizationRequestOpts', // Or if you want to generate schema for that one type only + schemaId: 'CreateAuthorizationRequestOptsSchema', + outputPath: 'src/schemas/AuthorizationRequestOpts.schema.ts', + // outputConstName: 'AuthorizationRequestOptsSchema', + skipTypeCheck: true +};*/ + +const responseOptsConf = { + path: '../src/authorization-response/types.ts', + tsconfig: 'tsconfig.json', + type: 'AuthorizationResponseOpts', // Or if you want to generate schema for that one type only + schemaId: 'AuthorizationResponseOptsSchema', + outputPath: 'src/schemas/AuthorizationResponseOpts.schema.ts', + // outputConstName: 'AuthorizationResponseOptsSchema', + skipTypeCheck: true, +}; + +const rPRegistrationMetadataPayload = { + path: '../src/types/SIOP.types.ts', + tsconfig: 'tsconfig.json', + type: 'RPRegistrationMetadataPayload', + schemaId: 'RPRegistrationMetadataPayloadSchema', + outputPath: 'src/schemas/RPRegistrationMetadataPayload.schema.ts', + // outputConstName: 'RPRegistrationMetadataPayloadSchema', + skipTypeCheck: true, +}; + +const discoveryMetadataPayload = { + path: '../src/types/SIOP.types.ts', + tsconfig: 'tsconfig.json', + type: 'DiscoveryMetadataPayload', + schemaId: 'DiscoveryMetadataPayloadSchema', + outputPath: 'src/schemas/DiscoveryMetadataPayload.schema.ts', + // outputConstName: 'DiscoveryMetadataPayloadSchema', + skipTypeCheck: true, +}; + +const authorizationRequestPayloadVID1 = { + path: '../src/types/SIOP.types.ts', + tsconfig: 'tsconfig.json', + type: 'AuthorizationRequestPayloadVID1', // Or if you want to generate schema for that one type only + schemaId: 'AuthorizationRequestPayloadVID1Schema', + outputPath: 'src/schemas/AuthorizationRequestPayloadVID1.schema.ts', + // outputConstName: 'AuthorizationRequestPayloadSchemaVID1', + skipTypeCheck: true, +}; + +const authorizationRequestPayloadVD11 = { + path: '../src/types/SIOP.types.ts', + tsconfig: 'tsconfig.json', + type: 'AuthorizationRequestPayloadVD11', // Or if you want to generate schema for that one type only + schemaId: 'AuthorizationRequestPayloadVD11Schema', + outputPath: 'src/schemas/AuthorizationRequestPayloadVD11.schema.ts', + // outputConstName: 'AuthorizationRequestPayloadSchemaVD11', + skipTypeCheck: true, +}; + +const authorizationRequestPayloadVD12OID4VPD18 = { + path: '../src/types/SIOP.types.ts', + tsconfig: 'tsconfig.json', + type: 'AuthorizationRequestPayloadVD12OID4VPD18', // Or if you want to generate schema for that one type only + schemaId: 'AuthorizationRequestPayloadVD12OID4VPD18Schema', + outputPath: 'src/schemas/AuthorizationRequestPayloadVD12OID4VPD18.schema.ts', + // outputConstName: 'AuthorizationRequestPayloadSchemaVD11', + skipTypeCheck: true, +}; + +const schemas: Schema[] = [ + writeSchema(authorizationRequestPayloadVID1), + writeSchema(authorizationRequestPayloadVD11), + writeSchema(authorizationRequestPayloadVD12OID4VPD18), + // writeSchema(requestOptsConf), + writeSchema(responseOptsConf), + writeSchema(rPRegistrationMetadataPayload), + writeSchema(discoveryMetadataPayload), +]; + +generateValidationCode(schemas); diff --git a/packages/siopv2/jest.config.cjs b/packages/siopv2/jest.config.cjs new file mode 100644 index 00000000..e3773a16 --- /dev/null +++ b/packages/siopv2/jest.config.cjs @@ -0,0 +1,27 @@ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + moduleNameMapper: { + "^jose/(.*)$": "/node_modules/jose/dist/node/cjs/$1", + }, + rootDir: ".", + roots: ["/src/", "/test/"], + testMatch: ["**/?(*.)+(spec|test).+(ts|tsx|js)"], + transform: { + "^.+\\.(ts|tsx)?$": "ts-jest", + }, + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"], + coverageDirectory: "./coverage/", + collectCoverageFrom: [ + "src/**/*.{ts,tsx}", + "!src/schemas/**", + "!src/**/*.d.ts", + "!**/node_modules/**", + "!jest.config.cjs", + "!generator/**", + "!index.ts", + + ], + collectCoverage: true, + reporters: ["default", ["jest-junit", { outputDirectory: "./coverage" }]], +}; diff --git a/packages/siopv2/package.json b/packages/siopv2/package.json new file mode 100644 index 00000000..3fd88850 --- /dev/null +++ b/packages/siopv2/package.json @@ -0,0 +1,114 @@ +{ + "name": "@sphereon/did-auth-siop", + "version": "0.6.0-unstable.0", + "source": "src/index.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "license": "Apache-2.0", + "repository": { + "url": "https://github.com/Sphereon-Opensource/SIOP-OID4VP.git" + }, + "author": "Sphereon", + "description": "Self Issued OpenID V2 (SIOPv2) and OpenID 4 Verifiable Presentations (OID4VP)", + "scripts": { + "build": "npm-run-all build:schemaGenerator build:main", + "build:main": "tsc -p tsconfig.build.json", + "build:schemaGenerator": "node --loader=tsimp/loader generator/schemaGenerator.ts", + "clean": "rimraf dist coverage", + "fix.old": "run-s fix:*", + "fix:prettier.old": "prettier \"{src,test}/**/*.ts\" --write", + "fix:lint.old": "eslint . --ext .ts --fix", + "test": "run-s build test:*", + "test:lint": "eslint . --ext .ts", + "test:prettier": "prettier \"{src,test}/**/*.ts\" --list-different", + "test:cov": "jest --ci --coverage && codecov", + "uninstall": "rimraf dist coverage node_modules" + }, + "engines": { + "node": ">=18" + }, + "dependencies": { + "@sphereon/did-uni-client": "^0.6.1", + "qs": "^6.11.2", + "@sphereon/pex": "^3.0.1", + "@sphereon/pex-models": "^2.1.5", + "@sphereon/ssi-types": "0.18.1", + "@sphereon/wellknown-dids-client": "^0.1.3", + "@sphereon/oid4vc-common": "workspace:*", + "@astronautlabs/jsonpath": "^1.1.2", + "sha.js": "^2.4.11", + "cross-fetch": "^4.0.0", + "did-jwt": "6.11.6", + "did-resolver": "^4.1.0", + "events": "^3.3.0", + "language-tags": "^1.0.9", + "multiformats": "^11.0.2", + "uint8arrays": "^3.1.1", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@types/qs": "^6.9.11", + "@digitalcredentials/did-method-key": "^2.0.3", + "@digitalcredentials/ed25519-signature-2020": "^3.0.2", + "@digitalcredentials/jsonld-signatures": "^9.3.2", + "@digitalcredentials/vc": "^6.0.0", + "@transmute/ed25519-key-pair": "0.7.0-unstable.82", + "@transmute/ed25519-signature-2018": "^0.7.0-unstable.82", + "@transmute/did-key-ed25519": "^0.3.0-unstable.10", + "did-resolver": "^4.1.0", + "@types/jest": "^29.5.11", + "@types/language-tags": "^1.0.4", + "@types/uuid": "^9.0.7", + "@typescript-eslint/eslint-plugin": "^5.52.0", + "@typescript-eslint/parser": "^5.52.0", + "ajv": "^8.12.0", + "bs58": "^5.0.0", + "codecov": "^3.8.3", + "cspell": "^6.26.3", + "dotenv": "^16.3.1", + "eslint": "^8.34.0", + "eslint-config-prettier": "^8.6.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-import": "^2.27.5", + "ethers": "^6.10.0", + "jest": "^29.4.3", + "jest-junit": "^15.0.0", + "jose": "^4.12.0", + "jwt-decode": "^3.1.2", + "moment": "^2.30.1", + "nock": "^13.4.0", + "npm-run-all": "^4.1.5", + "open-cli": "^7.1.0", + "prettier": "^2.8.8", + "ts-interface-checker": "^1.0.2", + "ts-jest": "^29.1.1", + "ts-json-schema-generator": "1.5.0", + "tsimp": "^2.0.10", + "typescript": "^5.3.3" + }, + "resolutions": { + "isomorphic-webcrypto": "npm:@sphereon/isomorphic-webcrypto@^2.4.0-unstable.4", + "esline/**/strip-ansi": "6.0.1" + }, + "files": [ + "dist" + ], + "prettier": { + "singleQuote": true, + "printWidth": 150 + }, + "keywords": [ + "Sphereon", + "SSI", + "Credentials", + "OpenID", + "SIOP", + "Self Issued OpenID Connect", + "SIOPv2", + "OID4VC", + "OID4VP", + "OpenID4VP", + "OpenID4VC", + "OIDC4VP" + ] +} diff --git a/packages/siopv2/src/authorization-request/AuthorizationRequest.ts b/packages/siopv2/src/authorization-request/AuthorizationRequest.ts new file mode 100644 index 00000000..93f9c552 --- /dev/null +++ b/packages/siopv2/src/authorization-request/AuthorizationRequest.ts @@ -0,0 +1,260 @@ +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 { RequestObject } from '../request-object'; +import { + AuthorizationRequestPayload, + PassBy, + RequestObjectJwt, + RequestObjectPayload, + RequestStateInfo, + ResponseURIType, + RPRegistrationMetadataPayload, + Schema, + SIOPErrors, + SupportedVersion, + VerifiedAuthorizationRequest, + VerifiedJWT, +} from '../types'; + +import { assertValidAuthorizationRequestOpts, assertValidVerifyAuthorizationRequestOpts } from './Opts'; +import { assertValidRPRegistrationMedataPayload, checkWellknownDIDFromRequest, createAuthorizationRequestPayload } from './Payload'; +import { URI } from './URI'; +import { CreateAuthorizationRequestOpts, VerifyAuthorizationRequestOpts } from './types'; + +export class AuthorizationRequest { + private readonly _requestObject?: RequestObject; + private readonly _payload: AuthorizationRequestPayload; + private readonly _options?: CreateAuthorizationRequestOpts; + private _uri?: URI; + + private constructor(payload: AuthorizationRequestPayload, requestObject?: RequestObject, opts?: CreateAuthorizationRequestOpts, uri?: URI) { + this._options = opts; + this._payload = removeNullUndefined(payload); + this._requestObject = requestObject; + this._uri = uri; + } + + public static async fromUriOrJwt(jwtOrUri: string | URI): Promise { + if (!jwtOrUri) { + throw Error(SIOPErrors.NO_REQUEST); + } + return typeof jwtOrUri === 'string' && jwtOrUri.startsWith('ey') + ? await AuthorizationRequest.fromJwt(jwtOrUri) + : await AuthorizationRequest.fromURI(jwtOrUri); + } + + public static async fromPayload(payload: AuthorizationRequestPayload): Promise { + if (!payload) { + throw Error(SIOPErrors.NO_REQUEST); + } + const requestObject = await RequestObject.fromAuthorizationRequestPayload(payload); + return new AuthorizationRequest(payload, requestObject); + } + + public static async fromOpts(opts: CreateAuthorizationRequestOpts, requestObject?: RequestObject): Promise { + // todo: response_uri/redirect_uri is not hooked up from opts! + if (!opts || !opts.requestObject) { + throw Error(SIOPErrors.BAD_PARAMS); + } + assertValidAuthorizationRequestOpts(opts); + + const requestObjectArg = + opts.requestObject.passBy !== PassBy.NONE ? (requestObject ? requestObject : await RequestObject.fromOpts(opts)) : undefined; + const requestPayload = opts?.payload ? await createAuthorizationRequestPayload(opts, requestObjectArg) : undefined; + return new AuthorizationRequest(requestPayload, requestObjectArg, opts); + } + + get payload(): AuthorizationRequestPayload { + return this._payload; + } + + get requestObject(): RequestObject | undefined { + return this._requestObject; + } + + get options(): CreateAuthorizationRequestOpts | undefined { + return this._options; + } + + public hasRequestObject(): boolean { + return this.requestObject !== undefined; + } + + public async getSupportedVersion() { + if (this.options?.version) { + return this.options.version; + } else if (this._uri?.encodedUri?.startsWith(Schema.OPENID_VC) || this._uri?.scheme?.startsWith(Schema.OPENID_VC)) { + return SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1; + } + + return (await this.getSupportedVersionsFromPayload())[0]; + } + + public async getSupportedVersionsFromPayload(): Promise { + const mergedPayload = { ...this.payload, ...(await this.requestObject?.getPayload()) }; + return authorizationRequestVersionDiscovery(mergedPayload); + } + + async uri(): Promise { + if (!this._uri) { + this._uri = await URI.fromAuthorizationRequest(this); + } + return this._uri; + } + + /** + * Verifies a SIOP Request JWT on OP side + * + * @param opts + */ + async verify(opts: VerifyAuthorizationRequestOpts): Promise { + assertValidVerifyAuthorizationRequestOpts(opts); + + let requestObjectPayload: RequestObjectPayload; + let verifiedJwt: VerifiedJWT; + + const jwt = await this.requestObjectJwt(); + if (jwt) { + parseJWT(jwt); + const resolver = getResolver(opts.verification.resolveOpts); + const options: JWTVerifyOptions = { + ...opts.verification?.resolveOpts?.jwtVerifyOpts, + resolver, + audience: getAudience(jwt), + }; + + verifiedJwt = await verifyDidJWT(jwt, resolver, options); + if (!verifiedJwt || !verifiedJwt.payload) { + 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 + this.payload.request = jwt; + } + } + + // AuthorizationRequest.assertValidRequestObject(origAuthenticationRequest); + + // We use the orig request for default values, but the JWT payload contains signed request object properties + const mergedPayload = { ...this.payload, ...requestObjectPayload }; + if (opts.state && mergedPayload.state !== opts.state) { + throw new Error(`${SIOPErrors.BAD_STATE} payload: ${mergedPayload.state}, supplied: ${opts.state}`); + } else if (opts.nonce && mergedPayload.nonce !== opts.nonce) { + throw new Error(`${SIOPErrors.BAD_NONCE} payload: ${mergedPayload.nonce}, supplied: ${opts.nonce}`); + } + + const registrationPropertyKey = mergedPayload['registration'] || mergedPayload['registration_uri'] ? 'registration' : 'client_metadata'; + let registrationMetadataPayload: RPRegistrationMetadataPayload; + if (mergedPayload[registrationPropertyKey] || mergedPayload[`${registrationPropertyKey}_uri`]) { + registrationMetadataPayload = await fetchByReferenceOrUseByValue( + mergedPayload[`${registrationPropertyKey}_uri`], + mergedPayload[registrationPropertyKey], + ); + assertValidRPRegistrationMedataPayload(registrationMetadataPayload); + // TODO: We need to do something with the metadata probably + } + // 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. + let responseURIType: ResponseURIType; + let responseURI: string; + if (mergedPayload.redirect_uri && mergedPayload.response_uri) { + throw new Error(`${SIOPErrors.INVALID_REQUEST}, redirect_uri cannot be used together with response_uri`); + } else if (mergedPayload.redirect_uri) { + responseURIType = 'redirect_uri'; + responseURI = mergedPayload.redirect_uri; + } else if (mergedPayload.response_uri) { + responseURIType = 'response_uri'; + responseURI = mergedPayload.response_uri; + } else if (mergedPayload.client_id_scheme === 'redirect_uri' && mergedPayload.client_id) { + responseURIType = 'redirect_uri'; + responseURI = mergedPayload.client_id; + } else { + 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, + responseURIType, + responseURI, + clientIdScheme: mergedPayload.client_id_scheme, + correlationId: opts.correlationId, + authorizationRequest: this, + verifyOpts: opts, + presentationDefinitions, + registrationMetadataPayload, + requestObject: this.requestObject, + authorizationRequestPayload: this.payload, + versions: await this.getSupportedVersionsFromPayload(), + }; + } + + static async verify(requestOrUri: string, verifyOpts: VerifyAuthorizationRequestOpts) { + assertValidVerifyAuthorizationRequestOpts(verifyOpts); + const authorizationRequest = await AuthorizationRequest.fromUriOrJwt(requestOrUri); + return await authorizationRequest.verify(verifyOpts); + } + + public async requestObjectJwt(): Promise { + return await this.requestObject?.toJwt(); + } + + private static async fromJwt(jwt: string): Promise { + if (!jwt) { + throw Error(SIOPErrors.BAD_PARAMS); + } + const requestObject = await RequestObject.fromJwt(jwt); + const payload: AuthorizationRequestPayload = { ...(await requestObject.getPayload()) } as AuthorizationRequestPayload; + // Although this was a RequestObject we instantiate it as AuthzRequest and then copy in the JWT as the request Object + payload.request = jwt; + return new AuthorizationRequest({ ...payload }, requestObject); + } + + private static async fromURI(uri: URI | string): Promise { + if (!uri) { + throw Error(SIOPErrors.BAD_PARAMS); + } + const uriObject = typeof uri === 'string' ? await URI.fromUri(uri) : uri; + const requestObject = await RequestObject.fromJwt(uriObject.requestObjectJwt); + return new AuthorizationRequest(uriObject.authorizationRequestPayload, requestObject, undefined, uriObject); + } + + public async toStateInfo(): Promise { + const requestObject = await this.requestObject.getPayload(); + return { + client_id: this.options.clientMetadata.client_id, + iat: requestObject.iat ?? this.payload.iat, + nonce: requestObject.nonce ?? this.payload.nonce, + state: this.payload.state, + }; + } + + public async containsResponseType(singleType: ResponseType | string): Promise { + const responseType: string = await this.getMergedProperty('response_type'); + return responseType?.includes(singleType) === true; + } + + public async getMergedProperty(key: string): Promise { + const merged = await this.mergedPayloads(); + return merged[key] as T; + } + + public async mergedPayloads(): Promise { + return { ...this.payload, ...(this.requestObject && (await this.requestObject.getPayload())) }; + } + + public async getPresentationDefinitions(version?: SupportedVersion): Promise { + return await PresentationExchange.findValidPresentationDefinitions(await this.mergedPayloads(), version); + } +} diff --git a/packages/siopv2/src/authorization-request/Opts.ts b/packages/siopv2/src/authorization-request/Opts.ts new file mode 100644 index 00000000..a534e598 --- /dev/null +++ b/packages/siopv2/src/authorization-request/Opts.ts @@ -0,0 +1,67 @@ +import { assertValidRequestObjectOpts } from '../request-object/Opts'; +import { ExternalVerification, InternalVerification, isExternalVerification, isInternalVerification, SIOPErrors } 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))) { + throw new Error(SIOPErrors.VERIFY_BAD_PARAMS); + } + if (!opts.correlationId) { + throw new Error('No correlation id found'); + } +}; + +export const assertValidAuthorizationRequestOpts = (opts: CreateAuthorizationRequestOpts) => { + if (!opts || !opts.requestObject || (!opts.payload && !opts.requestObject.payload) || (opts.payload?.request_uri && !opts.requestObject.payload)) { + throw new Error(SIOPErrors.BAD_PARAMS); + } + assertValidRequestObjectOpts(opts.requestObject, false); + assertValidRequestRegistrationOpts(opts['registration'] ? opts['registration'] : opts.clientMetadata); +}; + +export const mergeVerificationOpts = ( + classOpts: { + verification?: InternalVerification | ExternalVerification; + }, + requestOpts: { + correlationId: string; + verification?: InternalVerification | ExternalVerification; + }, +) => { + 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, + revocationVerificationCallback: + requestOpts.verification?.revocationOpts?.revocationVerificationCallback ?? + classOpts?.verification?.revocationOpts?.revocationVerificationCallback, + }, + }; +}; diff --git a/packages/siopv2/src/authorization-request/Payload.ts b/packages/siopv2/src/authorization-request/Payload.ts new file mode 100644 index 00000000..a9c29dcb --- /dev/null +++ b/packages/siopv2/src/authorization-request/Payload.ts @@ -0,0 +1,102 @@ +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'; + +export const createPresentationDefinitionClaimsProperties = (opts: ClaimPayloadOptsVID1): ClaimPayloadVID1 => { + if (!opts || !opts.vp_token || (!opts.vp_token.presentation_definition && !opts.vp_token.presentation_definition_uri)) { + return undefined; + } + const discoveryResult = PEX.definitionVersionDiscovery(opts.vp_token.presentation_definition); + if (discoveryResult.error) { + throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID); + } + + return { + ...(opts.id_token ? { id_token: opts.id_token } : {}), + ...((opts.vp_token.presentation_definition || opts.vp_token.presentation_definition_uri) && { + vp_token: { + ...(!opts.vp_token.presentation_definition_uri && { presentation_definition: opts.vp_token.presentation_definition }), + ...(opts.vp_token.presentation_definition_uri && { presentation_definition_uri: opts.vp_token.presentation_definition_uri }), + }, + }), + }; +}; + +export const createAuthorizationRequestPayload = async ( + opts: CreateAuthorizationRequestOpts, + requestObject?: RequestObject, +): Promise => { + const payload = opts.payload; + const state = payload?.state ?? undefined; + const nonce = payload?.nonce ? getNonce(state, payload.nonce) : undefined; + // TODO: if opts['registration] throw Error to get rid of test code using that key + const clientMetadata = opts['registration'] ? opts['registration'] : (opts.clientMetadata as ClientMetadataOpts); + const registration = await createRequestRegistration(clientMetadata, opts); + const claims = + opts.version >= SupportedVersion.SIOPv2_ID1 ? opts.payload.claims : createPresentationDefinitionClaimsProperties(opts.payload.claims); + const isRequestTarget = isTargetOrNoTargets(PropertyTarget.AUTHORIZATION_REQUEST, opts.requestObject.targets); + const isRequestByValue = opts.requestObject.passBy === PassBy.VALUE; + + if (isRequestTarget && isRequestByValue && !requestObject) { + throw Error(SIOPErrors.NO_JWT); + } + const request = isRequestByValue ? await requestObject.toJwt() : undefined; + + const authRequestPayload = { + ...payload, + //TODO implement /.well-known/openid-federation support in the OP side to resolve the client_id (URL) and retrieve the metadata + ...(isRequestTarget && opts.requestObject.passBy === PassBy.REFERENCE ? { request_uri: opts.requestObject.reference_uri } : {}), + ...(isRequestTarget && isRequestByValue && { request }), + ...(nonce && { nonce }), + ...(state && { state }), + ...(registration.payload && isTarget(PropertyTarget.AUTHORIZATION_REQUEST, registration.clientMetadataOpts.targets) ? registration.payload : {}), + ...(claims && { claims }), + }; + + return removeNullUndefined(authRequestPayload); +}; + +export const assertValidRPRegistrationMedataPayload = (regObj: RPRegistrationMetadataPayload) => { + if (regObj) { + const valid = RPRegistrationMetadataPayloadSchema(regObj); + if (!valid) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + throw new Error('Registration data validation error: ' + JSON.stringify(RPRegistrationMetadataPayloadSchema.errors)); + } + } + if (regObj?.subject_syntax_types_supported && regObj.subject_syntax_types_supported.length == 0) { + 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/packages/siopv2/src/authorization-request/RequestRegistration.ts b/packages/siopv2/src/authorization-request/RequestRegistration.ts new file mode 100644 index 00000000..1af7af4c --- /dev/null +++ b/packages/siopv2/src/authorization-request/RequestRegistration.ts @@ -0,0 +1,100 @@ +import { LanguageTagUtils, removeNullUndefined } from '../helpers'; +import { + ClientMetadataOpts, + PassBy, + RequestClientMetadataPayloadProperties, + RequestRegistrationPayloadProperties, + RPRegistrationMetadataOpts, + RPRegistrationMetadataPayload, + SIOPErrors, + SupportedVersion, +} from '../types'; + +import { CreateAuthorizationRequestOpts } from './types'; + +/*const ajv = new Ajv({ allowUnionTypes: true, strict: false }); +const validateRPRegistrationMetadata = ajv.compile(RPRegistrationMetadataPayloadSchema);*/ + +export const assertValidRequestRegistrationOpts = (opts: ClientMetadataOpts) => { + if (!opts) { + throw new Error(SIOPErrors.REGISTRATION_NOT_SET); + } else if (opts.passBy !== PassBy.REFERENCE && opts.passBy !== PassBy.VALUE) { + throw new Error(SIOPErrors.REGISTRATION_OBJECT_TYPE_NOT_SET); + } else if (opts.passBy === PassBy.REFERENCE && !opts.reference_uri) { + throw new Error(SIOPErrors.NO_REFERENCE_URI); + } +}; + +const createRequestRegistrationPayload = async ( + opts: ClientMetadataOpts, + metadataPayload: RPRegistrationMetadataPayload, + version: SupportedVersion, +): Promise => { + assertValidRequestRegistrationOpts(opts); + + if (opts.passBy == PassBy.VALUE) { + if (version >= SupportedVersion.SIOPv2_D11.valueOf()) { + return { client_metadata: removeNullUndefined(metadataPayload) }; + } else { + return { registration: removeNullUndefined(metadataPayload) }; + } + } else { + if (version >= SupportedVersion.SIOPv2_D11.valueOf()) { + return { + client_metadata_uri: opts.reference_uri, + }; + } else { + return { + registration_uri: opts.reference_uri, + }; + } + } +}; + +export const createRequestRegistration = async ( + clientMetadataOpts: ClientMetadataOpts, + createRequestOpts: CreateAuthorizationRequestOpts, +): Promise<{ + payload: RequestRegistrationPayloadProperties | RequestClientMetadataPayloadProperties; + metadata: RPRegistrationMetadataPayload; + createRequestOpts: CreateAuthorizationRequestOpts; + clientMetadataOpts: ClientMetadataOpts; +}> => { + const metadata = createRPRegistrationMetadataPayload(clientMetadataOpts); + const payload = await createRequestRegistrationPayload(clientMetadataOpts, metadata, createRequestOpts.version); + return { + payload, + metadata, + createRequestOpts, + clientMetadataOpts, + }; +}; + +const createRPRegistrationMetadataPayload = (opts: RPRegistrationMetadataOpts): RPRegistrationMetadataPayload => { + const rpRegistrationMetadataPayload = { + id_token_signing_alg_values_supported: opts.idTokenSigningAlgValuesSupported, + request_object_signing_alg_values_supported: opts.requestObjectSigningAlgValuesSupported, + response_types_supported: opts.responseTypesSupported, + scopes_supported: opts.scopesSupported, + subject_types_supported: opts.subjectTypesSupported, + subject_syntax_types_supported: opts.subject_syntax_types_supported || ['did:web:', 'did:ion:'], + vp_formats: opts.vpFormatsSupported, + client_name: opts.clientName, + logo_uri: opts.logo_uri, + tos_uri: opts.tos_uri, + client_purpose: opts.clientPurpose, + client_id: opts.client_id, + }; + + const languageTagEnabledFieldsNamesMapping = new Map(); + languageTagEnabledFieldsNamesMapping.set('clientName', 'client_name'); + languageTagEnabledFieldsNamesMapping.set('clientPurpose', 'client_purpose'); + + const languageTaggedFields: Map = LanguageTagUtils.getLanguageTaggedPropertiesMapped(opts, languageTagEnabledFieldsNamesMapping); + + languageTaggedFields.forEach((value: string, key: string) => { + rpRegistrationMetadataPayload[key] = value; + }); + + return removeNullUndefined(rpRegistrationMetadataPayload); +}; diff --git a/packages/siopv2/src/authorization-request/URI.ts b/packages/siopv2/src/authorization-request/URI.ts new file mode 100644 index 00000000..4494efe4 --- /dev/null +++ b/packages/siopv2/src/authorization-request/URI.ts @@ -0,0 +1,274 @@ +import { decodeJWT } from 'did-jwt'; + +import { PresentationExchange } from '../authorization-response/PresentationExchange'; +import { decodeUriAsJson, encodeJsonAsURI, fetchByReferenceOrUseByValue } from '../helpers'; +import { assertValidRequestObjectPayload, RequestObject } from '../request-object'; +import { + AuthorizationRequestPayload, + AuthorizationRequestURI, + ObjectBy, + PassBy, + RequestObjectJwt, + RequestObjectPayload, + RPRegistrationMetadataPayload, + SIOPErrors, + SupportedVersion, + UrlEncodingFormat, +} from '../types'; + +import { AuthorizationRequest } from './AuthorizationRequest'; +import { assertValidRPRegistrationMedataPayload } from './Payload'; +import { CreateAuthorizationRequestOpts } from './types'; + +export class URI implements AuthorizationRequestURI { + private readonly _scheme: string; + private readonly _requestObjectJwt: RequestObjectJwt | undefined; + private readonly _authorizationRequestPayload: AuthorizationRequestPayload; + private readonly _encodedUri: string; // The encoded URI + private readonly _encodingFormat: UrlEncodingFormat; + // private _requestObjectBy: ObjectBy; + + private _registrationMetadataPayload: RPRegistrationMetadataPayload; + + private constructor({ scheme, encodedUri, encodingFormat, authorizationRequestPayload, requestObjectJwt }: Partial) { + this._scheme = scheme; + this._encodedUri = encodedUri; + this._encodingFormat = encodingFormat; + this._authorizationRequestPayload = authorizationRequestPayload; + this._requestObjectJwt = requestObjectJwt; + } + + public static async fromUri(uri: string): Promise { + if (!uri) { + throw Error(SIOPErrors.BAD_PARAMS); + } + const { scheme, requestObjectJwt, authorizationRequestPayload, registrationMetadata } = await URI.parseAndResolve(uri); + const requestObjectPayload = requestObjectJwt ? (decodeJWT(requestObjectJwt).payload as RequestObjectPayload) : undefined; + if (requestObjectPayload) { + assertValidRequestObjectPayload(requestObjectPayload); + } + + const result = new URI({ + scheme, + encodingFormat: UrlEncodingFormat.FORM_URL_ENCODED, + encodedUri: uri, + authorizationRequestPayload, + requestObjectJwt, + }); + result._registrationMetadataPayload = registrationMetadata; + return result; + } + + /** + * Create a signed URL encoded URI with a signed SIOP request token on RP side + * + * @param opts Request input data to build a SIOP Request Token + * @remarks This method is used to generate a SIOP request with info provided by the RP. + * First it generates the request payload and then it creates the signed JWT, which is returned as a URI + * + * Normally you will want to use this method to create the request. + */ + public static async fromOpts(opts: CreateAuthorizationRequestOpts): Promise { + if (!opts) { + throw Error(SIOPErrors.BAD_PARAMS); + } + const authorizationRequest = await AuthorizationRequest.fromOpts(opts); + return await URI.fromAuthorizationRequest(authorizationRequest); + } + + public async toAuthorizationRequest(): Promise { + return await AuthorizationRequest.fromUriOrJwt(this); + } + + get requestObjectBy(): ObjectBy { + if (!this.requestObjectJwt) { + return { passBy: PassBy.NONE }; + } + if (this.authorizationRequestPayload.request_uri) { + return { passBy: PassBy.REFERENCE, reference_uri: this.authorizationRequestPayload.request_uri }; + } + return { passBy: PassBy.VALUE }; + } + + get metadataObjectBy(): ObjectBy { + if (!this.authorizationRequestPayload.registration_uri && !this.authorizationRequestPayload.registration) { + return { passBy: PassBy.NONE }; + } + if (this.authorizationRequestPayload.registration_uri) { + return { passBy: PassBy.REFERENCE, reference_uri: this.authorizationRequestPayload.registration_uri }; + } + return { passBy: PassBy.VALUE }; + } + + /** + * Create a URI from the request object, typically you will want to use the createURI version! + * + * @remarks This method is used to generate a SIOP request Object with info provided by the RP. + * First it generates the request object payload, and then it creates the signed JWT. + * + * Please note that the createURI method allows you to differentiate between OAuth2 and OpenID parameters that become + * part of the URI and which become part of the Request Object. If you generate a URI based upon the result of this method, + * the URI will be constructed based on the Request Object only! + */ + static async fromRequestObject(requestObject: RequestObject): Promise { + if (!requestObject) { + throw Error(SIOPErrors.BAD_PARAMS); + } + return await URI.fromAuthorizationRequestPayload(requestObject.options, await AuthorizationRequest.fromUriOrJwt(await requestObject.toJwt())); + } + + static async fromAuthorizationRequest(authorizationRequest: AuthorizationRequest): Promise { + if (!authorizationRequest) { + throw Error(SIOPErrors.BAD_PARAMS); + } + return await URI.fromAuthorizationRequestPayload( + { + ...authorizationRequest.options.requestObject, + version: authorizationRequest.options.version, + uriScheme: authorizationRequest.options.uriScheme, + }, + authorizationRequest.payload, + authorizationRequest.requestObject, + ); + } + + /** + * Creates an URI Request + * @param opts Options to define the Uri Request + * @param authorizationRequestPayload + * + */ + private static async fromAuthorizationRequestPayload( + opts: { uriScheme?: string; passBy: PassBy; reference_uri?: string; version?: SupportedVersion }, + authorizationRequestPayload: AuthorizationRequestPayload, + requestObject?: RequestObject, + ): Promise { + if (!authorizationRequestPayload) { + if (!requestObject || !(await requestObject.getPayload())) { + throw Error(SIOPErrors.BAD_PARAMS); + } + authorizationRequestPayload = {}; // No auth request payload, so the eventual URI will contain a `request_uri` or `request` value only + } + + const isJwt = typeof authorizationRequestPayload === 'string'; + const requestObjectJwt = requestObject + ? await requestObject.toJwt() + : 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; + + if (requestObjectPayload) { + // Only used to validate if the request object contains presentation definition(s) + await PresentationExchange.findValidPresentationDefinitions({ ...authorizationRequestPayload, ...requestObjectPayload }); + + assertValidRequestObjectPayload(requestObjectPayload); + if (requestObjectPayload.registration) { + assertValidRPRegistrationMedataPayload(requestObjectPayload.registration); + } + } + const uniformAuthorizationRequestPayload: AuthorizationRequestPayload = + typeof authorizationRequestPayload === 'string' ? (requestObjectPayload as AuthorizationRequestPayload) : authorizationRequestPayload; + if (!uniformAuthorizationRequestPayload) { + throw Error(SIOPErrors.BAD_PARAMS); + } + const type = opts.passBy; + if (!type) { + throw new Error(SIOPErrors.REQUEST_OBJECT_TYPE_NOT_SET); + } + const authorizationRequest = await AuthorizationRequest.fromUriOrJwt(requestObjectJwt); + + let scheme; + if (opts.uriScheme) { + scheme = opts.uriScheme.endsWith('://') ? opts.uriScheme : `${opts.uriScheme}://`; + } else if (opts.version) { + if (opts.version === SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1) { + scheme = 'openid-vc://'; + } else { + scheme = 'openid://'; + } + } else { + try { + scheme = + (await authorizationRequest.getSupportedVersion()) === SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1 ? 'openid-vc://' : 'openid://'; + } catch (error: unknown) { + scheme = 'openid://'; + } + } + + if (type === PassBy.REFERENCE) { + if (!opts.reference_uri) { + throw new Error(SIOPErrors.NO_REFERENCE_URI); + } + uniformAuthorizationRequestPayload.request_uri = opts.reference_uri; + delete uniformAuthorizationRequestPayload.request; + } else if (type === PassBy.VALUE) { + uniformAuthorizationRequestPayload.request = requestObjectJwt; + delete uniformAuthorizationRequestPayload.request_uri; + } + return new URI({ + scheme, + encodedUri: `${scheme}?${encodeJsonAsURI(uniformAuthorizationRequestPayload)}`, + encodingFormat: UrlEncodingFormat.FORM_URL_ENCODED, + // requestObjectBy: opts.requestBy, + authorizationRequestPayload: uniformAuthorizationRequestPayload, + requestObjectJwt: requestObjectJwt, + }); + } + + /** + * Create a Authentication Request Payload from a URI string + * + * @param uri + */ + public static parse(uri: string): { scheme: string; authorizationRequestPayload: AuthorizationRequestPayload } { + if (!uri) { + throw Error(SIOPErrors.BAD_PARAMS); + } + // We strip the uri scheme before passing it to the decode function + const scheme: string = uri.match(/^([a-zA-Z][a-zA-Z0-9-_]*:\/\/)/g)[0]; + const authorizationRequestPayload = decodeUriAsJson(uri) as AuthorizationRequestPayload; + return { scheme, authorizationRequestPayload }; + } + + public static async parseAndResolve(uri: string) { + if (!uri) { + throw Error(SIOPErrors.BAD_PARAMS); + } + const { authorizationRequestPayload, scheme } = this.parse(uri); + const requestObjectJwt = await fetchByReferenceOrUseByValue(authorizationRequestPayload.request_uri, authorizationRequestPayload.request, true); + const registrationMetadata: RPRegistrationMetadataPayload = await fetchByReferenceOrUseByValue( + authorizationRequestPayload['client_metadata_uri'] ?? authorizationRequestPayload['registration_uri'], + authorizationRequestPayload['client_metadata'] ?? authorizationRequestPayload['registration'], + ); + assertValidRPRegistrationMedataPayload(registrationMetadata); + return { scheme, authorizationRequestPayload, requestObjectJwt, registrationMetadata }; + } + + get encodingFormat(): UrlEncodingFormat { + return this._encodingFormat; + } + + get encodedUri(): string { + return this._encodedUri; + } + + get authorizationRequestPayload(): AuthorizationRequestPayload { + return this._authorizationRequestPayload; + } + + get requestObjectJwt(): RequestObjectJwt | undefined { + return this._requestObjectJwt; + } + + get scheme(): string { + return this._scheme; + } + + get registrationMetadataPayload(): RPRegistrationMetadataPayload { + return this._registrationMetadataPayload; + } +} diff --git a/packages/siopv2/src/authorization-request/index.ts b/packages/siopv2/src/authorization-request/index.ts new file mode 100644 index 00000000..d317938f --- /dev/null +++ b/packages/siopv2/src/authorization-request/index.ts @@ -0,0 +1,4 @@ +export * from './AuthorizationRequest'; +export * from './types'; +export * from './Payload'; +export * from './URI'; diff --git a/packages/siopv2/src/authorization-request/types.ts b/packages/siopv2/src/authorization-request/types.ts new file mode 100644 index 00000000..e21f28e6 --- /dev/null +++ b/packages/siopv2/src/authorization-request/types.ts @@ -0,0 +1,102 @@ +import { ResponseType } from '@sphereon/oid4vc-common'; +import { Hasher } from '@sphereon/ssi-types'; + +import { PresentationDefinitionPayloadOpts } from '../authorization-response'; +import { RequestObjectOpts } from '../request-object'; +import { + ClientMetadataOpts, + ExternalVerification, + IdTokenClaimPayload, + InternalVerification, + ResponseMode, + Schema, + Scope, + SigningAlgo, + SubjectType, + SupportedVersion, +} from '../types'; + +export interface ClaimPayloadOptsVID1 extends ClaimPayloadCommonOpts { + id_token?: IdTokenClaimPayload; + vp_token?: PresentationDefinitionPayloadOpts; +} + +export interface ClaimPayloadCommonOpts { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [x: string]: any; +} + +export interface AuthorizationRequestPayloadOpts extends Partial> { + request_uri?: string; // The Request object payload if provided by reference + // Note we do not list the request property here, as the lib constructs the value, and we do not want people to pass that value in directly as it will lead to people not understanding why things fail +} +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 + 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 + claims?: CT; // from openid-connect-self-issued-v2-1_0-ID1 look at https://openid.net/specs/openid-connect-core-1_0.html#Claims + nonce?: string; // An optional nonce, will be generated if not provided + state?: string; // An optional state, will be generated if not provided + authorization_endpoint?: string; + response_mode?: ResponseMode; // How the URI should be returned. This is not being used by the library itself, allows an implementor to make a decision + response_types_supported?: ResponseType[] | ResponseType; + scopes_supported?: Scope[] | Scope; + subject_types_supported?: SubjectType[] | SubjectType; + request_object_signing_alg_values_supported?: SigningAlgo[] | SigningAlgo; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [x: string]: any; +} + +interface AuthorizationRequestCommonOpts { + // Yes, this includes common payload properties both at the payload level as well as in the requestObject.payload property. That is to support OAuth2 with or without a signed OpenID requestObject + + version: SupportedVersion; + 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 +} + +export type AuthorizationRequestOptsVID1 = AuthorizationRequestCommonOpts; + +export interface AuthorizationRequestOptsVD11 extends AuthorizationRequestCommonOpts { + idTokenType?: string; // OPTIONAL. Space-separated string that specifies the types of ID token the RP wants to obtain, with the values appearing in order of preference. The allowed individual values are subject_signed and attester_signed (see Section 8.2). The default value is attester_signed. +} + +export type CreateAuthorizationRequestOpts = AuthorizationRequestOptsVID1 | AuthorizationRequestOptsVD11; + +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 + 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; +} + +/** + * Determines where a property will end up. Methods that support this argument are optional. If you do not provide any value it will default to all targets. + */ +export enum PropertyTarget { + // The property will end up in the oAuth2 authorization request + AUTHORIZATION_REQUEST = 'authorization-request', + + // OpenID Request Object (the JWT) + REQUEST_OBJECT = 'request-object', +} + +export type PropertyTargets = PropertyTarget | PropertyTarget[]; + +export interface RequestPropertyWithTargets { + targets?: PropertyTargets; + propertyValue: T; +} diff --git a/packages/siopv2/src/authorization-response/AuthorizationResponse.ts b/packages/siopv2/src/authorization-response/AuthorizationResponse.ts new file mode 100644 index 00000000..fab6dcf5 --- /dev/null +++ b/packages/siopv2/src/authorization-response/AuthorizationResponse.ts @@ -0,0 +1,179 @@ +import { ResponseType } from '@sphereon/oid4vc-common'; + +import { AuthorizationRequest, VerifyAuthorizationRequestOpts } from '../authorization-request'; +import { assertValidVerifyAuthorizationRequestOpts } from '../authorization-request/Opts'; +import { IDToken } from '../id-token'; +import { AuthorizationResponsePayload, SIOPErrors, VerifiedAuthorizationRequest, VerifiedAuthorizationResponse } from '../types'; + +import { assertValidVerifiablePresentations, extractPresentationsFromAuthorizationResponse, verifyPresentations } from './OpenID4VP'; +import { assertValidResponseOpts } from './Opts'; +import { createResponsePayload } from './Payload'; +import { AuthorizationResponseOpts, PresentationDefinitionWithLocation, VerifyAuthorizationResponseOpts } from './types'; + +export class AuthorizationResponse { + private readonly _authorizationRequest?: AuthorizationRequest | undefined; + // private _requestObject?: RequestObject | undefined + private readonly _idToken?: IDToken; + private readonly _payload: AuthorizationResponsePayload; + + private readonly _options?: AuthorizationResponseOpts; + + constructor({ + authorizationResponsePayload, + idToken, + responseOpts, + authorizationRequest, + }: { + authorizationResponsePayload: AuthorizationResponsePayload; + idToken?: IDToken; + responseOpts?: AuthorizationResponseOpts; + authorizationRequest?: AuthorizationRequest; + }) { + this._authorizationRequest = authorizationRequest; + this._options = responseOpts; + this._idToken = idToken; + this._payload = authorizationResponsePayload; + } + + /** + * Creates a SIOP Response Object + * + * @param requestObject + * @param responseOpts + * @param verifyOpts + */ + static async fromRequestObject( + requestObject: string, + responseOpts: AuthorizationResponseOpts, + verifyOpts: VerifyAuthorizationRequestOpts, + ): Promise { + assertValidVerifyAuthorizationRequestOpts(verifyOpts); + assertValidResponseOpts(responseOpts); + if (!requestObject || !requestObject.startsWith('ey')) { + throw new Error(SIOPErrors.NO_JWT); + } + const authorizationRequest = await AuthorizationRequest.fromUriOrJwt(requestObject); + return AuthorizationResponse.fromAuthorizationRequest(authorizationRequest, responseOpts, verifyOpts); + } + + static async fromPayload( + authorizationResponsePayload: AuthorizationResponsePayload, + responseOpts?: AuthorizationResponseOpts, + ): Promise { + if (!authorizationResponsePayload) { + throw new Error(SIOPErrors.NO_RESPONSE); + } + if (responseOpts) { + assertValidResponseOpts(responseOpts); + } + const idToken = authorizationResponsePayload.id_token ? await IDToken.fromIDToken(authorizationResponsePayload.id_token) : undefined; + return new AuthorizationResponse({ authorizationResponsePayload, idToken, responseOpts }); + } + + static async fromAuthorizationRequest( + authorizationRequest: AuthorizationRequest, + responseOpts: AuthorizationResponseOpts, + verifyOpts: VerifyAuthorizationRequestOpts, + ): Promise { + assertValidResponseOpts(responseOpts); + if (!authorizationRequest) { + throw new Error(SIOPErrors.NO_REQUEST); + } + const verifiedRequest = await authorizationRequest.verify(verifyOpts); + return await AuthorizationResponse.fromVerifiedAuthorizationRequest(verifiedRequest, responseOpts, verifyOpts); + } + + static async fromVerifiedAuthorizationRequest( + verifiedAuthorizationRequest: VerifiedAuthorizationRequest, + responseOpts: AuthorizationResponseOpts, + verifyOpts: VerifyAuthorizationRequestOpts, + ): Promise { + assertValidResponseOpts(responseOpts); + if (!verifiedAuthorizationRequest) { + throw new Error(SIOPErrors.NO_REQUEST); + } + + const authorizationRequest = verifiedAuthorizationRequest.authorizationRequest; + + // const merged = verifiedAuthorizationRequest.authorizationRequest.requestObject, verifiedAuthorizationRequest.requestObject); + // const presentationDefinitions = await PresentationExchange.findValidPresentationDefinitions(merged, await authorizationRequest.getSupportedVersion()); + const presentationDefinitions = JSON.parse( + JSON.stringify(verifiedAuthorizationRequest.presentationDefinitions), + ) as PresentationDefinitionWithLocation[]; + const wantsIdToken = await authorizationRequest.containsResponseType(ResponseType.ID_TOKEN); + // const hasVpToken = await authorizationRequest.containsResponseType(ResponseType.VP_TOKEN); + + const idToken = wantsIdToken ? await IDToken.fromVerifiedAuthorizationRequest(verifiedAuthorizationRequest, responseOpts) : undefined; + const idTokenPayload = wantsIdToken ? await idToken.payload() : undefined; + const authorizationResponsePayload = await createResponsePayload(authorizationRequest, responseOpts, idTokenPayload); + const response = new AuthorizationResponse({ + authorizationResponsePayload, + idToken, + responseOpts, + authorizationRequest, + }); + + const wrappedPresentations = await extractPresentationsFromAuthorizationResponse(response, { hasher: verifyOpts.hasher }); + + await assertValidVerifiablePresentations({ + presentationDefinitions, + presentations: wrappedPresentations, + verificationCallback: verifyOpts.verification.presentationVerificationCallback, + opts: { ...responseOpts.presentationExchange, hasher: verifyOpts.hasher }, + }); + + return response; + } + + public async verify(verifyOpts: VerifyAuthorizationResponseOpts): Promise { + // Merge payloads checks for inconsistencies in properties which are present in both the auth request and request object + const merged = await this.mergedPayloads(true); + if (verifyOpts.state && merged.state !== verifyOpts.state) { + throw Error(SIOPErrors.BAD_STATE); + } + + const verifiedIdToken = await this.idToken?.verify(verifyOpts); + const oid4vp = await verifyPresentations(this, verifyOpts); + + return { + authorizationResponse: this, + verifyOpts, + correlationId: verifyOpts.correlationId, + ...(this.idToken && { idToken: verifiedIdToken }), + ...(oid4vp && { oid4vpSubmission: oid4vp }), + }; + } + + get authorizationRequest(): AuthorizationRequest | undefined { + return this._authorizationRequest; + } + + get payload(): AuthorizationResponsePayload { + return this._payload; + } + + get options(): AuthorizationResponseOpts | undefined { + return this._options; + } + + get idToken(): IDToken | undefined { + return this._idToken; + } + + public async getMergedProperty(key: string, consistencyCheck?: boolean): Promise { + const merged = await this.mergedPayloads(consistencyCheck); + return merged[key] as T; + } + + public async mergedPayloads(consistencyCheck?: boolean): Promise { + const idTokenPayload = await this.idToken?.payload(); + if (consistencyCheck !== false && idTokenPayload) { + Object.entries(idTokenPayload).forEach((entry) => { + if (typeof entry[0] === 'string' && this.payload[entry[0]] && this.payload[entry[0]] !== entry[1]) { + throw Error(`Mismatch in Authorization Request and Request object value for ${entry[0]}`); + } + }); + } + return { ...this.payload, ...idTokenPayload }; + } +} diff --git a/packages/siopv2/src/authorization-response/OpenID4VP.ts b/packages/siopv2/src/authorization-response/OpenID4VP.ts new file mode 100644 index 00000000..894e254e --- /dev/null +++ b/packages/siopv2/src/authorization-response/OpenID4VP.ts @@ -0,0 +1,239 @@ +import { ResponseType } from '@sphereon/oid4vc-common'; +import { IPresentationDefinition, PEX } from '@sphereon/pex'; +import { Format } from '@sphereon/pex-models'; +import { CredentialMapper, Hasher, PresentationSubmission, W3CVerifiablePresentation, WrappedVerifiablePresentation } from '@sphereon/ssi-types'; + +import { AuthorizationRequest } from '../authorization-request'; +import { verifyRevocation } from '../helpers'; +import { + AuthorizationResponsePayload, + IDTokenPayload, + RevocationVerification, + SIOPErrors, + SupportedVersion, + VerifiedOpenID4VPSubmission, +} from '../types'; + +import { AuthorizationResponse } from './AuthorizationResponse'; +import { PresentationExchange } from './PresentationExchange'; +import { + AuthorizationResponseOpts, + PresentationDefinitionWithLocation, + PresentationVerificationCallback, + VerifyAuthorizationResponseOpts, + VPTokenLocation, +} from './types'; + +export const verifyPresentations = async ( + authorizationResponse: AuthorizationResponse, + verifyOpts: VerifyAuthorizationResponseOpts, +): Promise => { + const presentations = await extractPresentationsFromAuthorizationResponse(authorizationResponse, { hasher: verifyOpts.hasher }); + const presentationDefinitions = verifyOpts.presentationDefinitions + ? Array.isArray(verifyOpts.presentationDefinitions) + ? verifyOpts.presentationDefinitions + : [verifyOpts.presentationDefinitions] + : []; + let idPayload: IDTokenPayload | undefined; + if (authorizationResponse.idToken) { + idPayload = await authorizationResponse.idToken.payload(); + } + // todo: Probably wise to check against request for the location of the submission_data + const presentationSubmission = authorizationResponse.payload.presentation_submission + ? authorizationResponse.payload.presentation_submission + : idPayload?._vp_token?.presentation_submission; + await assertValidVerifiablePresentations({ + presentationDefinitions, + presentations, + verificationCallback: verifyOpts.verification.presentationVerificationCallback, + opts: { + presentationSubmission, + restrictToFormats: verifyOpts.restrictToFormats, + restrictToDIDMethods: verifyOpts.restrictToDIDMethods, + hasher: verifyOpts.hasher, + }, + }); + + const revocationVerification = verifyOpts.verification?.revocationOpts + ? verifyOpts.verification.revocationOpts.revocationVerification + : RevocationVerification.IF_PRESENT; + if (revocationVerification !== RevocationVerification.NEVER) { + if (!verifyOpts.verification.revocationOpts?.revocationVerificationCallback) { + throw Error(`Please provide a revocation callback as revocation checking of credentials and presentations is not disabled`); + } + for (const vp of presentations) { + await verifyRevocation(vp, verifyOpts.verification.revocationOpts.revocationVerificationCallback, revocationVerification); + } + } + return { presentations, presentationDefinitions, submissionData: presentationSubmission }; +}; + +export const extractPresentationsFromAuthorizationResponse = async ( + response: AuthorizationResponse, + opts?: { hasher?: Hasher }, +): Promise => { + if (!response.payload.vp_token) { + return []; + } + const presentations = Array.isArray(response.payload.vp_token) ? response.payload.vp_token : [response.payload.vp_token]; + return presentations.map((presentation) => CredentialMapper.toWrappedVerifiablePresentation(presentation, { hasher: opts?.hasher })); +}; + +export const createPresentationSubmission = async ( + verifiablePresentations: W3CVerifiablePresentation[], + opts?: { presentationDefinitions: (PresentationDefinitionWithLocation | IPresentationDefinition)[] }, +): Promise => { + let submission_data: PresentationSubmission; + for (const verifiablePresentation of verifiablePresentations) { + const wrappedPresentation = CredentialMapper.toWrappedVerifiablePresentation(verifiablePresentation); + + let submission = + CredentialMapper.isWrappedW3CVerifiablePresentation(wrappedPresentation) && + (wrappedPresentation.presentation.presentation_submission || + wrappedPresentation.decoded.presentation_submission || + (typeof wrappedPresentation.original !== 'string' && wrappedPresentation.original.presentation_submission)); + if (!submission && opts?.presentationDefinitions) { + console.warn(`No submission_data in VPs and not provided. Will try to deduce, but it is better to create the submission data beforehand`); + for (const definitionOpt of opts.presentationDefinitions) { + const definition = 'definition' in definitionOpt ? definitionOpt.definition : definitionOpt; + const result = new PEX().evaluatePresentation(definition, wrappedPresentation.original, { generatePresentationSubmission: true }); + if (result.areRequiredCredentialsPresent) { + submission = result.value; + break; + } + } + } + if (!submission) { + throw Error('Verifiable Presentation has no submission_data, it has not been provided separately, and could also not be deduced'); + } + // let's merge all submission data into one object + if (!submission_data) { + submission_data = submission; + } else { + // We are pushing multiple descriptors into one submission_data, as it seems this is something which is assumed in OpenID4VP, but not supported in Presentation Exchange (a single VP always has a single submission_data) + Array.isArray(submission_data.descriptor_map) + ? submission_data.descriptor_map.push(...submission.descriptor_map) + : (submission_data.descriptor_map = [...submission.descriptor_map]); + } + } + return submission_data; +}; + +export const putPresentationSubmissionInLocation = async ( + authorizationRequest: AuthorizationRequest, + responsePayload: AuthorizationResponsePayload, + resOpts: AuthorizationResponseOpts, + idTokenPayload?: IDTokenPayload, +): Promise => { + const version = await authorizationRequest.getSupportedVersion(); + const idTokenType = await authorizationRequest.containsResponseType(ResponseType.ID_TOKEN); + const authResponseType = await authorizationRequest.containsResponseType(ResponseType.VP_TOKEN); + // const requestPayload = await authorizationRequest.mergedPayloads(); + if (!resOpts.presentationExchange) { + return; + } else if (resOpts.presentationExchange.verifiablePresentations.length === 0) { + throw Error('Presentation Exchange options set, but no verifiable presentations provided'); + } + if ( + !resOpts.presentationExchange.presentationSubmission && + (!resOpts.presentationExchange.verifiablePresentations || resOpts.presentationExchange.verifiablePresentations.length === 0) + ) { + throw Error(`Either a presentationSubmission or verifiable presentations are needed at this point`); + } + const submissionData = + resOpts.presentationExchange.presentationSubmission ?? + (await createPresentationSubmission(resOpts.presentationExchange.verifiablePresentations, { + presentationDefinitions: await authorizationRequest.getPresentationDefinitions(), + })); + + const location = resOpts.presentationExchange?.vpTokenLocation ?? (idTokenType ? VPTokenLocation.ID_TOKEN : VPTokenLocation.AUTHORIZATION_RESPONSE); + + switch (location) { + case VPTokenLocation.TOKEN_RESPONSE: { + throw Error('Token response for VP token is not supported yet'); + } + case VPTokenLocation.ID_TOKEN: { + if (!idTokenPayload) { + throw Error('Cannot place submission data _vp_token in id token if no id token is present'); + } else if (version >= SupportedVersion.SIOPv2_D11) { + throw Error(`This version of the OpenID4VP spec does not allow to store the vp submission data in the ID token`); + } else if (!idTokenType) { + throw Error(`Cannot place vp token in ID token as the RP didn't provide an "openid" scope in the request`); + } + if (idTokenPayload._vp_token?.presentation_submission) { + if (submissionData !== idTokenPayload._vp_token.presentation_submission) { + throw Error('Different submission data was provided as an option, but exising submission data was already present in the id token'); + } + } else { + if (!idTokenPayload._vp_token) { + idTokenPayload._vp_token = { presentation_submission: submissionData }; + } else { + idTokenPayload._vp_token.presentation_submission = submissionData; + } + } + break; + } + case VPTokenLocation.AUTHORIZATION_RESPONSE: { + if (!authResponseType) { + throw Error('Cannot place vp token in Authorization Response as there is no vp_token scope in the auth request'); + } + if (responsePayload.presentation_submission) { + if (submissionData !== responsePayload.presentation_submission) { + throw Error( + 'Different submission data was provided as an option, but exising submission data was already present in the authorization response', + ); + } + } else { + responsePayload.presentation_submission = submissionData; + } + } + } + + responsePayload.vp_token = + resOpts.presentationExchange?.verifiablePresentations.length === 1 + ? resOpts.presentationExchange.verifiablePresentations[0] + : resOpts.presentationExchange?.verifiablePresentations; +}; + +export const assertValidVerifiablePresentations = async (args: { + presentationDefinitions: PresentationDefinitionWithLocation[]; + presentations: WrappedVerifiablePresentation[]; + verificationCallback: PresentationVerificationCallback; + opts?: { + limitDisclosureSignatureSuites?: string[]; + restrictToFormats?: Format; + restrictToDIDMethods?: string[]; + presentationSubmission?: PresentationSubmission; + hasher?: Hasher; + }; +}) => { + if ( + (!args.presentationDefinitions || args.presentationDefinitions.filter((a) => a.definition).length === 0) && + (!args.presentations || (Array.isArray(args.presentations) && args.presentations.filter((vp) => vp.presentation).length === 0)) + ) { + return; + } + PresentationExchange.assertValidPresentationDefinitionWithLocations(args.presentationDefinitions); + const presentationsWithFormat = args.presentations; + + if (args.presentationDefinitions && args.presentationDefinitions.length && (!presentationsWithFormat || presentationsWithFormat.length === 0)) { + throw new Error(SIOPErrors.AUTH_REQUEST_EXPECTS_VP); + } else if ( + (!args.presentationDefinitions || args.presentationDefinitions.length === 0) && + presentationsWithFormat && + presentationsWithFormat.length > 0 + ) { + throw new Error(SIOPErrors.AUTH_REQUEST_DOESNT_EXPECT_VP); + } else if (args.presentationDefinitions && presentationsWithFormat && args.presentationDefinitions.length != presentationsWithFormat.length) { + throw new Error(SIOPErrors.AUTH_REQUEST_EXPECTS_VP); + } else if (args.presentationDefinitions && !args.opts.presentationSubmission) { + throw new Error(`No presentation submission present. Please use presentationSubmission opt argument!`); + } else if (args.presentationDefinitions && presentationsWithFormat) { + await PresentationExchange.validatePresentationsAgainstDefinitions( + args.presentationDefinitions, + presentationsWithFormat, + args.verificationCallback, + args.opts, + ); + } +}; diff --git a/packages/siopv2/src/authorization-response/Opts.ts b/packages/siopv2/src/authorization-response/Opts.ts new file mode 100644 index 00000000..65c5f4e6 --- /dev/null +++ b/packages/siopv2/src/authorization-response/Opts.ts @@ -0,0 +1,17 @@ +import { isExternalSignature, isExternalVerification, isInternalSignature, isInternalVerification, isSuppliedSignature, SIOPErrors } from '../types'; + +import { AuthorizationResponseOpts, VerifyAuthorizationResponseOpts } from './types'; + +export const assertValidResponseOpts = (opts: AuthorizationResponseOpts) => { + if (!opts /*|| !opts.redirectUri*/ || !opts.signature /*|| !opts.nonce*/ /* || !opts.did*/) { + 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 || !opts.verification || (!isExternalVerification(opts.verification) && !isInternalVerification(opts.verification))) { + throw new Error(SIOPErrors.VERIFY_BAD_PARAMS); + } +}; diff --git a/packages/siopv2/src/authorization-response/Payload.ts b/packages/siopv2/src/authorization-response/Payload.ts new file mode 100644 index 00000000..31ec61a8 --- /dev/null +++ b/packages/siopv2/src/authorization-response/Payload.ts @@ -0,0 +1,57 @@ +import { AuthorizationRequest } from '../authorization-request'; +import { IDToken } from '../id-token'; +import { RequestObject } from '../request-object'; +import { AuthorizationRequestPayload, AuthorizationResponsePayload, IDTokenPayload, SIOPErrors } from '../types'; + +import { putPresentationSubmissionInLocation } from './OpenID4VP'; +import { assertValidResponseOpts } from './Opts'; +import { AuthorizationResponseOpts } from './types'; + +export const createResponsePayload = async ( + authorizationRequest: AuthorizationRequest, + responseOpts: AuthorizationResponseOpts, + idTokenPayload?: IDTokenPayload, +): Promise => { + assertValidResponseOpts(responseOpts); + if (!authorizationRequest) { + throw new Error(SIOPErrors.NO_REQUEST); + } + + // If state was in request, it must be in response + const state: string | undefined = await authorizationRequest.getMergedProperty('state'); + + const responsePayload: AuthorizationResponsePayload = { + ...(responseOpts.accessToken && { access_token: responseOpts.accessToken }), + ...(responseOpts.tokenType && { token_type: responseOpts.tokenType }), + ...(responseOpts.refreshToken && { refresh_token: responseOpts.refreshToken }), + expires_in: responseOpts.expiresIn || 3600, + state, + }; + + // vp tokens + await putPresentationSubmissionInLocation(authorizationRequest, responsePayload, responseOpts, idTokenPayload); + if (idTokenPayload) { + responsePayload.id_token = await IDToken.fromIDTokenPayload(idTokenPayload, responseOpts).then((id) => id.jwt()); + } + + return responsePayload; +}; + +/** + * Properties can be in oAUth2 and OpenID (JWT) style. If they are in both the OpenID prop takes precedence as they are signed. + * @param payload + * @param requestObject + */ +export const mergeOAuth2AndOpenIdInRequestPayload = async ( + payload: AuthorizationRequestPayload, + requestObject?: RequestObject, +): Promise => { + const payloadCopy = JSON.parse(JSON.stringify(payload)); + + const requestObj = requestObject ? requestObject : await RequestObject.fromAuthorizationRequestPayload(payload); + if (!requestObj) { + return payloadCopy; + } + const requestObjectPayload = await requestObj.getPayload(); + return { ...payloadCopy, ...requestObjectPayload }; +}; diff --git a/packages/siopv2/src/authorization-response/PresentationExchange.ts b/packages/siopv2/src/authorization-response/PresentationExchange.ts new file mode 100644 index 00000000..67d113a0 --- /dev/null +++ b/packages/siopv2/src/authorization-response/PresentationExchange.ts @@ -0,0 +1,394 @@ +import { + EvaluationResults, + IPresentationDefinition, + KeyEncoding, + PEX, + PresentationSubmissionLocation, + SelectResults, + Status, + VerifiablePresentationFromOpts, + VerifiablePresentationResult, +} from '@sphereon/pex'; +import { Format, PresentationDefinitionV1, PresentationDefinitionV2, PresentationSubmission } from '@sphereon/pex-models'; +import { + CredentialMapper, + Hasher, + IProofPurpose, + IProofType, + OriginalVerifiableCredential, + OriginalVerifiablePresentation, + W3CVerifiablePresentation, + WrappedVerifiablePresentation, +} from '@sphereon/ssi-types'; + +import { extractDataFromPath, getWithUrl } from '../helpers'; +import { AuthorizationRequestPayload, SIOPErrors, SupportedVersion } from '../types'; + +import { + PresentationDefinitionLocation, + PresentationDefinitionWithLocation, + PresentationSignCallback, + PresentationVerificationCallback, +} from './types'; + +export class PresentationExchange { + readonly pex: PEX; + readonly allVerifiableCredentials: OriginalVerifiableCredential[]; + readonly allDIDs; + + constructor(opts: { allDIDs?: string[]; allVerifiableCredentials: OriginalVerifiableCredential[]; hasher?: Hasher }) { + this.allDIDs = opts.allDIDs; + this.allVerifiableCredentials = opts.allVerifiableCredentials; + this.pex = new PEX({ hasher: opts.hasher }); + } + + /** + * Construct presentation submission from selected credentials + * @param presentationDefinition payload object received by the OP from the RP + * @param selectedCredentials + * @param presentationSignCallback + * @param options + */ + public async createVerifiablePresentation( + presentationDefinition: IPresentationDefinition, + selectedCredentials: OriginalVerifiableCredential[], + presentationSignCallback: PresentationSignCallback, + // options2?: { nonce?: string; domain?: string, proofType?: IProofType, verificationMethod?: string, signatureKeyEncoding?: KeyEncoding }, + options?: VerifiablePresentationFromOpts, + ): Promise { + if (!presentationDefinition) { + throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID); + } + + const signOptions: VerifiablePresentationFromOpts = { + ...options, + presentationSubmissionLocation: PresentationSubmissionLocation.EXTERNAL, + proofOptions: { + ...options.proofOptions, + proofPurpose: options?.proofOptions?.proofPurpose ?? IProofPurpose.authentication, + type: options?.proofOptions?.type ?? IProofType.EcdsaSecp256k1Signature2019, + /* challenge: options?.proofOptions?.challenge, + domain: options?.proofOptions?.domain,*/ + }, + signatureOptions: { + ...options.signatureOptions, + // verificationMethod: options?.signatureOptions?.verificationMethod, + keyEncoding: options?.signatureOptions?.keyEncoding ?? KeyEncoding.Hex, + }, + }; + + return await this.pex.verifiablePresentationFrom(presentationDefinition, selectedCredentials, presentationSignCallback, signOptions); + } + + /** + * This method will be called from the OP when we are certain that we have a + * PresentationDefinition object inside our requestPayload + * Finds a set of `VerifiableCredential`s from a list supplied to this class during construction, + * matching presentationDefinition object found in the requestPayload + * if requestPayload doesn't contain any valid presentationDefinition throws an error + * if PEX library returns any error in the process, throws the error + * returns the SelectResults object if successful + * @param presentationDefinition object received by the OP from the RP + * @param opts + */ + public async selectVerifiableCredentialsForSubmission( + presentationDefinition: IPresentationDefinition, + opts?: { + holderDIDs?: string[]; + restrictToFormats?: Format; + restrictToDIDMethods?: string[]; + }, + ): Promise { + if (!presentationDefinition) { + throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID); + } else if (!this.allVerifiableCredentials || this.allVerifiableCredentials.length == 0) { + throw new Error(`${SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD}, no VCs were provided`); + } + const selectResults: SelectResults = this.pex.selectFrom(presentationDefinition, this.allVerifiableCredentials, { + ...opts, + holderDIDs: opts?.holderDIDs ?? this.allDIDs, + // fixme limited disclosure + limitDisclosureSignatureSuites: [], + }); + if (selectResults.areRequiredCredentialsPresent === Status.ERROR) { + throw new Error(`message: ${SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD}, details: ${JSON.stringify(selectResults.errors)}`); + } + return selectResults; + } + + /** + * validatePresentationAgainstDefinition function is called mainly by the RP + * after receiving the VP from the OP + * @param presentationDefinition object containing PD + * @param verifiablePresentation + * @param opts + */ + public static async validatePresentationAgainstDefinition( + presentationDefinition: IPresentationDefinition, + verifiablePresentation: OriginalVerifiablePresentation | WrappedVerifiablePresentation, + opts?: { + limitDisclosureSignatureSuites?: string[]; + restrictToFormats?: Format; + restrictToDIDMethods?: string[]; + presentationSubmission?: PresentationSubmission; + hasher?: Hasher; + }, + ): Promise { + const wvp: WrappedVerifiablePresentation = + typeof verifiablePresentation === 'object' && 'original' in verifiablePresentation + ? (verifiablePresentation as WrappedVerifiablePresentation) + : CredentialMapper.toWrappedVerifiablePresentation(verifiablePresentation as OriginalVerifiablePresentation); + if (!presentationDefinition) { + throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID); + } else if ( + !wvp || + !wvp.presentation || + (CredentialMapper.isWrappedW3CVerifiablePresentation(wvp) && + (!wvp.presentation.verifiableCredential || wvp.presentation.verifiableCredential.length === 0)) + ) { + throw new Error(SIOPErrors.NO_VERIFIABLE_PRESENTATION_NO_CREDENTIALS); + } + // console.log(`Presentation (validate): ${JSON.stringify(verifiablePresentation)}`); + const evaluationResults: EvaluationResults = new PEX({ hasher: opts?.hasher }).evaluatePresentation(presentationDefinition, wvp.original, opts); + if (evaluationResults.errors.length) { + throw new Error(`message: ${SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD}, details: ${JSON.stringify(evaluationResults.errors)}`); + } + return evaluationResults; + } + + public static assertValidPresentationSubmission(presentationSubmission: PresentationSubmission) { + const validationResult = PEX.validateSubmission(presentationSubmission); + if (validationResult[0].message != 'ok') { + throw new Error(`${SIOPErrors.RESPONSE_OPTS_PRESENTATIONS_SUBMISSION_IS_NOT_VALID}, details ${JSON.stringify(validationResult[0])}`); + } + } + + /** + * Finds a valid PresentationDefinition inside the given AuthenticationRequestPayload + * throws exception if the PresentationDefinition is not valid + * returns null if no property named "presentation_definition" is found + * returns a PresentationDefinition if a valid instance found + * @param authorizationRequestPayload object that can have a presentation_definition inside + * @param version + */ + public static async findValidPresentationDefinitions( + authorizationRequestPayload: AuthorizationRequestPayload, + version?: SupportedVersion, + ): Promise { + const allDefinitions: PresentationDefinitionWithLocation[] = []; + + async function extractDefinitionFromVPToken() { + const vpTokens: PresentationDefinitionV1[] | PresentationDefinitionV2[] = extractDataFromPath( + authorizationRequestPayload, + '$..vp_token.presentation_definition', + ).map((d) => d.value); + const vpTokenRefs = extractDataFromPath(authorizationRequestPayload, '$..vp_token.presentation_definition_uri'); + if (vpTokens && vpTokens.length && vpTokenRefs && vpTokenRefs.length) { + throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_BY_REF_AND_VALUE_NON_EXCLUSIVE); + } + if (vpTokens && vpTokens.length) { + vpTokens.forEach((vpToken: PresentationDefinitionV1 | PresentationDefinitionV2) => { + if (allDefinitions.find((value) => value.definition.id === vpToken.id)) { + console.log( + `Warning. We encountered presentation definition with id ${vpToken.id}, more then once whilst processing! Make sure your payload is valid!`, + ); + return; + } + PresentationExchange.assertValidPresentationDefinition(vpToken); + allDefinitions.push({ + definition: vpToken, + location: PresentationDefinitionLocation.CLAIMS_VP_TOKEN, + version, + }); + }); + } else if (vpTokenRefs && vpTokenRefs.length) { + for (const vpTokenRef of vpTokenRefs) { + const pd: PresentationDefinitionV1 | PresentationDefinitionV2 = (await getWithUrl(vpTokenRef.value)) as unknown as + | PresentationDefinitionV1 + | PresentationDefinitionV2; + if (allDefinitions.find((value) => value.definition.id === pd.id)) { + console.log( + `Warning. We encountered presentation definition with id ${pd.id}, more then once whilst processing! Make sure your payload is valid!`, + ); + return; + } + PresentationExchange.assertValidPresentationDefinition(pd); + allDefinitions.push({ definition: pd, location: PresentationDefinitionLocation.CLAIMS_VP_TOKEN, version }); + } + } + } + + function addSingleToplevelPDToPDs(definition: IPresentationDefinition, version?: SupportedVersion): void { + if (allDefinitions.find((value) => value.definition.id === definition.id)) { + console.log( + `Warning. We encountered presentation definition with id ${definition.id}, more then once whilst processing! Make sure your payload is valid!`, + ); + return; + } + PresentationExchange.assertValidPresentationDefinition(definition); + allDefinitions.push({ + definition, + location: PresentationDefinitionLocation.TOPLEVEL_PRESENTATION_DEF, + version, + }); + } + + async function extractDefinitionFromTopLevelDefinitionProperty(version?: SupportedVersion) { + const definitions = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition'); + const definitionsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition[*]'); + const definitionRefs = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri'); + const definitionRefsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri[*]'); + const hasPD = (definitions && definitions.length > 0) || (definitionsFromList && definitionsFromList.length > 0); + const hasPdRef = (definitionRefs && definitionRefs.length > 0) || (definitionRefsFromList && definitionRefsFromList.length > 0); + if (hasPD && hasPdRef) { + throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_BY_REF_AND_VALUE_NON_EXCLUSIVE); + } + if (definitions && definitions.length > 0) { + definitions.forEach((definition) => { + addSingleToplevelPDToPDs(definition.value, version); + }); + } else if (definitionsFromList && definitionsFromList.length > 0) { + definitionsFromList.forEach((definition) => { + addSingleToplevelPDToPDs(definition.value, version); + }); + } else if (definitionRefs && definitionRefs.length > 0) { + for (const definitionRef of definitionRefs) { + const pd: PresentationDefinitionV1 | PresentationDefinitionV2 = await getWithUrl(definitionRef.value); + addSingleToplevelPDToPDs(pd, version); + } + } else if (definitionsFromList && definitionRefsFromList.length > 0) { + for (const definitionRef of definitionRefsFromList) { + const pd: PresentationDefinitionV1 | PresentationDefinitionV2 = await getWithUrl(definitionRef.value); + addSingleToplevelPDToPDs(pd, version); + } + } + } + + if (authorizationRequestPayload) { + if (!version || version < SupportedVersion.SIOPv2_D11) { + await extractDefinitionFromVPToken(); + } + await extractDefinitionFromTopLevelDefinitionProperty(); + } + return allDefinitions; + } + + public static assertValidPresentationDefinitionWithLocations(definitionsWithLocations: PresentationDefinitionWithLocation[]) { + if (definitionsWithLocations && definitionsWithLocations.length > 0) { + definitionsWithLocations.forEach((definitionWithLocation) => + PresentationExchange.assertValidPresentationDefinition(definitionWithLocation.definition), + ); + } + } + + private static assertValidPresentationDefinition(presentationDefinition: IPresentationDefinition) { + const validationResult = PEX.validateDefinition(presentationDefinition); + if (validationResult[0].message != 'ok') { + throw new Error(`${SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID}`); + } + } + + static async validatePresentationsAgainstDefinitions( + definitions: PresentationDefinitionWithLocation[], + vpPayloads: WrappedVerifiablePresentation[], + verifyPresentationCallback: PresentationVerificationCallback | undefined, + opts?: { + limitDisclosureSignatureSuites?: string[]; + restrictToFormats?: Format; + restrictToDIDMethods?: string[]; + presentationSubmission?: PresentationSubmission; + hasher?: Hasher; + }, + ) { + if (!definitions || !vpPayloads || !definitions.length || definitions.length !== vpPayloads.length) { + throw new Error(SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD); + } + await Promise.all( + definitions.map( + async (pd) => await PresentationExchange.validatePresentationsAgainstDefinition(pd.definition, vpPayloads, verifyPresentationCallback, opts), + ), + ); + } + + private static async validatePresentationsAgainstDefinition( + definition: IPresentationDefinition, + vpPayloads: WrappedVerifiablePresentation[], + verifyPresentationCallback: PresentationVerificationCallback | undefined, + opts?: { + limitDisclosureSignatureSuites?: string[]; + restrictToFormats?: Format; + restrictToDIDMethods?: string[]; + presentationSubmission?: PresentationSubmission; + hasher?: Hasher; + }, + ) { + const pex = new PEX({ hasher: opts?.hasher }); + + function filterOutCorrectPresentation() { + //TODO: add support for multiple VPs here + return vpPayloads.filter(async (vpw: WrappedVerifiablePresentation) => { + const presentationSubmission = + opts?.presentationSubmission ?? + (CredentialMapper.isWrappedW3CVerifiablePresentation(vpw) ? vpw.presentation.presentation_submission : undefined); + const presentation = vpw.presentation; + if (!definition) { + throw new Error(SIOPErrors.NO_PRESENTATION_SUBMISSION); + } else if ( + !vpw.presentation || + (CredentialMapper.isWrappedW3CVerifiablePresentation(vpw) && + (!vpw.presentation.verifiableCredential || vpw.presentation.verifiableCredential.length === 0)) + ) { + throw new Error(SIOPErrors.NO_VERIFIABLE_PRESENTATION_NO_CREDENTIALS); + } + // The verifyPresentationCallback function is mandatory for RP only, + // So the behavior here is to bypass it if not present + if (verifyPresentationCallback) { + try { + await verifyPresentationCallback(vpw.original as W3CVerifiablePresentation, presentationSubmission); + } catch (error: unknown) { + throw new Error(SIOPErrors.VERIFIABLE_PRESENTATION_SIGNATURE_NOT_VALID); + } + } + // console.log(`Presentation (filter): ${JSON.stringify(presentation)}`); + + const evaluationResults = pex.evaluatePresentation(definition, vpw.original, { + ...opts, + presentationSubmission, + }); + const submission = evaluationResults.value; + if (!presentation || !submission) { + throw new Error(SIOPErrors.NO_PRESENTATION_SUBMISSION); + } + return submission && submission.definition_id === definition.id; + }); + } + + const checkedPresentations: WrappedVerifiablePresentation[] = filterOutCorrectPresentation(); + + if (checkedPresentations.length !== 1) { + throw new Error(`${SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD}`); + } + const checkedPresentation = checkedPresentations[0]; + const presentation = checkedPresentation.presentation; + // console.log(`Presentation (checked): ${JSON.stringify(checkedPresentation.presentation)}`); + if ( + !checkedPresentation.presentation || + (CredentialMapper.isWrappedW3CVerifiablePresentation(checkedPresentation) && + (!checkedPresentation.presentation.verifiableCredential || checkedPresentation.presentation.verifiableCredential.length === 0)) + ) { + throw new Error(SIOPErrors.NO_VERIFIABLE_PRESENTATION_NO_CREDENTIALS); + } + const presentationSubmission = + opts?.presentationSubmission ?? (CredentialMapper.isW3cPresentation(presentation) ? presentation.presentation_submission : undefined); + const evaluationResults = pex.evaluatePresentation(definition, checkedPresentation.original, { + ...opts, + presentationSubmission, + }); + PresentationExchange.assertValidPresentationSubmission(evaluationResults.value); + await PresentationExchange.validatePresentationAgainstDefinition(definition, checkedPresentation, { + ...opts, + presentationSubmission, + hasher: opts?.hasher, + }); + } +} diff --git a/packages/siopv2/src/authorization-response/ResponseRegistration.ts b/packages/siopv2/src/authorization-response/ResponseRegistration.ts new file mode 100644 index 00000000..c7f7990a --- /dev/null +++ b/packages/siopv2/src/authorization-response/ResponseRegistration.ts @@ -0,0 +1,67 @@ +import { ResponseType } from '@sphereon/oid4vc-common'; + +import { LanguageTagUtils, removeNullUndefined } from '../helpers'; +import { DiscoveryMetadataOpts, DiscoveryMetadataPayload, ResponseIss, Schema, Scope, SigningAlgo, SubjectType } from '../types'; + +export const createDiscoveryMetadataPayload = (opts: DiscoveryMetadataOpts): DiscoveryMetadataPayload => { + const discoveryMetadataPayload: DiscoveryMetadataPayload = { + authorization_endpoint: opts.authorizationEndpoint || Schema.OPENID, + issuer: opts.issuer ?? ResponseIss.SELF_ISSUED_V2, + response_types_supported: opts.responseTypesSupported ?? ResponseType.ID_TOKEN, + scopes_supported: opts?.scopesSupported || [Scope.OPENID], + subject_types_supported: opts?.subjectTypesSupported || [SubjectType.PAIRWISE], + id_token_signing_alg_values_supported: opts?.idTokenSigningAlgValuesSupported || [SigningAlgo.ES256K, SigningAlgo.EDDSA], + request_object_signing_alg_values_supported: opts.requestObjectSigningAlgValuesSupported || [SigningAlgo.ES256K, SigningAlgo.EDDSA], + subject_syntax_types_supported: opts.subject_syntax_types_supported, + client_id: opts.client_id, + redirect_uris: opts.redirectUris, + client_name: opts.clientName, + token_endpoint_auth_method: opts.tokenEndpointAuthMethod, + application_type: opts.applicationType, + response_types: opts.responseTypes, + grant_types: opts.grantTypes, + vp_formats: opts.vpFormats, + token_endpoint: opts.tokenEndpoint, + userinfo_endpoint: opts.userinfoEndpoint, + jwks_uri: opts.jwksUri, + registration_endpoint: opts.registrationEndpoint, + response_modes_supported: opts.responseModesSupported, + grant_types_supported: opts.grantTypesSupported, + acr_values_supported: opts.acrValuesSupported, + id_token_encryption_alg_values_supported: opts.idTokenEncryptionAlgValuesSupported, + id_token_encryption_enc_values_supported: opts.idTokenEncryptionEncValuesSupported, + userinfo_signing_alg_values_supported: opts.userinfoSigningAlgValuesSupported, + userinfo_encryption_alg_values_supported: opts.userinfoEncryptionAlgValuesSupported, + userinfo_encryption_enc_values_supported: opts.userinfoEncryptionEncValuesSupported, + request_object_encryption_alg_values_supported: opts.requestObjectEncryptionAlgValuesSupported, + request_object_encryption_enc_values_supported: opts.requestObjectEncryptionEncValuesSupported, + token_endpoint_auth_methods_supported: opts.tokenEndpointAuthMethodsSupported, + token_endpoint_auth_signing_alg_values_supported: opts.tokenEndpointAuthSigningAlgValuesSupported, + display_values_supported: opts.displayValuesSupported, + claim_types_supported: opts.claimTypesSupported, + claims_supported: opts.claimsSupported, + service_documentation: opts.serviceDocumentation, + claims_locales_supported: opts.claimsLocalesSupported, + ui_locales_supported: opts.uiLocalesSupported, + claims_parameter_supported: opts.claimsParameterSupported, + request_parameter_supported: opts.requestParameterSupported, + request_uri_parameter_supported: opts.requestUriParameterSupported, + require_request_uri_registration: opts.requireRequestUriRegistration, + op_policy_uri: opts.opPolicyUri, + op_tos_uri: opts.opTosUri, + logo_uri: opts.logo_uri, + client_purpose: opts.clientPurpose, + id_token_types_supported: opts.idTokenTypesSupported, + }; + + const languageTagEnabledFieldsNamesMapping = new Map(); + languageTagEnabledFieldsNamesMapping.set('clientName', 'client_name'); + languageTagEnabledFieldsNamesMapping.set('clientPurpose', 'client_purpose'); + + const languageTaggedFields: Map = LanguageTagUtils.getLanguageTaggedPropertiesMapped(opts, languageTagEnabledFieldsNamesMapping); + languageTaggedFields.forEach((value: string, key: string) => { + discoveryMetadataPayload[key] = value; + }); + + return removeNullUndefined(discoveryMetadataPayload); +}; diff --git a/packages/siopv2/src/authorization-response/index.ts b/packages/siopv2/src/authorization-response/index.ts new file mode 100644 index 00000000..7425f3ea --- /dev/null +++ b/packages/siopv2/src/authorization-response/index.ts @@ -0,0 +1,4 @@ +export * from './AuthorizationResponse'; +export * from './types'; +export * from './Payload'; +export * from './ResponseRegistration'; diff --git a/packages/siopv2/src/authorization-response/types.ts b/packages/siopv2/src/authorization-response/types.ts new file mode 100644 index 00000000..750edf5f --- /dev/null +++ b/packages/siopv2/src/authorization-response/types.ts @@ -0,0 +1,118 @@ +import { IPresentationDefinition, PresentationSignCallBackParams } from '@sphereon/pex'; +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 { AuthorizationResponse } from './AuthorizationResponse'; + +export interface AuthorizationResponseOpts { + // redirectUri?: string; // It's typically comes from the request opts as a measure to prevent hijacking. + 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; + responseMode?: ResponseMode; + // did: string; + expiresIn?: number; + accessToken?: string; + tokenType?: string; + refreshToken?: string; + presentationExchange?: PresentationExchangeResponseOpts; +} + +export interface PresentationExchangeResponseOpts { + /* presentationSignCallback?: PresentationSignCallback; + signOptions?: PresentationSignOptions, +*/ + /* credentialsAndDefinitions: { + presentationDefinition: IPresentationDefinition, + selectedCredentials: W3CVerifiableCredential[] + }[],*/ + + verifiablePresentations: Array; + vpTokenLocation?: VPTokenLocation; + presentationSubmission?: PresentationSubmission; + restrictToFormats?: Format; + restrictToDIDMethods?: string[]; +} + +export interface PresentationExchangeRequestOpts { + presentationVerificationCallback?: PresentationVerificationCallback; +} + +export interface PresentationDefinitionPayloadOpts { + presentation_definition?: IPresentationDefinition; + presentation_definition_uri?: string; +} + +export interface PresentationDefinitionWithLocation { + version?: SupportedVersion; + location: PresentationDefinitionLocation; + definition: IPresentationDefinition; +} + +export interface VerifiablePresentationWithSubmissionData extends VerifiablePresentationWithFormat { + vpTokenLocation: VPTokenLocation; + + submissionData: PresentationSubmission; +} + +export enum PresentationDefinitionLocation { + CLAIMS_VP_TOKEN = 'claims.vp_token', + TOPLEVEL_PRESENTATION_DEF = 'presentation_definition', +} + +export enum VPTokenLocation { + AUTHORIZATION_RESPONSE = 'authorization_response', + ID_TOKEN = 'id_token', + TOKEN_RESPONSE = 'token_response', +} + +export type PresentationVerificationResult = { verified: boolean }; + +export type PresentationVerificationCallback = (args: W3CVerifiablePresentation, presentationSubmissionn) => Promise; + +export type PresentationSignCallback = (args: PresentationSignCallBackParams) => Promise; + +export interface VerifyAuthorizationResponseOpts { + correlationId: string; + verification: InternalVerification | ExternalVerification; + 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 + restrictToDIDMethods?: string[]; + // claims?: ClaimPayloadCommonOpts; // The claims, typically the same values used during request creation + // verifyCallback?: VerifyCallback; + // presentationVerificationCallback?: PresentationVerificationCallback; +} + +export interface AuthorizationResponseWithCorrelationId { + // The URI to send the response to. Can be derived from either the redirect_uri or the response_uri + responseURI: string; + response: AuthorizationResponse; + correlationId: string; +} diff --git a/packages/siopv2/src/did/DIDResolution.ts b/packages/siopv2/src/did/DIDResolution.ts new file mode 100644 index 00000000..b3b5f445 --- /dev/null +++ b/packages/siopv2/src/did/DIDResolution.ts @@ -0,0 +1,116 @@ +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/packages/siopv2/src/did/DidJWT.ts b/packages/siopv2/src/did/DidJWT.ts new file mode 100644 index 00000000..76e203bd --- /dev/null +++ b/packages/siopv2/src/did/DidJWT.ts @@ -0,0 +1,315 @@ +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/packages/siopv2/src/did/LinkedDomainValidations.ts b/packages/siopv2/src/did/LinkedDomainValidations.ts new file mode 100644 index 00000000..ab8af62c --- /dev/null +++ b/packages/siopv2/src/did/LinkedDomainValidations.ts @@ -0,0 +1,97 @@ +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/packages/siopv2/src/did/index.ts b/packages/siopv2/src/did/index.ts new file mode 100644 index 00000000..8c3866bd --- /dev/null +++ b/packages/siopv2/src/did/index.ts @@ -0,0 +1,3 @@ +export * from './DidJWT'; +export * from './DIDResolution'; +export * from './LinkedDomainValidations'; diff --git a/packages/siopv2/src/helpers/Encodings.ts b/packages/siopv2/src/helpers/Encodings.ts new file mode 100644 index 00000000..af335a7a --- /dev/null +++ b/packages/siopv2/src/helpers/Encodings.ts @@ -0,0 +1,103 @@ +import { InputDescriptorV1 } from '@sphereon/pex-models'; +import { parse, stringify } from 'qs'; +import * as ua8 from 'uint8arrays'; + +import { SIOPErrors } from '../types'; + +export function decodeUriAsJson(uri: string) { + if (!uri) { + throw new Error(SIOPErrors.BAD_PARAMS); + } + const queryString = uri.replace(/^([a-zA-Z][a-zA-Z0-9-_]*:\/\/.*[?])/, ''); + if (!queryString) { + throw new Error(SIOPErrors.BAD_PARAMS); + } + const parts = parse(queryString, { plainObjects: true, depth: 10, parameterLimit: 5000, ignoreQueryPrefix: true }); + + const descriptors = parts?.claims?.['vp_token']?.presentation_definition?.['input_descriptors']; + if (descriptors && Array.isArray(descriptors)) { + // Whenever we have a [{'uri': 'str1'}, 'uri': 'str2'] qs changes this to {uri: ['str1','str2']} which means schema validation fails. So we have to fix that + parts.claims['vp_token'].presentation_definition['input_descriptors'] = descriptors.map((descriptor: InputDescriptorV1) => { + if (Array.isArray(descriptor.schema)) { + descriptor.schema = descriptor.schema.flatMap((val) => { + if (typeof val === 'string') { + return { uri: val }; + } else if (typeof val === 'object' && Array.isArray(val.uri)) { + return val.uri.map((uri) => ({ uri: uri as string })); + } + return val; + }); + } + return descriptor; + }); + } + + const json = {}; + for (const key in parts) { + const value = parts[key]; + if (!value) { + continue; + } + const isBool = typeof value == 'boolean'; + const isNumber = typeof value == 'number'; + const isString = typeof value == 'string'; + + if (isBool || isNumber) { + json[decodeURIComponent(key)] = value; + } else if (isString) { + const decoded = decodeURIComponent(value); + if (decoded.startsWith('{') && decoded.endsWith('}')) { + json[decodeURIComponent(key)] = JSON.parse(decoded); + } else { + json[decodeURIComponent(key)] = decoded; + } + } + } + return JSON.parse(JSON.stringify(json)); +} + +export function encodeJsonAsURI(json: unknown, _opts?: { arraysWithIndex?: string[] }): string { + if (typeof json === 'string') { + return encodeJsonAsURI(JSON.parse(json)); + } + + const results: string[] = []; + + function encodeAndStripWhitespace(key: string): string { + return encodeURIComponent(key.replace(' ', '')); + } + + for (const [key, value] of Object.entries(json)) { + if (!value) { + continue; + } + const isBool = typeof value == 'boolean'; + const isNumber = typeof value == 'number'; + const isString = typeof value == 'string'; + const isArray = Array.isArray(value); + let encoded: string; + if (isBool || isNumber) { + encoded = `${encodeAndStripWhitespace(key)}=${value}`; + } else if (isString) { + encoded = `${encodeAndStripWhitespace(key)}=${encodeURIComponent(value)}`; + } else if (isArray && _opts?.arraysWithIndex?.includes(key)) { + encoded = `${encodeAndStripWhitespace(key)}=${stringify(value, { arrayFormat: 'brackets' })}`; + } else { + encoded = `${encodeAndStripWhitespace(key)}=${encodeURIComponent(JSON.stringify(value))}`; + } + results.push(encoded); + } + return results.join('&'); +} + +export function base64ToHexString(input: string, encoding?: 'base64url' | 'base64'): string { + return ua8.toString(ua8.fromString(input, encoding ?? 'base64url'), 'base16'); +} + +export function fromBase64(base64: string): string { + return base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); +} + +export function base64urlEncodeBuffer(buf: { toString: (arg0: 'base64') => string }): string { + return fromBase64(buf.toString('base64')); +} diff --git a/packages/siopv2/src/helpers/HttpUtils.ts b/packages/siopv2/src/helpers/HttpUtils.ts new file mode 100644 index 00000000..a44a17b5 --- /dev/null +++ b/packages/siopv2/src/helpers/HttpUtils.ts @@ -0,0 +1,133 @@ +import { fetch } from 'cross-fetch'; +import Debug from 'debug'; + +import { ContentType, SIOPErrors, SIOPResonse } from '../types'; + +const debug = Debug('sphereon:siopv2:http'); + +export const getJson = async ( + URL: string, + opts?: { + bearerToken?: string; + contentType?: string | ContentType; + accept?: string; + customHeaders?: HeadersInit; + exceptionOnHttpErrorStatus?: boolean; + }, +): Promise> => { + return await siopFetch(URL, undefined, { method: 'GET', ...opts }); +}; + +export const formPost = async ( + url: string, + body: BodyInit, + opts?: { + bearerToken?: string; + contentType?: string | ContentType; + accept?: string; + customHeaders?: HeadersInit; + exceptionOnHttpErrorStatus?: boolean; + }, +): Promise> => { + return await post(url, body, opts?.contentType ? { ...opts } : { contentType: ContentType.FORM_URL_ENCODED, ...opts }); +}; + +export const post = async ( + url: string, + body?: BodyInit, + opts?: { + bearerToken?: string; + contentType?: string | ContentType; + accept?: string; + customHeaders?: HeadersInit; + exceptionOnHttpErrorStatus?: boolean; + }, +): Promise> => { + return await siopFetch(url, body, { method: 'POST', ...opts }); +}; + +const siopFetch = async ( + url: string, + body?: BodyInit, + opts?: { + method?: string; + bearerToken?: string; + contentType?: string | ContentType; + accept?: string; + customHeaders?: HeadersInit; + exceptionOnHttpErrorStatus?: boolean; + }, +): Promise> => { + if (!url || url.toLowerCase().startsWith('did:')) { + throw Error(`Invalid URL supplied. Expected a http(s) URL. Recieved: ${url}`); + } + const headers = opts?.customHeaders ? opts.customHeaders : {}; + if (opts?.bearerToken) { + headers['Authorization'] = `Bearer ${opts.bearerToken}`; + } + const method = opts?.method ? opts.method : body ? 'POST' : 'GET'; + const accept = opts?.accept ? opts.accept : 'application/json'; + headers['Content-Type'] = opts?.contentType ? opts.contentType : method !== 'GET' ? 'application/json' : undefined; + headers['Accept'] = accept; + + const payload: RequestInit = { + method, + headers, + body, + }; + + debug(`START fetching url: ${url}`); + if (body) { + debug(`Body:\r\n${JSON.stringify(body)}`); + } + debug(`Headers:\r\n${JSON.stringify(payload.headers)}`); + const origResponse = await fetch(url, payload); + const clonedResponse = origResponse.clone(); + const success = origResponse && origResponse.status >= 200 && origResponse.status < 400; + const textResponseBody = await clonedResponse.text(); + + const isJSONResponse = + (accept === 'application/json' || origResponse.headers['Content-Type'] === 'application/json') && textResponseBody.trim().startsWith('{'); + const responseBody = isJSONResponse ? JSON.parse(textResponseBody) : textResponseBody; + + debug(`${success ? 'success' : 'error'} status: ${clonedResponse.status}, body:\r\n${JSON.stringify(responseBody)}`); + if (!success && opts?.exceptionOnHttpErrorStatus) { + const error = JSON.stringify(responseBody); + throw new Error(error === '{}' ? '{"error": "not found"}' : error); + } + debug(`END fetching url: ${url}`); + + return { + origResponse, + successBody: success ? responseBody : undefined, + errorBody: !success ? responseBody : undefined, + }; +}; + +export const getWithUrl = async (url: string, textResponse?: boolean): Promise => { + // try { + const response = await fetch(url); + if (response.status >= 400) { + return Promise.reject(Error(`${SIOPErrors.RESPONSE_STATUS_UNEXPECTED} ${response.status}:${response.statusText} URL: ${url}`)); + } + if (textResponse === true) { + return (await response.text()) as unknown as T; + } + return await response.json(); + /*} catch (e) { + return Promise.reject(Error(`${(e as Error).message}`)); + }*/ +}; + +export const fetchByReferenceOrUseByValue = async (referenceURI: string, valueObject: T, textResponse?: boolean): Promise => { + let response: T = valueObject; + if (referenceURI) { + try { + response = await getWithUrl(referenceURI, textResponse); + } catch (e) { + console.log(e); + throw new Error(`${SIOPErrors.REG_PASS_BY_REFERENCE_INCORRECTLY}: ${e.message}, URL: ${referenceURI}`); + } + } + return response; +}; diff --git a/packages/siopv2/src/helpers/Keys.ts b/packages/siopv2/src/helpers/Keys.ts new file mode 100644 index 00000000..896325a9 --- /dev/null +++ b/packages/siopv2/src/helpers/Keys.ts @@ -0,0 +1,137 @@ +// import { keyUtils as ed25519KeyUtils } from '@transmute/did-key-ed25519'; +// import { ec as EC } from 'elliptic'; +import * as u8a from 'uint8arrays'; + +import { JWK } from '../types'; + +const ED25519_DID_KEY = 'did:key:z6Mk'; + +export const isEd25519DidKeyMethod = (did?: string) => { + return did && did.includes(ED25519_DID_KEY); +}; + +/* +export const isEd25519JWK = (jwk: JWK): boolean => { + return jwk && !!jwk.crv && jwk.crv === KeyCurve.ED25519; +}; + +export const getBase58PrivateKeyFromHexPrivateKey = (hexPrivateKey: string): string => { + return bs58.encode(Buffer.from(hexPrivateKey, 'hex')); +}; + +export const getPublicED25519JWKFromHexPrivateKey = (hexPrivateKey: string, kid?: string): JWK => { + const ec = new EC('ed25519'); + const privKey = ec.keyFromPrivate(hexPrivateKey); + const pubPoint = privKey.getPublic(); + + return toJWK(kid, KeyCurve.ED25519, pubPoint); +}; + +const getPublicSECP256k1JWKFromHexPrivateKey = (hexPrivateKey: string, kid: string) => { + const ec = new EC('secp256k1'); + const privKey = ec.keyFromPrivate(hexPrivateKey.replace('0x', ''), 'hex'); + const pubPoint = privKey.getPublic(); + return toJWK(kid, KeyCurve.SECP256k1, pubPoint); +}; + +export const getPublicJWKFromHexPrivateKey = (hexPrivateKey: string, kid?: string, did?: string): JWK => { + if (isEd25519DidKeyMethod(did)) { + return getPublicED25519JWKFromHexPrivateKey(hexPrivateKey, kid); + } + return getPublicSECP256k1JWKFromHexPrivateKey(hexPrivateKey, kid); +}; + +const toJWK = (kid: string, crv: KeyCurve, pubPoint: EC.BN) => { + return { + kid, + kty: KeyType.EC, + crv: crv, + x: base64url.toBase64(pubPoint.getX().toArrayLike(Buffer)), + y: base64url.toBase64(pubPoint.getY().toArrayLike(Buffer)) + }; +}; + +// from fingerprintFromPublicKey function in @transmute/Ed25519KeyPair +const getThumbprintFromJwkDIDKeyImpl = (jwk: JWK): string => { + // ed25519 cryptonyms are multicodec encoded values, specifically: + // (multicodec ed25519-pub 0xed01 + key bytes) + const pubkeyBytes = base64url.toBuffer(jwk.x); + const buffer = new Uint8Array(2 + pubkeyBytes.length); + buffer[0] = 0xed; + buffer[1] = 0x01; + buffer.set(pubkeyBytes, 2); + + // prefix with `z` to indicate multi-base encodingFormat + + return base64url.encode(`z${u8a.toString(buffer, 'base58btc')}`); +}; + +export const getThumbprintFromJwk = async (jwk: JWK, did: string): Promise => { + if (isEd25519DidKeyMethod(did)) { + return getThumbprintFromJwkDIDKeyImpl(jwk); + } else { + return await calculateJwkThumbprint(jwk, 'sha256'); + } +}; + +export const getThumbprint = async (hexPrivateKey: string, did: string): Promise => { + return await getThumbprintFromJwk( + isEd25519DidKeyMethod(did) ? getPublicED25519JWKFromHexPrivateKey(hexPrivateKey) : getPublicJWKFromHexPrivateKey(hexPrivateKey), + did + ); +}; +*/ + +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 { + if (!jwk || typeof jwk !== 'object') { + throw new TypeError('JWK must be an object'); + } + const algorithm = digestAlgorithm ?? 'sha256'; + if (algorithm !== 'sha256' && algorithm !== 'sha384' && algorithm !== 'sha512') { + throw new TypeError('digestAlgorithm must one of "sha256", "sha384", or "sha512"'); + } + let components; + switch (jwk.kty) { + case 'EC': + check(jwk.crv, '"crv" (Curve) Parameter'); + check(jwk.x, '"x" (X Coordinate) Parameter'); + check(jwk.y, '"y" (Y Coordinate) Parameter'); + components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }; + break; + case 'OKP': + check(jwk.crv, '"crv" (Subtype of Key Pair) Parameter'); + check(jwk.x, '"x" (Public Key) Parameter'); + components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x }; + break; + case 'RSA': + check(jwk.e, '"e" (Exponent) Parameter'); + check(jwk.n, '"n" (Modulus) Parameter'); + components = { e: jwk.e, kty: jwk.kty, n: jwk.n }; + break; + case 'oct': + check(jwk.k, '"k" (Key Value) Parameter'); + components = { k: jwk.k, kty: jwk.kty }; + break; + default: + throw Error('"kty" (Key Type) Parameter missing or unsupported'); + } + const data = u8a.fromString(JSON.stringify(components), 'utf-8'); + return u8a.toString(await digest(algorithm, data), 'base64url'); +} + +const digest = async (algorithm: 'sha256' | 'sha384' | 'sha512', 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'); + const thumbprint = await calculateJwkThumbprint(jwk, digestAlgorithm); + return `urn:ietf:params:oauth:jwk-thumbprint:sha-${digestAlgorithm.slice(-3)}:${thumbprint}`; +} diff --git a/packages/siopv2/src/helpers/LanguageTagUtils.ts b/packages/siopv2/src/helpers/LanguageTagUtils.ts new file mode 100644 index 00000000..68ad2b8f --- /dev/null +++ b/packages/siopv2/src/helpers/LanguageTagUtils.ts @@ -0,0 +1,126 @@ +import Tags from 'language-tags'; + +import { SIOPErrors } from '../types'; + +import { isStringNullOrEmpty } from './ObjectUtils'; + +export class LanguageTagUtils { + private static readonly LANGUAGE_TAG_SEPARATOR = '#'; + + /** + * It will give back a fields which are language tag enabled. i.e. all fields with the fields names containing + * language tags e.g. fieldName#nl-NL + * + * @param source is the object from which the language enabled fields and their values will be extracted. + */ + static getAllLanguageTaggedProperties(source: unknown): Map { + return this.getLanguageTaggedPropertiesMapped(source, undefined); + } + + /** + * It will give back a fields which are language tag enabled and are listed in the required fields. + * + * @param source is the object from which the language enabled fields and their values will be extracted. + * @param requiredFieldNames the fields which are supposed to be language enabled. These are the only fields which should be returned. + */ + static getLanguageTaggedProperties(source: unknown, requiredFieldNames: Array): Map { + const languageTagEnabledFieldsNamesMapping: Map = new Map(); + requiredFieldNames.forEach((value) => languageTagEnabledFieldsNamesMapping.set(value, value)); + return this.getLanguageTaggedPropertiesMapped(source, languageTagEnabledFieldsNamesMapping); + } + + /** + * It will give back a fields which are language tag enabled and are mapped in the required fields. + * + * @param source is the object from which the language enabled fields and their values will be extracted. + * @param requiredFieldNamesMapping the fields which are supposed to be language enabled. These are the only fields which should be returned. And + * the fields names will be transformed as per the mapping provided. + */ + static getLanguageTaggedPropertiesMapped(source: unknown, requiredFieldNamesMapping: Map): Map { + this.assertSourceIsWorthChecking(source); + this.assertValidTargetFieldNames(requiredFieldNamesMapping); + + const discoveredLanguageTaggedFields: Map = new Map(); + + Object.entries(source).forEach(([key, value]) => { + const languageTagSeparatorIndexInKey: number = key.indexOf(this.LANGUAGE_TAG_SEPARATOR); + + if (this.isFieldLanguageTagged(languageTagSeparatorIndexInKey)) { + this.extractLanguageTaggedField( + key, + value as string, + languageTagSeparatorIndexInKey, + requiredFieldNamesMapping, + discoveredLanguageTaggedFields, + ); + } + }); + + return discoveredLanguageTaggedFields; + } + + private static extractLanguageTaggedField( + key: string, + value: string, + languageTagSeparatorIndexInKey: number, + languageTagEnabledFieldsNamesMapping: Map, + languageTaggedFields: Map, + ): void { + const fieldName = this.getFieldName(key, languageTagSeparatorIndexInKey); + + const languageTag = this.getLanguageTag(key, languageTagSeparatorIndexInKey); + if (Tags.check(languageTag)) { + if (languageTagEnabledFieldsNamesMapping?.size) { + if (languageTagEnabledFieldsNamesMapping.has(fieldName)) { + languageTaggedFields.set(this.getMappedFieldName(languageTagEnabledFieldsNamesMapping, fieldName, languageTag), value); + } + } else { + languageTaggedFields.set(key, value); + } + } + } + + private static getMappedFieldName(languageTagEnabledFieldsNamesMapping: Map, fieldName: string, languageTag: string): string { + return languageTagEnabledFieldsNamesMapping.get(fieldName) + this.LANGUAGE_TAG_SEPARATOR + languageTag; + } + + private static getLanguageTag(key: string, languageTagSeparatorIndex: number): string { + return key.substring(languageTagSeparatorIndex + 1); + } + + private static getFieldName(key: string, languageTagSeparatorIndex: number): string { + return key.substring(0, languageTagSeparatorIndex); + } + + /*** + * This function checks about the field to be language-tagged. + * + * @param languageTagSeparatorIndex + * @private + */ + private static isFieldLanguageTagged(languageTagSeparatorIndex: number): boolean { + return languageTagSeparatorIndex > 0; + } + + private static assertValidTargetFieldNames(languageTagEnabledFieldsNamesMapping: Map): void { + if (languageTagEnabledFieldsNamesMapping) { + if (!languageTagEnabledFieldsNamesMapping.size) { + throw new Error(SIOPErrors.BAD_PARAMS + ' LanguageTagEnabledFieldsNamesMapping must be non-null or non-empty'); + } else { + for (const entry of languageTagEnabledFieldsNamesMapping.entries()) { + const key = entry[0]; + const value = entry[1]; + if (isStringNullOrEmpty(key) || isStringNullOrEmpty(value)) { + throw new Error(SIOPErrors.BAD_PARAMS + '. languageTagEnabledFieldsName must be non-null or non-empty'); + } + } + } + } + } + + private static assertSourceIsWorthChecking(source: unknown): void { + if (!source) { + throw new Error(SIOPErrors.BAD_PARAMS + ' Source must be non-null i.e. not-initialized.'); + } + } +} diff --git a/packages/siopv2/src/helpers/Metadata.ts b/packages/siopv2/src/helpers/Metadata.ts new file mode 100644 index 00000000..b059fc16 --- /dev/null +++ b/packages/siopv2/src/helpers/Metadata.ts @@ -0,0 +1,120 @@ +import { Format } from '@sphereon/pex-models'; + +import { + CommonSupportedMetadata, + DiscoveryMetadataPayload, + RPRegistrationMetadataPayload, + SIOPErrors, + SubjectSyntaxTypesSupportedValues, +} from '../types'; + +export function assertValidMetadata(opMetadata: DiscoveryMetadataPayload, rpMetadata: RPRegistrationMetadataPayload): CommonSupportedMetadata { + let subjectSyntaxTypesSupported = []; + const credentials = supportedCredentialsFormats(rpMetadata.vp_formats, opMetadata.vp_formats); + const isValidSubjectSyntax = verifySubjectSyntaxes(rpMetadata.subject_syntax_types_supported); + if (isValidSubjectSyntax && rpMetadata.subject_syntax_types_supported) { + subjectSyntaxTypesSupported = supportedSubjectSyntaxTypes(rpMetadata.subject_syntax_types_supported, opMetadata.subject_syntax_types_supported); + } else if (isValidSubjectSyntax && (!rpMetadata.subject_syntax_types_supported || !rpMetadata.subject_syntax_types_supported.length)) { + if (opMetadata.subject_syntax_types_supported || opMetadata.subject_syntax_types_supported.length) { + subjectSyntaxTypesSupported = [...opMetadata.subject_syntax_types_supported]; + } + } + return { vp_formats: credentials, subject_syntax_types_supported: subjectSyntaxTypesSupported }; +} + +function getIntersection(rpMetadata: Array | T, opMetadata: Array | T): Array { + let arrayA, arrayB; + if (!Array.isArray(rpMetadata)) { + arrayA = [rpMetadata]; + } else { + arrayA = rpMetadata; + } + if (!Array.isArray(opMetadata)) { + arrayB = [opMetadata]; + } else { + arrayB = opMetadata; + } + return arrayA.filter((value) => arrayB.includes(value)); +} + +function verifySubjectSyntaxes(subjectSyntaxTypesSupported: string[]): boolean { + if (subjectSyntaxTypesSupported.length) { + if (Array.isArray(subjectSyntaxTypesSupported)) { + if ( + subjectSyntaxTypesSupported.length === + subjectSyntaxTypesSupported.filter( + (sst) => + sst.includes(SubjectSyntaxTypesSupportedValues.DID.valueOf()) || sst === SubjectSyntaxTypesSupportedValues.JWK_THUMBPRINT.valueOf(), + ).length + ) { + return true; + } + } + } + return false; +} + +function supportedSubjectSyntaxTypes(rpMethods: string[] | string, opMethods: string[] | string): Array { + const rpMethodsList = Array.isArray(rpMethods) ? rpMethods : [rpMethods]; + const opMethodsList = Array.isArray(opMethods) ? opMethods : [opMethods]; + const supportedSubjectSyntaxTypes = getIntersection(rpMethodsList, opMethodsList); + if (supportedSubjectSyntaxTypes.indexOf(SubjectSyntaxTypesSupportedValues.DID.valueOf()) !== -1) { + return [SubjectSyntaxTypesSupportedValues.DID.valueOf()]; + } + if (rpMethodsList.includes(SubjectSyntaxTypesSupportedValues.DID.valueOf())) { + const supportedExtendedDids: string[] = opMethodsList.filter((method) => method.startsWith('did:')); + if (supportedExtendedDids.length) { + return supportedExtendedDids; + } + } + if (opMethodsList.includes(SubjectSyntaxTypesSupportedValues.DID.valueOf())) { + const supportedExtendedDids: string[] = rpMethodsList.filter((method) => method.startsWith('did:')); + if (supportedExtendedDids.length) { + return supportedExtendedDids; + } + } + + if (!supportedSubjectSyntaxTypes.length) { + throw Error(SIOPErrors.DID_METHODS_NOT_SUPORTED); + } + const supportedDidMethods = supportedSubjectSyntaxTypes.filter((sst) => sst.includes('did:')); + if (supportedDidMethods.length) { + return supportedDidMethods; + } + return supportedSubjectSyntaxTypes; +} + +function getFormatIntersection(rpFormat: Format, opFormat: Format): Format { + const intersectionFormat: Format = {}; + const supportedCredentials = getIntersection(Object.keys(rpFormat), Object.keys(opFormat)); + if (!supportedCredentials.length) { + throw new Error(SIOPErrors.CREDENTIAL_FORMATS_NOT_SUPPORTED); + } + supportedCredentials.forEach(function (crFormat: string) { + const rpAlgs = []; + const opAlgs = []; + Object.keys(rpFormat[crFormat]).forEach((k) => rpAlgs.push(...rpFormat[crFormat][k])); + Object.keys(opFormat[crFormat]).forEach((k) => opAlgs.push(...opFormat[crFormat][k])); + let methodKeyRP = undefined; + let methodKeyOP = undefined; + Object.keys(rpFormat[crFormat]).forEach((k) => (methodKeyRP = k)); + Object.keys(opFormat[crFormat]).forEach((k) => (methodKeyOP = k)); + if (methodKeyRP !== methodKeyOP) { + throw new Error(SIOPErrors.CREDENTIAL_FORMATS_NOT_SUPPORTED); + } + const algs = getIntersection(rpAlgs, opAlgs); + if (!algs.length) { + throw new Error(SIOPErrors.CREDENTIAL_FORMATS_NOT_SUPPORTED); + } + intersectionFormat[crFormat] = {}; + intersectionFormat[crFormat][methodKeyOP] = algs; + }); + return intersectionFormat; +} + +export function supportedCredentialsFormats(rpFormat: Format, opFormat: Format): Format { + if (!rpFormat || !opFormat || !Object.keys(rpFormat).length || !Object.keys(opFormat).length) { + throw new Error(SIOPErrors.CREDENTIALS_FORMATS_NOT_PROVIDED); + } + return getFormatIntersection(rpFormat, opFormat); +} diff --git a/packages/siopv2/src/helpers/ObjectUtils.ts b/packages/siopv2/src/helpers/ObjectUtils.ts new file mode 100644 index 00000000..9cfbddca --- /dev/null +++ b/packages/siopv2/src/helpers/ObjectUtils.ts @@ -0,0 +1,26 @@ +import { JSONPath as jp } from '@astronautlabs/jsonpath'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function extractDataFromPath(obj: unknown, path: string): { path: string[]; value: any }[] { + return jp.nodes(obj, path); +} + +export function isStringNullOrEmpty(key: string) { + return !key || !key.length; +} + +export function removeNullUndefined(data: unknown) { + if (!data) { + return data; + } + //transform properties into key-values pairs and filter all the empty-values + const entries = Object.entries(data).filter(([, value]) => value != null); + //map through all the remaining properties and check if the value is an object. + //if value is object, use recursion to remove empty properties + const clean = entries.map(([key, v]) => { + const value = typeof v === 'object' && !Array.isArray(v) ? removeNullUndefined(v) : v; + return [key, value]; + }); + //transform the key-value pairs back to an object. + return Object.fromEntries(clean); +} diff --git a/packages/siopv2/src/helpers/Revocation.ts b/packages/siopv2/src/helpers/Revocation.ts new file mode 100644 index 00000000..2cd4486f --- /dev/null +++ b/packages/siopv2/src/helpers/Revocation.ts @@ -0,0 +1,57 @@ +import { CredentialMapper, W3CVerifiableCredential, WrappedVerifiableCredential, WrappedVerifiablePresentation } from '@sphereon/ssi-types'; + +import { RevocationStatus, RevocationVerification, RevocationVerificationCallback, VerifiableCredentialTypeFormat } from '../types'; + +export const verifyRevocation = async ( + vpToken: WrappedVerifiablePresentation, + revocationVerificationCallback: RevocationVerificationCallback, + revocationVerification: RevocationVerification, +): Promise => { + if (!vpToken) { + throw new Error(`VP token not provided`); + } + if (!revocationVerificationCallback) { + throw new Error(`Revocation callback not provided`); + } + + const vcs = CredentialMapper.isWrappedSdJwtVerifiablePresentation(vpToken) ? [vpToken.vcs[0]] : vpToken.presentation.verifiableCredential; + for (const vc of vcs) { + if ( + revocationVerification === RevocationVerification.ALWAYS || + (revocationVerification === RevocationVerification.IF_PRESENT && credentialHasStatus(vc)) + ) { + const result = await revocationVerificationCallback( + vc.original as W3CVerifiableCredential, + originalTypeToVerifiableCredentialTypeFormat(vc.format), + ); + if (result.status === RevocationStatus.INVALID) { + throw new Error(`Revocation invalid for vc. Error: ${result.error}`); + } + } + } +}; + +function originalTypeToVerifiableCredentialTypeFormat(original: WrappedVerifiableCredential['format']): VerifiableCredentialTypeFormat { + const mapping: { [T in WrappedVerifiableCredential['format']]: VerifiableCredentialTypeFormat } = { + 'vc+sd-jwt': VerifiableCredentialTypeFormat.SD_JWT_VC, + jwt: VerifiableCredentialTypeFormat.JWT_VC, + jwt_vc: VerifiableCredentialTypeFormat.JWT_VC, + ldp: VerifiableCredentialTypeFormat.LDP_VC, + ldp_vc: VerifiableCredentialTypeFormat.LDP_VC, + }; + + return mapping[original]; +} + +/** + * Checks whether a wrapped verifiable credential has a status in the credential. + * For w3c credentials it will check the presence of `credentialStatus` property + * For SD-JWT it will check the presence of `status` property + */ +function credentialHasStatus(wrappedVerifiableCredential: WrappedVerifiableCredential) { + if (CredentialMapper.isWrappedSdJwtVerifiableCredential(wrappedVerifiableCredential)) { + return wrappedVerifiableCredential.decoded.status !== undefined; + } else { + return wrappedVerifiableCredential.credential.credentialStatus !== undefined; + } +} diff --git a/packages/siopv2/src/helpers/SIOPSpecVersion.ts b/packages/siopv2/src/helpers/SIOPSpecVersion.ts new file mode 100644 index 00000000..2ae09084 --- /dev/null +++ b/packages/siopv2/src/helpers/SIOPSpecVersion.ts @@ -0,0 +1,84 @@ +import { AuthorizationRequestPayloadVD11Schema, AuthorizationRequestPayloadVID1Schema } from '../schemas'; +import { AuthorizationRequestPayloadVD12OID4VPD18Schema } from '../schemas/validation/schemaValidation'; +import { AuthorizationRequestPayload, ResponseMode, SupportedVersion } from '../types'; +import errors from '../types/Errors'; + +const validateJWTVCPresentationProfile = AuthorizationRequestPayloadVID1Schema; + +function isJWTVC1Payload(authorizationRequest: AuthorizationRequestPayload) { + return ( + authorizationRequest.scope && + authorizationRequest.scope.toLowerCase().includes('openid') && + authorizationRequest.response_type && + authorizationRequest.response_type.toLowerCase().includes('id_token') && + authorizationRequest.response_mode && + authorizationRequest.response_mode.toLowerCase() === 'post' && + authorizationRequest.client_id && + authorizationRequest.client_id.toLowerCase().startsWith('did:') && + authorizationRequest.redirect_uri && + (authorizationRequest.registration_uri || authorizationRequest.registration) && + authorizationRequest.claims && + 'vp_token' in authorizationRequest.claims + ); +} +function isID1Payload(authorizationRequest: AuthorizationRequestPayload) { + return ( + !authorizationRequest.client_metadata_uri && + !authorizationRequest.client_metadata && + !authorizationRequest.presentation_definition && + !authorizationRequest.presentation_definition_uri + ); +} + +export const authorizationRequestVersionDiscovery = (authorizationRequest: AuthorizationRequestPayload): SupportedVersion[] => { + const versions = []; + const authorizationRequestCopy: AuthorizationRequestPayload = JSON.parse(JSON.stringify(authorizationRequest)); + // todo: We could use v11 validation for v12 for now, as we do not differentiate in the schema at this point\ + const vd12Validation = AuthorizationRequestPayloadVD12OID4VPD18Schema(authorizationRequestCopy); + if (vd12Validation) { + if ( + !authorizationRequestCopy.registration_uri && + !authorizationRequestCopy.registration && + !(authorizationRequestCopy.claims && 'vp_token' in authorizationRequestCopy.claims) && + authorizationRequestCopy.response_mode !== ResponseMode.POST // Post has been replaced by direct post + ) { + versions.push(SupportedVersion.SIOPv2_D12_OID4VP_D18); + } + } + const vd11Validation = AuthorizationRequestPayloadVD11Schema(authorizationRequestCopy); + if (vd11Validation) { + if ( + !authorizationRequestCopy.registration_uri && + !authorizationRequestCopy.registration && + !(authorizationRequestCopy.claims && 'vp_token' in authorizationRequestCopy.claims) && + !authorizationRequestCopy.client_id_scheme && // introduced after v11 + !authorizationRequestCopy.response_uri && + authorizationRequestCopy.response_mode !== ResponseMode.DIRECT_POST // Direct post was used before v12 oid4vp18 + ) { + versions.push(SupportedVersion.SIOPv2_D11); + } + } + const jwtVC1Validation = validateJWTVCPresentationProfile(authorizationRequestCopy); + if (jwtVC1Validation && isJWTVC1Payload(authorizationRequest)) { + versions.push(SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1); + } + const vid1Validation = AuthorizationRequestPayloadVID1Schema(authorizationRequestCopy); + if (vid1Validation && isID1Payload(authorizationRequest)) { + versions.push(SupportedVersion.SIOPv2_ID1); + } + if (versions.length === 0) { + throw new Error(errors.SIOP_VERSION_NOT_SUPPORTED); + } + return versions; +}; + +export const checkSIOPSpecVersionSupported = async ( + payload: AuthorizationRequestPayload, + supportedVersions: SupportedVersion[], +): Promise => { + const versions: SupportedVersion[] = authorizationRequestVersionDiscovery(payload); + if (!supportedVersions || supportedVersions.length === 0) { + return versions; + } + return supportedVersions.filter((version) => versions.includes(version)); +}; diff --git a/packages/siopv2/src/helpers/State.ts b/packages/siopv2/src/helpers/State.ts new file mode 100644 index 00000000..bc872ed0 --- /dev/null +++ b/packages/siopv2/src/helpers/State.ts @@ -0,0 +1,21 @@ +import SHA from 'sha.js'; +import { v4 as uuidv4 } from 'uuid'; + +import { base64urlEncodeBuffer } from './Encodings'; + +export function getNonce(state: string, nonce?: string) { + return nonce || toNonce(state); +} + +export function toNonce(input: string): string { + const buff = SHA('sha256').update(input).digest(); + return base64urlEncodeBuffer(buff); +} + +export function getState(state?: string) { + return state || createState(); +} + +export function createState(): string { + return uuidv4(); +} diff --git a/packages/siopv2/src/helpers/index.ts b/packages/siopv2/src/helpers/index.ts new file mode 100644 index 00000000..4ba5c801 --- /dev/null +++ b/packages/siopv2/src/helpers/index.ts @@ -0,0 +1,8 @@ +export * from './Metadata'; +export * from './Encodings'; +export * from './HttpUtils'; +export * from './Keys'; +export * from './ObjectUtils'; +export * from './Revocation'; +export * from './State'; +export * from './LanguageTagUtils'; diff --git a/packages/siopv2/src/id-token/IDToken.ts b/packages/siopv2/src/id-token/IDToken.ts new file mode 100644 index 00000000..9c8437f6 --- /dev/null +++ b/packages/siopv2/src/id-token/IDToken.ts @@ -0,0 +1,197 @@ +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 { + CheckLinkedDomain, + IDTokenJwt, + IDTokenPayload, + JWTPayload, + ResponseIss, + SIOPErrors, + VerifiedAuthorizationRequest, + VerifiedIDToken, +} from '../types'; + +import { createIDTokenPayload } from './Payload'; + +export class IDToken { + private _header?: JWTHeader; + private _payload?: IDTokenPayload; + private _jwt?: IDTokenJwt; + private readonly _responseOpts: AuthorizationResponseOpts; + + private constructor(jwt?: IDTokenJwt, payload?: IDTokenPayload, responseOpts?: AuthorizationResponseOpts) { + this._jwt = jwt; + this._payload = payload; + this._responseOpts = responseOpts; + } + + public static async fromVerifiedAuthorizationRequest( + verifiedAuthorizationRequest: VerifiedAuthorizationRequest, + responseOpts: AuthorizationResponseOpts, + verifyOpts?: VerifyAuthorizationResponseOpts, + ) { + const authorizationRequestPayload = verifiedAuthorizationRequest.authorizationRequestPayload; + if (!authorizationRequestPayload) { + throw new Error(SIOPErrors.NO_REQUEST); + } + const idToken = new IDToken(null, await createIDTokenPayload(verifiedAuthorizationRequest, responseOpts), responseOpts); + if (verifyOpts) { + await idToken.verify(verifyOpts); + } + return idToken; + } + + public static async fromIDToken(idTokenJwt: IDTokenJwt, verifyOpts?: VerifyAuthorizationResponseOpts) { + if (!idTokenJwt) { + throw new Error(SIOPErrors.NO_JWT); + } + const idToken = new IDToken(idTokenJwt, undefined); + if (verifyOpts) { + await idToken.verify(verifyOpts); + } + return idToken; + } + + public static async fromIDTokenPayload( + idTokenPayload: IDTokenPayload, + responseOpts: AuthorizationResponseOpts, + verifyOpts?: VerifyAuthorizationResponseOpts, + ) { + if (!idTokenPayload) { + throw new Error(SIOPErrors.NO_JWT); + } + const idToken = new IDToken(null, idTokenPayload, responseOpts); + if (verifyOpts) { + await idToken.verify(verifyOpts); + } + return idToken; + } + + public async payload(): Promise { + if (!this._payload) { + if (!this._jwt) { + throw new Error(SIOPErrors.NO_JWT); + } + const { header, payload } = this.parseAndVerifyJwt(); + this._header = header; + this._payload = payload; + } + return this._payload; + } + + public async jwt(): Promise { + if (!this._jwt) { + if (!this.responseOpts) { + throw Error(SIOPErrors.BAD_SIGNATURE_PARAMS); + } + this._jwt = await signIDTokenPayload(this._payload, this.responseOpts); + const { header, payload } = this.parseAndVerifyJwt(); + this._header = header; + this._payload = payload; + } + return this._jwt; + } + + private parseAndVerifyJwt(): { header: JWTHeader; payload: IDTokenPayload } { + const { header, payload } = parseJWT(this._jwt); + this.assertValidResponseJWT({ header, payload }); + const idTokenPayload = payload as IDTokenPayload; + return { header, payload: idTokenPayload }; + } + + /** + * Verifies a SIOP ID Response JWT on the RP Side + * + * @param idToken ID token to be validated + * @param verifyOpts + */ + public async verify(verifyOpts: VerifyAuthorizationResponseOpts): Promise { + assertValidVerifyOpts(verifyOpts); + + const { header, payload } = parseJWT(await this.jwt()); + this.assertValidResponseJWT({ header, payload }); + + const verifiedJWT = await verifyDidJWT(await this.jwt(), getResolver(verifyOpts.verification.resolveOpts), { + ...verifyOpts.verification.resolveOpts?.jwtVerifyOpts, + audience: verifyOpts.audience ?? verifyOpts.verification.resolveOpts?.jwtVerifyOpts?.audience, + }); + + 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 verPayload = verifiedJWT.payload as IDTokenPayload; + this.assertValidResponseJWT({ 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, + payload: { ...verPayload }, + verifyOpts, + }; + } + + static async verify(idTokenJwt: IDTokenJwt, verifyOpts: VerifyAuthorizationResponseOpts): Promise { + const idToken = await IDToken.fromIDToken(idTokenJwt, verifyOpts); + const verifiedIdToken = await idToken.verify(verifyOpts); + + return { + ...verifiedIdToken, + }; + } + + 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}`); + } + } + + if (opts.verPayload) { + if (!opts.verPayload.nonce) { + throw Error(SIOPErrors.NO_NONCE); + // No need for our own expiration check. DID jwt already does that + /*} else if (!opts.verPayload.exp || opts.verPayload.exp < Date.now() / 1000) { + throw Error(SIOPErrors.EXPIRED); + /!*} else if (!opts.verPayload.iat || opts.verPayload.iat > (Date.now() / 1000)) { + throw Error(SIOPErrors.EXPIRED);*!/ + // todo: Add iat check + + */ + } + if ((opts.verPayload.aud && !opts.audience) || (!opts.verPayload.aud && opts.audience)) { + throw Error(SIOPErrors.NO_AUDIENCE); + } else if (opts.audience && opts.audience != opts.verPayload.aud) { + throw Error(SIOPErrors.INVALID_AUDIENCE); + } else if (opts.nonce && opts.nonce != opts.verPayload.nonce) { + throw Error(SIOPErrors.BAD_NONCE); + } + } + } + + get header(): JWTHeader { + return this._header; + } + + get responseOpts(): AuthorizationResponseOpts { + return this._responseOpts; + } + + public async isSelfIssued(): Promise { + const payload = await this.payload(); + return payload.iss === ResponseIss.SELF_ISSUED_V2 || (payload.sub !== undefined && payload.sub === payload.iss); + } +} diff --git a/packages/siopv2/src/id-token/Payload.ts b/packages/siopv2/src/id-token/Payload.ts new file mode 100644 index 00000000..927d5f6a --- /dev/null +++ b/packages/siopv2/src/id-token/Payload.ts @@ -0,0 +1,82 @@ +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'; + +export const createIDTokenPayload = async ( + verifiedAuthorizationRequest: VerifiedAuthorizationRequest, + responseOpts: AuthorizationResponseOpts, +): Promise => { + assertValidResponseOpts(responseOpts); + const authorizationRequestPayload = await verifiedAuthorizationRequest.authorizationRequest.mergedPayloads(); + const requestObject = verifiedAuthorizationRequest.requestObject; + if (!authorizationRequestPayload) { + throw new Error(SIOPErrors.VERIFY_BAD_PARAMS); + } + 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; + + const rpSupportedVersions = authorizationRequestVersionDiscovery(payload); + const maxRPVersion = rpSupportedVersions.reduce( + (previous, current) => (current.valueOf() > previous.valueOf() ? current : previous), + SupportedVersion.SIOPv2_ID1, + ); + if (responseOpts.version && rpSupportedVersions.length > 0 && !rpSupportedVersions.includes(responseOpts.version)) { + throw Error(`RP does not support spec version ${responseOpts.version}, supported versions: ${rpSupportedVersions.toString()}`); + } + const opVersion = responseOpts.version ?? maxRPVersion; + + const idToken: IDTokenPayload = { + // fixme: ID11 does not use this static value anymore + iss: + responseOpts?.registration?.issuer ?? + (opVersion === SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1 ? ResponseIss.JWT_VC_PRESENTATION_V1 : ResponseIss.SELF_ISSUED_V2), + 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, + 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/packages/siopv2/src/id-token/index.ts b/packages/siopv2/src/id-token/index.ts new file mode 100644 index 00000000..51318983 --- /dev/null +++ b/packages/siopv2/src/id-token/index.ts @@ -0,0 +1,2 @@ +export * from './IDToken'; +export * from './Payload'; diff --git a/packages/siopv2/src/index.ts b/packages/siopv2/src/index.ts new file mode 100644 index 00000000..8f9a639c --- /dev/null +++ b/packages/siopv2/src/index.ts @@ -0,0 +1,14 @@ +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'; +export * from './authorization-response'; +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/packages/siopv2/src/op/OP.ts b/packages/siopv2/src/op/OP.ts new file mode 100644 index 00000000..73b601e4 --- /dev/null +++ b/packages/siopv2/src/op/OP.ts @@ -0,0 +1,304 @@ +import { EventEmitter } from 'events'; + +import { IIssuerId } from '@sphereon/ssi-types'; +import { v4 as uuidv4 } from 'uuid'; + +import { AuthorizationRequest, URI, VerifyAuthorizationRequestOpts } from '../authorization-request'; +import { mergeVerificationOpts } from '../authorization-request/Opts'; +import { + AuthorizationResponse, + AuthorizationResponseOpts, + AuthorizationResponseWithCorrelationId, + PresentationExchangeResponseOpts, +} from '../authorization-response'; +import { encodeJsonAsURI, post } from '../helpers'; +import { authorizationRequestVersionDiscovery } from '../helpers/SIOPSpecVersion'; +import { + AuthorizationEvent, + AuthorizationEvents, + ContentType, + ExternalSignature, + ExternalVerification, + InternalSignature, + InternalVerification, + ParsedAuthorizationRequestURI, + RegisterEventListener, + ResponseIss, + ResponseMode, + SIOPErrors, + SIOPResonse, + SuppliedSignature, + SupportedVersion, + UrlEncodingFormat, + VerifiedAuthorizationRequest, +} from '../types'; + +import { OPBuilder } from './OPBuilder'; +import { createResponseOptsFromBuilderOrExistingOpts, createVerifyRequestOptsFromBuilderOrExistingOpts } from './Opts'; + +// The OP publishes the formats it supports using the vp_formats_supported metadata parameter as defined above in its "openid-configuration". +export class OP { + private readonly _createResponseOptions: AuthorizationResponseOpts; + private readonly _verifyRequestOptions: Partial; + private readonly _eventEmitter?: EventEmitter; + + private constructor(opts: { builder?: OPBuilder; responseOpts?: AuthorizationResponseOpts; verifyOpts?: VerifyAuthorizationRequestOpts }) { + this._createResponseOptions = { ...createResponseOptsFromBuilderOrExistingOpts(opts) }; + this._verifyRequestOptions = { ...createVerifyRequestOptsFromBuilderOrExistingOpts(opts) }; + this._eventEmitter = opts.builder?.eventEmitter; + } + + /** + * This method tries to infer the SIOP specs version based on the request payload. + * If the version cannot be inferred or is not supported it throws an exception. + * This method needs to be called to ensure the OP can handle the request + * @param requestJwtOrUri + * @param requestOpts + */ + + public async verifyAuthorizationRequest( + requestJwtOrUri: string | URI, + requestOpts?: { correlationId?: string; verification?: InternalVerification | ExternalVerification }, + ): Promise { + const correlationId = requestOpts?.correlationId || uuidv4(); + const authorizationRequest = await AuthorizationRequest.fromUriOrJwt(requestJwtOrUri) + .then((result: AuthorizationRequest) => { + void this.emitEvent(AuthorizationEvents.ON_AUTH_REQUEST_RECEIVED_SUCCESS, { correlationId, subject: result }); + return result; + }) + .catch((error: Error) => { + void this.emitEvent(AuthorizationEvents.ON_AUTH_REQUEST_RECEIVED_FAILED, { + correlationId, + subject: requestJwtOrUri, + error, + }); + throw error; + }); + + return authorizationRequest + .verify(this.newVerifyAuthorizationRequestOpts({ ...requestOpts, correlationId })) + .then((verifiedAuthorizationRequest: VerifiedAuthorizationRequest) => { + void this.emitEvent(AuthorizationEvents.ON_AUTH_REQUEST_VERIFIED_SUCCESS, { + correlationId, + subject: verifiedAuthorizationRequest.authorizationRequest, + }); + return verifiedAuthorizationRequest; + }) + .catch((error) => { + void this.emitEvent(AuthorizationEvents.ON_AUTH_REQUEST_VERIFIED_FAILED, { + correlationId, + subject: authorizationRequest, + error, + }); + throw error; + }); + } + + public async createAuthorizationResponse( + verifiedAuthorizationRequest: VerifiedAuthorizationRequest, + responseOpts?: { + version?: SupportedVersion; + correlationId?: string; + audience?: string; + issuer?: ResponseIss | string; + signature?: InternalSignature | ExternalSignature | SuppliedSignature; + verification?: InternalVerification | ExternalVerification; + presentationExchange?: PresentationExchangeResponseOpts; + }, + ): Promise { + if ( + verifiedAuthorizationRequest.correlationId && + responseOpts?.correlationId && + verifiedAuthorizationRequest.correlationId !== responseOpts.correlationId + ) { + throw new Error( + `Request correlation id ${verifiedAuthorizationRequest.correlationId} is different from option correlation id ${responseOpts.correlationId}`, + ); + } + let version = responseOpts?.version; + const rpSupportedVersions = authorizationRequestVersionDiscovery(await verifiedAuthorizationRequest.authorizationRequest.mergedPayloads()); + if (version && rpSupportedVersions.length > 0 && !rpSupportedVersions.includes(version)) { + throw Error(`RP does not support spec version ${version}, supported versions: ${rpSupportedVersions.toString()}`); + } else if (!version) { + version = rpSupportedVersions.reduce( + (previous, current) => (current.valueOf() > previous.valueOf() ? current : previous), + SupportedVersion.SIOPv2_ID1, + ); + } + const correlationId = responseOpts?.correlationId || verifiedAuthorizationRequest.correlationId || uuidv4(); + try { + // IF using DIRECT_POST, the response_uri takes precedence over the redirect_uri + let responseUri = verifiedAuthorizationRequest.responseURI; + if (verifiedAuthorizationRequest.authorizationRequestPayload.response_mode === ResponseMode.DIRECT_POST) { + responseUri = verifiedAuthorizationRequest.authorizationRequestPayload.response_uri ?? responseUri; + } + + const response = await AuthorizationResponse.fromVerifiedAuthorizationRequest( + verifiedAuthorizationRequest, + this.newAuthorizationResponseOpts({ + ...responseOpts, + version, + correlationId, + }), + verifiedAuthorizationRequest.verifyOpts, + ); + void this.emitEvent(AuthorizationEvents.ON_AUTH_RESPONSE_CREATE_SUCCESS, { + correlationId, + subject: response, + }); + return { correlationId, response, responseURI: responseUri }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + void this.emitEvent(AuthorizationEvents.ON_AUTH_RESPONSE_CREATE_FAILED, { + correlationId, + subject: verifiedAuthorizationRequest.authorizationRequest, + error, + }); + throw error; + } + } + + // TODO SK Can you please put some documentation on it? + public async submitAuthorizationResponse(authorizationResponse: AuthorizationResponseWithCorrelationId): Promise { + const { correlationId, response } = authorizationResponse; + if (!correlationId) { + throw Error('No correlation Id provided'); + } + if ( + !response || + (response.options?.responseMode && + !( + response.options.responseMode === ResponseMode.POST || + response.options.responseMode === ResponseMode.FORM_POST || + response.options.responseMode === ResponseMode.DIRECT_POST + )) + ) { + throw new Error(SIOPErrors.BAD_PARAMS); + } + + const payload = response.payload; + const idToken = await response.idToken?.payload(); + const responseUri = authorizationResponse.responseURI ?? idToken?.aud; + if (!responseUri) { + throw Error('No response URI present'); + } + const authResponseAsURI = encodeJsonAsURI(payload, { arraysWithIndex: ['presentation_submission', 'vp_token'] }); + return post(responseUri, authResponseAsURI, { contentType: ContentType.FORM_URL_ENCODED }) + .then((result: SIOPResonse) => { + void this.emitEvent(AuthorizationEvents.ON_AUTH_RESPONSE_SENT_SUCCESS, { correlationId, subject: response }); + return result.origResponse; + }) + .catch((error: Error) => { + void this.emitEvent(AuthorizationEvents.ON_AUTH_RESPONSE_SENT_FAILED, { correlationId, subject: response, error }); + throw error; + }); + } + + /** + * Create an Authentication Request Payload from a URI string + * + * @param encodedUri + */ + public async parseAuthorizationRequestURI(encodedUri: string): Promise { + const { scheme, requestObjectJwt, authorizationRequestPayload, registrationMetadata } = await URI.parseAndResolve(encodedUri); + + return { + encodedUri, + encodingFormat: UrlEncodingFormat.FORM_URL_ENCODED, + scheme: scheme, + requestObjectJwt, + authorizationRequestPayload, + registration: registrationMetadata, + }; + } + + private newAuthorizationResponseOpts(opts: { + correlationId: string; + version?: SupportedVersion; + issuer?: IIssuerId | ResponseIss; + audience?: string; + signature?: InternalSignature | ExternalSignature | SuppliedSignature; + presentationExchange?: PresentationExchangeResponseOpts; + }): AuthorizationResponseOpts { + const version = opts.version ?? this._createResponseOptions.version; + let issuer = opts.issuer ?? this._createResponseOptions?.registration?.issuer; + if (version === SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1) { + issuer = ResponseIss.JWT_VC_PRESENTATION_V1; + } else if (version === SupportedVersion.SIOPv2_ID1) { + issuer = ResponseIss.SELF_ISSUED_V2; + } + + if (!issuer) { + throw Error(`No issuer value present. Either use IDv1, JWT VC Presentation profile version, or provide a DID as issuer value`); + } + // We are taking the whole presentationExchange object from a certain location + const presentationExchange = opts.presentationExchange ?? this._createResponseOptions.presentationExchange; + const responseURI = opts.audience ?? this._createResponseOptions.responseURI; + return { + ...this._createResponseOptions, + ...opts, + signature: { + ...this._createResponseOptions?.signature, + ...opts.signature, + }, + ...(presentationExchange && { presentationExchange }), + registration: { ...this._createResponseOptions?.registration, issuer }, + responseURI, + responseURIType: + this._createResponseOptions.responseURIType ?? (version < SupportedVersion.SIOPv2_D12_OID4VP_D18 && responseURI ? 'redirect_uri' : undefined), + }; + } + + private newVerifyAuthorizationRequestOpts(requestOpts: { + correlationId: string; + verification?: InternalVerification | ExternalVerification; + }): VerifyAuthorizationRequestOpts { + const verification: VerifyAuthorizationRequestOpts = { + ...this._verifyRequestOptions, + ...requestOpts, + verification: mergeVerificationOpts(this._verifyRequestOptions, requestOpts), + correlationId: requestOpts.correlationId, + }; + + return verification; + } + + private async emitEvent( + type: AuthorizationEvents, + payload: { + correlationId: string; + subject: AuthorizationRequest | AuthorizationResponse | string | URI; + error?: Error; + }, + ): Promise { + if (this._eventEmitter) { + this._eventEmitter.emit(type, new AuthorizationEvent(payload)); + } + } + + public addEventListener(register: RegisterEventListener) { + if (!this._eventEmitter) { + throw Error('Cannot add listeners if no event emitter is available'); + } + const events = Array.isArray(register.event) ? register.event : [register.event]; + for (const event of events) { + this._eventEmitter.addListener(event, register.listener); + } + } + + public static fromOpts(responseOpts: AuthorizationResponseOpts, verifyOpts: VerifyAuthorizationRequestOpts): OP { + return new OP({ responseOpts, verifyOpts }); + } + + public static builder() { + return new OPBuilder(); + } + + get createResponseOptions(): AuthorizationResponseOpts { + return this._createResponseOptions; + } + + get verifyRequestOptions(): Partial { + return this._verifyRequestOptions; + } +} diff --git a/packages/siopv2/src/op/OPBuilder.ts b/packages/siopv2/src/op/OPBuilder.ts new file mode 100644 index 00000000..69d745f4 --- /dev/null +++ b/packages/siopv2/src/op/OPBuilder.ts @@ -0,0 +1,180 @@ +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 { 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; + 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; + + return this; + } + + withIssuer(issuer: ResponseIss | string): OPBuilder { + this.issuer = issuer; + 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; + } + + withRegistration(responseRegistration: ResponseRegistrationOpts, targets?: PropertyTargets): OPBuilder { + this.responseRegistration = { + targets, + ...responseRegistration, + }; + return this; + } + + /*//TODO registration object creation + authorizationEndpoint?: Schema.OPENID | string; + scopesSupported?: Scope[] | Scope; + subjectTypesSupported?: SubjectType[] | SubjectType; + idTokenSigningAlgValuesSupported?: SigningAlgo[] | SigningAlgo; + 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 }); + return this; + } + + withWellknownDIDVerifyCallback(wellknownDIDVerifyCallback: VerifyCallback): OPBuilder { + this.wellknownDIDVerifyCallback = wellknownDIDVerifyCallback; + return this; + } + + withSupportedVersions(supportedVersions: SupportedVersion[] | SupportedVersion | string[] | string): OPBuilder { + const versions = Array.isArray(supportedVersions) ? supportedVersions : [supportedVersions]; + for (const version of versions) { + this.addSupportedVersion(version); + } + return this; + } + + addSupportedVersion(supportedVersion: string | SupportedVersion): OPBuilder { + if (!this.supportedVersions) { + this.supportedVersions = []; + } + if (typeof supportedVersion === 'string') { + this.supportedVersions.push(SupportedVersion[supportedVersion]); + } else { + this.supportedVersions.push(supportedVersion); + } + return this; + } + + withPresentationSignCallback(presentationSignCallback: PresentationSignCallback): OPBuilder { + this.presentationSignCallback = presentationSignCallback; + return this; + } + + withEventEmitter(eventEmitter?: EventEmitter): OPBuilder { + this.eventEmitter = eventEmitter ?? new EventEmitter(); + return this; + } + + build(): OP { + /*if (!this.responseRegistration) { + throw Error('You need to provide response registrations values') + } else */ /*if (!this.withSignature) { + throw Error('You need to supply withSignature values'); + } else */ if (!this.supportedVersions || this.supportedVersions.length === 0) { + this.supportedVersions = [SupportedVersion.SIOPv2_D11, SupportedVersion.SIOPv2_ID1, SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1]; + } + // We ignore the private visibility, as we don't want others to use the OP directly + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return new OP({ builder: this }); + } +} diff --git a/packages/siopv2/src/op/Opts.ts b/packages/siopv2/src/op/Opts.ts new file mode 100644 index 00000000..d55fe3a8 --- /dev/null +++ b/packages/siopv2/src/op/Opts.ts @@ -0,0 +1,102 @@ +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 { OPBuilder } from './OPBuilder'; + +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 = { + registration: { + issuer: opts.builder.issuer, + ...(opts.builder.responseRegistration as ResponseRegistrationOpts), + }, + expiresIn: opts.builder.expiresIn, + signature: opts.builder.signature, + responseMode: opts.builder.responseMode, + ...(responseOpts?.version + ? { version: responseOpts.version } + : Array.isArray(opts.builder.supportedVersions) && opts.builder.supportedVersions.length > 0 + ? { version: opts.builder.supportedVersions[0] } + : {}), + }; + + if (!responseOpts.registration.passBy) { + responseOpts.registration.passBy = PassBy.VALUE; + } + const languageTagEnabledFieldsNames = ['clientName', 'clientPurpose']; + const languageTaggedFields: Map = LanguageTagUtils.getLanguageTaggedProperties( + opts.builder.responseRegistration, + languageTagEnabledFieldsNames, + ); + + languageTaggedFields.forEach((value: string, key: string) => { + responseOpts.registration[key] = value; + }); + } else { + responseOpts = { + ...opts.responseOpts, + }; + } + + const valid = AuthorizationResponseOptsSchema(responseOpts); + if (!valid) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + throw new Error('OP builder validation error: ' + JSON.stringify(AuthorizationResponseOptsSchema.errors)); + } + + return responseOpts; +}; + +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 + ? { + 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, + supportedVersions: opts.builder.supportedVersions, + correlationId: undefined, + } + : opts.verifyOpts; +}; diff --git a/packages/siopv2/src/op/index.ts b/packages/siopv2/src/op/index.ts new file mode 100644 index 00000000..a2dc854e --- /dev/null +++ b/packages/siopv2/src/op/index.ts @@ -0,0 +1,2 @@ +export * from './OP'; +export * from './OPBuilder'; diff --git a/packages/siopv2/src/request-object/Opts.ts b/packages/siopv2/src/request-object/Opts.ts new file mode 100644 index 00000000..d7315bf1 --- /dev/null +++ b/packages/siopv2/src/request-object/Opts.ts @@ -0,0 +1,22 @@ +import { ClaimPayloadCommonOpts } from '../authorization-request'; +import { PassBy, SIOPErrors } from '../types'; + +import { RequestObjectOpts } from './types'; + +export const assertValidRequestObjectOpts = (opts: RequestObjectOpts, checkRequestObject: boolean) => { + if (!opts) { + throw new Error(SIOPErrors.BAD_PARAMS); + } else if (opts.passBy !== PassBy.REFERENCE && opts.passBy !== PassBy.VALUE) { + throw new Error(SIOPErrors.REQUEST_OBJECT_TYPE_NOT_SET); + } else if (opts.passBy === PassBy.REFERENCE && !opts.reference_uri) { + throw new Error(SIOPErrors.NO_REFERENCE_URI); + } else if (!opts.payload) { + if (opts.reference_uri) { + // reference URI, but no actual payload to host there! + throw Error(SIOPErrors.REFERENCE_URI_NO_PAYLOAD); + } else if (checkRequestObject) { + throw Error(SIOPErrors.BAD_PARAMS); + } + } + // assertValidRequestRegistrationOpts(opts['registration'] ? opts['registration'] : opts['clientMetadata']); +}; diff --git a/packages/siopv2/src/request-object/Payload.ts b/packages/siopv2/src/request-object/Payload.ts new file mode 100644 index 00000000..f253d149 --- /dev/null +++ b/packages/siopv2/src/request-object/Payload.ts @@ -0,0 +1,67 @@ +import { ResponseType } from '@sphereon/oid4vc-common'; +import { v4 as uuidv4 } from 'uuid'; + +import { CreateAuthorizationRequestOpts, createPresentationDefinitionClaimsProperties } from '../authorization-request'; +import { createRequestRegistration } from '../authorization-request/RequestRegistration'; +import { getNonce, getState, removeNullUndefined } from '../helpers'; +import { RequestObjectPayload, ResponseMode, Scope, SIOPErrors, SupportedVersion } from '../types'; + +import { assertValidRequestObjectOpts } from './Opts'; + +export const createRequestObjectPayload = async (opts: CreateAuthorizationRequestOpts): Promise => { + assertValidRequestObjectOpts(opts.requestObject, false); + if (!opts.requestObject?.payload) { + return undefined; // No request object apparently + } + assertValidRequestObjectOpts(opts.requestObject, true); + + const payload = opts.requestObject.payload; + + const state = getState(payload.state); + 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 now = Math.round(new Date().getTime() / 1000); + const validInSec = 120; // todo config/option + const iat = payload.iat ?? now; + const nbf = payload.nbf ?? iat; + const exp = payload.exp ?? iat + validInSec; + const jti = payload.jti ?? uuidv4(); + + return removeNullUndefined({ + 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, + redirect_uri: payload.redirect_uri, + response_mode: payload.response_mode ?? ResponseMode.DIRECT_POST, + ...(payload.id_token_hint && { id_token_hint: payload.id_token_hint }), + registration_uri: registration.clientMetadataOpts.reference_uri, + nonce: getNonce(state, payload.nonce), + state, + ...registration.payload, + claims, + presentation_definition_uri: payload.presentation_definition_uri, + presentation_definition: payload.presentation_definition, + iat, + nbf, + exp, + jti, + }); +}; + +export const assertValidRequestObjectPayload = (verPayload: RequestObjectPayload): void => { + if (verPayload['registration_uri'] && verPayload['registration']) { + throw new Error(`${SIOPErrors.REG_OBJ_N_REG_URI_CANT_BE_SET_SIMULTANEOUSLY}`); + } +}; diff --git a/packages/siopv2/src/request-object/RequestObject.ts b/packages/siopv2/src/request-object/RequestObject.ts new file mode 100644 index 00000000..597217ea --- /dev/null +++ b/packages/siopv2/src/request-object/RequestObject.ts @@ -0,0 +1,136 @@ +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 { assertValidRequestObjectOpts } from './Opts'; +import { assertValidRequestObjectPayload, createRequestObjectPayload } from './Payload'; +import { RequestObjectOpts } from './types'; + +export class RequestObject { + private payload: RequestObjectPayload; + private jwt: RequestObjectJwt; + private readonly opts: RequestObjectOpts; + + private constructor( + opts?: CreateAuthorizationRequestOpts | RequestObjectOpts, + payload?: RequestObjectPayload, + jwt?: string, + ) { + this.opts = opts ? RequestObject.mergeOAuth2AndOpenIdProperties(opts) : undefined; + this.payload = payload; + this.jwt = jwt; + } + + /** + * Create a request object that typically is used as a JWT on RP side, typically this method is called automatically when creating an Authorization Request, but you could use it directly! + * + * @param authorizationRequestOpts Request Object options to build a Request Object + * @remarks This method is used to generate a SIOP request Object. + * First it generates the request object payload, and then it a signed JWT can be accessed on request. + * + * Normally you will want to use the Authorization Request class. That class creates a URI that includes the JWT from this class in the URI + * If you do use this class directly, you can call the `convertRequestObjectToURI` afterwards to get the URI. + * Please note that the Authorization Request allows you to differentiate between OAuth2 and OpenID parameters that become + * part of the URI and which become part of the Request Object. If you generate a URI based upon the result of this class, + * the URI will be constructed based on the Request Object only! + */ + 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 requestObjectOpts = RequestObject.mergeOAuth2AndOpenIdProperties(authorizationRequestOpts); + const mergedOpts = { + ...authorizationRequestOpts, + requestObject: { ...authorizationRequestOpts.requestObject, ...requestObjectOpts, signature }, + }; + return new RequestObject(mergedOpts, await createRequestObjectPayload(mergedOpts)); + } + + public static async fromJwt(requestObjectJwt: RequestObjectJwt) { + return requestObjectJwt ? new RequestObject(undefined, undefined, requestObjectJwt) : undefined; + } + + public static async fromPayload(requestObjectPayload: RequestObjectPayload, authorizationRequestOpts: CreateAuthorizationRequestOpts) { + return new RequestObject(authorizationRequestOpts, requestObjectPayload); + } + + public static async fromAuthorizationRequestPayload(payload: AuthorizationRequestPayload): Promise { + const requestObjectJwt = + payload.request || payload.request_uri ? await fetchByReferenceOrUseByValue(payload.request_uri, payload.request, true) : undefined; + return requestObjectJwt ? await RequestObject.fromJwt(requestObjectJwt) : undefined; + } + + public async toJwt(): Promise { + if (!this.jwt) { + if (!this.opts) { + throw Error(SIOPErrors.BAD_PARAMS); + } else if (!this.payload) { + return undefined; + } + this.removeRequestProperties(); + if (this.payload.registration_uri) { + delete this.payload.registration; + } + assertValidRequestObjectPayload(this.payload); + + this.jwt = await signRequestObjectPayload(this.payload, this.opts); + } + return this.jwt; + } + + public async getPayload(): Promise { + if (!this.payload) { + if (!this.jwt) { + return undefined; + } + this.payload = removeNullUndefined(decodeJWT(this.jwt).payload) as RequestObjectPayload; + this.removeRequestProperties(); + if (this.payload.registration_uri) { + delete this.payload.registration; + } else if (this.payload.registration) { + delete this.payload.registration_uri; + } + } + assertValidRequestObjectPayload(this.payload); + return this.payload; + } + + public async assertValid(): Promise { + if (this.options) { + assertValidRequestObjectOpts(this.options, false); + } + assertValidRequestObjectPayload(await this.getPayload()); + } + + public get options(): RequestObjectOpts | undefined { + return this.opts; + } + + private removeRequestProperties(): void { + if (this.payload) { + // https://openid.net/specs/openid-connect-core-1_0.html#RequestObject + // request and request_uri parameters MUST NOT be included in Request Objects. + delete this.payload.request; + delete this.payload.request_uri; + } + } + + private static mergeOAuth2AndOpenIdProperties( + opts: CreateAuthorizationRequestOpts | RequestObjectOpts, + ): RequestObjectOpts { + if (!opts) { + throw Error(SIOPErrors.BAD_PARAMS); + } + 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; + } + delete mergedOpts?.request?.requestObject; + return isAuthReq ? mergedOpts.requestObject : mergedOpts; + } +} diff --git a/packages/siopv2/src/request-object/index.ts b/packages/siopv2/src/request-object/index.ts new file mode 100644 index 00000000..7201fcfa --- /dev/null +++ b/packages/siopv2/src/request-object/index.ts @@ -0,0 +1,3 @@ +export * from './RequestObject'; +export * from './types'; +export * from './Payload'; diff --git a/packages/siopv2/src/request-object/types.ts b/packages/siopv2/src/request-object/types.ts new file mode 100644 index 00000000..c9588218 --- /dev/null +++ b/packages/siopv2/src/request-object/types.ts @@ -0,0 +1,7 @@ +import { ClaimPayloadCommonOpts, RequestObjectPayloadOpts } from '../authorization-request'; +import { ExternalSignature, InternalSignature, NoSignature, ObjectBy, SuppliedSignature } from '../types'; + +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) +} diff --git a/packages/siopv2/src/rp/InMemoryRPSessionManager.ts b/packages/siopv2/src/rp/InMemoryRPSessionManager.ts new file mode 100644 index 00000000..1c49dc7b --- /dev/null +++ b/packages/siopv2/src/rp/InMemoryRPSessionManager.ts @@ -0,0 +1,263 @@ +import { EventEmitter } from 'events'; + +import { AuthorizationRequest } from '../authorization-request'; +import { AuthorizationResponse } from '../authorization-response'; +import { + AuthorizationEvent, + AuthorizationEvents, + AuthorizationRequestState, + AuthorizationRequestStateStatus, + AuthorizationResponseState, + AuthorizationResponseStateStatus, +} from '../types'; + +import { IRPSessionManager } from './types'; + +/** + * Please note that this session manager is not really meant to be used in large production settings, as it stores everything in memory! + * It also doesn't do scheduled cleanups. It runs a cleanup whenever a request or response is received. In a high-volume production setting you will want scheduled cleanups running in the background + * Since this is a low level library we have not created a full-fledged implementation. + * We suggest to create your own implementation using the event system of the library + */ +export class InMemoryRPSessionManager implements IRPSessionManager { + private readonly authorizationRequests: Record = {}; + private readonly authorizationResponses: Record = {}; + + // stored by hashcode + private readonly nonceMapping: Record = {}; + // stored by hashcode + private readonly stateMapping: Record = {}; + private readonly maxAgeInSeconds: number; + + private static getKeysForCorrelationId(mapping: Record, correlationId: string): number[] { + return Object.entries(mapping) + .filter((entry) => entry[1] === correlationId) + .map((filtered) => Number.parseInt(filtered[0])); + } + + public constructor(eventEmitter: EventEmitter, opts?: { maxAgeInSeconds?: number }) { + if (!eventEmitter) { + throw Error('RP Session manager depends on an event emitter in the application'); + } + this.maxAgeInSeconds = opts?.maxAgeInSeconds ?? 5 * 60; + eventEmitter.on(AuthorizationEvents.ON_AUTH_REQUEST_CREATED_SUCCESS, this.onAuthorizationRequestCreatedSuccess.bind(this)); + eventEmitter.on(AuthorizationEvents.ON_AUTH_REQUEST_CREATED_FAILED, this.onAuthorizationRequestCreatedFailed.bind(this)); + eventEmitter.on(AuthorizationEvents.ON_AUTH_REQUEST_SENT_SUCCESS, this.onAuthorizationRequestSentSuccess.bind(this)); + eventEmitter.on(AuthorizationEvents.ON_AUTH_REQUEST_SENT_FAILED, this.onAuthorizationRequestSentFailed.bind(this)); + eventEmitter.on(AuthorizationEvents.ON_AUTH_RESPONSE_RECEIVED_SUCCESS, this.onAuthorizationResponseReceivedSuccess.bind(this)); + eventEmitter.on(AuthorizationEvents.ON_AUTH_RESPONSE_RECEIVED_FAILED, this.onAuthorizationResponseReceivedFailed.bind(this)); + eventEmitter.on(AuthorizationEvents.ON_AUTH_RESPONSE_VERIFIED_SUCCESS, this.onAuthorizationResponseVerifiedSuccess.bind(this)); + eventEmitter.on(AuthorizationEvents.ON_AUTH_RESPONSE_VERIFIED_FAILED, this.onAuthorizationResponseVerifiedFailed.bind(this)); + } + + async getRequestStateByCorrelationId(correlationId: string, errorOnNotFound?: boolean): Promise { + return await this.getFromMapping('correlationId', correlationId, this.authorizationRequests, errorOnNotFound); + } + + async getRequestStateByNonce(nonce: string, errorOnNotFound?: boolean): Promise { + return await this.getFromMapping('nonce', nonce, this.authorizationRequests, errorOnNotFound); + } + + async getRequestStateByState(state: string, errorOnNotFound?: boolean): Promise { + return await this.getFromMapping('state', state, this.authorizationRequests, errorOnNotFound); + } + + async getResponseStateByCorrelationId(correlationId: string, errorOnNotFound?: boolean): Promise { + return await this.getFromMapping('correlationId', correlationId, this.authorizationResponses, errorOnNotFound); + } + + async getResponseStateByNonce(nonce: string, errorOnNotFound?: boolean): Promise { + return await this.getFromMapping('nonce', nonce, this.authorizationResponses, errorOnNotFound); + } + + async getResponseStateByState(state: string, errorOnNotFound?: boolean): Promise { + return await this.getFromMapping('state', state, this.authorizationResponses, errorOnNotFound); + } + + private async getFromMapping( + type: 'nonce' | 'state' | 'correlationId', + value: string, + mapping: Record, + errorOnNotFound?: boolean, + ): Promise { + const correlationId = await this.getCorrelationIdImpl(type, value, errorOnNotFound); + const result = mapping[correlationId] as T; + if (!result && errorOnNotFound) { + throw Error(`Could not find ${type} from correlation id ${correlationId}`); + } + return result; + } + + private async onAuthorizationRequestCreatedSuccess(event: AuthorizationEvent): Promise { + this.cleanup().catch((error) => console.log(JSON.stringify(error))); + this.updateState('request', event, AuthorizationRequestStateStatus.CREATED).catch((error) => console.log(JSON.stringify(error))); + } + + private async onAuthorizationRequestCreatedFailed(event: AuthorizationEvent): Promise { + this.cleanup().catch((error) => console.log(JSON.stringify(error))); + this.updateState('request', event, AuthorizationRequestStateStatus.ERROR).catch((error) => console.log(JSON.stringify(error))); + } + + private async onAuthorizationRequestSentSuccess(event: AuthorizationEvent): Promise { + this.cleanup().catch((error) => console.log(JSON.stringify(error))); + this.updateState('request', event, AuthorizationRequestStateStatus.SENT).catch((error) => console.log(JSON.stringify(error))); + } + + private async onAuthorizationRequestSentFailed(event: AuthorizationEvent): Promise { + this.cleanup().catch((error) => console.log(JSON.stringify(error))); + this.updateState('request', event, AuthorizationRequestStateStatus.ERROR).catch((error) => console.log(JSON.stringify(error))); + } + + private async onAuthorizationResponseReceivedSuccess(event: AuthorizationEvent): Promise { + this.cleanup().catch((error) => console.log(JSON.stringify(error))); + await this.updateState('response', event, AuthorizationResponseStateStatus.RECEIVED); + } + + private async onAuthorizationResponseReceivedFailed(event: AuthorizationEvent): Promise { + this.cleanup().catch((error) => console.log(JSON.stringify(error))); + await this.updateState('response', event, AuthorizationResponseStateStatus.ERROR); + } + + private async onAuthorizationResponseVerifiedFailed(event: AuthorizationEvent): Promise { + await this.updateState('response', event, AuthorizationResponseStateStatus.ERROR); + } + + private async onAuthorizationResponseVerifiedSuccess(event: AuthorizationEvent): Promise { + await this.updateState('response', event, AuthorizationResponseStateStatus.VERIFIED); + } + + public async getCorrelationIdByNonce(nonce: string, errorOnNotFound?: boolean): Promise { + return await this.getCorrelationIdImpl('nonce', nonce, errorOnNotFound); + } + + public async getCorrelationIdByState(state: string, errorOnNotFound?: boolean): Promise { + return await this.getCorrelationIdImpl('state', state, errorOnNotFound); + } + + private async getCorrelationIdImpl( + type: 'nonce' | 'state' | 'correlationId', + value: string, + errorOnNotFound?: boolean, + ): Promise { + if (!value || !type) { + throw Error(`No type or value provided, type: ${type}, value: ${value}`); + } + if (type === 'correlationId') { + return value; + } + const hash = await hashCode(value); + const correlationId = type === 'nonce' ? this.nonceMapping[hash] : this.stateMapping[hash]; + if (!correlationId && errorOnNotFound) { + throw Error(`Could not find ${type} value for ${value}`); + } + return correlationId; + } + + private async updateMapping( + mapping: Record, + event: AuthorizationEvent, + key: string, + value: string | undefined, + allowExisting: boolean, + ) { + const hash = await hashcodeForValue(event, key); + const existing = mapping[hash]; + if (existing) { + if (!allowExisting) { + throw Error(`Mapping exists for key ${key} and we do not allow overwriting values`); + } else if (value && existing !== value) { + throw Error('Value changed for key'); + } + } + if (!value) { + delete mapping[hash]; + } else { + mapping[hash] = value; + } + } + + private async updateState( + type: 'request' | 'response', + event: AuthorizationEvent, + status: AuthorizationRequestStateStatus | AuthorizationResponseStateStatus, + ): Promise { + if (!event) { + throw new Error('event not present'); + } else if (!event.correlationId) { + throw new Error(`'${type} ${status}' event without correlation id received`); + } + try { + const eventState = { + correlationId: event.correlationId, + ...(type === 'request' ? { request: event.subject } : {}), + ...(type === 'response' ? { response: event.subject } : {}), + ...(event.error ? { error: event.error } : {}), + status, + timestamp: event.timestamp, + lastUpdated: event.timestamp, + }; + if (type === 'request') { + this.authorizationRequests[event.correlationId] = eventState as AuthorizationRequestState; + // We do not await these + this.updateMapping(this.nonceMapping, event, 'nonce', event.correlationId, true).catch((error) => console.log(JSON.stringify(error))); + this.updateMapping(this.stateMapping, event, 'state', event.correlationId, true).catch((error) => console.log(JSON.stringify(error))); + } else { + this.authorizationResponses[event.correlationId] = eventState as AuthorizationResponseState; + } + } catch (error: unknown) { + console.log(`Error in update state happened: ${error}`); + // TODO VDX-166 handle error + } + } + + async deleteStateForCorrelationId(correlationId: string) { + InMemoryRPSessionManager.cleanMappingForCorrelationId(this.nonceMapping, correlationId).catch((error) => console.log(JSON.stringify(error))); + InMemoryRPSessionManager.cleanMappingForCorrelationId(this.stateMapping, correlationId).catch((error) => console.log(JSON.stringify(error))); + delete this.authorizationRequests[correlationId]; + delete this.authorizationResponses[correlationId]; + } + private static async cleanMappingForCorrelationId(mapping: Record, correlationId: string): Promise { + const keys = InMemoryRPSessionManager.getKeysForCorrelationId(mapping, correlationId); + if (keys && keys.length > 0) { + keys.forEach((key) => delete mapping[key]); + } + } + + private async cleanup() { + const now = Date.now(); + const maxAgeInMS = this.maxAgeInSeconds * 1000; + + const cleanupCorrelations = (reqByCorrelationId: [string, AuthorizationRequestState | AuthorizationResponseState]) => { + const correlationId = reqByCorrelationId[0]; + const authRequest = reqByCorrelationId[1]; + if (authRequest) { + const ts = authRequest.lastUpdated || authRequest.timestamp; + if (maxAgeInMS !== 0 && now > ts + maxAgeInMS) { + this.deleteStateForCorrelationId(correlationId); + } + } + }; + + Object.entries(this.authorizationRequests).forEach((reqByCorrelationId) => { + cleanupCorrelations.call(this, reqByCorrelationId); + }); + Object.entries(this.authorizationResponses).forEach((resByCorrelationId) => { + cleanupCorrelations.call(this, resByCorrelationId); + }); + } +} + +async function hashcodeForValue(event: AuthorizationEvent, key: string): Promise { + const value = (await event.subject.getMergedProperty(key)) as string; + if (!value) { + throw Error(`No value found for key ${key} in Authorization Request`); + } + return hashCode(value); +} + +function hashCode(s: string): number { + let h = 1; + for (let i = 0; i < s.length; i++) h = (Math.imul(31, h) + s.charCodeAt(i)) | 0; + + return h; +} diff --git a/packages/siopv2/src/rp/Opts.ts b/packages/siopv2/src/rp/Opts.ts new file mode 100644 index 00000000..b3a6b72d --- /dev/null +++ b/packages/siopv2/src/rp/Opts.ts @@ -0,0 +1,117 @@ +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, RequestObjectPayload, SIOPErrors, Verification, VerificationMode } from '../types'; + +import { RPBuilder } from './RPBuilder'; + +export const createRequestOptsFromBuilderOrExistingOpts = (opts: { builder?: RPBuilder; createRequestOpts?: CreateAuthorizationRequestOpts }) => { + const version = opts.builder ? opts.builder.getSupportedRequestVersion() : opts.createRequestOpts.version; + if (!version) { + throw Error(SIOPErrors.NO_REQUEST_VERSION); + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const createRequestOpts: CreateAuthorizationRequestOpts = opts.builder + ? { + version, + payload: { + ...opts.builder.authorizationRequestPayload, + // ...(isTargetOrNoTargets(PropertyTarget.AUTHORIZATION_REQUEST, opts.builder.requestObjectBy.targets) ? {passBy: opts.builder.requestObjectBy.passBy, request_uri: opts.buigfdlder.requestObjectBy.referenceUri}: {}) + //response_types_supported: opts.builder.clientMetadata?.responseTypesSupported, + // subject_types_supported: opts.builder.clientMetadata?.subjectTypesSupported, + // request_object_signing_alg_values_supported: opts.builder.clientMetadata?.requestObjectSigningAlgValuesSupported + //scopes_supported: opts.builder.clientMetadata?.scopesSupported, + }, + requestObject: { + ...opts.builder.requestObjectBy, + payload: { + ...(opts.builder.requestObjectPayload as RequestObjectPayload), + subject_types_supported: opts.builder.clientMetadata?.subjectTypesSupported, + request_object_signing_alg_values_supported: opts.builder.clientMetadata?.requestObjectSigningAlgValuesSupported, + }, + signature: opts.builder.signature, + }, + clientMetadata: opts.builder.clientMetadata as ClientMetadataOpts, + } + : opts.createRequestOpts; + + /*const valid = true; // fixme: re-enable schema: CreateAuthorizationRequestOptsSchema(createRequestOpts); + if (!valid) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + throw new Error('RP builder validation error: ' + JSON.stringify(CreateAuthorizationRequestOptsSchema.errors)); + }*/ + return createRequestOpts; +}; + +export const createVerifyResponseOptsFromBuilderOrExistingOpts = (opts: { + builder?: RPBuilder; + verifyOpts?: VerifyAuthorizationResponseOpts; +}): Partial => { + 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, + 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 Verification, + audience: opts.builder.clientId ?? opts.builder.clientMetadata?.client_id, + } + : opts.verifyOpts; +}; + +export const isTargetOrNoTargets = (searchTarget: PropertyTarget, targets?: PropertyTargets): boolean => { + if (!targets) { + return true; + } + return isTarget(searchTarget, targets); +}; + +export const isTarget = (searchTarget: PropertyTarget, targets: PropertyTargets): boolean => { + return Array.isArray(targets) ? targets.includes(searchTarget) : targets === searchTarget; +}; + +export const assignIfAuth = (opt: RequestPropertyWithTargets, isDefaultTarget?: boolean): T => { + if ( + isDefaultTarget + ? isTargetOrNoTargets(PropertyTarget.AUTHORIZATION_REQUEST, opt.targets) + : isTarget(PropertyTarget.AUTHORIZATION_REQUEST, opt.targets) + ) { + return opt.propertyValue; + } + return undefined; +}; + +export const assignIfRequestObject = (opt: RequestPropertyWithTargets, isDefaultTarget?: boolean): T => { + if (isDefaultTarget ? isTargetOrNoTargets(PropertyTarget.REQUEST_OBJECT, opt.targets) : isTarget(PropertyTarget.REQUEST_OBJECT, opt.targets)) { + return opt.propertyValue; + } + return undefined; +}; diff --git a/packages/siopv2/src/rp/RP.ts b/packages/siopv2/src/rp/RP.ts new file mode 100644 index 00000000..fc481eae --- /dev/null +++ b/packages/siopv2/src/rp/RP.ts @@ -0,0 +1,367 @@ +import { EventEmitter } from 'events'; + +import { v4 as uuidv4 } from 'uuid'; + +import { + AuthorizationRequest, + ClaimPayloadCommonOpts, + CreateAuthorizationRequestOpts, + PropertyTarget, + RequestObjectPayloadOpts, + RequestPropertyWithTargets, + URI, +} from '../authorization-request'; +import { mergeVerificationOpts } from '../authorization-request/Opts'; +import { AuthorizationResponse, PresentationDefinitionWithLocation, VerifyAuthorizationResponseOpts } from '../authorization-response'; +import { getNonce, getState } from '../helpers'; +import { + AuthorizationEvent, + AuthorizationEvents, + AuthorizationResponsePayload, + CheckLinkedDomain, + ExternalVerification, + InternalVerification, + PassBy, + RegisterEventListener, + ResponseURIType, + SIOPErrors, + SupportedVersion, + VerifiedAuthorizationResponse, +} from '../types'; + +import { createRequestOptsFromBuilderOrExistingOpts, createVerifyResponseOptsFromBuilderOrExistingOpts, isTargetOrNoTargets } from './Opts'; +import { RPBuilder } from './RPBuilder'; +import { IRPSessionManager } from './types'; + +export class RP { + get sessionManager(): IRPSessionManager { + return this._sessionManager; + } + + private readonly _createRequestOptions: CreateAuthorizationRequestOpts; + private readonly _verifyResponseOptions: Partial; + private readonly _eventEmitter?: EventEmitter; + private readonly _sessionManager?: IRPSessionManager; + + private constructor(opts: { + builder?: RPBuilder; + createRequestOpts?: CreateAuthorizationRequestOpts; + verifyResponseOpts?: VerifyAuthorizationResponseOpts; + }) { + // const claims = opts.builder?.claims || opts.createRequestOpts?.payload.claims; + this._createRequestOptions = createRequestOptsFromBuilderOrExistingOpts(opts); + this._verifyResponseOptions = { ...createVerifyResponseOptsFromBuilderOrExistingOpts(opts) }; + this._eventEmitter = opts.builder?.eventEmitter; + this._sessionManager = opts.builder?.sessionManager; + } + + public static fromRequestOpts(opts: CreateAuthorizationRequestOpts): RP { + return new RP({ createRequestOpts: opts }); + } + + public static builder(opts?: { requestVersion?: SupportedVersion }): RPBuilder { + return RPBuilder.newInstance(opts?.requestVersion); + } + + public async createAuthorizationRequest(opts: { + correlationId: string; + nonce: string | RequestPropertyWithTargets; + state: string | RequestPropertyWithTargets; + claims?: ClaimPayloadCommonOpts | RequestPropertyWithTargets; + version?: SupportedVersion; + requestByReferenceURI?: string; + responseURI?: string; + responseURIType?: ResponseURIType; + }): Promise { + const authorizationRequestOpts = this.newAuthorizationRequestOpts(opts); + return AuthorizationRequest.fromOpts(authorizationRequestOpts) + .then((authorizationRequest: AuthorizationRequest) => { + void this.emitEvent(AuthorizationEvents.ON_AUTH_REQUEST_CREATED_SUCCESS, { + correlationId: opts.correlationId, + subject: authorizationRequest, + }); + return authorizationRequest; + }) + .catch((error: Error) => { + void this.emitEvent(AuthorizationEvents.ON_AUTH_REQUEST_CREATED_FAILED, { + correlationId: opts.correlationId, + error, + }); + throw error; + }); + } + + public async createAuthorizationRequestURI(opts: { + correlationId: string; + nonce: string | RequestPropertyWithTargets; + state: string | RequestPropertyWithTargets; + claims?: ClaimPayloadCommonOpts | RequestPropertyWithTargets; + version?: SupportedVersion; + requestByReferenceURI?: string; + responseURI?: string; + responseURIType?: ResponseURIType; + }): Promise { + const authorizationRequestOpts = this.newAuthorizationRequestOpts(opts); + + return await URI.fromOpts(authorizationRequestOpts) + .then(async (uri: URI) => { + void this.emitEvent(AuthorizationEvents.ON_AUTH_REQUEST_CREATED_SUCCESS, { + correlationId: opts.correlationId, + subject: await AuthorizationRequest.fromOpts(authorizationRequestOpts), + }); + return uri; + }) + .catch((error: Error) => { + void this.emitEvent(AuthorizationEvents.ON_AUTH_REQUEST_CREATED_FAILED, { + correlationId: opts.correlationId, + error, + }); + throw error; + }); + } + + public async signalAuthRequestRetrieved(opts: { correlationId: string; error?: Error }) { + if (!this.sessionManager) { + throw Error(`Cannot signal auth request retrieval when no session manager is registered`); + } + const state = await this.sessionManager.getRequestStateByCorrelationId(opts.correlationId, true); + void this.emitEvent(opts?.error ? AuthorizationEvents.ON_AUTH_REQUEST_SENT_FAILED : AuthorizationEvents.ON_AUTH_REQUEST_SENT_SUCCESS, { + correlationId: opts.correlationId, + ...(!opts?.error ? { subject: state.request } : {}), + ...(opts?.error ? { error: opts.error } : {}), + }); + } + + public async verifyAuthorizationResponse( + authorizationResponsePayload: AuthorizationResponsePayload, + opts?: { + correlationId?: string; + audience?: string; + state?: string; + nonce?: string; + verification?: InternalVerification | ExternalVerification; + presentationDefinitions?: PresentationDefinitionWithLocation | PresentationDefinitionWithLocation[]; + }, + ): Promise { + const state = opts?.state ?? this.verifyResponseOptions.state; + let correlationId: string | undefined = opts?.correlationId ?? state; + let authorizationResponse: AuthorizationResponse; + try { + authorizationResponse = await AuthorizationResponse.fromPayload(authorizationResponsePayload); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + void this.emitEvent(AuthorizationEvents.ON_AUTH_RESPONSE_RECEIVED_FAILED, { + correlationId: correlationId ?? uuidv4(), // correlation id possibly might not be derived from state in payload, hence a uuid as fallback + subject: authorizationResponsePayload, + error, + }); + throw error; + } + + try { + const verifyAuthenticationResponseOpts = await this.newVerifyAuthorizationResponseOpts(authorizationResponse, { + ...opts, + correlationId, + }); + correlationId = verifyAuthenticationResponseOpts.correlationId ?? correlationId; + void this.emitEvent(AuthorizationEvents.ON_AUTH_RESPONSE_RECEIVED_SUCCESS, { + correlationId, + subject: authorizationResponse, + }); + + const verifiedAuthorizationResponse = await authorizationResponse.verify(verifyAuthenticationResponseOpts); + void this.emitEvent(AuthorizationEvents.ON_AUTH_RESPONSE_VERIFIED_SUCCESS, { + correlationId, + subject: authorizationResponse, + }); + return verifiedAuthorizationResponse; + } catch (error) { + void this.emitEvent(AuthorizationEvents.ON_AUTH_RESPONSE_VERIFIED_FAILED, { + correlationId, + subject: authorizationResponse, + error, + }); + throw error; + } + } + + get createRequestOptions(): CreateAuthorizationRequestOpts { + return this._createRequestOptions; + } + + get verifyResponseOptions(): Partial { + return this._verifyResponseOptions; + } + + private newAuthorizationRequestOpts(opts: { + correlationId: string; + nonce: string | RequestPropertyWithTargets; + state: string | RequestPropertyWithTargets; + claims?: ClaimPayloadCommonOpts | RequestPropertyWithTargets; + version?: SupportedVersion; + requestByReferenceURI?: string; + responseURIType?: ResponseURIType; + responseURI?: string; + }): CreateAuthorizationRequestOpts { + const nonceWithTarget = + typeof opts.nonce === 'string' + ? { propertyValue: opts.nonce, targets: PropertyTarget.REQUEST_OBJECT } + : (opts?.nonce as RequestPropertyWithTargets); + const stateWithTarget = + typeof opts.state === 'string' + ? { propertyValue: opts.state, targets: PropertyTarget.REQUEST_OBJECT } + : (opts?.state as RequestPropertyWithTargets); + const claimsWithTarget = + opts?.claims && !('propertyValue' in opts.claims) + ? { propertyValue: opts.claims, targets: PropertyTarget.REQUEST_OBJECT } + : (opts?.claims as RequestPropertyWithTargets); + + const version = opts?.version ?? this._createRequestOptions.version; + if (!version) { + throw Error(SIOPErrors.NO_REQUEST_VERSION); + } + const referenceURI = opts.requestByReferenceURI ?? this._createRequestOptions?.requestObject?.reference_uri; + + let responseURIType: ResponseURIType = opts?.responseURIType; + let responseURI = this._createRequestOptions.requestObject.payload?.redirect_uri ?? this._createRequestOptions.payload?.redirect_uri; + if (responseURI) { + responseURIType = 'redirect_uri'; + } else { + responseURI = + opts.responseURI ?? this._createRequestOptions.requestObject.payload?.response_uri ?? this._createRequestOptions.payload?.response_uri; + responseURIType = opts?.responseURIType ?? 'response_uri'; + } + if (!responseURI) { + throw Error(`A response or redirect URI is required at this point`); + } else { + if (responseURIType === 'redirect_uri') { + if (this._createRequestOptions?.requestObject?.payload && !this._createRequestOptions.requestObject?.payload?.redirect_uri) { + this._createRequestOptions.requestObject.payload.redirect_uri = responseURI; + } + if (!referenceURI && !this._createRequestOptions.payload?.redirect_uri) { + this._createRequestOptions.payload.redirect_uri = responseURI; + } + } else if (responseURIType === 'response_uri') { + if (this._createRequestOptions?.requestObject?.payload && !this._createRequestOptions.requestObject?.payload?.response_uri) { + this._createRequestOptions.requestObject.payload.response_uri = responseURI; + } + if (!referenceURI && !this._createRequestOptions.payload?.response_uri) { + this._createRequestOptions.payload.response_uri = responseURI; + } + } + } + + const newOpts = { ...this._createRequestOptions, version }; + newOpts.requestObject.payload = newOpts.requestObject.payload ?? ({} as RequestObjectPayloadOpts); + newOpts.payload = newOpts.payload ?? {}; + if (referenceURI) { + if (newOpts.requestObject.passBy && newOpts.requestObject.passBy !== PassBy.REFERENCE) { + throw Error(`Cannot pass by reference with uri ${referenceURI} when mode is ${newOpts.requestObject.passBy}`); + } + newOpts.requestObject.reference_uri = referenceURI; + newOpts.requestObject.passBy = PassBy.REFERENCE; + } + + const state = getState(stateWithTarget.propertyValue); + if (stateWithTarget.propertyValue) { + if (isTargetOrNoTargets(PropertyTarget.AUTHORIZATION_REQUEST, stateWithTarget.targets)) { + newOpts.payload.state = state; + } + if (isTargetOrNoTargets(PropertyTarget.REQUEST_OBJECT, stateWithTarget.targets)) { + newOpts.requestObject.payload.state = state; + } + } + + const nonce = getNonce(state, nonceWithTarget.propertyValue); + if (nonceWithTarget.propertyValue) { + if (isTargetOrNoTargets(PropertyTarget.AUTHORIZATION_REQUEST, nonceWithTarget.targets)) { + newOpts.payload.nonce = nonce; + } + if (isTargetOrNoTargets(PropertyTarget.REQUEST_OBJECT, nonceWithTarget.targets)) { + newOpts.requestObject.payload.nonce = nonce; + } + } + if (claimsWithTarget?.propertyValue) { + if (isTargetOrNoTargets(PropertyTarget.AUTHORIZATION_REQUEST, claimsWithTarget.targets)) { + newOpts.payload.claims = { ...newOpts.payload.claims, ...claimsWithTarget.propertyValue }; + } + if (isTargetOrNoTargets(PropertyTarget.REQUEST_OBJECT, claimsWithTarget.targets)) { + newOpts.requestObject.payload.claims = { ...newOpts.requestObject.payload.claims, ...claimsWithTarget.propertyValue }; + } + } + return newOpts; + } + + private async newVerifyAuthorizationResponseOpts( + authorizationResponse: AuthorizationResponse, + opts: { + correlationId: string; + state?: string; + nonce?: string; + verification?: InternalVerification | ExternalVerification; + audience?: string; + checkLinkedDomain?: CheckLinkedDomain; + presentationDefinitions?: PresentationDefinitionWithLocation | PresentationDefinitionWithLocation[]; + }, + ): Promise { + let correlationId = opts?.correlationId ?? this._verifyResponseOptions.correlationId; + let state = opts?.state ?? this._verifyResponseOptions.state; + let nonce = opts?.nonce ?? this._verifyResponseOptions.nonce; + if (this.sessionManager) { + const resNonce = (await authorizationResponse.getMergedProperty('nonce', false)) as string; + const resState = (await authorizationResponse.getMergedProperty('state', false)) as string; + correlationId = await this.sessionManager.getCorrelationIdByNonce(resNonce, false); + if (!correlationId) { + correlationId = await this.sessionManager.getCorrelationIdByState(resState, false); + } + if (!correlationId) { + correlationId = nonce; + } + const requestState = await this.sessionManager.getRequestStateByCorrelationId(correlationId, false); + if (requestState) { + const reqNonce: string = await requestState.request.getMergedProperty('nonce'); + const reqState: string = await requestState.request.getMergedProperty('state'); + nonce = nonce ?? reqNonce; + state = state ?? reqState; + } + } + return { + ...this._verifyResponseOptions, + ...opts, + correlationId, + audience: + opts?.audience ?? + this._verifyResponseOptions.audience ?? + this._verifyResponseOptions.verification.resolveOpts.jwtVerifyOpts.audience ?? + this._createRequestOptions.payload.client_id, + state, + nonce, + verification: mergeVerificationOpts(this._verifyResponseOptions, opts), + presentationDefinitions: opts?.presentationDefinitions ?? this._verifyResponseOptions.presentationDefinitions, + }; + } + + private async emitEvent( + type: AuthorizationEvents, + payload: { correlationId: string; subject?: AuthorizationRequest | AuthorizationResponse | AuthorizationResponsePayload; error?: Error }, + ): Promise { + if (this._eventEmitter) { + try { + this._eventEmitter.emit(type, new AuthorizationEvent(payload)); + } catch (e) { + //Let's make sure events do not cause control flow issues + console.log(`Could not emit event ${type} for ${payload.correlationId} initial error if any: ${payload?.error}`); + } + } + } + + public addEventListener(register: RegisterEventListener) { + if (!this._eventEmitter) { + throw Error('Cannot add listeners if no event emitter is available'); + } + const events = Array.isArray(register.event) ? register.event : [register.event]; + for (const event of events) { + this._eventEmitter.addListener(event, register.listener); + } + } +} diff --git a/packages/siopv2/src/rp/RPBuilder.ts b/packages/siopv2/src/rp/RPBuilder.ts new file mode 100644 index 00000000..c23c97ed --- /dev/null +++ b/packages/siopv2/src/rp/RPBuilder.ts @@ -0,0 +1,373 @@ +import { EventEmitter } from 'events'; + +import { Config, getUniResolver, UniResolver } from '@sphereon/did-uni-client'; +import { ResponseType } from '@sphereon/oid4vc-common'; +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 { + AuthorizationRequestPayload, + CheckLinkedDomain, + ClientMetadataOpts, + EcdsaSignature, + ExternalSignature, + InternalSignature, + NoSignature, + ObjectBy, + PassBy, + RequestObjectPayload, + ResponseIss, + ResponseMode, + RevocationVerification, + RevocationVerificationCallback, + SigningAlgo, + SubjectSyntaxTypesSupportedValues, + SuppliedSignature, + SupportedVersion, +} from '../types'; + +import { assignIfAuth, assignIfRequestObject, isTarget, isTargetOrNoTargets } from './Opts'; +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; + revocationVerification?: RevocationVerification; + revocationVerificationCallback?: RevocationVerificationCallback; + presentationVerificationCallback?: PresentationVerificationCallback; + supportedVersions: SupportedVersion[]; + eventEmitter?: EventEmitter; + sessionManager?: IRPSessionManager; + private _authorizationRequestPayload: Partial = {}; + private _requestObjectPayload: Partial = {}; + + clientMetadata?: ClientMetadataOpts = undefined; + clientId: string; + + hasher: Hasher; + + private constructor(supportedRequestVersion?: SupportedVersion) { + if (supportedRequestVersion) { + this.addSupportedVersion(supportedRequestVersion); + } + } + + withScope(scope: string, targets?: PropertyTargets): RPBuilder { + this._authorizationRequestPayload.scope = assignIfAuth({ propertyValue: scope, targets }, false); + this._requestObjectPayload.scope = assignIfRequestObject({ propertyValue: scope, targets }, true); + return this; + } + + withResponseType(responseType: ResponseType | ResponseType[] | string, targets?: PropertyTargets): RPBuilder { + const propertyValue = Array.isArray(responseType) ? responseType.join(' ').trim() : responseType; + this._authorizationRequestPayload.response_type = assignIfAuth({ propertyValue, targets }, false); + this._requestObjectPayload.response_type = assignIfRequestObject({ propertyValue, targets }, true); + return this; + } + + withHasher(hasher: Hasher): RPBuilder { + this.hasher = hasher; + + return this; + } + + withClientId(clientId: string, targets?: PropertyTargets): RPBuilder { + this._authorizationRequestPayload.client_id = assignIfAuth({ propertyValue: clientId, targets }, false); + this._requestObjectPayload.client_id = assignIfRequestObject({ propertyValue: clientId, targets }, true); + this.clientId = clientId; + return this; + } + + withIssuer(issuer: ResponseIss, targets?: PropertyTargets): RPBuilder { + this._authorizationRequestPayload.iss = assignIfAuth({ propertyValue: issuer, targets }, false); + this._requestObjectPayload.iss = assignIfRequestObject({ propertyValue: issuer, targets }, true); + return this; + } + + withPresentationVerification(presentationVerificationCallback: PresentationVerificationCallback): RPBuilder { + this.presentationVerificationCallback = presentationVerificationCallback; + return this; + } + + withRevocationVerification(mode: RevocationVerification): RPBuilder { + this.revocationVerification = mode; + return this; + } + + withRevocationVerificationCallback(callback: RevocationVerificationCallback): RPBuilder { + this.revocationVerificationCallback = callback; + 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( + { + propertyValue: authorizationEndpoint, + targets, + }, + false, + ); + this._requestObjectPayload.authorization_endpoint = assignIfRequestObject( + { + propertyValue: authorizationEndpoint, + targets, + }, + true, + ); + 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); + return this; + } + + withRequestByReference(referenceUri: string): RPBuilder { + return this.withRequestBy(PassBy.REFERENCE, referenceUri /*, PropertyTarget.AUTHORIZATION_REQUEST*/); + } + withRequestByValue(): RPBuilder { + return this.withRequestBy(PassBy.VALUE, undefined /*, PropertyTarget.AUTHORIZATION_REQUEST*/); + } + + withRequestBy(passBy: PassBy, referenceUri?: string /*, targets?: PropertyTargets*/): RPBuilder { + if (passBy === PassBy.REFERENCE && !referenceUri) { + throw Error('Cannot use pass by reference without a reference URI'); + } + this.requestObjectBy = { + passBy, + reference_uri: referenceUri, + targets: PropertyTarget.AUTHORIZATION_REQUEST, + }; + return this; + } + + withResponseMode(responseMode: ResponseMode, targets?: PropertyTargets): RPBuilder { + this._authorizationRequestPayload.response_mode = assignIfAuth({ propertyValue: responseMode, targets }, false); + this._requestObjectPayload.response_mode = assignIfRequestObject({ propertyValue: responseMode, targets }, true); + return this; + } + + withClientMetadata(clientMetadata: ClientMetadataOpts, targets?: PropertyTargets): RPBuilder { + clientMetadata.targets = targets; + if (this.getSupportedRequestVersion() < SupportedVersion.SIOPv2_D11) { + this._authorizationRequestPayload.registration = assignIfAuth( + { + propertyValue: clientMetadata, + targets, + }, + false, + ); + this._requestObjectPayload.registration = assignIfRequestObject( + { + propertyValue: clientMetadata, + targets, + }, + true, + ); + } else { + this._authorizationRequestPayload.client_metadata = assignIfAuth( + { + propertyValue: clientMetadata, + targets, + }, + false, + ); + this._requestObjectPayload.client_metadata = assignIfRequestObject( + { + propertyValue: clientMetadata, + targets, + }, + true, + ); + } + this.clientMetadata = clientMetadata; + //fixme: Add URL + return this; + } + + // Only internal and supplied signatures supported for now + withSignature(signature: InternalSignature | SuppliedSignature): RPBuilder { + this.signature = signature; + 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 }); + return this; + } + + withPresentationDefinition(definitionOpts: { definition: IPresentationDefinition; definitionUri?: string }, targets?: PropertyTargets): RPBuilder { + const { definition, definitionUri } = definitionOpts; + + if (this.getSupportedRequestVersion() < SupportedVersion.SIOPv2_D11) { + const definitionProperties = { + presentation_definition: definition, + presentation_definition_uri: definitionUri, + }; + const vp_token = { ...definitionProperties }; + if (isTarget(PropertyTarget.AUTHORIZATION_REQUEST, targets)) { + this._authorizationRequestPayload.claims = { + ...(this._authorizationRequestPayload.claims ? this._authorizationRequestPayload.claims : {}), + vp_token: vp_token, + }; + } + if (isTargetOrNoTargets(PropertyTarget.REQUEST_OBJECT, targets)) { + this._requestObjectPayload.claims = { + ...(this._requestObjectPayload.claims ? this._requestObjectPayload.claims : {}), + vp_token: vp_token, + }; + } + } else { + this._authorizationRequestPayload.presentation_definition = assignIfAuth( + { + propertyValue: definition, + targets, + }, + false, + ); + this._authorizationRequestPayload.presentation_definition_uri = assignIfAuth( + { + propertyValue: definitionUri, + targets, + }, + true, + ); + this._requestObjectPayload.presentation_definition = assignIfRequestObject( + { + propertyValue: definition, + targets, + }, + true, + ); + this._requestObjectPayload.presentation_definition_uri = assignIfRequestObject( + { + propertyValue: definitionUri, + targets, + }, + true, + ); + } + return this; + } + + withWellknownDIDVerifyCallback(wellknownDIDVerifyCallback: VerifyCallback): RPBuilder { + this.wellknownDIDVerifyCallback = wellknownDIDVerifyCallback; + return this; + } + + private initSupportedVersions() { + if (!this.supportedVersions) { + this.supportedVersions = []; + } + } + + addSupportedVersion(supportedVersion: SupportedVersion): RPBuilder { + this.initSupportedVersions(); + if (!this.supportedVersions.includes(supportedVersion)) { + this.supportedVersions.push(supportedVersion); + } + return this; + } + + withSupportedVersions(supportedVersion: SupportedVersion[] | SupportedVersion): RPBuilder { + const versions = Array.isArray(supportedVersion) ? supportedVersion : [supportedVersion]; + for (const version of versions) { + this.addSupportedVersion(version); + } + return this; + } + + withEventEmitter(eventEmitter?: EventEmitter): RPBuilder { + this.eventEmitter = eventEmitter ?? new EventEmitter(); + return this; + } + + withSessionManager(sessionManager: IRPSessionManager): RPBuilder { + this.sessionManager = sessionManager; + return this; + } + + public getSupportedRequestVersion(requireVersion?: boolean): SupportedVersion | undefined { + if (!this.supportedVersions || this.supportedVersions.length === 0) { + if (requireVersion !== false) { + throw Error('No supported version supplied/available'); + } + return undefined; + } + return this.supportedVersions[0]; + } + + public static newInstance(supportedVersion?: SupportedVersion) { + return new RPBuilder(supportedVersion); + } + + build(): RP { + if (this.sessionManager && !this.eventEmitter) { + throw Error('Please enable the event emitter on the RP when using a replay registry'); + } + + // We do not want others to directly use the RP class + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return new RP({ builder: this }); + } + + get authorizationRequestPayload(): Partial { + return this._authorizationRequestPayload; + } + + get requestObjectPayload(): Partial { + return this._requestObjectPayload; + } + + /* public mergedPayload(): Partial { + return { ...this.authorizationRequestPayload, ...this.requestObjectPayload }; + }*/ +} diff --git a/packages/siopv2/src/rp/index.ts b/packages/siopv2/src/rp/index.ts new file mode 100644 index 00000000..505c7363 --- /dev/null +++ b/packages/siopv2/src/rp/index.ts @@ -0,0 +1,4 @@ +export * from './RP'; +export * from './RPBuilder'; +export * from './InMemoryRPSessionManager'; +export * from './types'; diff --git a/packages/siopv2/src/rp/types.ts b/packages/siopv2/src/rp/types.ts new file mode 100644 index 00000000..19743ab1 --- /dev/null +++ b/packages/siopv2/src/rp/types.ts @@ -0,0 +1,21 @@ +import { AuthorizationRequestState, AuthorizationResponseState } from '../types'; + +export interface IRPSessionManager { + getRequestStateByCorrelationId(correlationId: string, errorOnNotFound?: boolean): Promise; + + getRequestStateByNonce(nonce: string, errorOnNotFound?: boolean): Promise; + + getRequestStateByState(state: string, errorOnNotFound?: boolean): Promise; + + getResponseStateByCorrelationId(correlationId: string, errorOnNotFound?: boolean): Promise; + + getResponseStateByNonce(nonce: string, errorOnNotFound?: boolean): Promise; + + getResponseStateByState(state: string, errorOnNotFound?: boolean): Promise; + + getCorrelationIdByNonce(nonce: string, errorOnNotFound?: boolean): Promise; + + getCorrelationIdByState(state: string, errorOnNotFound?: boolean): Promise; + + deleteStateForCorrelationId(correlationId: string); +} diff --git a/packages/siopv2/src/schemas/AuthorizationRequestPayloadVD11.schema.ts b/packages/siopv2/src/schemas/AuthorizationRequestPayloadVD11.schema.ts new file mode 100644 index 00000000..13d85758 --- /dev/null +++ b/packages/siopv2/src/schemas/AuthorizationRequestPayloadVD11.schema.ts @@ -0,0 +1,1011 @@ +export const AuthorizationRequestPayloadVD11SchemaObj = { + "$id": "AuthorizationRequestPayloadVD11Schema", + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/AuthorizationRequestPayloadVD11", + "definitions": { + "AuthorizationRequestPayloadVD11": { + "type": "object", + "properties": { + "id_token_type": { + "type": "string" + }, + "client_metadata": { + "$ref": "#/definitions/RPRegistrationMetadataPayload" + }, + "client_metadata_uri": { + "type": "string" + }, + "iss": { + "type": "string" + }, + "sub": { + "type": "string" + }, + "aud": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "iat": { + "type": "number" + }, + "nbf": { + "type": "number" + }, + "type": { + "type": "string" + }, + "exp": { + "type": "number" + }, + "rexp": { + "type": "number" + }, + "jti": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "response_type": { + "anyOf": [ + { + "$ref": "#/definitions/ResponseType" + }, + { + "type": "string" + } + ] + }, + "client_id": { + "type": "string" + }, + "redirect_uri": { + "type": "string" + }, + "id_token_hint": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "state": { + "type": "string" + }, + "response_mode": { + "$ref": "#/definitions/ResponseMode" + }, + "request": { + "type": "string" + }, + "request_uri": { + "type": "string" + }, + "claims": { + "$ref": "#/definitions/ClaimPayloadCommon" + }, + "presentation_definition": { + "anyOf": [ + { + "$ref": "#/definitions/PresentationDefinitionV1" + }, + { + "$ref": "#/definitions/PresentationDefinitionV2" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/PresentationDefinitionV1" + } + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/PresentationDefinitionV2" + } + } + ] + }, + "presentation_definition_uri": { + "type": "string" + } + } + }, + "RPRegistrationMetadataPayload": { + "type": "object", + "properties": { + "client_id": { + "anyOf": [ + { + "type": "string" + }, + {} + ] + }, + "id_token_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "request_object_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "response_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseType" + } + }, + { + "$ref": "#/definitions/ResponseType" + } + ] + }, + "scopes_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/Scope" + } + }, + { + "$ref": "#/definitions/Scope" + } + ] + }, + "subject_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SubjectType" + } + }, + { + "$ref": "#/definitions/SubjectType" + } + ] + }, + "subject_syntax_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "vp_formats": { + "anyOf": [ + { + "$ref": "#/definitions/Format" + }, + {} + ] + }, + "client_name": { + "anyOf": [ + { + "type": "string" + }, + {} + ] + }, + "logo_uri": { + "anyOf": [ + {}, + { + "type": "string" + } + ] + }, + "client_purpose": { + "anyOf": [ + {}, + { + "type": "string" + } + ] + } + } + }, + "SigningAlgo": { + "type": "string", + "enum": [ + "EdDSA", + "RS256", + "PS256", + "ES256", + "ES256K" + ] + }, + "ResponseType": { + "type": "string", + "enum": [ + "code", + "id_token", + "vp_token" + ] + }, + "Scope": { + "type": "string", + "enum": [ + "openid", + "openid did_authn", + "profile", + "email", + "address", + "phone" + ] + }, + "SubjectType": { + "type": "string", + "enum": [ + "public", + "pairwise" + ] + }, + "Format": { + "type": "object", + "properties": { + "jwt": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc_json": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vp": { + "$ref": "#/definitions/JwtObject" + }, + "ldp": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vc": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vp": { + "$ref": "#/definitions/LdpObject" + }, + "vc+sd-jwt": { + "$ref": "#/definitions/SdJwtObject" + } + }, + "additionalProperties": false + }, + "JwtObject": { + "type": "object", + "properties": { + "alg": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "alg" + ], + "additionalProperties": false + }, + "LdpObject": { + "type": "object", + "properties": { + "proof_type": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "proof_type" + ], + "additionalProperties": false + }, + "SdJwtObject": { + "type": "object", + "properties": { + "sd_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + }, + "kb_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "ResponseMode": { + "type": "string", + "enum": [ + "fragment", + "form_post", + "post", + "direct_post", + "query" + ] + }, + "ClaimPayloadCommon": { + "type": "object" + }, + "PresentationDefinitionV1": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/definitions/Format" + }, + "submission_requirements": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmissionRequirement" + } + }, + "input_descriptors": { + "type": "array", + "items": { + "$ref": "#/definitions/InputDescriptorV1" + } + } + }, + "required": [ + "id", + "input_descriptors" + ], + "additionalProperties": false + }, + "SubmissionRequirement": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "rule": { + "$ref": "#/definitions/Rules" + }, + "count": { + "type": "number" + }, + "min": { + "type": "number" + }, + "max": { + "type": "number" + }, + "from": { + "type": "string" + }, + "from_nested": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmissionRequirement" + } + } + }, + "required": [ + "rule" + ], + "additionalProperties": false + }, + "Rules": { + "type": "string", + "enum": [ + "all", + "pick" + ] + }, + "InputDescriptorV1": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "group": { + "type": "array", + "items": { + "type": "string" + } + }, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Schema" + } + }, + "issuance": { + "type": "array", + "items": { + "$ref": "#/definitions/Issuance" + } + }, + "constraints": { + "$ref": "#/definitions/ConstraintsV1" + } + }, + "required": [ + "id", + "schema" + ], + "additionalProperties": false + }, + "Schema": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "uri" + ], + "additionalProperties": false + }, + "Issuance": { + "type": "object", + "properties": { + "manifest": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "ConstraintsV1": { + "type": "object", + "properties": { + "limit_disclosure": { + "$ref": "#/definitions/Optionality" + }, + "statuses": { + "$ref": "#/definitions/Statuses" + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/FieldV1" + } + }, + "subject_is_issuer": { + "$ref": "#/definitions/Optionality" + }, + "is_holder": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + }, + "same_subject": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + } + }, + "additionalProperties": false + }, + "Optionality": { + "type": "string", + "enum": [ + "required", + "preferred" + ] + }, + "Statuses": { + "type": "object", + "properties": { + "active": { + "$ref": "#/definitions/PdStatus" + }, + "suspended": { + "$ref": "#/definitions/PdStatus" + }, + "revoked": { + "$ref": "#/definitions/PdStatus" + } + }, + "additionalProperties": false + }, + "PdStatus": { + "type": "object", + "properties": { + "directive": { + "$ref": "#/definitions/Directives" + } + }, + "additionalProperties": false + }, + "Directives": { + "type": "string", + "enum": [ + "required", + "allowed", + "disallowed" + ] + }, + "FieldV1": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "array", + "items": { + "type": "string" + } + }, + "purpose": { + "type": "string" + }, + "filter": { + "$ref": "#/definitions/FilterV1" + }, + "predicate": { + "$ref": "#/definitions/Optionality" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + }, + "FilterV1": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "OneOfNumberString": { + "type": [ + "number", + "string" + ] + }, + "HolderSubject": { + "type": "object", + "properties": { + "field_id": { + "type": "array", + "items": { + "type": "string" + } + }, + "directive": { + "$ref": "#/definitions/Optionality" + } + }, + "required": [ + "field_id", + "directive" + ], + "additionalProperties": false + }, + "PresentationDefinitionV2": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/definitions/Format" + }, + "submission_requirements": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmissionRequirement" + } + }, + "input_descriptors": { + "type": "array", + "items": { + "$ref": "#/definitions/InputDescriptorV2" + } + }, + "frame": { + "type": "object" + } + }, + "required": [ + "id", + "input_descriptors" + ], + "additionalProperties": false + }, + "InputDescriptorV2": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/definitions/Format" + }, + "group": { + "type": "array", + "items": { + "type": "string" + } + }, + "issuance": { + "type": "array", + "items": { + "$ref": "#/definitions/Issuance" + } + }, + "constraints": { + "$ref": "#/definitions/ConstraintsV2" + } + }, + "required": [ + "id", + "constraints" + ], + "additionalProperties": false + }, + "ConstraintsV2": { + "type": "object", + "properties": { + "limit_disclosure": { + "$ref": "#/definitions/Optionality" + }, + "statuses": { + "$ref": "#/definitions/Statuses" + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/FieldV2" + } + }, + "subject_is_issuer": { + "$ref": "#/definitions/Optionality" + }, + "is_holder": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + }, + "same_subject": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + } + }, + "additionalProperties": false + }, + "FieldV2": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "array", + "items": { + "type": "string" + } + }, + "purpose": { + "type": "string" + }, + "filter": { + "$ref": "#/definitions/FilterV2" + }, + "predicate": { + "$ref": "#/definitions/Optionality" + }, + "name": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + }, + "FilterV2": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "formatMaximum": { + "type": "string" + }, + "formatMinimum": { + "type": "string" + }, + "formatExclusiveMaximum": { + "type": "string" + }, + "formatExclusiveMinimum": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + }, + "contains": { + "$ref": "#/definitions/FilterV2Base" + }, + "items": { + "$ref": "#/definitions/FilterV2BaseItems" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "FilterV2Base": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "formatMaximum": { + "type": "string" + }, + "formatMinimum": { + "type": "string" + }, + "formatExclusiveMaximum": { + "type": "string" + }, + "formatExclusiveMinimum": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + }, + "contains": { + "$ref": "#/definitions/FilterV2Base" + }, + "items": { + "$ref": "#/definitions/FilterV2BaseItems" + } + }, + "additionalProperties": false + }, + "FilterV2BaseItems": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "formatMaximum": { + "type": "string" + }, + "formatMinimum": { + "type": "string" + }, + "formatExclusiveMaximum": { + "type": "string" + }, + "formatExclusiveMinimum": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + }, + "contains": { + "$ref": "#/definitions/FilterV2Base" + }, + "items": { + "$ref": "#/definitions/FilterV2BaseItems" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + } +}; \ No newline at end of file diff --git a/packages/siopv2/src/schemas/AuthorizationRequestPayloadVD12OID4VPD18.schema.ts b/packages/siopv2/src/schemas/AuthorizationRequestPayloadVD12OID4VPD18.schema.ts new file mode 100644 index 00000000..15d152d1 --- /dev/null +++ b/packages/siopv2/src/schemas/AuthorizationRequestPayloadVD12OID4VPD18.schema.ts @@ -0,0 +1,1026 @@ +export const AuthorizationRequestPayloadVD12OID4VPD18SchemaObj = { + "$id": "AuthorizationRequestPayloadVD12OID4VPD18Schema", + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/AuthorizationRequestPayloadVD12OID4VPD18", + "definitions": { + "AuthorizationRequestPayloadVD12OID4VPD18": { + "type": "object", + "properties": { + "id_token_type": { + "type": "string" + }, + "client_metadata": { + "$ref": "#/definitions/RPRegistrationMetadataPayload" + }, + "client_metadata_uri": { + "type": "string" + }, + "iss": { + "type": "string" + }, + "sub": { + "type": "string" + }, + "aud": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "iat": { + "type": "number" + }, + "nbf": { + "type": "number" + }, + "type": { + "type": "string" + }, + "exp": { + "type": "number" + }, + "rexp": { + "type": "number" + }, + "jti": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "response_type": { + "anyOf": [ + { + "$ref": "#/definitions/ResponseType" + }, + { + "type": "string" + } + ] + }, + "client_id": { + "type": "string" + }, + "redirect_uri": { + "type": "string" + }, + "id_token_hint": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "state": { + "type": "string" + }, + "response_mode": { + "$ref": "#/definitions/ResponseMode" + }, + "request": { + "type": "string" + }, + "request_uri": { + "type": "string" + }, + "claims": { + "$ref": "#/definitions/ClaimPayloadCommon" + }, + "presentation_definition": { + "anyOf": [ + { + "$ref": "#/definitions/PresentationDefinitionV1" + }, + { + "$ref": "#/definitions/PresentationDefinitionV2" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/PresentationDefinitionV1" + } + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/PresentationDefinitionV2" + } + } + ] + }, + "presentation_definition_uri": { + "type": "string" + }, + "client_id_scheme": { + "$ref": "#/definitions/ClientIdScheme" + }, + "response_uri": { + "type": "string" + } + } + }, + "RPRegistrationMetadataPayload": { + "type": "object", + "properties": { + "client_id": { + "anyOf": [ + { + "type": "string" + }, + {} + ] + }, + "id_token_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "request_object_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "response_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseType" + } + }, + { + "$ref": "#/definitions/ResponseType" + } + ] + }, + "scopes_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/Scope" + } + }, + { + "$ref": "#/definitions/Scope" + } + ] + }, + "subject_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SubjectType" + } + }, + { + "$ref": "#/definitions/SubjectType" + } + ] + }, + "subject_syntax_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "vp_formats": { + "anyOf": [ + { + "$ref": "#/definitions/Format" + }, + {} + ] + }, + "client_name": { + "anyOf": [ + { + "type": "string" + }, + {} + ] + }, + "logo_uri": { + "anyOf": [ + {}, + { + "type": "string" + } + ] + }, + "client_purpose": { + "anyOf": [ + {}, + { + "type": "string" + } + ] + } + } + }, + "SigningAlgo": { + "type": "string", + "enum": [ + "EdDSA", + "RS256", + "PS256", + "ES256", + "ES256K" + ] + }, + "ResponseType": { + "type": "string", + "enum": [ + "code", + "id_token", + "vp_token" + ] + }, + "Scope": { + "type": "string", + "enum": [ + "openid", + "openid did_authn", + "profile", + "email", + "address", + "phone" + ] + }, + "SubjectType": { + "type": "string", + "enum": [ + "public", + "pairwise" + ] + }, + "Format": { + "type": "object", + "properties": { + "jwt": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc_json": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vp": { + "$ref": "#/definitions/JwtObject" + }, + "ldp": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vc": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vp": { + "$ref": "#/definitions/LdpObject" + }, + "vc+sd-jwt": { + "$ref": "#/definitions/SdJwtObject" + } + }, + "additionalProperties": false + }, + "JwtObject": { + "type": "object", + "properties": { + "alg": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "alg" + ], + "additionalProperties": false + }, + "LdpObject": { + "type": "object", + "properties": { + "proof_type": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "proof_type" + ], + "additionalProperties": false + }, + "SdJwtObject": { + "type": "object", + "properties": { + "sd_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + }, + "kb_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "ResponseMode": { + "type": "string", + "enum": [ + "fragment", + "form_post", + "post", + "direct_post", + "query" + ] + }, + "ClaimPayloadCommon": { + "type": "object" + }, + "PresentationDefinitionV1": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/definitions/Format" + }, + "submission_requirements": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmissionRequirement" + } + }, + "input_descriptors": { + "type": "array", + "items": { + "$ref": "#/definitions/InputDescriptorV1" + } + } + }, + "required": [ + "id", + "input_descriptors" + ], + "additionalProperties": false + }, + "SubmissionRequirement": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "rule": { + "$ref": "#/definitions/Rules" + }, + "count": { + "type": "number" + }, + "min": { + "type": "number" + }, + "max": { + "type": "number" + }, + "from": { + "type": "string" + }, + "from_nested": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmissionRequirement" + } + } + }, + "required": [ + "rule" + ], + "additionalProperties": false + }, + "Rules": { + "type": "string", + "enum": [ + "all", + "pick" + ] + }, + "InputDescriptorV1": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "group": { + "type": "array", + "items": { + "type": "string" + } + }, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Schema" + } + }, + "issuance": { + "type": "array", + "items": { + "$ref": "#/definitions/Issuance" + } + }, + "constraints": { + "$ref": "#/definitions/ConstraintsV1" + } + }, + "required": [ + "id", + "schema" + ], + "additionalProperties": false + }, + "Schema": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "uri" + ], + "additionalProperties": false + }, + "Issuance": { + "type": "object", + "properties": { + "manifest": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "ConstraintsV1": { + "type": "object", + "properties": { + "limit_disclosure": { + "$ref": "#/definitions/Optionality" + }, + "statuses": { + "$ref": "#/definitions/Statuses" + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/FieldV1" + } + }, + "subject_is_issuer": { + "$ref": "#/definitions/Optionality" + }, + "is_holder": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + }, + "same_subject": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + } + }, + "additionalProperties": false + }, + "Optionality": { + "type": "string", + "enum": [ + "required", + "preferred" + ] + }, + "Statuses": { + "type": "object", + "properties": { + "active": { + "$ref": "#/definitions/PdStatus" + }, + "suspended": { + "$ref": "#/definitions/PdStatus" + }, + "revoked": { + "$ref": "#/definitions/PdStatus" + } + }, + "additionalProperties": false + }, + "PdStatus": { + "type": "object", + "properties": { + "directive": { + "$ref": "#/definitions/Directives" + } + }, + "additionalProperties": false + }, + "Directives": { + "type": "string", + "enum": [ + "required", + "allowed", + "disallowed" + ] + }, + "FieldV1": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "array", + "items": { + "type": "string" + } + }, + "purpose": { + "type": "string" + }, + "filter": { + "$ref": "#/definitions/FilterV1" + }, + "predicate": { + "$ref": "#/definitions/Optionality" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + }, + "FilterV1": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "OneOfNumberString": { + "type": [ + "number", + "string" + ] + }, + "HolderSubject": { + "type": "object", + "properties": { + "field_id": { + "type": "array", + "items": { + "type": "string" + } + }, + "directive": { + "$ref": "#/definitions/Optionality" + } + }, + "required": [ + "field_id", + "directive" + ], + "additionalProperties": false + }, + "PresentationDefinitionV2": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/definitions/Format" + }, + "submission_requirements": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmissionRequirement" + } + }, + "input_descriptors": { + "type": "array", + "items": { + "$ref": "#/definitions/InputDescriptorV2" + } + }, + "frame": { + "type": "object" + } + }, + "required": [ + "id", + "input_descriptors" + ], + "additionalProperties": false + }, + "InputDescriptorV2": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/definitions/Format" + }, + "group": { + "type": "array", + "items": { + "type": "string" + } + }, + "issuance": { + "type": "array", + "items": { + "$ref": "#/definitions/Issuance" + } + }, + "constraints": { + "$ref": "#/definitions/ConstraintsV2" + } + }, + "required": [ + "id", + "constraints" + ], + "additionalProperties": false + }, + "ConstraintsV2": { + "type": "object", + "properties": { + "limit_disclosure": { + "$ref": "#/definitions/Optionality" + }, + "statuses": { + "$ref": "#/definitions/Statuses" + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/FieldV2" + } + }, + "subject_is_issuer": { + "$ref": "#/definitions/Optionality" + }, + "is_holder": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + }, + "same_subject": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + } + }, + "additionalProperties": false + }, + "FieldV2": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "array", + "items": { + "type": "string" + } + }, + "purpose": { + "type": "string" + }, + "filter": { + "$ref": "#/definitions/FilterV2" + }, + "predicate": { + "$ref": "#/definitions/Optionality" + }, + "name": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + }, + "FilterV2": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "formatMaximum": { + "type": "string" + }, + "formatMinimum": { + "type": "string" + }, + "formatExclusiveMaximum": { + "type": "string" + }, + "formatExclusiveMinimum": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + }, + "contains": { + "$ref": "#/definitions/FilterV2Base" + }, + "items": { + "$ref": "#/definitions/FilterV2BaseItems" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "FilterV2Base": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "formatMaximum": { + "type": "string" + }, + "formatMinimum": { + "type": "string" + }, + "formatExclusiveMaximum": { + "type": "string" + }, + "formatExclusiveMinimum": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + }, + "contains": { + "$ref": "#/definitions/FilterV2Base" + }, + "items": { + "$ref": "#/definitions/FilterV2BaseItems" + } + }, + "additionalProperties": false + }, + "FilterV2BaseItems": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "formatMaximum": { + "type": "string" + }, + "formatMinimum": { + "type": "string" + }, + "formatExclusiveMaximum": { + "type": "string" + }, + "formatExclusiveMinimum": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + }, + "contains": { + "$ref": "#/definitions/FilterV2Base" + }, + "items": { + "$ref": "#/definitions/FilterV2BaseItems" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "ClientIdScheme": { + "type": "string", + "enum": [ + "pre-registered", + "redirect_uri", + "entity_id", + "did" + ] + } + } +}; \ No newline at end of file diff --git a/packages/siopv2/src/schemas/AuthorizationRequestPayloadVID1.schema.ts b/packages/siopv2/src/schemas/AuthorizationRequestPayloadVID1.schema.ts new file mode 100644 index 00000000..ff1b5faf --- /dev/null +++ b/packages/siopv2/src/schemas/AuthorizationRequestPayloadVID1.schema.ts @@ -0,0 +1,1013 @@ +export const AuthorizationRequestPayloadVID1SchemaObj = { + "$id": "AuthorizationRequestPayloadVID1Schema", + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/AuthorizationRequestPayloadVID1", + "definitions": { + "AuthorizationRequestPayloadVID1": { + "type": "object", + "properties": { + "registration": { + "$ref": "#/definitions/RPRegistrationMetadataPayload" + }, + "registration_uri": { + "type": "string" + }, + "iss": { + "type": "string" + }, + "sub": { + "type": "string" + }, + "aud": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "iat": { + "type": "number" + }, + "nbf": { + "type": "number" + }, + "type": { + "type": "string" + }, + "exp": { + "type": "number" + }, + "rexp": { + "type": "number" + }, + "jti": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "response_type": { + "anyOf": [ + { + "$ref": "#/definitions/ResponseType" + }, + { + "type": "string" + } + ] + }, + "client_id": { + "type": "string" + }, + "redirect_uri": { + "type": "string" + }, + "id_token_hint": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "state": { + "type": "string" + }, + "response_mode": { + "$ref": "#/definitions/ResponseMode" + }, + "request": { + "type": "string" + }, + "request_uri": { + "type": "string" + }, + "claims": { + "$ref": "#/definitions/ClaimPayloadVID1" + } + } + }, + "RPRegistrationMetadataPayload": { + "type": "object", + "properties": { + "client_id": { + "anyOf": [ + { + "type": "string" + }, + {} + ] + }, + "id_token_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "request_object_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "response_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseType" + } + }, + { + "$ref": "#/definitions/ResponseType" + } + ] + }, + "scopes_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/Scope" + } + }, + { + "$ref": "#/definitions/Scope" + } + ] + }, + "subject_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SubjectType" + } + }, + { + "$ref": "#/definitions/SubjectType" + } + ] + }, + "subject_syntax_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "vp_formats": { + "anyOf": [ + { + "$ref": "#/definitions/Format" + }, + {} + ] + }, + "client_name": { + "anyOf": [ + { + "type": "string" + }, + {} + ] + }, + "logo_uri": { + "anyOf": [ + {}, + { + "type": "string" + } + ] + }, + "client_purpose": { + "anyOf": [ + {}, + { + "type": "string" + } + ] + } + } + }, + "SigningAlgo": { + "type": "string", + "enum": [ + "EdDSA", + "RS256", + "PS256", + "ES256", + "ES256K" + ] + }, + "ResponseType": { + "type": "string", + "enum": [ + "code", + "id_token", + "vp_token" + ] + }, + "Scope": { + "type": "string", + "enum": [ + "openid", + "openid did_authn", + "profile", + "email", + "address", + "phone" + ] + }, + "SubjectType": { + "type": "string", + "enum": [ + "public", + "pairwise" + ] + }, + "Format": { + "type": "object", + "properties": { + "jwt": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc_json": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vp": { + "$ref": "#/definitions/JwtObject" + }, + "ldp": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vc": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vp": { + "$ref": "#/definitions/LdpObject" + }, + "vc+sd-jwt": { + "$ref": "#/definitions/SdJwtObject" + } + }, + "additionalProperties": false + }, + "JwtObject": { + "type": "object", + "properties": { + "alg": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "alg" + ], + "additionalProperties": false + }, + "LdpObject": { + "type": "object", + "properties": { + "proof_type": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "proof_type" + ], + "additionalProperties": false + }, + "SdJwtObject": { + "type": "object", + "properties": { + "sd_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + }, + "kb_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "ResponseMode": { + "type": "string", + "enum": [ + "fragment", + "form_post", + "post", + "direct_post", + "query" + ] + }, + "ClaimPayloadVID1": { + "type": "object", + "properties": { + "id_token": { + "$ref": "#/definitions/IdTokenClaimPayload" + }, + "vp_token": { + "$ref": "#/definitions/VpTokenClaimPayload" + } + } + }, + "IdTokenClaimPayload": { + "type": "object" + }, + "VpTokenClaimPayload": { + "type": "object", + "properties": { + "presentation_definition": { + "anyOf": [ + { + "$ref": "#/definitions/PresentationDefinitionV1" + }, + { + "$ref": "#/definitions/PresentationDefinitionV2" + } + ] + }, + "presentation_definition_uri": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PresentationDefinitionV1": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/definitions/Format" + }, + "submission_requirements": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmissionRequirement" + } + }, + "input_descriptors": { + "type": "array", + "items": { + "$ref": "#/definitions/InputDescriptorV1" + } + } + }, + "required": [ + "id", + "input_descriptors" + ], + "additionalProperties": false + }, + "SubmissionRequirement": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "rule": { + "$ref": "#/definitions/Rules" + }, + "count": { + "type": "number" + }, + "min": { + "type": "number" + }, + "max": { + "type": "number" + }, + "from": { + "type": "string" + }, + "from_nested": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmissionRequirement" + } + } + }, + "required": [ + "rule" + ], + "additionalProperties": false + }, + "Rules": { + "type": "string", + "enum": [ + "all", + "pick" + ] + }, + "InputDescriptorV1": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "group": { + "type": "array", + "items": { + "type": "string" + } + }, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Schema" + } + }, + "issuance": { + "type": "array", + "items": { + "$ref": "#/definitions/Issuance" + } + }, + "constraints": { + "$ref": "#/definitions/ConstraintsV1" + } + }, + "required": [ + "id", + "schema" + ], + "additionalProperties": false + }, + "Schema": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "uri" + ], + "additionalProperties": false + }, + "Issuance": { + "type": "object", + "properties": { + "manifest": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "ConstraintsV1": { + "type": "object", + "properties": { + "limit_disclosure": { + "$ref": "#/definitions/Optionality" + }, + "statuses": { + "$ref": "#/definitions/Statuses" + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/FieldV1" + } + }, + "subject_is_issuer": { + "$ref": "#/definitions/Optionality" + }, + "is_holder": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + }, + "same_subject": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + } + }, + "additionalProperties": false + }, + "Optionality": { + "type": "string", + "enum": [ + "required", + "preferred" + ] + }, + "Statuses": { + "type": "object", + "properties": { + "active": { + "$ref": "#/definitions/PdStatus" + }, + "suspended": { + "$ref": "#/definitions/PdStatus" + }, + "revoked": { + "$ref": "#/definitions/PdStatus" + } + }, + "additionalProperties": false + }, + "PdStatus": { + "type": "object", + "properties": { + "directive": { + "$ref": "#/definitions/Directives" + } + }, + "additionalProperties": false + }, + "Directives": { + "type": "string", + "enum": [ + "required", + "allowed", + "disallowed" + ] + }, + "FieldV1": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "array", + "items": { + "type": "string" + } + }, + "purpose": { + "type": "string" + }, + "filter": { + "$ref": "#/definitions/FilterV1" + }, + "predicate": { + "$ref": "#/definitions/Optionality" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + }, + "FilterV1": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "OneOfNumberString": { + "type": [ + "number", + "string" + ] + }, + "HolderSubject": { + "type": "object", + "properties": { + "field_id": { + "type": "array", + "items": { + "type": "string" + } + }, + "directive": { + "$ref": "#/definitions/Optionality" + } + }, + "required": [ + "field_id", + "directive" + ], + "additionalProperties": false + }, + "PresentationDefinitionV2": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/definitions/Format" + }, + "submission_requirements": { + "type": "array", + "items": { + "$ref": "#/definitions/SubmissionRequirement" + } + }, + "input_descriptors": { + "type": "array", + "items": { + "$ref": "#/definitions/InputDescriptorV2" + } + }, + "frame": { + "type": "object" + } + }, + "required": [ + "id", + "input_descriptors" + ], + "additionalProperties": false + }, + "InputDescriptorV2": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/definitions/Format" + }, + "group": { + "type": "array", + "items": { + "type": "string" + } + }, + "issuance": { + "type": "array", + "items": { + "$ref": "#/definitions/Issuance" + } + }, + "constraints": { + "$ref": "#/definitions/ConstraintsV2" + } + }, + "required": [ + "id", + "constraints" + ], + "additionalProperties": false + }, + "ConstraintsV2": { + "type": "object", + "properties": { + "limit_disclosure": { + "$ref": "#/definitions/Optionality" + }, + "statuses": { + "$ref": "#/definitions/Statuses" + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/FieldV2" + } + }, + "subject_is_issuer": { + "$ref": "#/definitions/Optionality" + }, + "is_holder": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + }, + "same_subject": { + "type": "array", + "items": { + "$ref": "#/definitions/HolderSubject" + } + } + }, + "additionalProperties": false + }, + "FieldV2": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "array", + "items": { + "type": "string" + } + }, + "purpose": { + "type": "string" + }, + "filter": { + "$ref": "#/definitions/FilterV2" + }, + "predicate": { + "$ref": "#/definitions/Optionality" + }, + "name": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + }, + "FilterV2": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "formatMaximum": { + "type": "string" + }, + "formatMinimum": { + "type": "string" + }, + "formatExclusiveMaximum": { + "type": "string" + }, + "formatExclusiveMinimum": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + }, + "contains": { + "$ref": "#/definitions/FilterV2Base" + }, + "items": { + "$ref": "#/definitions/FilterV2BaseItems" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "FilterV2Base": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "formatMaximum": { + "type": "string" + }, + "formatMinimum": { + "type": "string" + }, + "formatExclusiveMaximum": { + "type": "string" + }, + "formatExclusiveMinimum": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + }, + "contains": { + "$ref": "#/definitions/FilterV2Base" + }, + "items": { + "$ref": "#/definitions/FilterV2BaseItems" + } + }, + "additionalProperties": false + }, + "FilterV2BaseItems": { + "type": "object", + "properties": { + "const": { + "$ref": "#/definitions/OneOfNumberString" + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/definitions/OneOfNumberString" + } + }, + "exclusiveMinimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "formatMaximum": { + "type": "string" + }, + "formatMinimum": { + "type": "string" + }, + "formatExclusiveMaximum": { + "type": "string" + }, + "formatExclusiveMinimum": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "minimum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "maximum": { + "$ref": "#/definitions/OneOfNumberString" + }, + "not": { + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + }, + "contains": { + "$ref": "#/definitions/FilterV2Base" + }, + "items": { + "$ref": "#/definitions/FilterV2BaseItems" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + } +}; \ No newline at end of file diff --git a/packages/siopv2/src/schemas/AuthorizationResponseOpts.schema.ts b/packages/siopv2/src/schemas/AuthorizationResponseOpts.schema.ts new file mode 100644 index 00000000..879a5d82 --- /dev/null +++ b/packages/siopv2/src/schemas/AuthorizationResponseOpts.schema.ts @@ -0,0 +1,2078 @@ +export const AuthorizationResponseOptsSchemaObj = { + "$id": "AuthorizationResponseOptsSchema", + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/AuthorizationResponseOpts", + "definitions": { + "AuthorizationResponseOpts": { + "type": "object", + "properties": { + "responseURI": { + "type": "string" + }, + "responseURIType": { + "$ref": "#/definitions/ResponseURIType" + }, + "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" + } + ] + }, + "responseMode": { + "$ref": "#/definitions/ResponseMode" + }, + "expiresIn": { + "type": "number" + }, + "accessToken": { + "type": "string" + }, + "tokenType": { + "type": "string" + }, + "refreshToken": { + "type": "string" + }, + "presentationExchange": { + "$ref": "#/definitions/PresentationExchangeResponseOpts" + } + }, + "additionalProperties": false + }, + "ResponseURIType": { + "type": "string", + "enum": [ + "response_uri", + "redirect_uri" + ] + }, + "ResponseRegistrationOpts": { + "anyOf": [ + { + "type": "object", + "properties": { + "passBy": { + "$ref": "#/definitions/PassBy" + }, + "reference_uri": { + "type": "string" + }, + "targets": { + "$ref": "#/definitions/PropertyTargets" + }, + "id_token_encrypted_response_alg": { + "$ref": "#/definitions/EncKeyAlgorithm" + }, + "id_token_encrypted_response_enc": { + "$ref": "#/definitions/EncSymmetricAlgorithmCode" + }, + "authorizationEndpoint": { + "anyOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "type": "string" + } + ] + }, + "issuer": { + "anyOf": [ + { + "$ref": "#/definitions/ResponseIss" + }, + { + "type": "string" + } + ] + }, + "responseTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseType" + } + }, + { + "$ref": "#/definitions/ResponseType" + } + ] + }, + "scopesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/Scope" + } + }, + { + "$ref": "#/definitions/Scope" + } + ] + }, + "subjectTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SubjectType" + } + }, + { + "$ref": "#/definitions/SubjectType" + } + ] + }, + "idTokenSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "requestObjectSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "subject_syntax_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "tokenEndpoint": { + "type": "string" + }, + "userinfoEndpoint": { + "type": "string" + }, + "jwksUri": { + "type": "string" + }, + "registrationEndpoint": { + "type": "string" + }, + "responseModesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseMode" + } + }, + { + "$ref": "#/definitions/ResponseMode" + } + ] + }, + "grantTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/GrantType" + } + }, + { + "$ref": "#/definitions/GrantType" + } + ] + }, + "acrValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/AuthenticationContextReferences" + } + }, + { + "$ref": "#/definitions/AuthenticationContextReferences" + } + ] + }, + "idTokenEncryptionAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "idTokenEncryptionEncValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "userinfoSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfoEncryptionAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfoEncryptionEncValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "requestObjectEncryptionAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "requestObjectEncryptionEncValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "tokenEndpointAuthMethodsSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + }, + { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + ] + }, + "tokenEndpointAuthSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "displayValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "claimTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ClaimType" + } + }, + { + "$ref": "#/definitions/ClaimType" + } + ] + }, + "claimsSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "serviceDocumentation": { + "type": "string" + }, + "claimsLocalesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "uiLocalesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "claimsParameterSupported": { + "type": "boolean" + }, + "requestParameterSupported": { + "type": "boolean" + }, + "requestUriParameterSupported": { + "type": "boolean" + }, + "requireRequestUriRegistration": { + "type": "boolean" + }, + "opPolicyUri": { + "type": "string" + }, + "opTosUri": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "redirectUris": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "clientName": { + "type": "string" + }, + "tokenEndpointAuthMethod": { + "type": "string" + }, + "applicationType": { + "type": "string" + }, + "responseTypes": { + "type": "string" + }, + "grantTypes": { + "type": "string" + }, + "vpFormats": { + "$ref": "#/definitions/Format" + }, + "logo_uri": { + "type": "string" + }, + "clientPurpose": { + "type": "string" + } + }, + "required": [ + "passBy" + ] + }, + { + "type": "object", + "properties": { + "passBy": { + "$ref": "#/definitions/PassBy" + }, + "reference_uri": { + "type": "string" + }, + "targets": { + "$ref": "#/definitions/PropertyTargets" + }, + "id_token_encrypted_response_alg": { + "$ref": "#/definitions/EncKeyAlgorithm" + }, + "id_token_encrypted_response_enc": { + "$ref": "#/definitions/EncSymmetricAlgorithmCode" + }, + "authorizationEndpoint": { + "anyOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "type": "string" + } + ] + }, + "issuer": { + "anyOf": [ + { + "$ref": "#/definitions/ResponseIss" + }, + { + "type": "string" + } + ] + }, + "responseTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseType" + } + }, + { + "$ref": "#/definitions/ResponseType" + } + ] + }, + "scopesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/Scope" + } + }, + { + "$ref": "#/definitions/Scope" + } + ] + }, + "subjectTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SubjectType" + } + }, + { + "$ref": "#/definitions/SubjectType" + } + ] + }, + "idTokenSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "requestObjectSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "subject_syntax_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "tokenEndpoint": { + "type": "string" + }, + "userinfoEndpoint": { + "type": "string" + }, + "jwksUri": { + "type": "string" + }, + "registrationEndpoint": { + "type": "string" + }, + "responseModesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseMode" + } + }, + { + "$ref": "#/definitions/ResponseMode" + } + ] + }, + "grantTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/GrantType" + } + }, + { + "$ref": "#/definitions/GrantType" + } + ] + }, + "acrValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/AuthenticationContextReferences" + } + }, + { + "$ref": "#/definitions/AuthenticationContextReferences" + } + ] + }, + "idTokenEncryptionAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "idTokenEncryptionEncValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "userinfoSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfoEncryptionAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfoEncryptionEncValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "requestObjectEncryptionAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "requestObjectEncryptionEncValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "tokenEndpointAuthMethodsSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + }, + { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + ] + }, + "tokenEndpointAuthSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "displayValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "claimTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ClaimType" + } + }, + { + "$ref": "#/definitions/ClaimType" + } + ] + }, + "claimsSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "serviceDocumentation": { + "type": "string" + }, + "claimsLocalesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "uiLocalesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "claimsParameterSupported": { + "type": "boolean" + }, + "requestParameterSupported": { + "type": "boolean" + }, + "requestUriParameterSupported": { + "type": "boolean" + }, + "requireRequestUriRegistration": { + "type": "boolean" + }, + "opPolicyUri": { + "type": "string" + }, + "opTosUri": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "redirectUris": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "clientName": { + "type": "string" + }, + "tokenEndpointAuthMethod": { + "type": "string" + }, + "applicationType": { + "type": "string" + }, + "responseTypes": { + "type": "string" + }, + "grantTypes": { + "type": "string" + }, + "vpFormats": { + "$ref": "#/definitions/Format" + } + }, + "required": [ + "passBy" + ] + }, + { + "type": "object", + "properties": { + "passBy": { + "$ref": "#/definitions/PassBy" + }, + "reference_uri": { + "type": "string" + }, + "targets": { + "$ref": "#/definitions/PropertyTargets" + }, + "id_token_encrypted_response_alg": { + "$ref": "#/definitions/EncKeyAlgorithm" + }, + "id_token_encrypted_response_enc": { + "$ref": "#/definitions/EncSymmetricAlgorithmCode" + }, + "authorizationEndpoint": { + "anyOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "type": "string" + } + ] + }, + "issuer": { + "anyOf": [ + { + "$ref": "#/definitions/ResponseIss" + }, + { + "type": "string" + } + ] + }, + "responseTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseType" + } + }, + { + "$ref": "#/definitions/ResponseType" + } + ] + }, + "scopesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/Scope" + } + }, + { + "$ref": "#/definitions/Scope" + } + ] + }, + "subjectTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SubjectType" + } + }, + { + "$ref": "#/definitions/SubjectType" + } + ] + }, + "idTokenSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "requestObjectSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "subject_syntax_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "tokenEndpoint": { + "type": "string" + }, + "userinfoEndpoint": { + "type": "string" + }, + "jwksUri": { + "type": "string" + }, + "registrationEndpoint": { + "type": "string" + }, + "responseModesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseMode" + } + }, + { + "$ref": "#/definitions/ResponseMode" + } + ] + }, + "grantTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/GrantType" + } + }, + { + "$ref": "#/definitions/GrantType" + } + ] + }, + "acrValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/AuthenticationContextReferences" + } + }, + { + "$ref": "#/definitions/AuthenticationContextReferences" + } + ] + }, + "idTokenEncryptionAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "idTokenEncryptionEncValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "userinfoSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfoEncryptionAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfoEncryptionEncValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "requestObjectEncryptionAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "requestObjectEncryptionEncValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "tokenEndpointAuthMethodsSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + }, + { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + ] + }, + "tokenEndpointAuthSigningAlgValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "displayValuesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "claimTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ClaimType" + } + }, + { + "$ref": "#/definitions/ClaimType" + } + ] + }, + "claimsSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "serviceDocumentation": { + "type": "string" + }, + "claimsLocalesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "uiLocalesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "claimsParameterSupported": { + "type": "boolean" + }, + "requestParameterSupported": { + "type": "boolean" + }, + "requestUriParameterSupported": { + "type": "boolean" + }, + "requireRequestUriRegistration": { + "type": "boolean" + }, + "opPolicyUri": { + "type": "string" + }, + "opTosUri": { + "type": "string" + }, + "idTokenTypesSupported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/IdTokenType" + } + }, + { + "$ref": "#/definitions/IdTokenType" + } + ] + }, + "vpFormatsSupported": { + "$ref": "#/definitions/Format" + } + }, + "required": [ + "passBy" + ] + } + ] + }, + "PassBy": { + "type": "string", + "enum": [ + "NONE", + "REFERENCE", + "VALUE" + ] + }, + "PropertyTargets": { + "anyOf": [ + { + "$ref": "#/definitions/PropertyTarget" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/PropertyTarget" + } + } + ] + }, + "PropertyTarget": { + "type": "string", + "enum": [ + "authorization-request", + "request-object" + ], + "description": "Determines where a property will end up. Methods that support this argument are optional. If you do not provide any value it will default to all targets." + }, + "EncKeyAlgorithm": { + "type": "string", + "const": "ECDH-ES" + }, + "EncSymmetricAlgorithmCode": { + "type": "string", + "const": "XC20P" + }, + "Schema": { + "type": "string", + "enum": [ + "openid:", + "openid-vc:" + ] + }, + "ResponseIss": { + "type": "string", + "enum": [ + "https://self-issued.me", + "https://self-issued.me/v2", + "https://self-issued.me/v2/openid-vc" + ] + }, + "ResponseType": { + "type": "string", + "enum": [ + "code", + "id_token", + "vp_token" + ] + }, + "Scope": { + "type": "string", + "enum": [ + "openid", + "openid did_authn", + "profile", + "email", + "address", + "phone" + ] + }, + "SubjectType": { + "type": "string", + "enum": [ + "public", + "pairwise" + ] + }, + "SigningAlgo": { + "type": "string", + "enum": [ + "EdDSA", + "RS256", + "PS256", + "ES256", + "ES256K" + ] + }, + "ResponseMode": { + "type": "string", + "enum": [ + "fragment", + "form_post", + "post", + "direct_post", + "query" + ] + }, + "GrantType": { + "type": "string", + "enum": [ + "authorization_code", + "implicit" + ] + }, + "AuthenticationContextReferences": { + "type": "string", + "enum": [ + "phr", + "phrh" + ] + }, + "TokenEndpointAuthMethod": { + "type": "string", + "enum": [ + "client_secret_post", + "client_secret_basic", + "client_secret_jwt", + "private_key_jwt" + ] + }, + "ClaimType": { + "type": "string", + "enum": [ + "normal", + "aggregated", + "distributed" + ] + }, + "Format": { + "type": "object", + "properties": { + "jwt": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc_json": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vp": { + "$ref": "#/definitions/JwtObject" + }, + "ldp": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vc": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vp": { + "$ref": "#/definitions/LdpObject" + }, + "vc+sd-jwt": { + "$ref": "#/definitions/SdJwtObject" + } + }, + "additionalProperties": false + }, + "JwtObject": { + "type": "object", + "properties": { + "alg": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "alg" + ], + "additionalProperties": false + }, + "LdpObject": { + "type": "object", + "properties": { + "proof_type": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "proof_type" + ], + "additionalProperties": false + }, + "SdJwtObject": { + "type": "object", + "properties": { + "sd_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + }, + "kb_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "IdTokenType": { + "type": "string", + "enum": [ + "subject_signed", + "attester_signed" + ] + }, + "CheckLinkedDomain": { + "type": "string", + "enum": [ + "never", + "if_present", + "always" + ] + }, + "SupportedVersion": { + "type": "number", + "enum": [ + 70, + 110, + 180, + 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": { + "properties": { + "isFunction": { + "type": "boolean", + "const": true + } + } + }, + "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": { + "properties": { + "isFunction": { + "type": "boolean", + "const": true + } + } + }, + "alg": { + "$ref": "#/definitions/SigningAlgo" + }, + "did": { + "type": "string" + }, + "kid": { + "type": "string" + } + }, + "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 + }, + "PresentationExchangeResponseOpts": { + "type": "object", + "properties": { + "verifiablePresentations": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/W3CVerifiablePresentation" + }, + { + "$ref": "#/definitions/CompactSdJwtVc" + } + ] + } + }, + "vpTokenLocation": { + "$ref": "#/definitions/VPTokenLocation" + }, + "presentationSubmission": { + "$ref": "#/definitions/PresentationSubmission" + }, + "restrictToFormats": { + "$ref": "#/definitions/Format" + }, + "restrictToDIDMethods": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "verifiablePresentations" + ], + "additionalProperties": false + }, + "W3CVerifiablePresentation": { + "anyOf": [ + { + "$ref": "#/definitions/IVerifiablePresentation" + }, + { + "$ref": "#/definitions/CompactJWT" + } + ], + "description": "Represents a signed Verifiable Presentation (includes proof), in either JSON or compact JWT format. See {@link https://www.w3.org/TR/vc-data-model/#presentations VC data model } See {@link https://www.w3.org/TR/vc-data-model/#proof-formats proof formats }" + }, + "IVerifiablePresentation": { + "type": "object", + "properties": { + "proof": { + "anyOf": [ + { + "$ref": "#/definitions/IProof" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/IProof" + } + } + ] + }, + "id": { + "type": "string" + }, + "@context": { + "anyOf": [ + { + "$ref": "#/definitions/ICredentialContextType" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/ICredentialContextType" + } + } + ] + }, + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "verifiableCredential": { + "type": "array", + "items": { + "$ref": "#/definitions/W3CVerifiableCredential" + } + }, + "presentation_submission": { + "$ref": "#/definitions/PresentationSubmission" + }, + "holder": { + "type": "string" + }, + "verifier": { + "type": "string" + } + }, + "required": [ + "@context", + "proof" + ] + }, + "IProof": { + "type": "object", + "properties": { + "type": { + "anyOf": [ + { + "$ref": "#/definitions/IProofType" + }, + { + "type": "string" + } + ] + }, + "created": { + "type": "string" + }, + "proofPurpose": { + "anyOf": [ + { + "$ref": "#/definitions/IProofPurpose" + }, + { + "type": "string" + } + ] + }, + "verificationMethod": { + "type": "string" + }, + "challenge": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "proofValue": { + "type": "string" + }, + "jws": { + "type": "string" + }, + "jwt": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "requiredRevealStatements": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "type", + "created", + "proofPurpose", + "verificationMethod" + ] + }, + "IProofType": { + "type": "string", + "enum": [ + "Ed25519Signature2018", + "Ed25519Signature2020", + "EcdsaSecp256k1Signature2019", + "EcdsaSecp256k1RecoverySignature2020", + "JsonWebSignature2020", + "RsaSignature2018", + "GpgSignature2020", + "JcsEd25519Signature2020", + "BbsBlsSignatureProof2020", + "BbsBlsBoundSignatureProof2020", + "JwtProof2020" + ] + }, + "IProofPurpose": { + "type": "string", + "enum": [ + "verificationMethod", + "assertionMethod", + "authentication", + "keyAgreement", + "contactAgreement", + "capabilityInvocation", + "capabilityDelegation" + ] + }, + "ICredentialContextType": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "did": { + "type": "string" + } + } + }, + { + "type": "string" + } + ] + }, + "W3CVerifiableCredential": { + "anyOf": [ + { + "$ref": "#/definitions/IVerifiableCredential" + }, + { + "$ref": "#/definitions/CompactJWT" + } + ], + "description": "Represents a signed Verifiable Credential (includes proof), in either JSON, compact JWT or compact SD-JWT VC format. See {@link https://www.w3.org/TR/vc-data-model/#credentials VC data model } See {@link https://www.w3.org/TR/vc-data-model/#proof-formats proof formats }" + }, + "IVerifiableCredential": { + "type": "object", + "properties": { + "proof": { + "anyOf": [ + { + "$ref": "#/definitions/IProof" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/IProof" + } + } + ] + }, + "@context": { + "anyOf": [ + { + "$ref": "#/definitions/ICredentialContextType" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/ICredentialContextType" + } + } + ] + }, + "type": { + "type": "array", + "items": { + "type": "string" + } + }, + "credentialSchema": { + "anyOf": [ + { + "$ref": "#/definitions/ICredentialSchemaType" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/ICredentialSchemaType" + } + } + ] + }, + "issuer": { + "anyOf": [ + { + "$ref": "#/definitions/IIssuerId" + }, + { + "$ref": "#/definitions/IIssuer" + } + ] + }, + "issuanceDate": { + "type": "string" + }, + "credentialSubject": { + "anyOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + } + } + ] + }, + "expirationDate": { + "type": "string" + }, + "id": { + "type": "string" + }, + "credentialStatus": { + "$ref": "#/definitions/ICredentialStatus" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "@context", + "credentialSubject", + "issuanceDate", + "issuer", + "proof", + "type" + ] + }, + "ICredentialSchemaType": { + "anyOf": [ + { + "$ref": "#/definitions/ICredentialSchema" + }, + { + "type": "string" + } + ] + }, + "ICredentialSchema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "IIssuerId": { + "type": "string" + }, + "IIssuer": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "ICredentialStatus": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "additionalProperties": false + }, + "CompactJWT": { + "type": "string", + "description": "Represents a Json Web Token in compact form." + }, + "PresentationSubmission": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "A UUID or some other unique ID to identify this Presentation Submission" + }, + "definition_id": { + "type": "string", + "description": "A UUID or some other unique ID to identify this Presentation Definition" + }, + "descriptor_map": { + "type": "array", + "items": { + "$ref": "#/definitions/Descriptor" + }, + "description": "List of descriptors of how the claims are being mapped to presentation definition" + } + }, + "required": [ + "id", + "definition_id", + "descriptor_map" + ], + "additionalProperties": false, + "description": "It expresses how the inputs are presented as proofs to a Verifier." + }, + "Descriptor": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID to identify the descriptor from Presentation Definition Input Descriptor it coresponds to." + }, + "path": { + "type": "string", + "description": "The path where the verifiable credential is located in the presentation submission json" + }, + "path_nested": { + "$ref": "#/definitions/Descriptor" + }, + "format": { + "type": "string", + "description": "The Proof or JWT algorith that the proof is in" + } + }, + "required": [ + "id", + "path", + "format" + ], + "additionalProperties": false, + "description": "descriptor map laying out the structure of the presentation submission." + }, + "CompactSdJwtVc": { + "type": "string", + "description": "Represents a selective disclosure JWT vc in compact form." + }, + "VPTokenLocation": { + "type": "string", + "enum": [ + "authorization_response", + "id_token", + "token_response" + ] + } + } +}; \ No newline at end of file diff --git a/packages/siopv2/src/schemas/DiscoveryMetadataPayload.schema.ts b/packages/siopv2/src/schemas/DiscoveryMetadataPayload.schema.ts new file mode 100644 index 00000000..ff093dc3 --- /dev/null +++ b/packages/siopv2/src/schemas/DiscoveryMetadataPayload.schema.ts @@ -0,0 +1,1320 @@ +export const DiscoveryMetadataPayloadSchemaObj = { + "$id": "DiscoveryMetadataPayloadSchema", + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/DiscoveryMetadataPayload", + "definitions": { + "DiscoveryMetadataPayload": { + "anyOf": [ + { + "type": "object", + "properties": { + "authorization_endpoint": { + "anyOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "type": "string" + } + ] + }, + "issuer": { + "anyOf": [ + { + "$ref": "#/definitions/ResponseIss" + }, + { + "type": "string" + } + ] + }, + "response_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseType" + } + }, + { + "$ref": "#/definitions/ResponseType" + } + ] + }, + "scopes_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/Scope" + } + }, + { + "$ref": "#/definitions/Scope" + } + ] + }, + "subject_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SubjectType" + } + }, + { + "$ref": "#/definitions/SubjectType" + } + ] + }, + "id_token_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "request_object_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "subject_syntax_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "token_endpoint": { + "type": "string" + }, + "userinfo_endpoint": { + "type": "string" + }, + "jwks_uri": { + "type": "string" + }, + "registration_endpoint": { + "type": "string" + }, + "response_modes_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseMode" + } + }, + { + "$ref": "#/definitions/ResponseMode" + } + ] + }, + "grant_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/GrantType" + } + }, + { + "$ref": "#/definitions/GrantType" + } + ] + }, + "acr_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/AuthenticationContextReferences" + } + }, + { + "$ref": "#/definitions/AuthenticationContextReferences" + } + ] + }, + "id_token_encryption_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "id_token_encryption_enc_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for the ID Token to encode the Claims in a JWT [JWT]." + }, + "userinfo_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfo_encryption_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfo_encryption_enc_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) [JWA] supported by the UserInfo Endpoint to encode the Claims in a JWT [JWT]." + }, + "request_object_encryption_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "request_object_encryption_enc_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for Request Objects. These algorithms are used both when the Request Object is passed by value and when it is passed by reference." + }, + "token_endpoint_auth_methods_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + }, + { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + ] + }, + "token_endpoint_auth_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "display_values_supported": { + "anyOf": [ + { + "type": "array", + "items": {} + }, + {} + ], + "description": "OPTIONAL. JSON array containing a list of the display parameter values that the OpenID Provider supports. These values are described in Section 3.1.2.1 of OpenID Connect Core 1.0 [OpenID.Core]." + }, + "claim_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ClaimType" + } + }, + { + "$ref": "#/definitions/ClaimType" + } + ], + "description": "OPTIONAL. JSON array containing a list of the Claim Types that the OpenID Provider supports. These Claim Types are described in Section 5.6 of OpenID Connect Core 1.0 [OpenID.Core]. Values defined by this specification are normal, aggregated, and distributed. If omitted, the implementation supports only normal Claims." + }, + "claims_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "RECOMMENDED. JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for. Note that for privacy or other reasons, this might not be an exhaustive list." + }, + "service_documentation": { + "type": "string" + }, + "claims_locales_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "ui_locales_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "claims_parameter_supported": { + "type": "boolean" + }, + "request_parameter_supported": { + "type": "boolean" + }, + "request_uri_parameter_supported": { + "type": "boolean" + }, + "require_request_uri_registration": { + "type": "boolean" + }, + "op_policy_uri": { + "type": "string" + }, + "op_tos_uri": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "redirect_uris": { + "type": "array", + "items": { + "type": "string" + } + }, + "client_name": { + "type": "string" + }, + "token_endpoint_auth_method": { + "type": "string" + }, + "application_type": { + "type": "string" + }, + "response_types": { + "type": "string" + }, + "grant_types": { + "type": "string" + }, + "vp_formats": { + "$ref": "#/definitions/Format" + } + } + }, + { + "type": "object", + "properties": { + "authorization_endpoint": { + "anyOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "type": "string" + } + ] + }, + "issuer": { + "anyOf": [ + { + "$ref": "#/definitions/ResponseIss" + }, + { + "type": "string" + } + ] + }, + "response_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseType" + } + }, + { + "$ref": "#/definitions/ResponseType" + } + ] + }, + "scopes_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/Scope" + } + }, + { + "$ref": "#/definitions/Scope" + } + ] + }, + "subject_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SubjectType" + } + }, + { + "$ref": "#/definitions/SubjectType" + } + ] + }, + "id_token_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "request_object_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "subject_syntax_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "token_endpoint": { + "type": "string" + }, + "userinfo_endpoint": { + "type": "string" + }, + "jwks_uri": { + "type": "string" + }, + "registration_endpoint": { + "type": "string" + }, + "response_modes_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseMode" + } + }, + { + "$ref": "#/definitions/ResponseMode" + } + ] + }, + "grant_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/GrantType" + } + }, + { + "$ref": "#/definitions/GrantType" + } + ] + }, + "acr_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/AuthenticationContextReferences" + } + }, + { + "$ref": "#/definitions/AuthenticationContextReferences" + } + ] + }, + "id_token_encryption_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "id_token_encryption_enc_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for the ID Token to encode the Claims in a JWT [JWT]." + }, + "userinfo_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfo_encryption_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfo_encryption_enc_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) [JWA] supported by the UserInfo Endpoint to encode the Claims in a JWT [JWT]." + }, + "request_object_encryption_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "request_object_encryption_enc_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for Request Objects. These algorithms are used both when the Request Object is passed by value and when it is passed by reference." + }, + "token_endpoint_auth_methods_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + }, + { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + ] + }, + "token_endpoint_auth_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "display_values_supported": { + "anyOf": [ + { + "type": "array", + "items": {} + }, + {} + ], + "description": "OPTIONAL. JSON array containing a list of the display parameter values that the OpenID Provider supports. These values are described in Section 3.1.2.1 of OpenID Connect Core 1.0 [OpenID.Core]." + }, + "claim_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ClaimType" + } + }, + { + "$ref": "#/definitions/ClaimType" + } + ], + "description": "OPTIONAL. JSON array containing a list of the Claim Types that the OpenID Provider supports. These Claim Types are described in Section 5.6 of OpenID Connect Core 1.0 [OpenID.Core]. Values defined by this specification are normal, aggregated, and distributed. If omitted, the implementation supports only normal Claims." + }, + "claims_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "RECOMMENDED. JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for. Note that for privacy or other reasons, this might not be an exhaustive list." + }, + "service_documentation": { + "type": "string" + }, + "claims_locales_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "ui_locales_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "claims_parameter_supported": { + "type": "boolean" + }, + "request_parameter_supported": { + "type": "boolean" + }, + "request_uri_parameter_supported": { + "type": "boolean" + }, + "require_request_uri_registration": { + "type": "boolean" + }, + "op_policy_uri": { + "type": "string" + }, + "op_tos_uri": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "redirect_uris": { + "type": "array", + "items": { + "type": "string" + } + }, + "client_name": { + "type": "string" + }, + "token_endpoint_auth_method": { + "type": "string" + }, + "application_type": { + "type": "string" + }, + "response_types": { + "type": "string" + }, + "grant_types": { + "type": "string" + }, + "vp_formats": { + "$ref": "#/definitions/Format" + }, + "logo_uri": { + "type": "string" + }, + "client_purpose": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "authorization_endpoint": { + "anyOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "type": "string" + } + ] + }, + "issuer": { + "anyOf": [ + { + "$ref": "#/definitions/ResponseIss" + }, + { + "type": "string" + } + ] + }, + "response_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseType" + } + }, + { + "$ref": "#/definitions/ResponseType" + } + ] + }, + "scopes_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/Scope" + } + }, + { + "$ref": "#/definitions/Scope" + } + ] + }, + "subject_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SubjectType" + } + }, + { + "$ref": "#/definitions/SubjectType" + } + ] + }, + "id_token_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "request_object_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "subject_syntax_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "token_endpoint": { + "type": "string" + }, + "userinfo_endpoint": { + "type": "string" + }, + "jwks_uri": { + "type": "string" + }, + "registration_endpoint": { + "type": "string" + }, + "response_modes_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseMode" + } + }, + { + "$ref": "#/definitions/ResponseMode" + } + ] + }, + "grant_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/GrantType" + } + }, + { + "$ref": "#/definitions/GrantType" + } + ] + }, + "acr_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/AuthenticationContextReferences" + } + }, + { + "$ref": "#/definitions/AuthenticationContextReferences" + } + ] + }, + "id_token_encryption_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "id_token_encryption_enc_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for the ID Token to encode the Claims in a JWT [JWT]." + }, + "userinfo_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfo_encryption_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "userinfo_encryption_enc_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) [JWA] supported by the UserInfo Endpoint to encode the Claims in a JWT [JWT]." + }, + "request_object_encryption_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "request_object_encryption_enc_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for Request Objects. These algorithms are used both when the Request Object is passed by value and when it is passed by reference." + }, + "token_endpoint_auth_methods_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + }, + { + "$ref": "#/definitions/TokenEndpointAuthMethod" + } + ] + }, + "token_endpoint_auth_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "display_values_supported": { + "anyOf": [ + { + "type": "array", + "items": {} + }, + {} + ], + "description": "OPTIONAL. JSON array containing a list of the display parameter values that the OpenID Provider supports. These values are described in Section 3.1.2.1 of OpenID Connect Core 1.0 [OpenID.Core]." + }, + "claim_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ClaimType" + } + }, + { + "$ref": "#/definitions/ClaimType" + } + ], + "description": "OPTIONAL. JSON array containing a list of the Claim Types that the OpenID Provider supports. These Claim Types are described in Section 5.6 of OpenID Connect Core 1.0 [OpenID.Core]. Values defined by this specification are normal, aggregated, and distributed. If omitted, the implementation supports only normal Claims." + }, + "claims_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "RECOMMENDED. JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for. Note that for privacy or other reasons, this might not be an exhaustive list." + }, + "service_documentation": { + "type": "string" + }, + "claims_locales_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "ui_locales_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "claims_parameter_supported": { + "type": "boolean" + }, + "request_parameter_supported": { + "type": "boolean" + }, + "request_uri_parameter_supported": { + "type": "boolean" + }, + "require_request_uri_registration": { + "type": "boolean" + }, + "op_policy_uri": { + "type": "string" + }, + "op_tos_uri": { + "type": "string" + }, + "id_token_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/IdTokenType" + } + }, + { + "$ref": "#/definitions/IdTokenType" + } + ] + }, + "vp_formats_supported": { + "$ref": "#/definitions/Format" + } + } + } + ] + }, + "Schema": { + "type": "string", + "enum": [ + "openid:", + "openid-vc:" + ] + }, + "ResponseIss": { + "type": "string", + "enum": [ + "https://self-issued.me", + "https://self-issued.me/v2", + "https://self-issued.me/v2/openid-vc" + ] + }, + "ResponseType": { + "type": "string", + "enum": [ + "code", + "id_token", + "vp_token" + ] + }, + "Scope": { + "type": "string", + "enum": [ + "openid", + "openid did_authn", + "profile", + "email", + "address", + "phone" + ] + }, + "SubjectType": { + "type": "string", + "enum": [ + "public", + "pairwise" + ] + }, + "SigningAlgo": { + "type": "string", + "enum": [ + "EdDSA", + "RS256", + "PS256", + "ES256", + "ES256K" + ] + }, + "ResponseMode": { + "type": "string", + "enum": [ + "fragment", + "form_post", + "post", + "direct_post", + "query" + ] + }, + "GrantType": { + "type": "string", + "enum": [ + "authorization_code", + "implicit" + ] + }, + "AuthenticationContextReferences": { + "type": "string", + "enum": [ + "phr", + "phrh" + ] + }, + "TokenEndpointAuthMethod": { + "type": "string", + "enum": [ + "client_secret_post", + "client_secret_basic", + "client_secret_jwt", + "private_key_jwt" + ] + }, + "ClaimType": { + "type": "string", + "enum": [ + "normal", + "aggregated", + "distributed" + ] + }, + "Format": { + "type": "object", + "properties": { + "jwt": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc_json": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vp": { + "$ref": "#/definitions/JwtObject" + }, + "ldp": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vc": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vp": { + "$ref": "#/definitions/LdpObject" + }, + "vc+sd-jwt": { + "$ref": "#/definitions/SdJwtObject" + } + }, + "additionalProperties": false + }, + "JwtObject": { + "type": "object", + "properties": { + "alg": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "alg" + ], + "additionalProperties": false + }, + "LdpObject": { + "type": "object", + "properties": { + "proof_type": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "proof_type" + ], + "additionalProperties": false + }, + "SdJwtObject": { + "type": "object", + "properties": { + "sd_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + }, + "kb_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "IdTokenType": { + "type": "string", + "enum": [ + "subject_signed", + "attester_signed" + ] + } + } +}; \ No newline at end of file diff --git a/packages/siopv2/src/schemas/RPRegistrationMetadataPayload.schema.ts b/packages/siopv2/src/schemas/RPRegistrationMetadataPayload.schema.ts new file mode 100644 index 00000000..c4bf90bf --- /dev/null +++ b/packages/siopv2/src/schemas/RPRegistrationMetadataPayload.schema.ts @@ -0,0 +1,237 @@ +export const RPRegistrationMetadataPayloadSchemaObj = { + "$id": "RPRegistrationMetadataPayloadSchema", + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/RPRegistrationMetadataPayload", + "definitions": { + "RPRegistrationMetadataPayload": { + "type": "object", + "properties": { + "client_id": { + "anyOf": [ + { + "type": "string" + }, + {} + ] + }, + "id_token_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "request_object_signing_alg_values_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SigningAlgo" + } + }, + { + "$ref": "#/definitions/SigningAlgo" + } + ] + }, + "response_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/ResponseType" + } + }, + { + "$ref": "#/definitions/ResponseType" + } + ] + }, + "scopes_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/Scope" + } + }, + { + "$ref": "#/definitions/Scope" + } + ] + }, + "subject_types_supported": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/SubjectType" + } + }, + { + "$ref": "#/definitions/SubjectType" + } + ] + }, + "subject_syntax_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "vp_formats": { + "anyOf": [ + { + "$ref": "#/definitions/Format" + }, + {} + ] + }, + "client_name": { + "anyOf": [ + { + "type": "string" + }, + {} + ] + }, + "logo_uri": { + "anyOf": [ + {}, + { + "type": "string" + } + ] + }, + "client_purpose": { + "anyOf": [ + {}, + { + "type": "string" + } + ] + } + } + }, + "SigningAlgo": { + "type": "string", + "enum": [ + "EdDSA", + "RS256", + "PS256", + "ES256", + "ES256K" + ] + }, + "ResponseType": { + "type": "string", + "enum": [ + "code", + "id_token", + "vp_token" + ] + }, + "Scope": { + "type": "string", + "enum": [ + "openid", + "openid did_authn", + "profile", + "email", + "address", + "phone" + ] + }, + "SubjectType": { + "type": "string", + "enum": [ + "public", + "pairwise" + ] + }, + "Format": { + "type": "object", + "properties": { + "jwt": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vc_json": { + "$ref": "#/definitions/JwtObject" + }, + "jwt_vp": { + "$ref": "#/definitions/JwtObject" + }, + "ldp": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vc": { + "$ref": "#/definitions/LdpObject" + }, + "ldp_vp": { + "$ref": "#/definitions/LdpObject" + }, + "vc+sd-jwt": { + "$ref": "#/definitions/SdJwtObject" + } + }, + "additionalProperties": false + }, + "JwtObject": { + "type": "object", + "properties": { + "alg": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "alg" + ], + "additionalProperties": false + }, + "LdpObject": { + "type": "object", + "properties": { + "proof_type": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "proof_type" + ], + "additionalProperties": false + }, + "SdJwtObject": { + "type": "object", + "properties": { + "sd_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + }, + "kb_jwt_alg_values": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + } +}; \ No newline at end of file diff --git a/packages/siopv2/src/schemas/index.ts b/packages/siopv2/src/schemas/index.ts new file mode 100644 index 00000000..105c7f33 --- /dev/null +++ b/packages/siopv2/src/schemas/index.ts @@ -0,0 +1,7 @@ +export * from './AuthorizationRequestPayloadVID1.schema'; +export * from './AuthorizationRequestPayloadVD11.schema'; +// export * from './AuthorizationRequestOpts.schema'; +export * from './AuthorizationResponseOpts.schema'; +export * from './DiscoveryMetadataPayload.schema'; +export * from './RPRegistrationMetadataPayload.schema'; +export * from './validation'; diff --git a/packages/siopv2/src/schemas/validation/index.ts b/packages/siopv2/src/schemas/validation/index.ts new file mode 100644 index 00000000..e38af318 --- /dev/null +++ b/packages/siopv2/src/schemas/validation/index.ts @@ -0,0 +1,12 @@ +import { + AuthorizationRequestPayloadVD11Schema, + AuthorizationRequestPayloadVID1Schema, + AuthorizationResponseOptsSchema, + /*CreateAuthorizationRequestOptsSchema, */ RPRegistrationMetadataPayloadSchema, +} from './schemaValidation.js'; +export { + AuthorizationRequestPayloadVID1Schema, + AuthorizationRequestPayloadVD11Schema, + RPRegistrationMetadataPayloadSchema, + /*CreateAuthorizationRequestOptsSchema, */ AuthorizationResponseOptsSchema, +}; diff --git a/packages/siopv2/src/types/Errors.ts b/packages/siopv2/src/types/Errors.ts new file mode 100644 index 00000000..b9f23bca --- /dev/null +++ b/packages/siopv2/src/types/Errors.ts @@ -0,0 +1,74 @@ +enum SIOPErrors { + // todo: INVALID_REQUEST mapping onto response conforming to spec + 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', + 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", + + 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?', + CREDENTIAL_FORMATS_NOT_SUPPORTED = 'CREDENTIAL_FORMATS_NOT_SUPPORTED', + CREDENTIALS_FORMATS_NOT_PROVIDED = 'Credentials format not provided by RP/OP', + 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.', + 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', + 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', +} + +export default SIOPErrors; diff --git a/packages/siopv2/src/types/Events.ts b/packages/siopv2/src/types/Events.ts new file mode 100644 index 00000000..523c61dc --- /dev/null +++ b/packages/siopv2/src/types/Events.ts @@ -0,0 +1,67 @@ +export enum AuthorizationEvents { + ON_AUTH_REQUEST_CREATED_SUCCESS = 'onAuthRequestCreatedSuccess', + ON_AUTH_REQUEST_CREATED_FAILED = 'onAuthRequestCreatedFailed', + + ON_AUTH_REQUEST_SENT_SUCCESS = 'onAuthRequestSentSuccess', + ON_AUTH_REQUEST_SENT_FAILED = 'onAuthRequestSentFailed', + + ON_AUTH_REQUEST_RECEIVED_SUCCESS = 'onAuthRequestReceivedSuccess', + ON_AUTH_REQUEST_RECEIVED_FAILED = 'onAuthRequestReceivedFailed', + + ON_AUTH_REQUEST_VERIFIED_SUCCESS = 'onAuthRequestVerifiedSuccess', + ON_AUTH_REQUEST_VERIFIED_FAILED = 'onAuthRequestVerifiedFailed', + + ON_AUTH_RESPONSE_CREATE_SUCCESS = 'onAuthResponseCreateSuccess', + ON_AUTH_RESPONSE_CREATE_FAILED = 'onAuthResponseCreateFailed', + + ON_AUTH_RESPONSE_SENT_SUCCESS = 'onAuthResponseSentSuccess', + ON_AUTH_RESPONSE_SENT_FAILED = 'onAuthResponseSentFailed', + + ON_AUTH_RESPONSE_RECEIVED_SUCCESS = 'onAuthResponseReceivedSuccess', + ON_AUTH_RESPONSE_RECEIVED_FAILED = 'onAuthResponseReceivedFailed', + + ON_AUTH_RESPONSE_VERIFIED_SUCCESS = 'onAuthResponseVerifiedSuccess', + ON_AUTH_RESPONSE_VERIFIED_FAILED = 'onAuthResponseVerifiedFailed', +} + +export class AuthorizationEvent { + private readonly _subject: T | undefined; + private readonly _error?: Error; + private readonly _timestamp: number; + private readonly _correlationId: string; + + public constructor(args: { correlationId: string; subject?: T; error?: Error }) { + //fixme: Create correlationId if not provided. Might need to be deferred to registry though + this._correlationId = args.correlationId; + this._timestamp = Date.now(); + this._subject = args.subject; + this._error = args.error; + } + + get subject(): T { + return this._subject; + } + + get timestamp(): number { + return this._timestamp; + } + + get error(): Error { + return this._error; + } + + public hasError(): boolean { + return !!this._error; + } + + get correlationId(): string { + return this._correlationId; + } +} + +export interface RegisterEventListener { + event: AuthorizationEvents | AuthorizationEvents[]; + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + listener: (...args: any[]) => void; +} diff --git a/packages/siopv2/src/types/JWT.types.ts b/packages/siopv2/src/types/JWT.types.ts new file mode 100644 index 00000000..c4070541 --- /dev/null +++ b/packages/siopv2/src/types/JWT.types.ts @@ -0,0 +1,81 @@ +import type { DIDResolutionResult, VerificationMethod } from 'did-resolver'; + +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 interface JWTPayload { + iss?: string; + sub?: string; + aud?: string | string[]; + iat?: number; + nbf?: number; + type?: string; + exp?: number; + rexp?: number; + jti?: string; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [x: string]: any; +} + +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 +} + +/** + * JSON Web Key ({@link https://www.rfc-editor.org/rfc/rfc7517 JWK}). "RSA", "EC", "OKP", and "oct" + * key types are supported. + */ +export interface JWK { + /** JWK "alg" (Algorithm) Parameter. */ + alg?: string; + crv?: string; + d?: string; + dp?: string; + dq?: string; + e?: string; + /** JWK "ext" (Extractable) Parameter. */ + ext?: boolean; + k?: string; + /** JWK "key_ops" (Key Operations) Parameter. */ + key_ops?: string[]; + /** JWK "kid" (Key ID) Parameter. */ + kid?: string; + /** JWK "kty" (Key Type) Parameter. */ + kty?: string; + n?: string; + oth?: Array<{ + d?: string; + r?: string; + t?: string; + }>; + p?: string; + q?: string; + qi?: string; + /** JWK "use" (Public Key Use) Parameter. */ + use?: string; + x?: string; + y?: string; + /** JWK "x5c" (X.509 Certificate Chain) Parameter. */ + x5c?: string[]; + /** JWK "x5t" (X.509 Certificate SHA-1 Thumbprint) Parameter. */ + x5t?: string; + /** "x5t#S256" (X.509 Certificate SHA-256 Thumbprint) Parameter. */ + 'x5t#S256'?: string; + /** JWK "x5u" (X.509 URL) Parameter. */ + x5u?: string; + + [propName: string]: unknown; +} + +// export declare type ECCurve = 'P-256' | 'secp256k1' | 'P-384' | 'P-521'; diff --git a/packages/siopv2/src/types/SIOP.types.ts b/packages/siopv2/src/types/SIOP.types.ts new file mode 100644 index 00000000..5e76c147 --- /dev/null +++ b/packages/siopv2/src/types/SIOP.types.ts @@ -0,0 +1,787 @@ +// noinspection JSUnusedGlobalSymbols + +import { ResponseType } from '@sphereon/oid4vc-common'; +import { Format, PresentationDefinitionV1, PresentationDefinitionV2 } from '@sphereon/pex-models'; +import { + AdditionalClaims, + CompactSdJwtVc, + IPresentation, + IVerifiablePresentation, + PresentationSubmission, + W3CVerifiableCredential, + 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 { + AuthorizationResponse, + AuthorizationResponseOpts, + PresentationDefinitionWithLocation, + PresentationVerificationCallback, + VerifyAuthorizationResponseOpts, +} from '../authorization-response'; +import { RequestObject, RequestObjectOpts } from '../request-object'; +import { IRPSessionManager } from '../rp'; + +import { EcdsaSignature, JWTPayload, ResolveOpts, VerifiedJWT } from './index'; +export const DEFAULT_EXPIRATION_TIME = 10 * 60; + +// https://openid.net/specs/openid-connect-core-1_0.html#RequestObject +// request and request_uri parameters MUST NOT be included in Request Objects. +export interface RequestObjectPayload extends RequestCommonPayload, JWTPayload { + scope: string; // REQUIRED. As specified in Section 3.1.2 of [OpenID.Core]. + response_type: ResponseType | string; // REQUIRED. Constant string value id_token. + client_id: string; // REQUIRED. RP's identifier at the Self-Issued OP. + client_id_scheme?: ClientIdScheme; // The client_id_scheme enables deployments of this specification to use different mechanisms to obtain and validate metadata of the Verifier beyond the scope of [RFC6749]. The term client_id_scheme is used since the Verifier is acting as an OAuth 2.0 Client. + redirect_uri?: string; // REQUIRED before OID4VP v18, now optional because of response_uri. URI to which the Self-Issued OP Response will be sent + 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. + nonce: string; + state: string; +} + +export type RequestObjectJwt = string; + +// https://openid.net/specs/openid-connect-self-issued-v2-1_0.html#section-8 + +export interface AuthorizationRequestCommonPayload extends RequestCommonPayload, JWTPayload { + request?: string; // OPTIONAL. Request Object value, as specified in Section 6.1 of [OpenID.Core]. The Request Object MAY be encrypted to the Self-Issued OP by the RP. In this case, the sub (subject) of a previously issued ID Token for this RP MUST be sent as the kid (Key ID) of the JWE. + request_uri?: string; // OPTIONAL. URL where Request Object value can be retrieved from, as specified in Section 6.2 of [OpenID.Core]. +} + +export interface RequestCommonPayload extends JWTPayload { + scope?: string; // REQUIRED. As specified in Section 3.1.2 of [OpenID.Core]. + response_type?: ResponseType | string; // REQUIRED. Constant string value id_token. + client_id?: string; // REQUIRED. RP's identifier at the Self-Issued OP. + redirect_uri?: string; // REQUIRED. URI to which the Self-Issued OP Response will be sent + + id_token_hint?: string; // OPTIONAL. As specified in Section 3.1.2 of [OpenID.Core]. If the ID Token is encrypted for the Self-Issued OP, the sub (subject) of the signed ID Token MUST be sent as the kid (Key ID) of the JWE. + // claims?: ClaimPayloadCommon; // OPTIONAL. As specified in Section 5.5 of [OpenID.Core] + nonce?: string; + state?: string; + response_mode?: ResponseMode; // This specification introduces a new response mode post in accordance with [OAuth.Responses]. This response mode is used to request the Self-Issued OP to deliver the result of the authentication process to a certain endpoint using the HTTP POST method. The additional parameter response_mode is used to carry this value. +} + +export interface AuthorizationRequestPayloadVID1 extends AuthorizationRequestCommonPayload, RequestRegistrationPayloadProperties { + claims?: ClaimPayloadVID1; +} + +export interface AuthorizationRequestPayloadVD11 + extends AuthorizationRequestCommonPayload, + RequestClientMetadataPayloadProperties, + RequestIdTokenPayloadProperties { + claims?: ClaimPayloadCommon; // OPTIONAL. As specified in Section 5.5 of [OpenID.Core] + presentation_definition?: PresentationDefinitionV1 | PresentationDefinitionV2 | PresentationDefinitionV1[] | PresentationDefinitionV2[]; + presentation_definition_uri?: string; +} + +export interface AuthorizationRequestPayloadVD12OID4VPD18 + extends AuthorizationRequestCommonPayload, + RequestClientMetadataPayloadProperties, + RequestIdTokenPayloadProperties { + claims?: ClaimPayloadCommon; // OPTIONAL. As specified in Section 5.5 of [OpenID.Core] + presentation_definition?: PresentationDefinitionV1 | PresentationDefinitionV2 | PresentationDefinitionV1[] | PresentationDefinitionV2[]; + presentation_definition_uri?: string; + client_id_scheme?: ClientIdScheme; + 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'; + +// https://openid.bitbucket.io/connect/openid-connect-self-issued-v2-1_0.html#section-10 +export type AuthorizationRequestPayload = + | AuthorizationRequestPayloadVID1 + | AuthorizationRequestPayloadVD11 + | AuthorizationRequestPayloadVD12OID4VPD18; + +export type JWTVcPresentationProfileAuthenticationRequestPayload = RequestIdTokenPayloadProperties; + +export interface RequestIdTokenPayloadProperties { + id_token_type?: string; // OPTIONAL. Space-separated string that specifies the types of ID token the RP wants to obtain, with the values appearing in order of preference. The allowed individual values are subject_signed and attester_signed (see Section 8.2). The default value is attester_signed. The RP determines the type if ID token returned based on the comparison of the iss and sub claims values (see(see Section 12.1). In order to preserve compatibility with existing OpenID Connect deployments, the OP MAY return an ID token that does not fulfill the requirements as expressed in this parameter. So the RP SHOULD be prepared to reliably handle such an outcome. +} + +export interface RequestClientMetadataPayloadProperties { + client_metadata?: RPRegistrationMetadataPayload; // 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}. + client_metadata_uri?: string; // 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}. +} + +export interface RequestRegistrationPayloadProperties { + registration?: RPRegistrationMetadataPayload; //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 Section 2.2.1. + registration_uri?: string; // 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 2.2.1. +} + +export type ResponseURIType = 'response_uri' | 'redirect_uri'; + +export interface VerifiedAuthorizationRequest extends Partial { + responseURIType: ResponseURIType; + responseURI?: string; + clientIdScheme?: string; + correlationId: string; + authorizationRequest: AuthorizationRequest; + authorizationRequestPayload: AuthorizationRequestPayload; + requestObject?: RequestObject; // The Request object + registrationMetadataPayload: RPRegistrationMetadataPayload; + presentationDefinitions?: PresentationDefinitionWithLocation[]; // The optional presentation definition objects that the RP requests + verifyOpts: VerifyAuthorizationRequestOpts; // The verification options for the authentication request + versions: SupportedVersion[]; +} + +export type IDTokenJwt = string; + +export interface IDTokenPayload extends JWTPayload { + iss?: ResponseIss.SELF_ISSUED_V2 | string; + sub?: string; // did (or thumbprint of sub_jwk key when type is jkt) + aud?: string; // redirect_uri from request + iat?: number; // Issued at time + exp?: number; // Expiration time + auth_time?: number; + nonce?: string; + _vp_token?: { + /* + This profile currently supports including only a single VP in the VP Token. + In such cases, as defined in section 5.2 of OpenID4VP ID1, when the Self-Issued OP returns a single VP in the vp_token, + VP Token is not an array, and a single VP is passed as a vp_token. In this case, the descriptor map would contain a simple path expression “$”. + * It's not clear from the ID1 specs how to handle presentation submission in case of multiple VPs + */ + presentation_submission: PresentationSubmission; + }; +} + +export interface AuthorizationResponsePayload { + access_token?: string; + token_type?: string; + refresh_token?: string; + expires_in?: number; + state?: string; + id_token?: string; + vp_token?: Array | W3CVerifiablePresentation | CompactSdJwtVc; + presentation_submission?: PresentationSubmission; + verifiedData?: IPresentation | AdditionalClaims; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [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; +} + +export interface VpTokenClaimPayload { + presentation_definition?: PresentationDefinitionV1 | PresentationDefinitionV2; + presentation_definition_uri?: string; +} + +export interface ClaimPayloadCommon { + [x: string]: any; +} + +export interface ClaimPayloadVID1 extends ClaimPayloadCommon { + id_token?: IdTokenClaimPayload; + vp_token?: VpTokenClaimPayload; +} + +/** + * A wrapper for verifiablePresentation + * + */ +export interface VerifiablePresentationWithFormat { + format: VerifiablePresentationTypeFormat; + presentation: W3CVerifiablePresentation; +} + +export interface RequestStateInfo { + client_id: string; // RP ID + + // sub: string + nonce: string; + state: string; + 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; + // this is a confusion point. In the interop profile it mentions "https://self-issued.me/v2/openid-vc", but in the SIOPv2 it's mentioning "https://self-issued.me/v2" + // @Niels also created an issue here: https://github.com/decentralized-identity/jwt-vc-presentation-profile/issues/63 so we can keep an eye on this for clarification + //TODO add the check: Mandatory if PassBy.Value + issuer?: ResponseIss | string; + //TODO add the check: Mandatory if PassBy.Value + responseTypesSupported?: ResponseType[] | ResponseType; + scopesSupported?: Scope[] | Scope; + subjectTypesSupported?: SubjectType[] | SubjectType; + idTokenSigningAlgValuesSupported?: SigningAlgo[] | SigningAlgo; + requestObjectSigningAlgValuesSupported?: SigningAlgo[] | SigningAlgo; + //TODO add the check: Mandatory if PassBy.Value + subject_syntax_types_supported?: string[]; + tokenEndpoint?: string; // from openid connect discovery 1_0 + userinfoEndpoint?: string; // from openid connect discovery 1_0 + jwksUri?: string; // from openid connect discovery 1_0 + registrationEndpoint?: string; // from openid connect discovery 1_0 + responseModesSupported?: ResponseMode[] | ResponseMode; // from openid connect discovery 1_0 + grantTypesSupported?: GrantType[] | GrantType; // from openid connect discovery 1_0 + acrValuesSupported?: AuthenticationContextReferences[] | AuthenticationContextReferences; // from openid connect discovery 1_0 + idTokenEncryptionAlgValuesSupported?: SigningAlgo[] | SigningAlgo; // from openid connect discovery 1_0 + idTokenEncryptionEncValuesSupported?: string[] | string; // from openid connect discovery 1_0 + userinfoSigningAlgValuesSupported?: SigningAlgo[] | SigningAlgo; // from openid connect discovery 1_0 + userinfoEncryptionAlgValuesSupported?: SigningAlgo[] | SigningAlgo; // from openid connect discovery 1_0 + userinfoEncryptionEncValuesSupported?: string[] | string; // from openid connect discovery 1_0 + requestObjectEncryptionAlgValuesSupported?: SigningAlgo[] | SigningAlgo; // from openid connect discovery 1_0 + requestObjectEncryptionEncValuesSupported?: string[] | string; // from openid connect discovery 1_0 + tokenEndpointAuthMethodsSupported?: TokenEndpointAuthMethod[] | TokenEndpointAuthMethod; // from openid connect discovery 1_0 + tokenEndpointAuthSigningAlgValuesSupported?: SigningAlgo[] | SigningAlgo; // from openid connect discovery 1_0 + displayValuesSupported?: string[] | string; // from openid connect discovery 1_0 + claimTypesSupported?: ClaimType[] | ClaimType; // from openid connect discovery 1_0 + claimsSupported?: string[] | string; // recommended, from openid connect discovery 1_0 + serviceDocumentation?: string; // from openid connect discovery 1_0 + claimsLocalesSupported?: string[] | string; // from openid connect discovery 1_0 + uiLocalesSupported?: string[] | string; // from openid connect discovery 1_0 + claimsParameterSupported?: boolean; // from openid connect discovery 1_0 + requestParameterSupported?: boolean; // from openid connect discovery 1_0 + requestUriParameterSupported?: boolean; // from openid connect discovery 1_0 + requireRequestUriRegistration?: boolean; // from openid connect discovery 1_0 + opPolicyUri?: string; // from openid connect discovery 1_0 + opTosUri?: string; // from openid connect discovery 1_0 + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [x: string]: any; +} + +//same for jwt_vc +interface DiscoveryMetadataOptsVID1 extends DiscoveryMetadataCommonOpts { + client_id?: string; // from oidc4vp + redirectUris?: string[] | string; // from oidc4vp + clientName?: string; // from oidc4vp + tokenEndpointAuthMethod?: string; // from oidc4vp + applicationType?: string; // from oidc4vp + responseTypes?: string; // from oidc4vp, also name suggests array + grantTypes?: string; // from oidc4vp, also name suggests array + //TODO add the check: Mandatory if PassBy.Value + vpFormats?: Format; // from oidc4vp +} + +interface JWT_VCDiscoveryMetadataOpts extends DiscoveryMetadataOptsVID1 { + logo_uri?: string; + clientPurpose?: string; +} + +interface DiscoveryMetadataOptsVD11 extends DiscoveryMetadataCommonOpts { + idTokenTypesSupported?: IdTokenType[] | IdTokenType; + vpFormatsSupported?: Format; // from oidc4vp +} + +// https://openid.net/specs/openid-connect-self-issued-v2-1_0.html#section-8.2 +// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata +interface DiscoveryMetadataCommonPayload { + authorization_endpoint?: Schema | string; + issuer?: ResponseIss | string; + response_types_supported?: ResponseType[] | ResponseType; + scopes_supported?: Scope[] | Scope; + subject_types_supported?: SubjectType[] | SubjectType; + id_token_signing_alg_values_supported?: SigningAlgo[] | SigningAlgo; + request_object_signing_alg_values_supported?: SigningAlgo[] | SigningAlgo; + subject_syntax_types_supported?: string[]; + token_endpoint?: string; + userinfo_endpoint?: string; + jwks_uri?: string; + // marked as required by https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata + registration_endpoint?: string; + response_modes_supported?: ResponseMode[] | ResponseMode; + grant_types_supported?: GrantType[] | GrantType; + acr_values_supported?: AuthenticationContextReferences[] | AuthenticationContextReferences; + id_token_encryption_alg_values_supported?: SigningAlgo[] | SigningAlgo; + /** + * OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for the ID Token to encode the Claims in a JWT [JWT]. + */ + //TODO: maybe add an enum for this with: A256GCM, A128CBC-HS256, ... + id_token_encryption_enc_values_supported?: string[] | string; + userinfo_signing_alg_values_supported?: SigningAlgo[] | SigningAlgo; + userinfo_encryption_alg_values_supported?: SigningAlgo[] | SigningAlgo; + /** + * OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) [JWA] supported by the UserInfo Endpoint to encode the Claims in a JWT [JWT]. + */ + userinfo_encryption_enc_values_supported?: string[] | string; + request_object_encryption_alg_values_supported?: SigningAlgo[] | SigningAlgo; + /** + * OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for Request Objects. These algorithms are used both when the Request Object is passed by value and when it is passed by reference. + */ + request_object_encryption_enc_values_supported?: string[] | string; + token_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[] | TokenEndpointAuthMethod; + token_endpoint_auth_signing_alg_values_supported?: SigningAlgo[] | SigningAlgo; + /** + * OPTIONAL. JSON array containing a list of the display parameter values that the OpenID Provider supports. These values are described in Section 3.1.2.1 of OpenID Connect Core 1.0 [OpenID.Core]. + */ + display_values_supported?: unknown[] | unknown; + /** + * OPTIONAL. JSON array containing a list of the Claim Types that the OpenID Provider supports. These Claim Types are described in Section 5.6 of OpenID Connect Core 1.0 [OpenID.Core]. Values defined by this specification are normal, aggregated, and distributed. If omitted, the implementation supports only normal Claims. + */ + claim_types_supported?: ClaimType[] | ClaimType; + /** + * RECOMMENDED. JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for. Note that for privacy or other reasons, this might not be an exhaustive list. + */ + claims_supported?: string[] | string; + service_documentation?: string; + claims_locales_supported?: string[] | string; + ui_locales_supported?: string[] | string; + claims_parameter_supported?: boolean; + request_parameter_supported?: boolean; + request_uri_parameter_supported?: boolean; + require_request_uri_registration?: boolean; + op_policy_uri?: string; + op_tos_uri?: string; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [x: string]: any; +} + +interface DiscoveryMetadataPayloadVID1 extends DiscoveryMetadataCommonPayload { + client_id?: string; + redirect_uris?: string[]; + client_name?: string; + token_endpoint_auth_method?: string; + application_type?: string; + response_types?: string; + grant_types?: string; + vp_formats?: Format; +} + +interface JWT_VCDiscoveryMetadataPayload extends DiscoveryMetadataPayloadVID1 { + logo_uri?: string; + client_purpose?: string; +} + +interface DiscoveryMetadataPayloadVD11 extends DiscoveryMetadataCommonPayload { + id_token_types_supported?: IdTokenType[] | IdTokenType; + vp_formats_supported?: Format; // from oidc4vp +} + +export type DiscoveryMetadataPayload = DiscoveryMetadataPayloadVID1 | JWT_VCDiscoveryMetadataPayload | DiscoveryMetadataPayloadVD11; + +export type DiscoveryMetadataOpts = (JWT_VCDiscoveryMetadataOpts | DiscoveryMetadataOptsVID1 | DiscoveryMetadataOptsVD11) & + DiscoveryMetadataCommonOpts; + +export type ClientMetadataOpts = RPRegistrationMetadataOpts & ClientMetadataProperties; + +export type ResponseRegistrationOpts = DiscoveryMetadataOpts & ClientMetadataProperties; + +export type RPRegistrationMetadataOpts = Partial< + Pick< + DiscoveryMetadataOpts, + | 'client_id' + | 'idTokenSigningAlgValuesSupported' + | 'requestObjectSigningAlgValuesSupported' + | 'responseTypesSupported' + | 'scopesSupported' + | 'subjectTypesSupported' + | 'subject_syntax_types_supported' + | 'vpFormatsSupported' + | 'clientName' + | 'logo_uri' + | 'tos_uri' + | 'clientPurpose' + > +> & { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [x: string]: any; +}; + +export type RPRegistrationMetadataPayload = Pick< + DiscoveryMetadataPayload, + | 'client_id' + | 'id_token_signing_alg_values_supported' + | 'request_object_signing_alg_values_supported' + | 'response_types_supported' + | 'scopes_supported' + | 'subject_types_supported' + | 'subject_syntax_types_supported' + | 'vp_formats' + | 'client_name' + | 'logo_uri' + | 'client_purpose' +> & { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [x: string]: any; +}; + +export interface CommonSupportedMetadata { + subject_syntax_types_supported?: string[]; + vp_formats: Format; +} + +export interface ObjectBy { + passBy: PassBy; + reference_uri?: string; // for pass by reference + + targets?: PropertyTargets; +} + +export enum AuthenticationContextReferences { + PHR = 'phr', + PHRH = 'phrh', +} + +export enum ClaimType { + NORMAL = 'normal', + AGGREGATED = 'aggregated', + DISTRIBUTED = 'distributed', +} + +export enum IdTokenType { + SUBJECT_SIGNED = 'subject_signed', + ATTESTER_SIGNED = 'attester_signed', +} + +export interface ClientMetadataProperties extends ObjectBy { + id_token_encrypted_response_alg?: EncKeyAlgorithm; + id_token_encrypted_response_enc?: EncSymmetricAlgorithmCode; +} + +export enum VerifiablePresentationTypeFormat { + JWT_VP = 'jwt_vp', + LDP_VP = 'ldp_vp', + SD_JWT_VC = 'vc+sd-jwt', +} + +export enum VerifiableCredentialTypeFormat { + LDP_VC = 'ldp_vc', + JWT_VC = 'jwt_vc', + SD_JWT_VC = 'vc+sd-jwt', +} + +export enum EncSymmetricAlgorithmCode { + XC20P = 'XC20P', // default +} + +export enum EncKeyAlgorithm { + ECDH_ES = 'ECDH-ES', // default +} + +export enum PassBy { + NONE = 'NONE', + REFERENCE = 'REFERENCE', + VALUE = 'VALUE', +} + +export enum ResponseContext { + RP = 'rp', + 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; +} + +export interface VerifiedOpenID4VPSubmission { + submissionData: PresentationSubmission; + presentationDefinitions: PresentationDefinitionWithLocation[]; + presentations: WrappedVerifiablePresentation[]; +} + +export interface VerifiedAuthorizationResponse { + correlationId: string; + + authorizationResponse: AuthorizationResponse; + + oid4vpSubmission?: VerifiedOpenID4VPSubmission; + + idToken?: VerifiedIDToken; + verifyOpts?: VerifyAuthorizationResponseOpts; +} + +export enum GrantType { + AUTHORIZATION_CODE = 'authorization_code', + IMPLICIT = 'implicit', +} + +export enum ResponseMode { + FRAGMENT = 'fragment', + FORM_POST = 'form_post', + POST = 'post', // Used in OID4VP spec <= version 17 + // Defined in openid4vp spec > 17 and replaces POST above + // See https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-response-mode-direct_post + DIRECT_POST = 'direct_post', + QUERY = 'query', +} + +export enum ProtocolFlow { + SAME_DEVICE = 'same_device', + CROSS_DEVICE = 'cross_device', +} + +export interface SignatureResponse { + jws: string; +} + +export enum UrlEncodingFormat { + FORM_URL_ENCODED = 'application/x-www-form-urlencoded', +} + +export type SIOPURI = { + encodedUri: string; // The encoded URI + encodingFormat: UrlEncodingFormat; // The encoding format used +}; + +export interface UriResponse extends SIOPURI { + responseMode?: ResponseMode; // The response mode as passed in during creation + bodyEncoded?: string; // The URI encoded body (JWS) +} + +export interface AuthorizationRequestURI extends SIOPURI { + scheme: string; + requestObjectBy: ObjectBy; // The supplied request opts as passed in to the method + authorizationRequestPayload: AuthorizationRequestPayload; // The authorization request payload + requestObjectJwt?: RequestObjectJwt; // The JWT request object +} + +export interface ParsedAuthorizationRequestURI extends SIOPURI { + scheme: string; + requestObjectJwt?: RequestObjectJwt; + authorizationRequestPayload: AuthorizationRequestPayload; // The json payload that ends up signed in the JWT + registration: RPRegistrationMetadataPayload; +} + +export enum KeyType { + EC = 'EC', +} + +export enum KeyCurve { + SECP256k1 = 'secp256k1', + ED25519 = 'ed25519', +} + +export enum TokenEndpointAuthMethod { + CLIENT_SECRET_POST = 'client_secret_post', + CLIENT_SECRET_BASIC = 'client_secret_basic', + CLIENT_SECRET_JWT = 'client_secret_jwt', + PRIVATE_KEY_JWT = 'private_key_jwt', +} + +export enum SigningAlgo { + EDDSA = 'EdDSA', + RS256 = 'RS256', + PS256 = 'PS256', + ES256 = 'ES256', + ES256K = 'ES256K', +} + +export enum Scope { + OPENID = 'openid', + OPENID_DIDAUTHN = 'openid did_authn', + //added based on the https://openid.net/specs/openid-connect-implicit-1_0.html#SelfIssuedDiscovery + PROFILE = 'profile', + EMAIL = 'email', + ADDRESS = 'address', + PHONE = 'phone', +} + +export enum SubjectIdentifierType { + JKT = 'jkt', + DID = 'did', +} + +export enum SubjectSyntaxTypesSupportedValues { + DID = 'did', + JWK_THUMBPRINT = 'urn:ietf:params:oauth:jwk-thumbprint', +} + +export enum CredentialFormat { + JSON_LD = 'w3cvc-jsonld', + JWT = 'jwt', +} + +export enum SubjectType { + PUBLIC = 'public', + PAIRWISE = 'pairwise', +} + +export enum Schema { + OPENID = 'openid:', + OPENID_VC = 'openid-vc:', +} + +export enum ResponseIss { + SELF_ISSUED_V1 = 'https://self-issued.me', + SELF_ISSUED_V2 = 'https://self-issued.me/v2', + 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; + +export const isResponseOpts = ( + object: RequestObjectOpts | AuthorizationResponseOpts, +): object is RequestObjectOpts => 'did' in object; + +export const isRequestPayload = ( + object: AuthorizationRequestPayload | RequestObjectPayload | AuthorizationResponsePayload | IDTokenPayload, +): object is AuthorizationRequestPayload => 'response_mode' in object && 'response_type' in object; + +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; + +export enum RevocationStatus { + VALID = 'valid', + INVALID = 'invalid', +} + +export interface IRevocationVerificationStatus { + status: RevocationStatus; + error?: string; +} + +export type RevocationVerificationCallback = ( + vc: W3CVerifiableCredential, + type: VerifiableCredentialTypeFormat, +) => Promise; + +export enum RevocationVerification { + NEVER = 'never', // We don't want to verify revocation + IF_PRESENT = 'if_present', // If credentialStatus is present, did-auth-siop will verify revocation. If present and not valid an exception is thrown + ALWAYS = 'always', // We'll always check the revocation, if not present or not valid, throws an exception +} + +export interface RevocationOpts { + revocationVerification: RevocationVerification; + revocationVerificationCallback?: RevocationVerificationCallback; +} + +export enum SupportedVersion { + SIOPv2_ID1 = 70, + SIOPv2_D11 = 110, + SIOPv2_D12_OID4VP_D18 = 180, + JWT_VC_PRESENTATION_PROFILE_v1 = 71, +} + +export interface SIOPResonse { + origResponse: Response; + successBody?: T; + errorBody?: ErrorResponse; +} + +export interface ErrorResponse extends Response { + error: string; + error_description?: string; + error_uri?: string; + state?: string; +} + +export enum ContentType { + FORM_URL_ENCODED = 'application/x-www-form-urlencoded', + UTF_8 = 'UTF-8', +} diff --git a/packages/siopv2/src/types/SSI.types.ts b/packages/siopv2/src/types/SSI.types.ts new file mode 100644 index 00000000..9faedda8 --- /dev/null +++ b/packages/siopv2/src/types/SSI.types.ts @@ -0,0 +1,59 @@ +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/packages/siopv2/src/types/SessionManager.ts b/packages/siopv2/src/types/SessionManager.ts new file mode 100644 index 00000000..299ebd87 --- /dev/null +++ b/packages/siopv2/src/types/SessionManager.ts @@ -0,0 +1,36 @@ +import { AuthorizationRequest } from '../authorization-request'; +import { AuthorizationResponse } from '../authorization-response'; + +export interface AuthorizationRequestState { + correlationId?: string; + request: AuthorizationRequest; + status: AuthorizationRequestStateStatus; + timestamp: number; + lastUpdated: number; + error?: Error; +} + +export interface AuthorizationResponseState { + correlationId?: string; + response: AuthorizationResponse; + status: AuthorizationResponseStateStatus; + timestamp: number; + lastUpdated: number; + error?: Error; +} + +export enum AuthorizationRequestStateStatus { + CREATED = 'created', + SENT = 'sent', + RECEIVED = 'received', + VERIFIED = 'verified', + ERROR = 'error', +} + +export enum AuthorizationResponseStateStatus { + CREATED = 'created', + SENT = 'sent', + RECEIVED = 'received', + VERIFIED = 'verified', + ERROR = 'error', +} diff --git a/packages/siopv2/src/types/index.ts b/packages/siopv2/src/types/index.ts new file mode 100644 index 00000000..3ae6982b --- /dev/null +++ b/packages/siopv2/src/types/index.ts @@ -0,0 +1,8 @@ +import SIOPErrors from './Errors'; + +export { SIOPErrors }; +export * from './JWT.types'; +export * from './SIOP.types'; +export * from './SSI.types'; +export * from './Events'; +export * from './SessionManager'; diff --git a/packages/siopv2/test/AuthenticationRequest.request.spec.ts b/packages/siopv2/test/AuthenticationRequest.request.spec.ts new file mode 100644 index 00000000..cfaf604d --- /dev/null +++ b/packages/siopv2/test/AuthenticationRequest.request.spec.ts @@ -0,0 +1,702 @@ +import { parse } from 'querystring'; + +import { ResponseType } from '@sphereon/oid4vc-common'; +import { IPresentationDefinition } from '@sphereon/pex'; +import { IProofType } from '@sphereon/ssi-types'; + +import { + CreateAuthorizationRequestOpts, + PassBy, + RequestObject, + Scope, + SigningAlgo, + SubjectIdentifierType, + SubjectType, + SupportedVersion, + URI, +} from '../src'; +import SIOPErrors from '../src/types/Errors'; + +import { WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; +import { + VERIFIER_LOGO_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT_NL, + VERIFIERZ_PURPOSE_TO_VERIFY, + 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'; +const HEX_KEY = 'f857544a9d1097e242ff0b287a7e6e90f19cf973efe2317f2a4678739664420f'; +const DID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0'; +const KID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0#keys-1'; + +describe('create Request Uri should', () => { + it('throw BAD_PARAMS when no responseOpts is passed', async () => { + expect.assertions(1); + await expect(URI.fromOpts(undefined as never)).rejects.toThrow(SIOPErrors.BAD_PARAMS); + }); + + it('throw BAD_PARAMS when no responseOpts.redirectUri is passed', async () => { + expect.assertions(1); + const opts = {}; + await expect(URI.fromOpts(opts as never)).rejects.toThrow(SIOPErrors.BAD_PARAMS); + }); + + it('throw BAD_PARAMS when no responseOpts.requestObject is passed', async () => { + expect.assertions(1); + const opts = { payload: { redirect_uri: EXAMPLE_REDIRECT_URL } }; + await expect(URI.fromOpts(opts as never)).rejects.toThrow(SIOPErrors.BAD_PARAMS); + }); + + it('throw BAD_PARAMS when no responseOpts.requestBy is passed', async () => { + expect.assertions(1); + const opts = { + payload: { + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + requestObject: {}, + }; + await expect(URI.fromOpts(opts as never)).rejects.toThrow(SIOPErrors.REQUEST_OBJECT_TYPE_NOT_SET); + }); + + it('throw REQUEST_OBJECT_TYPE_NOT_SET when responseOpts.requestBy type is different from REFERENCE or VALUE', async () => { + expect.assertions(1); + const opts = { + payload: { + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + requestObject: { + passBy: 'other type', + }, + }; + await expect(URI.fromOpts(opts as never)).rejects.toThrow(SIOPErrors.REQUEST_OBJECT_TYPE_NOT_SET); + }); + + it('throw NO_REFERENCE_URI when responseOpts.requestBy type is REFERENCE and no referenceUri is passed', async () => { + expect.assertions(1); + const opts = { + payload: { + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + requestObject: { + passBy: PassBy.REFERENCE, + }, + }; + await expect(URI.fromOpts(opts as never)).rejects.toThrow(SIOPErrors.NO_REFERENCE_URI); + }); + + it('return a reference url', async () => { + expect.assertions(12); + const opts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + payload: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'openid', + response_type: 'id_token', + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + signature: { + hexPrivateKey: HEX_KEY, + alg: SigningAlgo.ES256, + did: DID, + kid: KID, + }, + payload: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'openid', + response_type: 'id_token', + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100300', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + + const uriRequest = await URI.fromOpts(opts); + expect(uriRequest).toBeDefined(); + expect(uriRequest).toHaveProperty('encodedUri'); + expect(uriRequest).toHaveProperty('encodingFormat'); + expect(uriRequest).toHaveProperty('requestObjectJwt'); + expect(uriRequest).toHaveProperty('authorizationRequestPayload'); + expect(uriRequest.authorizationRequestPayload).toBeDefined(); + + const uriDecoded = decodeURIComponent(uriRequest.encodedUri); + expect(uriDecoded).toContain(`openid://`); + expect(uriDecoded).toContain(`response_type=${ResponseType.ID_TOKEN}`); + expect(uriDecoded).toContain(`&redirect_uri=${opts.payload.redirect_uri}`); + expect(uriDecoded).toContain(`&scope=${Scope.OPENID}`); + expect(uriDecoded).toContain(`&request_uri=`); + + const data = parse(uriDecoded); + expect(data.request_uri).toStrictEqual(opts.requestObject.reference_uri); + // expect(data.registration).toContain('client_purpose#nl-NL'); + }); + + it('return a reference url when using did:key', async () => { + expect.assertions(4); + const opts: 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, + }, + payload: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'test', + response_type: 'id_token', + request_object_signing_alg_values_supported: [SigningAlgo.ES256, SigningAlgo.EDDSA], + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100301', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + + const uriRequest = await URI.fromOpts(opts); + const uriDecoded = decodeURIComponent(uriRequest.encodedUri); + + const data = URI.parse(uriDecoded); + expect(uriRequest).toHaveProperty('requestObjectJwt'); + expect(uriRequest.authorizationRequestPayload).toBeDefined(); + expect(data.authorizationRequestPayload.request_uri).toEqual(opts.requestObject.reference_uri); + expect(uriRequest.authorizationRequestPayload.request_uri).toEqual(EXAMPLE_REFERENCE_URL); + }); + + it('return an url with an embedded token value', async () => { + expect.assertions(3); + const opts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + + requestObject: { + passBy: PassBy.VALUE, + + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + alg: SigningAlgo.ES256K, + }, + payload: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'test', + response_type: 'id_token', + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100302', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + + const uriRequest = await URI.fromOpts(opts); + + const uriDecoded = decodeURIComponent(uriRequest.encodedUri); + expect(uriDecoded).toContain(`openid://?request=eyJhbGciOi`); + + const data = URI.parse(uriDecoded); + expect(data.scheme).toEqual('openid://'); + expect(data.authorizationRequestPayload.request).toContain(`eyJhbGciOi`); + }); +}); + +describe('create Request JWT should', () => { + it('throw REQUEST_OBJECT_TYPE_NOT_SET when requestBy type is different from REFERENCE and VALUE', async () => { + expect.assertions(1); + const opts = { + version: SupportedVersion.SIOPv2_ID1, + payload: { + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + + requestObject: { + passBy: 'other type', + + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + }, + }, + registration: { + 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(RequestObject.fromOpts(opts as never)).rejects.toThrow(SIOPErrors.REQUEST_OBJECT_TYPE_NOT_SET); + }); + + it('throw NO_REFERENCE_URI when no referenceUri is passed with REFERENCE requestBy type is set', async () => { + expect.assertions(1); + const opts = { + payload: { + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + + requestObject: { + passBy: PassBy.REFERENCE, + + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + }, + }, + registration: { + 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(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 = { + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + }, + payload: { + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + }, + registration: { + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + type: 'FAILURE', + }, + }; + await expect(RequestObject.fromOpts(opts as never)).rejects.toThrow(SIOPErrors.REGISTRATION_OBJECT_TYPE_NOT_SET); + }); + + it('throw NO_REFERENCE_URI when registrationBy type is REFERENCE and no referenceUri is passed', async () => { + expect.assertions(1); + const opts = { + version: SupportedVersion.SIOPv2_ID1, + + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + }, + payload: { + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + }, + registration: { + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + passBy: PassBy.REFERENCE, + }, + }; + await expect(RequestObject.fromOpts(opts as never)).rejects.toThrow(SIOPErrors.NO_REFERENCE_URI); + }); + + it('succeed when all params are set', async () => { + // expect.assertions(1); + const opts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + payload: { + client_id: 'test_client_id', + scope: 'test', + response_type: 'id_token', + request_object_signing_alg_values_supported: [SigningAlgo.ES256, SigningAlgo.EDDSA], + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + alg: SigningAlgo.ES256K, + }, + payload: { + client_id: 'test_client_id', + scope: 'test', + response_type: 'id_token', + request_object_signing_alg_values_supported: [SigningAlgo.ES256, SigningAlgo.EDDSA], + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + }, + clientMetadata: { + client_id: 'test_client_id', + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + + passBy: PassBy.VALUE, + + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100303', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + + const expected = { + response_type: 'id_token', + scope: 'test', + client_id: 'test_client_id', + redirect_uri: 'https://acme.com/hello', + registration: { + id_token_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + response_types_supported: [ResponseType.ID_TOKEN], + scopes_supported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subject_types_supported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did:ethr:', 'did'], + vp_formats: { + ldp_vc: { + proof_type: ['EcdsaSecp256k1Signature2019', 'EcdsaSecp256k1Signature2019'], + }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + client_name: VERIFIER_NAME_FOR_CLIENT, + 'client_name#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100303', + client_purpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'client_purpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + + /*opts: { + redirectUri: 'https://acme.com/hello', + requestBy: { + type: 'REFERENCE', + reference_uri: 'https://rp.acme.com/siop/jwts', + }, + withSignature: { + hexPrivateKey: 'f857544a9d1097e242ff0b287a7e6e90f19cf973efe2317f2a4678739664420f', + did: 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0', + kid: 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0#keys-1', + }, + registration: { + idTokenSigningAlgValuesSupported: ['EdDSA', 'ES256'], + subjectSyntaxTypesSupported: ['did:ethr:', 'did'], + vpFormatsSupported: { + ldp_vc: { + proof_type: ['EcdsaSecp256k1Signature2019', 'EcdsaSecp256k1Signature2019'], + }, + }, + registrationBy: { + type: 'VALUE', + }, + }, + },*/ + }; + + // await URI.fromOpts(opts).then((uri) => console.log(uri.encodedUri)); + await expect((await RequestObject.fromOpts(opts)).getPayload()).resolves.toMatchObject(expected); + }); + + it('succeed when requesting with a valid PD', async () => { + const opts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + /*payload: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'test', + response_type: 'id_token', + redirect_uri: EXAMPLE_REDIRECT_URL, + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + claims: { + vp_token: { + presentation_definition: { + id: 'Insurance Plans', + input_descriptors: [ + { + id: 'Ontario Health Insurance Plan', + schema: [ + { + uri: 'https://did.itsourweb.org:3000/smartcredential/Ontario-Health-Insurance-Plan', + }, + ], + }, + ], + }, + }, + }, + },*/ + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + alg: SigningAlgo.ES256K, + }, + payload: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'test', + response_type: 'id_token', + redirect_uri: EXAMPLE_REDIRECT_URL, + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + claims: { + vp_token: { + presentation_definition: { + id: 'Insurance Plans', + input_descriptors: [ + { + id: 'Ontario Health Insurance Plan', + schema: [ + { + uri: 'https://did.itsourweb.org:3000/smartcredential/Ontario-Health-Insurance-Plan', + }, + ], + }, + ], + }, + }, + }, + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + + passBy: PassBy.VALUE, + + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100305', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + + const uriRequest = await URI.fromOpts(opts); + + const uriDecoded = decodeURIComponent(uriRequest.encodedUri); + expect(uriDecoded).toEqual(`openid://?request_uri=https://rp.acme.com/siop/jwts`); + expect((await (await uriRequest.toAuthorizationRequest()).requestObject.getPayload()).claims.vp_token).toBeDefined(); + }); + + it('should throw error if presentation definition object is not valid', async () => { + const opts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + payload: { + client_id: 'test_client_id', + scope: 'test', + response_type: 'id_token', + redirect_uri: EXAMPLE_REDIRECT_URL, + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + claims: { + vp_token: { + presentation_definition: { + input_descriptors: [ + { + id: 'Ontario Health Insurance Plan', + schema: [ + { + uri: 'https://did.itsourweb.org:3000/smartcredential/Ontario-Health-Insurance-Plan', + }, + ], + }, + ], + } as IPresentationDefinition, + }, + }, + }, + + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + alg: SigningAlgo.ES256K, + }, + payload: { + client_id: 'test_client_id', + scope: 'test', + response_type: 'id_token', + redirect_uri: EXAMPLE_REDIRECT_URL, + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + claims: { + vp_token: { + presentation_definition: { + input_descriptors: [ + { + id: 'Ontario Health Insurance Plan', + schema: [ + { + uri: 'https://did.itsourweb.org:3000/smartcredential/Ontario-Health-Insurance-Plan', + }, + ], + }, + ], + } as IPresentationDefinition, + }, + }, + }, + }, + clientMetadata: { + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + + passBy: PassBy.VALUE, + + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100306', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + await expect(URI.fromOpts(opts)).rejects.toThrow(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID); + }); +}); diff --git a/packages/siopv2/test/AuthenticationRequest.verify.spec.ts b/packages/siopv2/test/AuthenticationRequest.verify.spec.ts new file mode 100644 index 00000000..b648624d --- /dev/null +++ b/packages/siopv2/test/AuthenticationRequest.verify.spec.ts @@ -0,0 +1,444 @@ +import { ResponseType } from '@sphereon/oid4vc-common'; +import { IProofType } from '@sphereon/ssi-types'; +import { IVerifyCallbackArgs, IVerifyCredentialResult } from '@sphereon/wellknown-dids-client'; +import Ajv from 'ajv'; +import * as dotenv from 'dotenv'; + +import { + AuthorizationRequest, + CreateAuthorizationRequestOpts, + PassBy, + RequestObject, + Scope, + SigningAlgo, + SubjectSyntaxTypesSupportedValues, + SubjectType, + SupportedVersion, + VerificationMode, + VerifyAuthorizationRequestOpts, +} from '../src'; +import { RPRegistrationMetadataPayloadSchemaObj } from '../src/schemas'; +import SIOPErrors from '../src/types/Errors'; + +import { metadata, mockedGetEnterpriseAuthToken, WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; +import { + UNIT_TEST_TIMEOUT, + VERIFIER_LOGO_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT_NL, + VERIFIERZ_PURPOSE_TO_VERIFY, + 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', () => { + it('should compile schema', async () => { + const schema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $ref: '#/definitions/RPRegistrationMetadataPayload', + definitions: { + RPRegistrationMetadataPayload: { + type: 'object', + properties: { + client_id: { + anyOf: [ + { + type: 'string', + }, + {}, + ], + }, + id_token_signing_alg_values_supported: { + anyOf: [ + { + type: 'array', + items: { + $ref: '#/definitions/SigningAlgo', + }, + }, + { + $ref: '#/definitions/SigningAlgo', + }, + ], + }, + request_object_signing_alg_values_supported: { + anyOf: [ + { + type: 'array', + items: { + $ref: '#/definitions/SigningAlgo', + }, + }, + { + $ref: '#/definitions/SigningAlgo', + }, + ], + }, + response_types_supported: { + anyOf: [ + { + type: 'array', + items: { + $ref: '#/definitions/ResponseType', + }, + }, + { + $ref: '#/definitions/ResponseType', + }, + ], + }, + scopes_supported: { + anyOf: [ + { + type: 'array', + items: { + $ref: '#/definitions/Scope', + }, + }, + { + $ref: '#/definitions/Scope', + }, + ], + }, + subject_types_supported: { + anyOf: [ + { + type: 'array', + items: { + $ref: '#/definitions/SubjectType', + }, + }, + { + $ref: '#/definitions/SubjectType', + }, + ], + }, + subject_syntax_types_supported: { + type: 'array', + items: { + type: 'string', + }, + }, + vp_formats: { + anyOf: [ + { + $ref: '#/definitions/Format', + }, + {}, + ], + }, + client_name: { + anyOf: [ + { + type: 'string', + }, + {}, + ], + }, + logo_uri: { + anyOf: [ + {}, + { + type: 'string', + }, + ], + }, + client_purpose: { + anyOf: [ + {}, + { + type: 'string', + }, + ], + }, + }, + }, + SigningAlgo: { + type: 'string', + enum: ['EdDSA', 'RS256', 'ES256', 'ES256K'], + }, + ResponseType: { + type: 'string', + enum: ['id_token', 'vp_token'], + }, + Scope: { + type: 'string', + enum: ['openid', 'openid did_authn', 'profile', 'email', 'address', 'phone'], + }, + SubjectType: { + type: 'string', + enum: ['public', 'pairwise'], + }, + Format: { + type: 'object', + properties: { + jwt: { + $ref: '#/definitions/JwtObject', + }, + jwt_vc: { + $ref: '#/definitions/JwtObject', + }, + jwt_vp: { + $ref: '#/definitions/JwtObject', + }, + ldp: { + $ref: '#/definitions/LdpObject', + }, + ldp_vc: { + $ref: '#/definitions/LdpObject', + }, + ldp_vp: { + $ref: '#/definitions/LdpObject', + }, + }, + additionalProperties: false, + }, + JwtObject: { + type: 'object', + properties: { + alg: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + required: ['alg'], + additionalProperties: false, + }, + LdpObject: { + type: 'object', + properties: { + proof_type: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + required: ['proof_type'], + additionalProperties: false, + }, + }, + }; + const ajv = new Ajv({ allowUnionTypes: true, strict: false }); + ajv.compile(RPRegistrationMetadataPayloadSchemaObj); + ajv.compile(schema); + }); + it('throw VERIFY_BAD_PARAMETERS when no JWT is passed', async () => { + expect.assertions(1); + await expect(AuthorizationRequest.verify(undefined as never, undefined as never)).rejects.toThrow(SIOPErrors.VERIFY_BAD_PARAMS); + }); + + it('throw VERIFY_BAD_PARAMETERS when no responseOpts is passed', async () => { + expect.assertions(1); + await expect(AuthorizationRequest.verify('an invalid JWT bypassing the undefined check', undefined as never)).rejects.toThrow( + SIOPErrors.VERIFY_BAD_PARAMS, + ); + }); + + it('throw VERIFY_BAD_PARAMETERS when no responseOpts.verification is passed', async () => { + expect.assertions(1); + await expect(AuthorizationRequest.verify('an invalid JWT bypassing the undefined check', {} as never)).rejects.toThrow( + SIOPErrors.VERIFY_BAD_PARAMS, + ); + }); + + it('throw BAD_NONCE when a different nonce is supplied during verification', async () => { + expect.assertions(1); + 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, + }, + payload: { + state: 'expected state', + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'test', + response_type: 'id_token', + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + redirect_uri: EXAMPLE_REDIRECT_URL, + nonce: 'expected nonce', + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID, Scope.OPENID_DIDAUTHN], + subjectTypesSupported: [SubjectType.PAIRWISE], + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256K], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256K], + subject_syntax_types_supported: ['did:ethr:', SubjectSyntaxTypesSupportedValues.DID], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100308', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + + const requestObject = await RequestObject.fromOpts(requestOpts); + + 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', + supportedVersions: [SupportedVersion.SIOPv2_ID1], + nonce: 'This nonce is different and should throw error', + }; + + // expect.assertions(1); + await expect(AuthorizationRequest.verify(await requestObject.toJwt(), 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: { + passBy: PassBy.REFERENCE, + reference_uri: 'https://my-request.com/here', + signature: { + hexPrivateKey: mockEntity.hexPrivateKey, + did: mockEntity.did, + kid: `${mockEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }, + payload: { + 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], + authorization_endpoint: '', + redirect_uri: 'https://acme.com/hello', + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID, Scope.OPENID_DIDAUTHN], + subjectTypesSupported: [SubjectType.PAIRWISE], + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256K], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256K], + subject_syntax_types_supported: ['did:ethr:'], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + '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 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 }), + }, + supportedVersions: [SupportedVersion.SIOPv2_ID1], + correlationId: '1234', + }; + + const verifyJWT = await AuthorizationRequest.verify(await requestObject.toJwt(), verifyOpts); + expect(verifyJWT.jwt).toMatch(/^eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZXRocjowe.*$/); + }, + UNIT_TEST_TIMEOUT, + ); +}); + +describe('OP and RP communication should', () => { + it('work if both support the same did methods', () => { + const actualResult = metadata.verify(); + const expectedResult = { + vp_formats: { + jwt_vc: { alg: [SigningAlgo.ES256, SigningAlgo.ES256K] }, + ldp_vc: { + proof_type: ['EcdsaSecp256k1Signature2019', 'EcdsaSecp256k1Signature2019'], + }, + }, + subject_syntax_types_supported: ['did:web'], + }; + expect(actualResult).toEqual(expectedResult); + }); + + it('work if RP supports any OP did methods', () => { + metadata.opMetadata.vp_formats = { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }; + metadata.rpMetadata.subject_syntax_types_supported = ['did:web']; + expect(metadata.verify()).toEqual({ + subject_syntax_types_supported: ['did:web'], + vp_formats: { + ldp_vc: { + proof_type: ['EcdsaSecp256k1Signature2019', 'EcdsaSecp256k1Signature2019'], + }, + }, + }); + }); + + it('work if RP supports any OP credential formats', () => { + metadata.opMetadata.vp_formats = { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }; + const result = metadata.verify(); + expect(result['subject_syntax_types_supported']).toContain('did:web'); + expect(result['vp_formats']).toStrictEqual({ + ldp_vc: { + proof_type: ['EcdsaSecp256k1Signature2019', 'EcdsaSecp256k1Signature2019'], + }, + }); + }); + + it('not work if RP does not support any OP did method', () => { + metadata.rpMetadata.subject_syntax_types_supported = ['did:notsupported']; + expect(() => metadata.verify()).toThrowError(SIOPErrors.DID_METHODS_NOT_SUPORTED); + }); + + it('not work if RP does not support any OP credentials', () => { + metadata.rpMetadata.vp_formats = undefined; + expect(() => metadata.verify()).toThrowError(SIOPErrors.CREDENTIALS_FORMATS_NOT_PROVIDED); + }); +}); diff --git a/packages/siopv2/test/AuthenticationResponse.response.spec.ts b/packages/siopv2/test/AuthenticationResponse.response.spec.ts new file mode 100644 index 00000000..43b01a92 --- /dev/null +++ b/packages/siopv2/test/AuthenticationResponse.response.spec.ts @@ -0,0 +1,637 @@ +import { ResponseType } from '@sphereon/oid4vc-common'; +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, + PresentationSignCallback, + RequestObject, + ResponseIss, + ResponseMode, + Scope, + SigningAlgo, + SubjectIdentifierType, + SubjectType, + SupportedVersion, + VerificationMode, + VerifyAuthorizationRequestOpts, + VPTokenLocation, +} from '../src'; +import { createPresentationSubmission } from '../src/authorization-response/OpenID4VP'; +import SIOPErrors from '../src/types/Errors'; + +import { mockedGetEnterpriseAuthToken, WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; +import { + UNIT_TEST_TIMEOUT, + VERIFIER_LOGO_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT_NL, + VERIFIERZ_PURPOSE_TO_VERIFY, + VERIFIERZ_PURPOSE_TO_VERIFY_NL, +} from './data/mockedData'; + +jest.setTimeout(30000); + +const EXAMPLE_REFERENCE_URL = 'https://rp.acme.com/siop/jwts'; +const HEX_KEY = 'f857544a9d1097e242ff0b287a7e6e90f19cf973efe2317f2a4678739664420f'; +const DID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0'; +const KID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0#keys-1'; + +const validButExpiredJWT = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1NjEzNTEyOTAsImV4cCI6MTU2MTM1MTg5MCwicmVzcG9uc2VfdHlwZSI6ImlkX3Rva2VuIiwic2NvcGUiOiJvcGVuaWQiLCJjbGllbnRfaWQiOiJkaWQ6ZXRocjoweDQ4NzNFQzc0MUQ4RDFiMjU4YUYxQjUyNDczOEIzNjNhQTIxOTk5MjAiLCJyZWRpcmVjdF91cmkiOiJodHRwczovL2FjbWUuY29tL2hlbGxvIiwiaXNzIjoiZGlkOmV0aHI6MHg0ODczRUM3NDFEOEQxYjI1OGFGMUI1MjQ3MzhCMzYzYUEyMTk5OTIwIiwicmVzcG9uc2VfbW9kZSI6InBvc3QiLCJyZXNwb25zZV9jb250ZXh0IjoicnAiLCJub25jZSI6IlVTLU9wY1FHLXlXS3lWUTRlTU53UFB3Um10UVVGdmpkOHJXeTViRC10MXciLCJzdGF0ZSI6IjdmMjcxYzZjYjk2ZThmOThhMzkxYWU5ZCIsInJlZ2lzdHJhdGlvbiI6eyJpZF90b2tlbl9zaWduaW5nX2FsZ192YWx1ZXNfc3VwcG9ydGVkIjpbIkVkRFNBIiwiRVMyNTYiXSwicmVxdWVzdF9vYmplY3Rfc2lnbmluZ19hbGdfdmFsdWVzX3N1cHBvcnRlZCI6WyJFZERTQSIsIkVTMjU2Il0sInJlc3BvbnNlX3R5cGVzX3N1cHBvcnRlZCI6WyJpZF90b2tlbiJdLCJzY29wZXNfc3VwcG9ydGVkIjpbIm9wZW5pZCBkaWRfYXV0aG4iLCJvcGVuaWQiXSwic3ViamVjdF90eXBlc19zdXBwb3J0ZWQiOlsicGFpcndpc2UiXSwic3ViamVjdF9zeW50YXhfdHlwZXNfc3VwcG9ydGVkIjpbImRpZDpldGhyOiIsImRpZCJdLCJ2cF9mb3JtYXRzIjp7ImxkcF92YyI6eyJwcm9vZl90eXBlIjpbIkVjZHNhU2VjcDI1NmsxU2lnbmF0dXJlMjAxOSIsIkVjZHNhU2VjcDI1NmsxU2lnbmF0dXJlMjAxOSJdfX19fQ.Wd6I7BT7fWZSuYozUwHnyEsEoAe6OjdyzEEKXnWk8bY'; + +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, + registration: { + authorizationEndpoint: 'www.myauthorizationendpoint.com', + responseTypesSupported: [ResponseType.ID_TOKEN], + subject_syntax_types_supported: ['did:web'], + vpFormats: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + issuer: ResponseIss.SELF_ISSUED_V2, + + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100310', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + signature: { + did: DID, + hexPrivateKey: HEX_KEY, + kid: KID, + alg: SigningAlgo.ES256K, + }, + }; + 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 }), + }, + supportedVersions: [SupportedVersion.SIOPv2_ID1], + correlationId: '1234', + }; + + it('throw NO_JWT when no jwt is passed', async () => { + expect.assertions(1); + await expect(AuthorizationResponse.fromRequestObject(undefined as never, responseOpts, verifyOpts)).rejects.toThrow(SIOPErrors.NO_JWT); + }); + it('throw BAD_PARAMS when no responseOpts is passed', async () => { + expect.assertions(1); + await expect(AuthorizationResponse.fromRequestObject(validButExpiredJWT, undefined as never, verifyOpts)).rejects.toThrow(SIOPErrors.BAD_PARAMS); + }); + it('throw VERIFY_BAD_PARAMS when no verifyOpts is passed', async () => { + expect.assertions(1); + await expect(AuthorizationResponse.fromRequestObject(validButExpiredJWT, responseOpts, undefined as never)).rejects.toThrow( + SIOPErrors.VERIFY_BAD_PARAMS, + ); + }); + + it('throw JWT_ERROR when expired but valid JWT is passed in', async () => { + expect.assertions(1); + const mockReqEntity = await mockedGetEnterpriseAuthToken('REQ COMPANY'); + const mockResEntity = await mockedGetEnterpriseAuthToken('RES COMPANY'); + const requestOpts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + /*payload: { + nonce: '12345', + state: '12345', + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'test', + response_type: 'id_token', + redirect_uri: EXAMPLE_REDIRECT_URL, + },*/ + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: 'https://my-request.com/here', + signature: { + hexPrivateKey: mockReqEntity.hexPrivateKey, + did: mockReqEntity.did, + kid: `${mockReqEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }, + payload: { + nonce: '12345', + state: '12345', + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'test', + response_type: 'id_token', + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100311', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + const responseOpts: AuthorizationResponseOpts = { + checkLinkedDomain: CheckLinkedDomain.NEVER, + responseURI: EXAMPLE_REDIRECT_URL, + responseURIType: 'redirect_uri', + registration: { + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + issuer: ResponseIss.SELF_ISSUED_V2, + responseTypesSupported: [ResponseType.ID_TOKEN], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + vpFormats: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100312', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + signature: { + did: mockResEntity.did, + hexPrivateKey: mockResEntity.hexPrivateKey, + kid: `${mockResEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }, + responseMode: ResponseMode.POST, + }; + + jest.useFakeTimers().setSystemTime(new Date('2020-01-01')); + jest.useFakeTimers().setSystemTime(new Date('2020-01-01')); + + const requestObject = await RequestObject.fromOpts(requestOpts); + const jwt = await requestObject.toJwt(); + jest.useRealTimers(); + await expect(AuthorizationResponse.fromRequestObject(jwt, responseOpts, verifyOpts)).rejects.toThrow(/invalid_jwt: JWT has expired: exp: /); + }); + + it( + 'succeed when valid JWT is passed in', + async () => { + expect.assertions(1); + + const mockReqEntity = await mockedGetEnterpriseAuthToken('REQ COMPANY'); + const mockResEntity = await mockedGetEnterpriseAuthToken('RES COMPANY'); + const requestOpts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: 'https://my-request.com/here', + signature: { + hexPrivateKey: mockReqEntity.hexPrivateKey, + did: mockReqEntity.did, + kid: `${mockReqEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }, + payload: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'test', + response_type: 'id_token', + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100313', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + const responseOpts: AuthorizationResponseOpts = { + checkLinkedDomain: CheckLinkedDomain.NEVER, + responseURI: EXAMPLE_REDIRECT_URL, + responseURIType: 'redirect_uri', + registration: { + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + issuer: ResponseIss.SELF_ISSUED_V2, + responseTypesSupported: [ResponseType.ID_TOKEN], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + vpFormats: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100314', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + signature: { + did: mockResEntity.did, + hexPrivateKey: mockResEntity.hexPrivateKey, + kid: `${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(); + }, + UNIT_TEST_TIMEOUT, + ); + + it('succeed when valid JWT with PD is passed in', async () => { + expect.assertions(1); + + const mockReqEntity = await mockedGetEnterpriseAuthToken('REQ COMPANY'); + const mockResEntity = await mockedGetEnterpriseAuthToken('RES COMPANY'); + const presentationSignCallback: PresentationSignCallback = async (_args) => ({ + ...(_args.presentation as IPresentation), + proof: { + type: 'RsaSignature2018', + created: '2018-09-14T21:19:10Z', + proofPurpose: 'authentication', + verificationMethod: 'did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1', + challenge: '1f44d55f-f161-4938-a659-f8026467f126', + domain: '4jt78h47fh47', + jws: 'eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78', + }, + }); + const definition: IPresentationDefinition = { + id: 'Credentials', + input_descriptors: [ + { + id: 'ID Card Credential', + schema: [ + { + uri: 'https://www.w3.org/2018/credentials/examples/v1/IDCardCredential', + }, + ], + constraints: { + limit_disclosure: 'required', + fields: [ + { + path: ['$.issuer.id'], + purpose: 'We can only verify bank accounts if they are attested by a source.', + filter: { + type: 'string', + pattern: 'did:example:issuer', + }, + }, + ], + }, + }, + ], + }; + const requestOpts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: 'https://my-request.com/here', + signature: { + hexPrivateKey: mockReqEntity.hexPrivateKey, + did: mockReqEntity.did, + kid: `${mockReqEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }, + payload: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'test', + response_type: 'id_token vp_token', + redirect_uri: EXAMPLE_REDIRECT_URL, + claims: { + vp_token: { + presentation_definition: definition, + }, + }, + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100315', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + const vc: ICredential = { + id: 'https://example.com/credentials/1872', + type: ['VerifiableCredential', 'IDCardCredential'], + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1/IDCardCredential'], + issuer: { + id: 'did:example:issuer', + }, + issuanceDate: '2010-01-01T19:23:24Z', + credentialSubject: { + given_name: 'Fredrik', + family_name: 'Stremberg', + birthdate: '1949-01-22', + }, + }; + const presentation: IVerifiablePresentation = { + '@context': ['https://www.w3.org/2018/credentials/v1'], + presentation_submission: undefined, + type: ['verifiablePresentation'], + holder: 'did:example:holder', + verifiableCredential: [vc as IVerifiableCredential], + proof: undefined, + }; + + const pex = new PresentationExchange({ + allDIDs: ['did:example:holder'], + allVerifiableCredentials: presentation.verifiableCredential, + }); + await pex.selectVerifiableCredentialsForSubmission(definition); + const verifiablePresentationResult = await pex.createVerifiablePresentation( + definition, + presentation.verifiableCredential, + presentationSignCallback, + {}, + ); + const responseOpts: AuthorizationResponseOpts = { + checkLinkedDomain: CheckLinkedDomain.NEVER, + responseURI: EXAMPLE_REDIRECT_URL, + responseURIType: 'redirect_uri', + registration: { + authorizationEndpoint: 'www.myauthorizationendpoint.com', + issuer: ResponseIss.SELF_ISSUED_V2, + responseTypesSupported: [ResponseType.ID_TOKEN], + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + vpFormats: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100316', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + signature: { + did: mockResEntity.did, + hexPrivateKey: mockResEntity.hexPrivateKey, + kid: `${mockResEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }, + presentationExchange: { + verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], + vpTokenLocation: VPTokenLocation.ID_TOKEN, + presentationSubmission: await createPresentationSubmission([verifiablePresentationResult.verifiablePresentation], { + presentationDefinitions: [definition], + }), + }, + responseMode: ResponseMode.POST, + }; + + const requestObject = await RequestObject.fromOpts(requestOpts); + /* console.log( + JSON.stringify(await AuthenticationResponse.createJWTFromRequestJWT(requestWithJWT.jwt, responseOpts, verifyOpts)) + );*/ + await expect(await AuthorizationResponse.fromRequestObject(await requestObject.toJwt(), responseOpts, verifyOpts)).toBeDefined(); + }); + + it('succeed when valid JWT with PD is passed in for id_token', async () => { + const mockReqEntity = await mockedGetEnterpriseAuthToken('REQ COMPANY'); + const mockResEntity = await mockedGetEnterpriseAuthToken('RES COMPANY'); + const definition: IPresentationDefinition = { + id: 'Credentials', + input_descriptors: [ + { + id: 'ID Card Credential', + schema: [ + { + uri: 'https://www.w3.org/2018/credentials/examples/v1/IDCardCredential', + }, + ], + constraints: { + limit_disclosure: 'required', + fields: [ + { + path: ['$.issuer.id'], + purpose: 'We can only verify bank accounts if they are attested by a source.', + filter: { + type: 'string', + pattern: 'did:example:issuer', + }, + }, + ], + }, + }, + ], + }; + const requestOpts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + payload: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + /*scope: 'test', + response_type: 'token_id', + redirect_uri: EXAMPLE_REDIRECT_URL, + claims: { + vp_token: { + presentation_definition: definition, + }, + },*/ + }, + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: 'https://my-request.com/here', + signature: { + hexPrivateKey: mockReqEntity.hexPrivateKey, + did: mockReqEntity.did, + kid: `${mockReqEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }, + payload: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'test', + response_type: ResponseType.ID_TOKEN, + redirect_uri: EXAMPLE_REDIRECT_URL, + claims: { + vp_token: { + presentation_definition: definition, + }, + }, + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL, + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + const vc: ICredential = { + id: 'https://example.com/credentials/1872', + type: ['VerifiableCredential', 'IDCardCredential'], + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1/IDCardCredential'], + issuer: { + id: 'did:example:issuer', + }, + issuanceDate: '2010-01-01T19:23:24Z', + credentialSubject: { + given_name: 'Fredrik', + family_name: 'Stremberg', + birthdate: '1949-01-22', + }, + }; + const presentation: IVerifiablePresentation = { + '@context': ['https://www.w3.org/2018/credentials/v1'], + presentation_submission: undefined, + type: ['verifiablePresentation'], + holder: 'did:example:holder', + verifiableCredential: [vc as IVerifiableCredential], + proof: undefined, + }; + + const pex = new PresentationExchange({ + allDIDs: ['did:example:holder'], + allVerifiableCredentials: presentation.verifiableCredential, + }); + await pex.selectVerifiableCredentialsForSubmission(definition); + const presentationSignCallback: PresentationSignCallback = async (_args) => ({ + ...(_args.presentation as IPresentation), + proof: { + type: 'RsaSignature2018', + created: '2018-09-14T21:19:10Z', + proofPurpose: 'authentication', + verificationMethod: 'did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1', + challenge: '1f44d55f-f161-4938-a659-f8026467f126', + domain: '4jt78h47fh47', + jws: 'eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78', + }, + }); + const verifiablePresentationResult = await pex.createVerifiablePresentation( + definition, + presentation.verifiableCredential, + presentationSignCallback, + {}, + ); + const responseOpts: AuthorizationResponseOpts = { + checkLinkedDomain: CheckLinkedDomain.NEVER, + responseURI: EXAMPLE_REDIRECT_URL, + responseURIType: 'redirect_uri', + registration: { + authorizationEndpoint: 'www.myauthorizationendpoint.com', + issuer: ResponseIss.SELF_ISSUED_V2, + responseTypesSupported: [ResponseType.ID_TOKEN], + + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + vpFormats: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL, + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + signature: { + did: mockResEntity.did, + hexPrivateKey: mockResEntity.hexPrivateKey, + kid: `${mockResEntity.did}#controller`, + alg: SigningAlgo.ES256K, + }, + presentationExchange: { + verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], + presentationSubmission: await createPresentationSubmission([verifiablePresentationResult.verifiablePresentation], { + presentationDefinitions: [definition], + }), + vpTokenLocation: VPTokenLocation.ID_TOKEN, + }, + + responseMode: ResponseMode.POST, + }; + + const requestObject = await RequestObject.fromOpts(requestOpts); + await expect(AuthorizationResponse.fromRequestObject(await requestObject.toJwt(), responseOpts, verifyOpts)).resolves.toBeDefined(); + }); +}); diff --git a/packages/siopv2/test/AuthenticationResponse.verify.spec.ts b/packages/siopv2/test/AuthenticationResponse.verify.spec.ts new file mode 100644 index 00000000..1eb497f0 --- /dev/null +++ b/packages/siopv2/test/AuthenticationResponse.verify.spec.ts @@ -0,0 +1,48 @@ +import { IVerifyCallbackArgs, IVerifyCredentialResult } from '@sphereon/wellknown-dids-client'; + +import { IDToken, VerificationMode, VerifyAuthorizationResponseOpts } from '../src'; +import SIOPErrors from '../src/types/Errors'; + +// const EXAMPLE_REDIRECT_URL = "https://acme.com/hello"; +const DID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0'; + +const validButExpiredResJWT = + 'eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZXRocjoweDk3NTgzNmREM0Y1RTk4QzE5RjBmM2I4N0Y5OWFGMzA1MDAyNkREQzIjY29udHJvbGxlciIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzIyNzE4MDMuMjEyLCJleHAiOjE2MzIyNzI0MDMuMjEyLCJpc3MiOiJodHRwczovL3NlbGYtaXNzdWVkLm1lL3YyIiwic3ViIjoiZGlkOmV0aHI6MHg5NzU4MzZkRDNGNUU5OEMxOUYwZjNiODdGOTlhRjMwNTAwMjZEREMyIiwiYXVkIjoiaHR0cHM6Ly9hY21lLmNvbS9oZWxsbyIsImRpZCI6ImRpZDpldGhyOjB4OTc1ODM2ZEQzRjVFOThDMTlGMGYzYjg3Rjk5YUYzMDUwMDI2RERDMiIsInN1Yl90eXBlIjoiZGlkIiwic3ViX2p3ayI6eyJraWQiOiJkaWQ6ZXRocjoweDk3NTgzNmREM0Y1RTk4QzE5RjBmM2I4N0Y5OWFGMzA1MDAyNkREQzIjY29udHJvbGxlciIsImt0eSI6IkVDIiwiY3J2Ijoic2VjcDI1NmsxIiwieCI6IkloUXVEek5BY1dvczVXeDd4U1NHMks2Zkp6MnBobU1nbUZ4UE1xaEU4XzgiLCJ5IjoiOTlreGpCMVgzaUtkRXZkbVFDbllqVm5PWEJyc2VwRGdlMFJrek1aUDN1TSJ9LCJzdGF0ZSI6ImQ2NzkzYjQ2YWIyMzdkMzczYWRkNzQwMCIsIm5vbmNlIjoiU1JXSzltSVpFd1F6S3dsZlZoMkE5SV9weUtBT0tnNDAtWDJqbk5aZEN0byIsInJlZ2lzdHJhdGlvbiI6eyJpc3N1ZXIiOiJodHRwczovL3NlbGYtaXNzdWVkLm1lL3YyIiwicmVzcG9uc2VfdHlwZXNfc3VwcG9ydGVkIjoiaWRfdG9rZW4iLCJhdXRob3JpemF0aW9uX2VuZHBvaW50Ijoib3BlbmlkOiIsInNjb3Blc19zdXBwb3J0ZWQiOiJvcGVuaWQiLCJpZF90b2tlbl9zaWduaW5nX2FsZ192YWx1ZXNfc3VwcG9ydGVkIjpbIkVTMjU2SyIsIkVkRFNBIl0sInJlcXVlc3Rfb2JqZWN0X3NpZ25pbmdfYWxnX3ZhbHVlc19zdXBwb3J0ZWQiOlsiRVMyNTZLIiwiRWREU0EiXSwic3ViamVjdF90eXBlc19zdXBwb3J0ZWQiOiJwYWlyd2lzZSJ9fQ.coLQr2hQuMwEfYUd3HdFt-ixhsaicc37cC9cwmQ2U5hfxRhAb871s9G1GAo3qhsa9v3t0G1bTX2J9WhLaC5J_Q'; + +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 }), + }, + }; + + it('throw NO_JWT when no jwt is passed', async () => { + expect.assertions(1); + await expect(IDToken.verify(undefined as never, verifyOpts)).rejects.toThrow(SIOPErrors.NO_JWT); + }); + it('throw VERIFY_BAD_PARAMS when no verifyOpts is passed', async () => { + expect.assertions(1); + await expect(IDToken.verify(validButExpiredResJWT, undefined as never)).rejects.toThrow(SIOPErrors.VERIFY_BAD_PARAMS); + }); + + it('throw JWT_ERROR when expired but valid JWT is passed in', async () => { + expect.assertions(1); + await expect(IDToken.verify(validButExpiredResJWT, { ...verifyOpts, audience: 'https://acme.com/hello' })).rejects.toThrow( + /invalid_jwt: JWT has expired: exp: 1632272403/, + ); + }); + + it('throw JWT_ERROR when expired but valid JWT is passed in', async () => { + expect.assertions(1); + await expect(IDToken.verify(validButExpiredResJWT, { ...verifyOpts, audience: 'https://acme.com/hello' })).rejects.toThrow( + /invalid_jwt: JWT has expired: exp: 1632272403/, + ); + }); +}); diff --git a/packages/siopv2/test/DocumentLoader.ts b/packages/siopv2/test/DocumentLoader.ts new file mode 100644 index 00000000..e304a9b3 --- /dev/null +++ b/packages/siopv2/test/DocumentLoader.ts @@ -0,0 +1,48 @@ +import { extendContextLoader } from '@digitalcredentials/jsonld-signatures'; +import vc from '@digitalcredentials/vc'; +import fetch from 'cross-fetch'; + +export class DocumentLoader { + getLoader() { + return extendContextLoader(async (url: string) => { + if (url === 'https://identity.foundation/.well-known/did-configuration/v1') { + // Not sure what is happening, but this URL is failing in Github. Probably, cloudflare getting in the way, which might have impact in production settings to + return { + document: { + documentUrl: url, + '@context': [ + { + '@version': 1.1, + '@protected': true, + LinkedDomains: 'https://identity.foundation/.well-known/resources/did-configuration/#LinkedDomains', + DomainLinkageCredential: 'https://identity.foundation/.well-known/resources/did-configuration/#DomainLinkageCredential', + origin: 'https://identity.foundation/.well-known/resources/did-configuration/#origin', + linked_dids: 'https://identity.foundation/.well-known/resources/did-configuration/#linked_dids', + }, + ], + }, + }; + } + try { + const response = await fetch(url); + if (response.status >= 200 && response.status < 300) { + const document = await response.json(); + return { + contextUrl: null, + documentUrl: url, + document, + }; + } else { + console.log(`ERROR: ${url}`); + console.log(`url: ${url}, status: ${response.status}: ${response.statusText}`); + console.log(`response: ${await response.text()}`); + } + } catch (error) { + console.log(`ERROR:::::::: ${url}: ${JSON.stringify(error.message)}`); + } + + const { nodeDocumentLoader } = vc; + return nodeDocumentLoader(url); + }); + } +} diff --git a/packages/siopv2/test/HttpUtils.fetch.spec.ts b/packages/siopv2/test/HttpUtils.fetch.spec.ts new file mode 100644 index 00000000..909e2094 --- /dev/null +++ b/packages/siopv2/test/HttpUtils.fetch.spec.ts @@ -0,0 +1,40 @@ +import nock from 'nock'; + +import { post } from '../src'; + +const URL = 'https://example.com'; +nock(URL) + .post('/404', { iss: 'mock' }, { reqheaders: { Authorization: 'Bearer bearerToken' } }) + .reply(404, 'Not found'); +nock(URL) + .post('/200', { iss: 'mock' }, { reqheaders: { Authorization: 'Bearer bearerToken' } }) + .reply(200, '{"status": "ok"}'); +nock(URL) + .post('/201', { iss: 'mock' }, { reqheaders: { Authorization: 'Bearer bearerToken' } }) + .reply(201, '{"status": "ok"}'); + +describe('HttpUtils should', () => { + it('have an error body when response is not 200 or 201', async () => { + expect.assertions(1); + await expect( + post(`${URL}/404`, JSON.stringify({ iss: 'mock' }), { bearerToken: 'bearerToken' }).then((value) => value.errorBody), + ).resolves.toMatch('Not found'); + }); + + it('return response when response HTTP status is 200', async () => { + expect.assertions(1); + await expect( + post(`${URL}/200`, JSON.stringify({ iss: 'mock' }), { bearerToken: 'bearerToken' }).then((value) => value.successBody), + ).resolves.toMatchObject({ + status: 'ok', + }); + }); + it('return response when response HTTP status is 201', async () => { + expect.assertions(1); + await expect( + post(`${URL}/201`, JSON.stringify({ iss: 'mock' }), { bearerToken: 'bearerToken' }).then((value) => value.successBody), + ).resolves.toMatchObject({ + status: 'ok', + }); + }); +}); diff --git a/packages/siopv2/test/IT.spec.ts b/packages/siopv2/test/IT.spec.ts new file mode 100644 index 00000000..5b4dc25b --- /dev/null +++ b/packages/siopv2/test/IT.spec.ts @@ -0,0 +1,1868 @@ +import { EventEmitter } from 'events'; + +import { ResponseType } from '@sphereon/oid4vc-common'; +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 nock from 'nock'; + +import { + CheckLinkedDomain, + OP, + PassBy, + PresentationDefinitionWithLocation, + PresentationExchange, + PresentationSignCallback, + PresentationVerificationCallback, + PropertyTarget, + ResponseIss, + RevocationStatus, + RevocationVerification, + RP, + Scope, + SigningAlgo, + SubjectType, + SupportedVersion, + VerificationMode, + verifyRevocation, + VPTokenLocation, +} from '../src'; +import { InMemoryRPSessionManager } from '../src'; +import { checkSIOPSpecVersionSupported } from '../src/helpers/SIOPSpecVersion'; + +import { mockedGetEnterpriseAuthToken, WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; +import { + UNIT_TEST_TIMEOUT, + VERIFIER_LOGO_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT_NL, + VERIFIERZ_PURPOSE_TO_VERIFY, + VERIFIERZ_PURPOSE_TO_VERIFY_NL, +} from './data/mockedData'; + +jest.setTimeout(30000); + +const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello'; +const EXAMPLE_REFERENCE_URL = 'https://rp.acme.com/siop/jwts'; + +const HOLDER_DID = 'did:example:ebfeb1f712ebc6f1c276e12ec21'; + +const presentationSignCallback: PresentationSignCallback = async (_args) => ({ + ...(_args.presentation as IPresentation), + proof: { + type: 'RsaSignature2018', + created: '2018-09-14T21:19:10Z', + proofPurpose: 'authentication', + verificationMethod: 'did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1', + challenge: '1f44d55f-f161-4938-a659-f8026467f126', + domain: '4jt78h47fh47', + jws: 'eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78', + }, +}); + +// 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, +}); + +function getPresentationDefinition(): IPresentationDefinition { + return { + id: 'Insurance Plans', + input_descriptors: [ + { + id: 'Ontario Health Insurance Plan', + schema: [ + { + uri: 'https://did.itsourweb.org:3000/smartcredential/Ontario-Health-Insurance-Plan', + }, + { + uri: 'https://www.w3.org/2018/credentials/v1', + }, + ], + constraints: { + limit_disclosure: 'preferred', + fields: [ + { + path: ['$.issuer.id'], + purpose: 'We can only verify bank accounts if they are attested by a source.', + filter: { + type: 'string', + pattern: 'did:example:issuer', + }, + }, + ], + }, + }, + ], + }; +} + +function getVCs(): IVerifiableCredential[] { + const vcs: IVerifiableCredential[] = [ + { + identifier: '83627465', + name: 'Permanent Resident Card', + type: ['PermanentResidentCard', 'VerifiableCredential'], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465dsdsdsd', + credentialSubject: { + birthCountry: 'Bahamas', + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + gender: 'Female', + familyName: 'SMITH', + givenName: 'JANE', + residentSince: '2015-01-01', + lprNumber: '999-999-999', + birthDate: '1958-07-17', + commuterClassification: 'C1', + lprCategory: 'C09', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + }, + expirationDate: '2029-12-03T12:19:52Z', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://w3id.org/citizenship/v1', 'https://w3id.org/security/suites/ed25519-2020/v1'], + issuer: 'did:key:z6MkhfRoL9n7ko9d6LnB5jLB4aejd3ir2q6E2xkuzKUYESig', + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2020-04-25', + verificationMethod: 'did:example:489398593#test', + proofPurpose: 'assertionMethod', + proofValue: + 'kTTbA3pmDa6Qia/JkOnIXDLmoBz3vsi7L5t3DWySI/VLmBqleJ/Tbus5RoyiDERDBEh5rnACXlnOqJ/U8yFQFtcp/mBCc2FtKNPHae9jKIv1dm9K9QK1F3GI1AwyGoUfjLWrkGDObO1ouNAhpEd0+et+qiOf2j8p3MTTtRRx4Hgjcl0jXCq7C7R5/nLpgimHAAAAdAx4ouhMk7v9dXijCIMaG0deicn6fLoq3GcNHuH5X1j22LU/hDu7vvPnk/6JLkZ1xQAAAAIPd1tu598L/K3NSy0zOy6obaojEnaqc1R5Ih/6ZZgfEln2a6tuUp4wePExI1DGHqwj3j2lKg31a/6bSs7SMecHBQdgIYHnBmCYGNQnu/LZ9TFV56tBXY6YOWZgFzgLDrApnrFpixEACM9rwrJ5ORtxAAAAAgE4gUIIC9aHyJNa5TBklMOh6lvQkMVLXa/vEl+3NCLXblxjgpM7UEMqBkE9/QcoD3Tgmy+z0hN+4eky1RnJsEg=', + nonce: '6i3dTz5yFfWJ8zgsamuyZa4yAHPm75tUOOXddR6krCvCYk77sbCOuEVcdBCDd/l6tIY=', + }, + }, + ]; + vcs[0]['@context'] = ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1']; + vcs[0]['issuer'] = { + id: 'did:example:issuer', + }; + return vcs; +} + +describe('RP and OP interaction should', () => { + it( + 'succeed when calling each other in the full flow', + async () => { + // expect.assertions(1); + 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 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') + .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 + '2022100317', + 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) + .withIssuer(ResponseIss.SELF_ISSUED_V2) + .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, `${opMockEntity.did}#controller`, SigningAlgo.ES256K) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + //FIXME: Move payload options to seperate property + .withRegistration({ + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], + issuer: ResponseIss.SELF_ISSUED_V2, + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormats: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did:ethr'], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100318', + 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: { propertyValue: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg' }, + state: { propertyValue: 'b32f0087fc9816eb813fd11f' }, + }); + + nock('https://rp.acme.com').get('/siop/jwts').times(3).reply(200, requestURI.requestObjectJwt); + + 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); + + nock(EXAMPLE_REDIRECT_URL).post(/.*/).times(3).reply(200, { result: 'ok' }); + const response = await op.submitAuthorizationResponse(authenticationResponseWithJWT); + await expect(response.json()).resolves.toMatchObject({ result: 'ok' }); + + const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { + // audience: EXAMPLE_REDIRECT_URL, + }); + + expect(verifiedAuthResponseWithJWT.idToken.jwt).toBeDefined(); + expect(verifiedAuthResponseWithJWT.idToken.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); + }, + UNIT_TEST_TIMEOUT, + ); + + it('succeed when calling optional steps in the full flow', async () => { + // expect.assertions(1); + 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', + }; + + // 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 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') + .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 + '2022100319', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); + const op = OP.builder() + .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], + 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 + '2022100320', + 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: { propertyValue: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg' }, + state: { propertyValue: 'b32f0087fc9816eb813fd11f' }, + }); + + // Let's test the parsing + const parsedAuthReqURI = await op.parseAuthorizationRequestURI(requestURI.encodedUri); + expect(parsedAuthReqURI.authorizationRequestPayload).toBeDefined(); + 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); + expect(authenticationResponseWithJWT).toBeDefined(); + expect(authenticationResponseWithJWT.correlationId).toEqual('1234'); + expect(authenticationResponseWithJWT.response.payload).toBeDefined(); + expect(authenticationResponseWithJWT.response.idToken).toBeDefined(); + + const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { + /*audience: EXAMPLE_REDIRECT_URL,*/ + }); + + expect(verifiedAuthResponseWithJWT.idToken.jwt).toBeDefined(); + expect(verifiedAuthResponseWithJWT.idToken.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); + }); + + it('fail when calling with presentation definitions and without verifiable presentation', async () => { + 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', + }; + + // 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 rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) + .withClientId(WELL_KNOWN_OPENID_FEDERATION) + .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') + .withClientMetadata({ + client_id: rpMockEntity.did, + 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 + '2022100321', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withPresentationDefinition({ definition: getPresentationDefinition() }) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); + const op = OP.builder() + .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.ES256K], + responseTypesSupported: [ResponseType.ID_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 + '2022100321', + 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: { propertyValue: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg' }, + state: { propertyValue: 'b32f0087fc9816eb813fd11f' }, + }); + + //The schema validation needs to be done here otherwise it fails because of JWT properties + await checkSIOPSpecVersionSupported(requestURI.authorizationRequestPayload, op.verifyRequestOptions.supportedVersions); + // 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); + await expect(op.createAuthorizationResponse(verifiedAuthReqWithJWT)).rejects.toThrow( + Error('authentication request expects a verifiable presentation in the response'), + ); + + expect(verifiedAuthReqWithJWT.payload['registration'].client_name).toEqual(VERIFIER_NAME_FOR_CLIENT); + expect(verifiedAuthReqWithJWT.payload['registration']['client_name#nl-NL']).toEqual(VERIFIER_NAME_FOR_CLIENT_NL + '2022100321'); + }); + + 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 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 }); + + 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 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(); + + 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: [ + { + presentation: vp, + format: VerifiablePresentationTypeFormat.LDP_VP, + vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, + }, + ],*/ + }, + }); + 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 }], + }); + + expect(verifiedAuthResponseWithJWT.idToken.jwt).toBeDefined(); + expect(verifiedAuthResponseWithJWT.idToken.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); + } catch (error) { + console.error(error); + throw error; + } + }); + + it( + 'should fail when calling with CheckLinkedDomain.ALWAYS', + async () => { + 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', + }; + // 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 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) + .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 + '2022100324', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withPresentationDefinition({ definition: getPresentationDefinition() }, [ + PropertyTarget.REQUEST_OBJECT, + PropertyTarget.AUTHORIZATION_REQUEST, + ]) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); + const op = OP.builder() + .withPresentationSignCallback(presentationSignCallback) + .withExpiresIn(1000) + .withWellknownDIDVerifyCallback(verifyCallback) + .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], + vpFormats: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did'], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100325', + 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', + }); + + // 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], + presentationSubmission: verifiablePresentationResult.presentationSubmission, + // vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, + /*credentialsAndDefinitions: [ + { + presentation: verifiablePresentationResult, + format: VerifiablePresentationTypeFormat.LDP_VP, + vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, + }, + ],*/ + }, + }); + expect(authenticationResponseWithJWT.response.payload).toBeDefined(); + await expect( + rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { + // 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 + revocationVerificationCallback: (_credential, _type) => Promise.resolve({ status: RevocationStatus.VALID }), + }, + }, + }), + ).rejects.toThrow(new Error(WDCErrors.PROPERTY_SERVICE_NOT_PRESENT)); + }, + UNIT_TEST_TIMEOUT, + ); + + it('succeed when calling with CheckLinkedDomain.ALWAYS', async () => { + const rpMockEntity = { + hexPrivateKey: '2bbd6a78be9ab2193bcf74aa6d39ab59c1d1e2f7e9ef899a38fb4d94d8aa90e2', + did: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024', + didKey: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024#controllerKey', + }; + + const opMockEntity = { + hexPrivateKey: '88a62d50de38dc22f5b4e7cc80d68a0f421ea489dda0e3bd5c165f08ce46e666', + did: 'did:ion:EiCMvVdXv6iL3W8i4n-LmqUhE614kX4TYxVR5kTY2QGOjg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXkxIiwicHVibGljS2V5SndrIjp7ImNydiI6InNlY3AyNTZrMSIsImt0eSI6IkVDIiwieCI6Ii1MbHNpQVk5b3JmMXpKQlJOV0NuN0RpNUpoYl8tY2xhNlY5R3pHa3FmSFUiLCJ5IjoiRXBIU25GZHQ2ZU5lRkJEZzNVNVFIVDE0TVRsNHZIc0h5NWRpWU9DWEs1TSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiIsImFzc2VydGlvbk1ldGhvZCJdLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFWZXJpZmljYXRpb25LZXkyMDE5In1dLCJzZXJ2aWNlcyI6W3siaWQiOiJsZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHBzOi8vbGR0ZXN0LnNwaGVyZW9uLmNvbSIsInR5cGUiOiJMaW5rZWREb21haW5zIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlBem8wTVVZUW5HNWM0VFJKZVFsNFR5WVRrSmRyeTJoeXlQUlpENzdFQm1CdyJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQUwtaEtrLUVsODNsRVJiZkFDUk1kSWNQVjRXWGJqZ3dsZ1ZDWTNwbDhhMGciLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUItT2NSbTlTNXdhU3QxbU4zSG4zM2RnMzJKN25MOEdBVHpGQ2ZXaWdIXzh3In19', + didKey: + '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 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) + .withAuthorizationEndpoint('www.myauthorizationendpoint.com') + .withClientMetadata({ + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], + requestObjectSigningAlgValuesSupported: [SigningAlgo.ES256K], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormatsSupported: { + jwt_vc: { alg: [SigningAlgo.EDDSA] }, + ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp_vp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did', 'did:ion'], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100326', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withPresentationDefinition({ definition: getPresentationDefinition() }, [PropertyTarget.REQUEST_OBJECT, PropertyTarget.AUTHORIZATION_REQUEST]) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); + const op = OP.builder() + .withPresentationSignCallback(presentationSignCallback) + .withWellknownDIDVerifyCallback(verifyCallback) + .withExpiresIn(1000) + .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withRegistration({ + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], + issuer: ResponseIss.SELF_ISSUED_V2, + requestObjectSigningAlgValuesSupported: [SigningAlgo.ES256K], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormats: { + jwt_vc: { alg: [SigningAlgo.EDDSA] }, + ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp_vp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100327', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did:ethr'], + passBy: PassBy.VALUE, + }) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); + + 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(); + + 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], + presentationSubmission: verifiablePresentationResult.presentationSubmission, + vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, + /*credentialsAndDefinitions: [ + { + presentation: vp, + format: VerifiablePresentationTypeFormat.LDP_VP, + vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, + }, + ],*/ + }, + }); + expect(authenticationResponseWithJWT.response.payload).toBeDefined(); + + 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', + ], + }; + 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 }], + // audience: EXAMPLE_REDIRECT_URL, + }); + expect(verifiedAuthResponseWithJWT.idToken.jwt).toBeDefined(); + expect(verifiedAuthResponseWithJWT.idToken.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); + }); + + it( + 'should succeed when calling with CheckLinkedDomain.IF_PRESENT', + async () => { + 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', + }; + + // 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 rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) + .withClientId(rpMockEntity.did) + .withScope('test') + .withResponseType([ResponseType.ID_TOKEN, ResponseType.VP_TOKEN]) + .withCheckLinkedDomain(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) + .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 + '2022100328', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withPresentationDefinition({ definition: getPresentationDefinition() }, [ + PropertyTarget.REQUEST_OBJECT, + PropertyTarget.AUTHORIZATION_REQUEST, + ]) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .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) + .withRegistration({ + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], + issuer: ResponseIss.SELF_ISSUED_V2, + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_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 + '2022100329', + 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', + }); + + // 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], + presentationSubmission: verifiablePresentationResult.presentationSubmission, + vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, + /*credentialsAndDefinitions: [ + { + presentation: vp, + format: VerifiablePresentationTypeFormat.LDP_VP, + vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, + }, + ],*/ + }, + }); + expect(authenticationResponseWithJWT.response.payload).toBeDefined(); + expect(authenticationResponseWithJWT.response.idToken).toBeDefined(); + + const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { + presentationDefinitions: [{ definition: pd[0].definition, location: pd[0].location }], + // audience: EXAMPLE_REDIRECT_URL, + }); + expect(verifiedAuthResponseWithJWT.idToken.jwt).toBeDefined(); + expect(verifiedAuthResponseWithJWT.idToken.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); + }, + UNIT_TEST_TIMEOUT, + ); + + it('succeed when calling with RevocationVerification.ALWAYS with ldp_vp', async () => { + 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', + }; + + // 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 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) + .withAuthorizationEndpoint('www.myauthorizationendpoint.com') + .addDidMethod('ion') + .withClientMetadata({ + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], + requestObjectSigningAlgValuesSupported: [SigningAlgo.ES256K], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormatsSupported: { + jwt_vc: { alg: [SigningAlgo.EDDSA] }, + jwt_vp: { alg: [SigningAlgo.EDDSA] }, + ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp_vp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did', 'did:ion'], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100330', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withPresentationDefinition({ definition: getPresentationDefinition() }, [PropertyTarget.REQUEST_OBJECT, PropertyTarget.AUTHORIZATION_REQUEST]) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); + + const op = OP.builder() + .withPresentationSignCallback(presentationSignCallback) + .withExpiresIn(1000) + .addDidMethod('ethr') + .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withPresentationSignCallback(presentationSignCallback) + .withCheckLinkedDomain(CheckLinkedDomain.NEVER) + .withWellknownDIDVerifyCallback(verifyCallback) + .withRegistration({ + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], + issuer: ResponseIss.SELF_ISSUED_V2, + requestObjectSigningAlgValuesSupported: [SigningAlgo.ES256K], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormats: { + jwt_vc: { alg: [SigningAlgo.EDDSA] }, + jwt_vp: { alg: [SigningAlgo.EDDSA] }, + ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp_vp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + }, + 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 + '2022100331', + 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', + }); + + await checkSIOPSpecVersionSupported(requestURI.authorizationRequestPayload, op.verifyRequestOptions.supportedVersions); + // 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); //, rp.authRequestOpts + 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], + presentationSubmission: verifiablePresentationResult.presentationSubmission, + vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, + /*credentialsAndDefinitions: [ + { + presentation: vp, + format: VerifiablePresentationTypeFormat.LDP_VP, + vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, + }, + ],*/ + }, + }); + expect(authenticationResponseWithJWT.response.payload).toBeDefined(); + expect(authenticationResponseWithJWT.response.idToken).toBeDefined(); + + 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('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 }], + // audience: EXAMPLE_REDIRECT_URL, + }); + expect(verifiedAuthResponseWithJWT.idToken.jwt).toBeDefined(); + expect(verifiedAuthResponseWithJWT.idToken.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); + }); + + it('should verify revocation ldp_vp with RevocationVerification.ALWAYS', async () => { + const presentation = { + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://identity.foundation/presentation-exchange/submission/v1'], + type: ['VerifiablePresentation', 'PresentationSubmission'], + presentation_submission: { + id: 'K7Zu3C6yJv3TGXYCB3B3n', + definition_id: 'Insurance Plans', + descriptor_map: [ + { + id: 'Ontario Health Insurance Plan', + format: 'ldp_vc', + path: '$.verifiableCredential[0]', + }, + ], + }, + verifiableCredential: [ + { + identifier: '83627465', + name: 'Permanent Resident Card', + type: ['PermanentResidentCard', 'VerifiableCredential'], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465dsdsdsd', + credentialSubject: { + birthCountry: 'Bahamas', + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + gender: 'Female', + familyName: 'SMITH', + givenName: 'JANE', + residentSince: '2015-01-01', + lprNumber: '999-999-999', + birthDate: '1958-07-17', + commuterClassification: 'C1', + lprCategory: 'C09', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + }, + expirationDate: '2029-12-03T12:19:52Z', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'], + issuer: { + id: 'did:example:issuer', + }, + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2020-04-25', + verificationMethod: 'did:example:489398593#test', + proofPurpose: 'assertionMethod', + proofValue: + 'kTTbA3pmDa6Qia/JkOnIXDLmoBz3vsi7L5t3DWySI/VLmBqleJ/Tbus5RoyiDERDBEh5rnACXlnOqJ/U8yFQFtcp/mBCc2FtKNPHae9jKIv1dm9K9QK1F3GI1AwyGoUfjLWrkGDObO1ouNAhpEd0+et+qiOf2j8p3MTTtRRx4Hgjcl0jXCq7C7R5/nLpgimHAAAAdAx4ouhMk7v9dXijCIMaG0deicn6fLoq3GcNHuH5X1j22LU/hDu7vvPnk/6JLkZ1xQAAAAIPd1tu598L/K3NSy0zOy6obaojEnaqc1R5Ih/6ZZgfEln2a6tuUp4wePExI1DGHqwj3j2lKg31a/6bSs7SMecHBQdgIYHnBmCYGNQnu/LZ9TFV56tBXY6YOWZgFzgLDrApnrFpixEACM9rwrJ5ORtxAAAAAgE4gUIIC9aHyJNa5TBklMOh6lvQkMVLXa/vEl+3NCLXblxjgpM7UEMqBkE9/QcoD3Tgmy+z0hN+4eky1RnJsEg=', + nonce: '6i3dTz5yFfWJ8zgsamuyZa4yAHPm75tUOOXddR6krCvCYk77sbCOuEVcdBCDd/l6tIY=', + }, + }, + ], + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2020-04-25', + verificationMethod: 'did:example:489398593#test', + proofPurpose: 'assertionMethod', + proofValue: + 'kTTbA3pmDa6Qia/JkOnIXDLmoBz3vsi7L5t3DWySI/VLmBqleJ/Tbus5RoyiDERDBEh5rnACXlnOqJ/U8yFQFtcp/mBCc2FtKNPHae9jKIv1dm9K9QK1F3GI1AwyGoUfjLWrkGDObO1ouNAhpEd0+et+qiOf2j8p3MTTtRRx4Hgjcl0jXCq7C7R5/nLpgimHAAAAdAx4ouhMk7v9dXijCIMaG0deicn6fLoq3GcNHuH5X1j22LU/hDu7vvPnk/6JLkZ1xQAAAAIPd1tu598L/K3NSy0zOy6obaojEnaqc1R5Ih/6ZZgfEln2a6tuUp4wePExI1DGHqwj3j2lKg31a/6bSs7SMecHBQdgIYHnBmCYGNQnu/LZ9TFV56tBXY6YOWZgFzgLDrApnrFpixEACM9rwrJ5ORtxAAAAAgE4gUIIC9aHyJNa5TBklMOh6lvQkMVLXa/vEl+3NCLXblxjgpM7UEMqBkE9/QcoD3Tgmy+z0hN+4eky1RnJsEg=', + nonce: '6i3dTz5yFfWJ8zgsamuyZa4yAHPm75tUOOXddR6krCvCYk77sbCOuEVcdBCDd/l6tIY=', + }, + }; + + await expect( + verifyRevocation( + CredentialMapper.toWrappedVerifiablePresentation(presentation), + async () => { + return { status: RevocationStatus.VALID }; + }, + RevocationVerification.ALWAYS, + ), + ).resolves.not.toThrow(); + }); + + it('should verify revocation ldp_vp with RevocationVerification.IF_PRESENT', async () => { + const presentation = { + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://identity.foundation/presentation-exchange/submission/v1'], + type: ['VerifiablePresentation', 'PresentationSubmission'], + presentation_submission: { + id: 'K7Zu3C6yJv3TGXYCB3B3n', + definition_id: 'Insurance Plans', + descriptor_map: [ + { + id: 'Ontario Health Insurance Plan', + format: 'ldp_vc', + path: '$.verifiableCredential[0]', + }, + ], + }, + verifiableCredential: [ + { + identifier: '83627465', + name: 'Permanent Resident Card', + type: ['PermanentResidentCard', 'VerifiableCredential'], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465dsdsdsd', + credentialSubject: { + birthCountry: 'Bahamas', + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + gender: 'Female', + familyName: 'SMITH', + givenName: 'JANE', + residentSince: '2015-01-01', + lprNumber: '999-999-999', + birthDate: '1958-07-17', + commuterClassification: 'C1', + lprCategory: 'C09', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + }, + credentialStatus: { + id: 'https://example.com/credentials/status/3#94567', + type: 'StatusList2021Entry', + statusPurpose: 'revocation', + statusListIndex: '94567', + statusListCredential: 'https://example.com/credentials/status/3', + }, + expirationDate: '2029-12-03T12:19:52Z', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'], + issuer: { + id: 'did:example:issuer', + }, + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2020-04-25', + verificationMethod: 'did:example:489398593#test', + proofPurpose: 'assertionMethod', + proofValue: + 'kTTbA3pmDa6Qia/JkOnIXDLmoBz3vsi7L5t3DWySI/VLmBqleJ/Tbus5RoyiDERDBEh5rnACXlnOqJ/U8yFQFtcp/mBCc2FtKNPHae9jKIv1dm9K9QK1F3GI1AwyGoUfjLWrkGDObO1ouNAhpEd0+et+qiOf2j8p3MTTtRRx4Hgjcl0jXCq7C7R5/nLpgimHAAAAdAx4ouhMk7v9dXijCIMaG0deicn6fLoq3GcNHuH5X1j22LU/hDu7vvPnk/6JLkZ1xQAAAAIPd1tu598L/K3NSy0zOy6obaojEnaqc1R5Ih/6ZZgfEln2a6tuUp4wePExI1DGHqwj3j2lKg31a/6bSs7SMecHBQdgIYHnBmCYGNQnu/LZ9TFV56tBXY6YOWZgFzgLDrApnrFpixEACM9rwrJ5ORtxAAAAAgE4gUIIC9aHyJNa5TBklMOh6lvQkMVLXa/vEl+3NCLXblxjgpM7UEMqBkE9/QcoD3Tgmy+z0hN+4eky1RnJsEg=', + nonce: '6i3dTz5yFfWJ8zgsamuyZa4yAHPm75tUOOXddR6krCvCYk77sbCOuEVcdBCDd/l6tIY=', + }, + }, + ], + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2020-04-25', + verificationMethod: 'did:example:489398593#test', + proofPurpose: 'assertionMethod', + proofValue: + 'kTTbA3pmDa6Qia/JkOnIXDLmoBz3vsi7L5t3DWySI/VLmBqleJ/Tbus5RoyiDERDBEh5rnACXlnOqJ/U8yFQFtcp/mBCc2FtKNPHae9jKIv1dm9K9QK1F3GI1AwyGoUfjLWrkGDObO1ouNAhpEd0+et+qiOf2j8p3MTTtRRx4Hgjcl0jXCq7C7R5/nLpgimHAAAAdAx4ouhMk7v9dXijCIMaG0deicn6fLoq3GcNHuH5X1j22LU/hDu7vvPnk/6JLkZ1xQAAAAIPd1tu598L/K3NSy0zOy6obaojEnaqc1R5Ih/6ZZgfEln2a6tuUp4wePExI1DGHqwj3j2lKg31a/6bSs7SMecHBQdgIYHnBmCYGNQnu/LZ9TFV56tBXY6YOWZgFzgLDrApnrFpixEACM9rwrJ5ORtxAAAAAgE4gUIIC9aHyJNa5TBklMOh6lvQkMVLXa/vEl+3NCLXblxjgpM7UEMqBkE9/QcoD3Tgmy+z0hN+4eky1RnJsEg=', + nonce: '6i3dTz5yFfWJ8zgsamuyZa4yAHPm75tUOOXddR6krCvCYk77sbCOuEVcdBCDd/l6tIY=', + }, + }; + + await expect( + verifyRevocation( + CredentialMapper.toWrappedVerifiablePresentation(presentation), + async () => { + return { status: RevocationStatus.VALID }; + }, + RevocationVerification.ALWAYS, + ), + ).resolves.not.toThrow(); + }); + + it('should verify revocation ldp_vp with location id_token', async () => { + 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', + }; + + // 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 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) + .withAuthorizationEndpoint('www.myauthorizationendpoint.com') + .withClientMetadata({ + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], + requestObjectSigningAlgValuesSupported: [SigningAlgo.ES256K], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormatsSupported: { + jwt_vc: { alg: [SigningAlgo.EDDSA] }, + ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp_vp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did', 'did:ion'], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + }) + .withPresentationDefinition({ definition: getPresentationDefinition() }, [PropertyTarget.REQUEST_OBJECT, PropertyTarget.AUTHORIZATION_REQUEST]) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); + const op = OP.builder() + .withPresentationSignCallback(presentationSignCallback) + .withWellknownDIDVerifyCallback(verifyCallback) + .withExpiresIn(1000) + .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withCheckLinkedDomain(CheckLinkedDomain.NEVER) + .withRegistration({ + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], + issuer: ResponseIss.SELF_ISSUED_V2, + requestObjectSigningAlgValuesSupported: [SigningAlgo.ES256K], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormats: { + jwt_vc: { alg: [SigningAlgo.EDDSA] }, + ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp_vp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did:ethr'], + passBy: PassBy.VALUE, + }) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); + + 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(); + + 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], + presentationSubmission: verifiablePresentationResult.presentationSubmission, + vpTokenLocation: VPTokenLocation.ID_TOKEN, + /*credentialsAndDefinitions: [ + { + presentation: vp, + format: VerifiablePresentationTypeFormat.LDP_VP, + vpTokenLocation: VPTokenLocation.ID_TOKEN + } + ]*/ + }, + }); + expect(authenticationResponseWithJWT.response.payload).toBeDefined(); + expect(authenticationResponseWithJWT.response.idToken).toBeDefined(); + + const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { + presentationDefinitions: [{ definition: pd[0].definition, location: pd[0].location }], + audience: 'test_client_id', + }); + expect(verifiedAuthResponseWithJWT.idToken.jwt).toBeDefined(); + expect(verifiedAuthResponseWithJWT.idToken.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); + }); + + it('succeed with nonce verification with ldp_vp', async () => { + 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 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) + .withAuthorizationEndpoint('www.myauthorizationendpoint.com') + .addDidMethod('ion') + .withClientMetadata({ + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], + requestObjectSigningAlgValuesSupported: [SigningAlgo.ES256K], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormatsSupported: { + jwt_vc: { alg: [SigningAlgo.EDDSA] }, + jwt_vp: { alg: [SigningAlgo.EDDSA] }, + ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp_vp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did', 'did:ion'], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100330', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withPresentationDefinition({ definition: getPresentationDefinition() }, [PropertyTarget.REQUEST_OBJECT, PropertyTarget.AUTHORIZATION_REQUEST]) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); + + const op = OP.builder() + .withPresentationSignCallback(presentationSignCallback) + .withExpiresIn(1000) + .addDidMethod('ethr') + .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, opMockEntity.didKey, SigningAlgo.ES256K) + .withPresentationSignCallback(presentationSignCallback) + .withCheckLinkedDomain(CheckLinkedDomain.NEVER) + .withWellknownDIDVerifyCallback(verifyCallback) + .withRegistration({ + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], + issuer: ResponseIss.SELF_ISSUED_V2, + requestObjectSigningAlgValuesSupported: [SigningAlgo.ES256K], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormats: { + jwt_vc: { alg: [SigningAlgo.EDDSA] }, + jwt_vp: { alg: [SigningAlgo.EDDSA] }, + ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp_vp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + }, + 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 + '2022100331', + 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 parsedAuthReqURI = await op.parseAuthorizationRequestURI(requestURI.encodedUri); + const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt); + 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], + presentationSubmission: verifiablePresentationResult.presentationSubmission, + vpTokenLocation: VPTokenLocation.ID_TOKEN, + /*credentialsAndDefinitions: [ + { + presentation: vp, + format: VerifiablePresentationTypeFormat.LDP_VP, + vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE + } + ]*/ + }, + }); + + 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('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 }], + audience: 'test_client_id', + }); + expect(verifiedAuthResponseWithJWT.idToken.jwt).toBeDefined(); + expect(verifiedAuthResponseWithJWT.idToken.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); + }); + + it('should register authorization request on create', async () => { + const rpMockEntity = { + hexPrivateKey: '2bbd6a78be9ab2193bcf74aa6d39ab59c1d1e2f7e9ef899a38fb4d94d8aa90e2', + did: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024', + didKey: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024#controllerKey', + }; + + const eventEmitter = new EventEmitter(); + const replayRegistry = new InMemoryRPSessionManager(eventEmitter); + 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) + .withAuthorizationEndpoint('www.myauthorizationendpoint.com') + .addDidMethod('ion') + .withClientMetadata({ + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.ES256K], + requestObjectSigningAlgValuesSupported: [SigningAlgo.ES256K], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormatsSupported: { + jwt_vc: { alg: [SigningAlgo.EDDSA] }, + jwt_vp: { alg: [SigningAlgo.EDDSA] }, + ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp_vp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + ldp: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did', 'did:ion'], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100330', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withPresentationDefinition({ definition: getPresentationDefinition() }) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .withSessionManager(replayRegistry) + .withEventEmitter(eventEmitter) + .build(); + + await rp.createAuthorizationRequest({ + correlationId: '1234', + nonce: { propertyValue: 'bcceb347-1374-49b8-ace0-b868162c122d', targets: PropertyTarget.REQUEST_OBJECT }, + state: { propertyValue: '8006b5fb-6e3b-42d1-a2be-55ed2a08073d', targets: PropertyTarget.REQUEST_OBJECT }, + claims: { + propertyValue: { + vp_token: { + presentation_definition: { + input_descriptors: [ + { + schema: [ + { + uri: 'https://VerifiedEmployee', + }, + ], + purpose: 'We need to verify that you have a valid VerifiedEmployee Verifiable Credential.', + name: 'VerifiedEmployeeVC', + id: 'VerifiedEmployeeVC', + }, + ], + id: '8006b5fb-6e3b-42d1-a2be-55ed2a08073d', + }, + }, + }, + targets: PropertyTarget.REQUEST_OBJECT, + }, + }); + + const state = await replayRegistry.getRequestStateByCorrelationId('1234', true); + expect(state.status).toBe('created'); + }); + + it('should register authorization request on create with uri', async () => { + const rpMockEntity = { + hexPrivateKey: '2bbd6a78be9ab2193bcf74aa6d39ab59c1d1e2f7e9ef899a38fb4d94d8aa90e2', + did: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024', + didKey: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024#controllerKey', + }; + const eventEmitter = new EventEmitter(); + const replayRegistry = new InMemoryRPSessionManager(eventEmitter); + + const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) + .withClientId(WELL_KNOWN_OPENID_FEDERATION) + .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') + .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 + '2022100317', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withSupportedVersions([SupportedVersion.SIOPv2_ID1]) + .withSessionManager(replayRegistry) + .withEventEmitter(eventEmitter) + .build(); + + await rp.createAuthorizationRequestURI({ + correlationId: '1234', + nonce: { propertyValue: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg' }, + state: { propertyValue: 'b32f0087fc9816eb813fd11f' }, + }); + + const state = await replayRegistry.getRequestStateByCorrelationId('1234'); + expect(state.status).toBe('created'); + }); + + it('should register authorization response on successful verification', async () => { + await nock.cleanAll(); + 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 eventEmitter = new EventEmitter(); + const replayRegistry = new InMemoryRPSessionManager(eventEmitter); + + 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') + .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 + '2022100317', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withSupportedVersions([SupportedVersion.SIOPv2_ID1]) + .withEventEmitter(eventEmitter) + .withSessionManager(replayRegistry) + .build(); + const op = OP.builder() + .withPresentationSignCallback(presentationSignCallback) + .withExpiresIn(1000) + .withWellknownDIDVerifyCallback(verifyCallback) + .withIssuer(ResponseIss.SELF_ISSUED_V2) + .withInternalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, `${opMockEntity.didKey}`, SigningAlgo.ES256K) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + //FIXME: Move payload options to seperate property + .withRegistration({ + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], + issuer: ResponseIss.SELF_ISSUED_V2, + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormats: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did:ethr'], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100318', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(); + const requestURI = await rp.createAuthorizationRequestURI({ + correlationId: '12345', + nonce: { propertyValue: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg' }, + state: { propertyValue: 'b32f0087fc9816eb813fd11f1' }, + }); + const reqStateCreated = await replayRegistry.getRequestStateByState('b32f0087fc9816eb813fd11f1', true); + 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); + nock(EXAMPLE_REDIRECT_URL).post(/.*/).times(3).reply(200, { result: 'ok' }); + await op.submitAuthorizationResponse(authenticationResponseWithJWT); + await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { + // audience: EXAMPLE_REDIRECT_URL, + }); + const reqStateAfterResponse = await replayRegistry.getRequestStateByState('incorrect', false); + expect(reqStateAfterResponse).toBeUndefined(); + + const resStateAfterResponse = await replayRegistry.getResponseStateByState('b32f0087fc9816eb813fd11f1', true); + expect(resStateAfterResponse.status).toBe('verified'); + }); + + it('should set error status on failed authorization response verification', async () => { + const rpMockEntity = { + hexPrivateKey: '2bbd6a78be9ab2193bcf74aa6d39ab59c1d1e2f7e9ef899a38fb4d94d8aa90e2', + did: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024', + didKey: 'did:ethr:goerli:0x038f8d21b0446c46b05aecdc603f73831578e28857adba14de569f31f3e569c024#controllerKey', + }; + const opMockEntity = await mockedGetEnterpriseAuthToken('ACME OP'); + const eventEmitter = new EventEmitter(); + const replayRegistry = new InMemoryRPSessionManager(eventEmitter); + + 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') + .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 + '2022100317', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withSupportedVersions([SupportedVersion.SIOPv2_ID1]) + .withSessionManager(replayRegistry) + .withEventEmitter(eventEmitter) + .build(); + 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) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + //FIXME: Move payload options to seperate property + .withRegistration({ + authorizationEndpoint: 'www.myauthorizationendpoint.com', + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], + issuer: ResponseIss.SELF_ISSUED_V2, + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormats: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: ['did:ethr'], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100318', + 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: { propertyValue: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg' }, + state: { propertyValue: 'b32f0087fc9816eb813fd11f' }, + }); + const state = await replayRegistry.getRequestStateByCorrelationId('1234', true); + expect(state.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); + nock(EXAMPLE_REDIRECT_URL).post(/.*/).reply(200, { result: 'ok' }); + await op.submitAuthorizationResponse(authenticationResponseWithJWT); + authenticationResponseWithJWT.response.payload.state = 'wrong_value'; + await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { correlationId: '1234' }).catch(() => { + //swallow this exception; + }); + const reqState = await replayRegistry.getRequestStateByCorrelationId('1234', true); + expect(reqState.status).toBe('created'); + + const resState = await replayRegistry.getResponseStateByCorrelationId('1234', true); + expect(resState.status).toBe('error'); + }); +}); diff --git a/packages/siopv2/test/OP.request.spec.ts b/packages/siopv2/test/OP.request.spec.ts new file mode 100644 index 00000000..ca9b6bd2 --- /dev/null +++ b/packages/siopv2/test/OP.request.spec.ts @@ -0,0 +1,269 @@ +import { ResponseType } from '@sphereon/oid4vc-common'; +import { IProofType } from '@sphereon/ssi-types'; +import { IVerifyCallbackArgs, IVerifyCredentialResult } from '@sphereon/wellknown-dids-client'; +import nock from 'nock'; + +import { + AuthorizationResponseOpts, + CheckLinkedDomain, + CreateAuthorizationRequestOpts, + OP, + PassBy, + ResponseIss, + ResponseMode, + RP, + Scope, + SigningAlgo, + SubjectIdentifierType, + SubjectType, + SupportedVersion, + VerificationMode, + VerifyAuthorizationRequestOpts, +} from '../src'; + +import { mockedGetEnterpriseAuthToken, WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; +import { + UNIT_TEST_TIMEOUT, + VERIFIER_LOGO_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT_NL, + VERIFIERZ_PURPOSE_TO_VERIFY, + 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'; + +const HEX_KEY = 'f857544a9d1097e242ff0b287a7e6e90f19cf973efe2317f2a4678739664420f'; +const DID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0'; +const KID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0#controller'; + +describe('OP OPBuilder should', () => { + /*it('throw Error when no arguments are passed', async () => { + expect.assertions(1); + await expect(() => new OPBuilder().build()).toThrowError(Error); + });*/ + it('build an OP when all arguments are set', async () => { + expect.assertions(1); + + expect( + OP.builder() + .withCheckLinkedDomain(CheckLinkedDomain.NEVER) + .addDidMethod('ethr') + .withIssuer(ResponseIss.SELF_ISSUED_V2) + .withResponseMode(ResponseMode.POST) + .withRegistration({ + passBy: PassBy.REFERENCE, + reference_uri: 'https://registration.here', + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100332', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .withInternalSignature('myprivatekey', 'did:example:123', 'did:example:123#key', SigningAlgo.ES256K) + .withExpiresIn(1000) + .withSupportedVersions([SupportedVersion.SIOPv2_ID1]) + .build(), + ).toBeInstanceOf(OP); + }); +}); + +describe('OP should', () => { + const responseOpts: AuthorizationResponseOpts = { + checkLinkedDomain: CheckLinkedDomain.NEVER, + responseURI: EXAMPLE_REDIRECT_URL, + responseURIType: 'redirect_uri', + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + alg: SigningAlgo.ES256K, + }, + registration: { + authorizationEndpoint: 'www.myauthorizationendpoint.com', + responseTypesSupported: [ResponseType.ID_TOKEN], + subject_syntax_types_supported: ['did:web'], + vpFormats: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100333', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + //TODO: fill it up with actual value + issuer: ResponseIss.SELF_ISSUED_V2, + passBy: PassBy.VALUE, + }, + responseMode: ResponseMode.POST, + expiresIn: 2000, + }; + + 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 }), + }, + correlationId: '1234', + supportedVersions: [SupportedVersion.SIOPv2_ID1], + nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + }; + + /*it('throw Error when build from request opts without enough params', async () => { + expect.assertions(1); + await expect(() => OP.fromOpts({} as never, {} as never)).toThrowError(Error); + });*/ + + it('return an OP when all request arguments are set', async () => { + expect.assertions(1); + + expect(OP.fromOpts(responseOpts, verifyOpts)).toBeInstanceOf(OP); + }); + + it( + 'succeed from request opts when all params are set', + async () => { + const mockEntity = await mockedGetEnterpriseAuthToken('ACME Corp'); + const requestOpts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + + signature: { + 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, + scope: 'test', + response_type: 'id_token', + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + subject_syntax_types_supported: ['did:ethr', SubjectIdentifierType.DID], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + jwt_vc: { alg: [SigningAlgo.EDDSA, SigningAlgo.ES256K, SigningAlgo.ES256] }, + jwt_vp: { alg: [SigningAlgo.EDDSA, SigningAlgo.ES256K, SigningAlgo.ES256] }, + jwt: { alg: [SigningAlgo.EDDSA, SigningAlgo.ES256K, SigningAlgo.ES256] }, + }, + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100334', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + + const requestURI = await RP.fromRequestOpts(requestOpts).createAuthorizationRequestURI({ + correlationId: '1234', + nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + state: 'b32f0087fc9816eb813fd11f', + }); + + nock('https://rp.acme.com').get('/siop/jwts').reply(200, requestURI.requestObjectJwt); + + 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, + ); + + it('succeed from builder when all params are set', async () => { + const rpMockEntity = await mockedGetEnterpriseAuthToken('ACME RP'); + const opMockEntity = await mockedGetEnterpriseAuthToken('ACME OP'); + + const requestURI = await RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) + .withClientId(WELL_KNOWN_OPENID_FEDERATION) + .withScope('test') + .withResponseType(ResponseType.ID_TOKEN) + .withCheckLinkedDomain(CheckLinkedDomain.NEVER) + .withAuthorizationEndpoint('www.myauthorizationendpoint.com') + .withRedirectUri(EXAMPLE_REFERENCE_URL) + .withRequestBy(PassBy.VALUE) + .withInternalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, `${rpMockEntity.did}#controller`, SigningAlgo.ES256K) + .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 + '2022100335', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .build() + + .createAuthorizationRequestURI({ + correlationId: '1234', + nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + state: 'b32f0087fc9816eb813fd11f', + }); + + 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) + .withRegistration({ + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormats: { ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] } }, + 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 + '2022100336', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + .build() + + .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/packages/siopv2/test/PresentationExchange.spec.ts b/packages/siopv2/test/PresentationExchange.spec.ts new file mode 100644 index 00000000..78cc5a22 --- /dev/null +++ b/packages/siopv2/test/PresentationExchange.spec.ts @@ -0,0 +1,425 @@ +import { ResponseType } from '@sphereon/oid4vc-common'; +import { PresentationDefinitionV1 } from '@sphereon/pex-models'; +import { CredentialMapper, IPresentation, IProofType, IVerifiableCredential } from '@sphereon/ssi-types'; +import { W3CVerifiablePresentation } from '@sphereon/ssi-types/src/types/w3c-vc'; +import nock from 'nock'; + +import { + AuthorizationRequestPayload, + AuthorizationRequestPayloadVID1, + getNonce, + getState, + IdTokenType, + PresentationDefinitionWithLocation, + PresentationExchange, + PresentationSignCallback, + Scope, + SigningAlgo, + SubjectIdentifierType, + SubjectType, +} from '../src'; +import { SIOPErrors } from '../src/types'; + +import { mockedGetEnterpriseAuthToken } from './TestUtils'; +import { + VERIFIER_LOGO_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT_NL, + VERIFIERZ_PURPOSE_TO_VERIFY, + VERIFIERZ_PURPOSE_TO_VERIFY_NL, +} from './data/mockedData'; + +const HOLDER_DID = 'did:example:ebfeb1f712ebc6f1c276e12ec21'; +const EXAMPLE_PD_URL = 'http://my_own_pd.com/pd/'; + +async function getPayloadVID1Val(): Promise { + const mockEntity = await mockedGetEnterpriseAuthToken('ACME Corp'); + const state = getState(); + return { + redirect_uri: '', + scope: Scope.OPENID, + response_type: ResponseType.ID_TOKEN, + client_id: mockEntity.did, + state, + nonce: getNonce(state), + registration: { + client_id: mockEntity.did, + id_token_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + id_token_types_supported: [IdTokenType.SUBJECT_SIGNED], + request_object_signing_alg_values_supported: [SigningAlgo.ES256K, SigningAlgo.ES256, SigningAlgo.EDDSA], + response_types_supported: [ResponseType.ID_TOKEN], + scopes_supported: [Scope.OPENID, Scope.OPENID_DIDAUTHN], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + subject_types_supported: [SubjectType.PAIRWISE], + vp_formats: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + jwt_vc: { + alg: [SigningAlgo.ES256, SigningAlgo.ES256K], + }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + client_name: VERIFIER_NAME_FOR_CLIENT, + 'client_name#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100337', + client_purpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'client_purpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + claims: { + id_token: { + acr: null, + }, + vp_token: { + presentation_definition: { + id: 'Insurance Plans', + input_descriptors: [ + { + id: 'Ontario Health Insurance Plan', + schema: [ + { + uri: 'https://did.itsourweb.org:3000/smartcredential/Ontario-Health-Insurance-Plan', + }, + { + uri: 'https://www.w3.org/2018/credentials/v1', + }, + ], + constraints: { + limit_disclosure: 'preferred', + fields: [ + { + path: ['$.issuer.id'], + purpose: 'We can only verify bank accounts if they are attested by a source.', + filter: { + type: 'string', + pattern: 'did:example:issuer', + }, + }, + ], + }, + }, + ], + }, + }, + }, + }; +} + +async function getPayloadPdRef(): Promise { + const mockEntity = await mockedGetEnterpriseAuthToken('ACME Corp'); + const state = getState(); + return { + redirect_uri: '', + scope: Scope.OPENID, + response_type: ResponseType.ID_TOKEN, + client_id: mockEntity.did, + state, + nonce: getNonce(state), + registration: { + id_token_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + id_token_types_supported: [IdTokenType.SUBJECT_SIGNED], + request_object_signing_alg_values_supported: [SigningAlgo.ES256K, SigningAlgo.ES256, SigningAlgo.EDDSA], + response_types_supported: [ResponseType.ID_TOKEN], + scopes_supported: [Scope.OPENID, Scope.OPENID_DIDAUTHN], + subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID], + subject_types_supported: [SubjectType.PAIRWISE], + vp_formats: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + jwt_vc: { + alg: [SigningAlgo.ES256, SigningAlgo.ES256K], + }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + client_name: VERIFIER_NAME_FOR_CLIENT, + 'client_name#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100338', + client_purpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'client_purpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + claims: { + id_token: { + acr: null, + }, + vp_token: { + presentation_definition: { + id: 'Insurance Plans', + input_descriptors: [ + { + id: 'Ontario Health Insurance Plan', + schema: [ + { + uri: 'https://did.itsourweb.org:3000/smartcredential/Ontario-Health-Insurance-Plan', + }, + { + uri: 'https://www.w3.org/2018/credentials/v1', + }, + ], + constraints: { + limit_disclosure: 'preferred', + fields: [ + { + path: ['$.issuer.id'], + purpose: 'We can only verify bank accounts if they are attested by a source.', + filter: { + type: 'string', + pattern: 'did:example:issuer', + }, + }, + ], + }, + }, + ], + }, + }, + }, + }; +} + +function getVCs(): IVerifiableCredential[] { + return [ + { + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'], + issuanceDate: '2021-11-01T03:05:06T000z', + id: 'https://example.com/credentials/1872', + type: ['VerifiableCredential', 'IDCardCredential'], + issuer: { + id: 'did:example:issuer', + }, + credentialSubject: { + given_name: 'Fredrik', + family_name: 'Stremberg', + birthdate: '1949-01-22', + }, + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2020-04-25', + verificationMethod: 'did:example:489398593#test', + proofPurpose: 'assertionMethod', + proofValue: + 'kTTbA3pmDa6Qia/JkOnIXDLmoBz3vsi7L5t3DWySI/VLmBqleJ/Tbus5RoyiDERDBEh5rnACXlnOqJ/U8yFQFtcp/mBCc2FtKNPHae9jKIv1dm9K9QK1F3GI1AwyGoUfjLWrkGDObO1ouNAhpEd0+et+qiOf2j8p3MTTtRRx4Hgjcl0jXCq7C7R5/nLpgimHAAAAdAx4ouhMk7v9dXijCIMaG0deicn6fLoq3GcNHuH5X1j22LU/hDu7vvPnk/6JLkZ1xQAAAAIPd1tu598L/K3NSy0zOy6obaojEnaqc1R5Ih/6ZZgfEln2a6tuUp4wePExI1DGHqwj3j2lKg31a/6bSs7SMecHBQdgIYHnBmCYGNQnu/LZ9TFV56tBXY6YOWZgFzgLDrApnrFpixEACM9rwrJ5ORtxAAAAAgE4gUIIC9aHyJNa5TBklMOh6lvQkMVLXa/vEl+3NCLXblxjgpM7UEMqBkE9/QcoD3Tgmy+z0hN+4eky1RnJsEg=', + }, + }, + ]; +} + +const presentation_submission = { + id: 'L4tK2DK3rLC9sJhkPgeg_', + definition_id: 'Insurance Plans', + descriptor_map: [ + { + id: 'Ontario Health Insurance Plan', + format: 'ldp_vc', + path: '$.verifiableCredential[0]', + }, + ], +}; + +describe('presentation exchange manager tests', () => { + it("validatePresentationAgainstDefinition: should throw error if provided VP doesn't match the PD val", async function () { + const payload: AuthorizationRequestPayload = await getPayloadVID1Val(); + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions(payload); + const vcs = getVCs(); + vcs[0].issuer = { id: 'did:example:totallyDifferentIssuer' }; + await expect( + PresentationExchange.validatePresentationAgainstDefinition( + pd[0].definition, + CredentialMapper.toWrappedVerifiablePresentation({ + '@context': ['https://www.w3.org/2018/credentials/v1'], + type: ['VerifiablePresentation', 'PresentationSubmission'], + verifiableCredential: vcs, + presentation_submission, + } as W3CVerifiablePresentation), + ), + ).rejects.toThrow(SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD); + }); + + it("validatePresentationAgainstDefinition: should throw error if provided VP doesn't match the PD ref", async function () { + const payload: AuthorizationRequestPayload = await getPayloadPdRef(); + const response = { + id: 'Insurance Plans', + input_descriptors: [ + { + id: 'Ontario Health Insurance Plan', + schema: [ + { + uri: 'https://did.itsourweb.org:3000/smartcredential/Ontario-Health-Insurance-Plan', + }, + { + uri: 'https://www.w3.org/2018/credentials/v1', + }, + ], + constraints: { + limit_disclosure: 'preferred', + fields: [ + { + path: ['$.issuer.id'], + purpose: 'We can only verify bank accounts if they are attested by a source.', + filter: { + type: 'string', + pattern: 'did:example:issuer', + }, + }, + ], + }, + }, + ], + }; + nock('http://my_own_pd.com') + .persist() + .get(/pd/) + .reply(200, { ...response }); + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions(payload); + const vcs = getVCs(); + vcs[0].issuer = { id: 'did:example:totallyDifferentIssuer' }; + await expect( + PresentationExchange.validatePresentationAgainstDefinition( + pd[0].definition, + CredentialMapper.toWrappedVerifiablePresentation({ + '@context': ['https://www.w3.org/2018/credentials/v1'], + type: ['VerifiablePresentation', 'PresentationSubmission'], + presentation_submission, + verifiableCredential: vcs, + } as W3CVerifiablePresentation), + ), + ).rejects.toThrow(SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD); + }); + + it('validatePresentationAgainstDefinition: should throw error if both pd and pd_ref is present', async function () { + const payload = await getPayloadVID1Val(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (payload.claims.vp_token as any).presentation_definition_uri = EXAMPLE_PD_URL; + await expect(PresentationExchange.findValidPresentationDefinitions(payload)).rejects.toThrow( + SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_BY_REF_AND_VALUE_NON_EXCLUSIVE, + ); + }); + + it('validatePresentationAgainstDefinition: should pass if provided VP match the PD', async function () { + const payload = await getPayloadVID1Val(); + const vcs = getVCs(); + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions(payload); + const result = await PresentationExchange.validatePresentationAgainstDefinition( + pd[0].definition, + CredentialMapper.toWrappedVerifiablePresentation({ + '@context': ['https://www.w3.org/2018/credentials/v1'], + type: ['VerifiablePresentation', 'PresentationSubmission'], + presentation_submission, + verifiableCredential: vcs, + } as W3CVerifiablePresentation), + ); + expect(result.errors.length).toBe(0); + expect(result.value.definition_id).toBe('Insurance Plans'); + }); + + it('submissionFrom: should pass if a valid presentationSubmission object created', async function () { + const vcs = getVCs(); + const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: vcs }); + const payload: AuthorizationRequestPayload = await getPayloadVID1Val(); + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions(payload); + await PresentationExchange.validatePresentationAgainstDefinition( + pd[0].definition, + CredentialMapper.toWrappedVerifiablePresentation({ + '@context': ['https://www.w3.org/2018/credentials/v1'], + type: ['VerifiablePresentation', 'PresentationSubmission'], + presentation_submission, + verifiableCredential: vcs, + } as W3CVerifiablePresentation), + ); + await pex.selectVerifiableCredentialsForSubmission(pd[0].definition); + const presentationSignCallback: PresentationSignCallback = async (_args) => ({ + ...(_args.presentation as IPresentation), + proof: { + type: 'RsaSignature2018', + created: '2018-09-14T21:19:10Z', + proofPurpose: 'authentication', + verificationMethod: 'did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1', + challenge: '1f44d55f-f161-4938-a659-f8026467f126', + domain: '4jt78h47fh47', + jws: 'eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78', + }, + }); + const result = await pex.createVerifiablePresentation(pd[0].definition, vcs, presentationSignCallback, {}); + expect(result.presentationSubmission.definition_id).toBe('Insurance Plans'); + expect(result.presentationSubmission.descriptor_map.length).toBe(1); + expect(result.presentationSubmission.descriptor_map[0]).toStrictEqual({ + format: 'ldp_vp', + id: 'Ontario Health Insurance Plan', + path: '$', + path_nested: { + format: 'ldp_vc', + id: 'Ontario Health Insurance Plan', + path: '$.verifiableCredential[0]', + }, + }); + }); + + it('selectVerifiableCredentialsForSubmission: should fail if selectResults object contains error', async function () { + const payload: AuthorizationRequestPayload = await getPayloadVID1Val(); + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions(payload); + const vcs = getVCs(); + vcs[0].issuer = undefined; + const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: vcs }); + try { + await expect(pex.selectVerifiableCredentialsForSubmission(pd[0].definition)).rejects.toThrow(); + } catch (e) { + expect(e.message).toContain(SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD); + } + }); + + it('selectVerifiableCredentialsForSubmission: should pass if a valid selectResults object created', async function () { + const vcs = getVCs(); + const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: vcs }); + const payload: AuthorizationRequestPayload = await getPayloadVID1Val(); + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions(payload); + const result = await pex.selectVerifiableCredentialsForSubmission(pd[0].definition); + expect(result.errors.length).toBe(0); + expect(result.matches.length).toBe(1); + expect(result.matches[0].vc_path.length).toBe(1); + expect(result.matches[0].vc_path[0]).toBe('$.verifiableCredential[0]'); + }); + + it('pass if no PresentationDefinition is found', async () => { + const payload: AuthorizationRequestPayload = await getPayloadVID1Val(); + payload.claims = undefined; + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions(payload); + expect(pd.length).toBe(0); + }); + + it('pass if findValidPresentationDefinitions finds a valid presentation_definition', async () => { + const payload: AuthorizationRequestPayload = await getPayloadVID1Val(); + const pd = await PresentationExchange.findValidPresentationDefinitions(payload); + const definition = pd[0].definition as PresentationDefinitionV1; + expect(definition['id']).toBe('Insurance Plans'); + expect(definition['input_descriptors'][0].schema.length).toBe(2); + }); + + it('should validate a list of VerifiablePresentations against a list of PresentationDefinitions', async () => { + const payload: AuthorizationRequestPayload = await getPayloadVID1Val(); + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions(payload); + const vcs = getVCs(); + const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: vcs }); + await pex.selectVerifiableCredentialsForSubmission(pd[0].definition); + const presentationSignCallback: PresentationSignCallback = async (_args) => ({ + ...(_args.presentation as IPresentation), + proof: { + type: 'RsaSignature2018', + created: '2018-09-14T21:19:10Z', + proofPurpose: 'authentication', + verificationMethod: 'did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1', + challenge: '1f44d55f-f161-4938-a659-f8026467f126', + domain: '4jt78h47fh47', + jws: 'eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78', + }, + }); + const verifiablePresentationResult = await pex.createVerifiablePresentation(pd[0].definition, vcs, presentationSignCallback, {}); + try { + await PresentationExchange.validatePresentationsAgainstDefinitions( + pd, + [CredentialMapper.toWrappedVerifiablePresentation(verifiablePresentationResult.verifiablePresentation)], + undefined, + {}, + ); + } catch (e) { + console.log(e); + } + }); +}); diff --git a/packages/siopv2/test/RP.request.spec.ts b/packages/siopv2/test/RP.request.spec.ts new file mode 100644 index 00000000..a8d72fe0 --- /dev/null +++ b/packages/siopv2/test/RP.request.spec.ts @@ -0,0 +1,302 @@ +import { getUniResolver } from '@sphereon/did-uni-client'; +import { ResponseType } from '@sphereon/oid4vc-common'; +import { IProofType } from '@sphereon/ssi-types'; +import { Resolver } from 'did-resolver'; + +import { + CheckLinkedDomain, + CreateAuthorizationRequestOpts, + PassBy, + PropertyTarget, + ResponseMode, + RP, + Scope, + SigningAlgo, + SubjectIdentifierType, + SubjectType, + SupportedVersion, +} from '../src'; + +import { WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; +import { + VERIFIER_LOGO_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT_NL, + VERIFIERZ_PURPOSE_TO_VERIFY, + 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'; +const HEX_KEY = 'f857544a9d1097e242ff0b287a7e6e90f19cf973efe2317f2a4678739664420f'; +const DID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0'; +const KID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0#keys-1'; + +const alltargets = [PropertyTarget.AUTHORIZATION_REQUEST, PropertyTarget.REQUEST_OBJECT]; + +describe('RP OPBuilder should', () => { + /*it('throw Error when no arguments are passed', async () => { + expect.assertions(1); + await expect(() => new OPBuilder().build()).toThrowError(Error); + });*/ + + it('build an RP when all arguments are set', async () => { + expect.assertions(1); + + 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'))) + .withRedirectUri('https://redirect.me') + .withRequestBy(PassBy.VALUE) + .withResponseMode(ResponseMode.POST) + .withClientMetadata({ + passBy: PassBy.REFERENCE, + reference_uri: 'https://registration.here', + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100339', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }) + + .withInternalSignature('myprivatekye', 'did:example:123', 'did:example:123#key', SigningAlgo.ES256K) + .withSupportedVersions(SupportedVersion.SIOPv2_ID1) + .build(), + ).toBeInstanceOf(RP); + }); +}); + +describe('RP should', () => { + it('throw Error when build from request opts without enough params', async () => { + expect.assertions(1); + await expect(() => RP.fromRequestOpts({} as never)).toThrowError(Error); + }); + it('return an RP when all request arguments are set', async () => { + expect.assertions(1); + + const opts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + payload: { + client_id: 'test', + scope: 'test', + response_type: 'test', + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + alg: SigningAlgo.ES256K, + }, + }, + clientMetadata: { + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + subject_syntax_types_supported: ['did:ethr', SubjectIdentifierType.DID], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + jwt_vc: { alg: [SigningAlgo.EDDSA, SigningAlgo.ES256K, SigningAlgo.ES256] }, + jwt_vp: { alg: [SigningAlgo.EDDSA, SigningAlgo.ES256K, SigningAlgo.ES256] }, + jwt: { alg: [SigningAlgo.EDDSA, SigningAlgo.ES256K, SigningAlgo.ES256] }, + }, + + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT, + clientName: VERIFIER_NAME_FOR_CLIENT, + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '202210040', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, + }, + }; + + expect(RP.fromRequestOpts(opts)).toBeInstanceOf(RP); + }); + + it('succeed from request opts when all params are set', async () => { + // expect.assertions(1); + const opts: CreateAuthorizationRequestOpts = { + version: SupportedVersion.SIOPv2_ID1, + payload: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + scope: 'openid', + response_type: 'id_token', + response_mode: ResponseMode.POST, + redirect_uri: EXAMPLE_REDIRECT_URL, + }, + requestObject: { + passBy: PassBy.REFERENCE, + reference_uri: EXAMPLE_REFERENCE_URL, + + signature: { + hexPrivateKey: HEX_KEY, + did: DID, + kid: KID, + alg: SigningAlgo.ES256K, + }, + }, + clientMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + subject_syntax_types_supported: ['did:ethr', SubjectIdentifierType.DID], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + vpFormatsSupported: { + jwt_vc: { alg: [SigningAlgo.EDDSA, SigningAlgo.ES256K, SigningAlgo.ES256] }, + jwt_vp: { alg: [SigningAlgo.EDDSA, SigningAlgo.ES256K, SigningAlgo.ES256] }, + jwt: { alg: [SigningAlgo.EDDSA, SigningAlgo.ES256K, SigningAlgo.ES256] }, + ldp_vc: { proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019] }, + }, + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT + ' 2022-09-29 00', + clientName: VERIFIER_NAME_FOR_CLIENT + ' 2022-09-29 00', + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + ' 2022-09-29 00', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY + ' 2022-09-29 00', + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL + ' 2022-09-29 00', + }, + }; + + const expectedPayloadWithoutRequest = { + response_type: 'id_token', + scope: 'openid', + client_id: WELL_KNOWN_OPENID_FEDERATION, + redirect_uri: 'https://acme.com/hello', + // nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + /* registration: { + id_token_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + response_types_supported: [ResponseType.ID_TOKEN], + scopes_supported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subject_syntax_types_supported: ['did:ethr', 'did'], + subject_types_supported: [SubjectType.PAIRWISE], + vp_formats: { + jwt: { + alg: ['EdDSA', 'ES256K', 'ES256'], + }, + jwt_vc: { + alg: ['EdDSA', 'ES256K', 'ES256'], + }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT + ' 2022-09-29 00', + client_name: VERIFIER_NAME_FOR_CLIENT + ' 2022-09-29 00', + 'client_name#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + ' 2022-09-29 00', + client_purpose: VERIFIERZ_PURPOSE_TO_VERIFY + ' 2022-09-29 00', + 'client_purpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL + ' 2022-09-29 00', + },*/ + }; + + const expectedUri = + 'openid://?client_id=https%3A%2F%2Fwww.example.com%2F.well-known%2Fopenid-federation&scope=openid&response_type=id_token&response_mode=post&redirect_uri=https%3A%2F%2Facme.com%2Fhello&request_uri=https%3A%2F%2Frp.acme.com%2Fsiop%2Fjwts'; + const expectedJwtRegex = + /^eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZXRocjoweDAxMDZhMmU5ODViMUUxRGU5QjVkZGI0YUY2ZEM5ZTkyOEY0ZTk5RDAja2V5cy0xIiwidHlwIjoiSldUIn0\.ey.*$/; + + const request = await RP.fromRequestOpts(opts).createAuthorizationRequestURI({ + correlationId: '1234', + state: 'b32f0087fc9816eb813fd11f', + nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + }); + expect(request.authorizationRequestPayload).toMatchObject(expectedPayloadWithoutRequest); + expect(request.encodedUri).toMatch(expectedUri); + expect(request.requestObjectJwt).toMatch(expectedJwtRegex); + }); + + it('succeed from builder when all params are set', async () => { + const expectedPayloadWithoutRequest = { + client_id: WELL_KNOWN_OPENID_FEDERATION, + // nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + redirect_uri: 'https://acme.com/hello', + registration: { + id_token_signing_alg_values_supported: [SigningAlgo.EDDSA], + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + response_types_supported: [ResponseType.ID_TOKEN], + scopes_supported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subject_syntax_types_supported: ['did:ethr'], + subject_types_supported: [SubjectType.PAIRWISE], + vp_formats: { + jwt: { + alg: ['EdDSA', 'ES256K', 'ES256'], + }, + jwt_vc: { + alg: ['EdDSA', 'ES256K', 'ES256'], + }, + jwt_vp: { + alg: ['EdDSA', 'ES256K', 'ES256'], + }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT + ' 2022-09-29 01', + client_name: VERIFIER_NAME_FOR_CLIENT + ' 2022-09-29 01', + 'client_name#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + ' 2022-09-29 01', + client_purpose: VERIFIERZ_PURPOSE_TO_VERIFY + ' 2022-09-29 01', + 'client_purpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL + ' 2022-09-29 01', + }, + }; + + const expectedUri = + 'openid://?client_id=https%3A%2F%2Fwww.example.com%2F.well-known%2Fopenid-federation&scope=test&response_type=id_token&redirect_uri=https%3A%2F%2Facme.com%2Fhello®istration=%7B%22id_token_signing_alg_values_supported%22%3A%5B%22EdDSA%22%5D%2C%22request_object_signing_alg_values_supported%22%3A%5B%22EdDSA%22%2C%22ES256%22%5D%2C%22response_types_supported%22%3A%5B%22id_token%22%5D%2C%22scopes_supported%22%3A%5B%22openid%20did_authn%22%2C%22openid%22%5D%2C%22subject_types_supported%22%3A%5B%22pairwise%22%5D%2C%22subject_syntax_types_supported%22%3A%5B%22did%3Aethr%22%5D%2C%22vp_formats%22%3A%7B%22jwt%22%3A%7B%22alg%22%3A%5B%22EdDSA%22%2C%22ES256K%22%2C%22ES256%22%5D%7D%2C%22jwt_vc%22%3A%7B%22alg%22%3A%5B%22EdDSA%22%2C%22ES256K%22%2C%22ES256%22%5D%7D%2C%22jwt_vp%22%3A%7B%22alg%22%3A%5B%22EdDSA%22%2C%22ES256K%22%2C%22ES256%22%5D%7D%7D%2C%22client_name%22%3A%22Client%20Verifier%20Relying%20Party%20Sphereon%20INC%202022-09-29%2001%22%2C%22logo_uri%22%3A%22https%3A%2F%2Fsphereon.com%2Fcontent%2Fthemes%2Fsphereon%2Fassets%2Ffavicons%2Fsafari-pinned-tab.svg%202022-09-29%2001%22%2C%22client_purpose%22%3A%22To%20request%2C%20receive%20and%20verify%20your%20credential%20about%20the%20the%20valid%20subject.%202022-09-29%2001%22%2C%22client_id%22%3A%22https%3A%2F%2Fwww.example.com%2F.well-known%2Fopenid-federation%22%2C%22client_name%23nl-NL%22%3A%22%20***%20dutch%20***%20Client%20Verifier%20Relying%20Party%20Sphereon%20B.V.%202022-09-29%2001%22%2C%22client_purpose%23nl-NL%22%3A%22%20***%20Dutch%20***%20To%20request%2C%20receive%20and%20verify%20your%20credential%20about%20the%20the%20valid%20subject.%202022-09-29%2001%22%7D&request_uri=https%3A%2F%2Frp.acme.com%2Fsiop%2Fjwts'; + + const expectedJwtRegex = + /^eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZXRocjoweDAxMDZhMmU5ODViMUUxRGU5QjVkZGI0YUY2ZEM5ZTkyOEY0ZTk5RDAja2V5cy0xIiwidHlwIjoiSldUIn0\.eyJpYXQiO.*$/; + + const request = await RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) + .withClientId(WELL_KNOWN_OPENID_FEDERATION, alltargets) + .withScope('test', alltargets) + .withResponseType(ResponseType.ID_TOKEN, alltargets) + .withCheckLinkedDomain(CheckLinkedDomain.NEVER) + .withRedirectUri(EXAMPLE_REDIRECT_URL, alltargets) + .withRequestBy(PassBy.REFERENCE, EXAMPLE_REFERENCE_URL) + .withInternalSignature(HEX_KEY, DID, KID, SigningAlgo.ES256K) + .withClientMetadata( + { + client_id: WELL_KNOWN_OPENID_FEDERATION, + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], + responseTypesSupported: [ResponseType.ID_TOKEN], + vpFormatsSupported: { + jwt: { + alg: ['EdDSA', 'ES256K', 'ES256'], + }, + jwt_vc: { + alg: ['EdDSA', 'ES256K', 'ES256'], + }, + jwt_vp: { + alg: ['EdDSA', 'ES256K', 'ES256'], + }, + }, + scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: [], + passBy: PassBy.VALUE, + logo_uri: VERIFIER_LOGO_FOR_CLIENT + ' 2022-09-29 01', + clientName: VERIFIER_NAME_FOR_CLIENT + ' 2022-09-29 01', + 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + ' 2022-09-29 01', + clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY + ' 2022-09-29 01', + 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL + ' 2022-09-29 01', + }, + alltargets, + ) + .addDidMethod('did:ethr') + .withSupportedVersions([SupportedVersion.SIOPv2_D11]) + .build() + + .createAuthorizationRequestURI({ + correlationId: '1234', + state: 'b32f0087fc9816eb813fd11f', + nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + }); + expect(request.authorizationRequestPayload).toMatchObject(expectedPayloadWithoutRequest); + expect(request.encodedUri).toMatch(expectedUri); + expect(request.requestObjectJwt).toMatch(expectedJwtRegex); + }); +}); diff --git a/packages/siopv2/test/SdJwt.spec.ts b/packages/siopv2/test/SdJwt.spec.ts new file mode 100644 index 00000000..3e571cf0 --- /dev/null +++ b/packages/siopv2/test/SdJwt.spec.ts @@ -0,0 +1,228 @@ +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, + PassBy, + PresentationDefinitionWithLocation, + PresentationExchange, + PresentationSignCallback, + PresentationVerificationCallback, + PropertyTarget, + ResponseIss, + ResponseType, + RevocationVerification, + RP, + Scope, + SigningAlgo, + SubjectType, + SupportedVersion, + VPTokenLocation, +} from '../src'; + +import { WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'; +import { + VERIFIER_LOGO_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT_NL, + VERIFIERZ_PURPOSE_TO_VERIFY, + VERIFIERZ_PURPOSE_TO_VERIFY_NL, +} from './data/mockedData'; + +const hasher = (data: string) => createHash('sha256').update(data).digest(); +jest.setTimeout(30000); + +const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello'; + +const HOLDER_DID = 'did:example:ebfeb1f712ebc6f1c276e12ec21'; +const SD_JWT_VC = + 'eyJhbGciOiJFZERTQSIsInR5cCI6InZjK3NkLWp3dCJ9.eyJpYXQiOjE3MDA0NjQ3MzYwNzYsImlzcyI6ImRpZDprZXk6c29tZS1yYW5kb20tZGlkLWtleSIsIm5iZiI6MTcwMDQ2NDczNjE3NiwidmN0IjoiaHR0cHM6Ly9oaWdoLWFzc3VyYW5jZS5jb20vU3RhdGVCdXNpbmVzc0xpY2Vuc2UiLCJ1c2VyIjp7Il9zZCI6WyI5QmhOVDVsSG5QVmpqQUp3TnR0NDIzM216MFVVMUd3RmFmLWVNWkFQV0JNIiwiSVl5d1FQZl8tNE9hY2Z2S2l1cjRlSnFMa1ZleWRxcnQ1Y2UwMGJReWNNZyIsIlNoZWM2TUNLakIxeHlCVl91QUtvLURlS3ZvQllYbUdBd2VGTWFsd05xbUEiLCJXTXpiR3BZYmhZMkdoNU9pWTRHc2hRU1dQREtSeGVPZndaNEhaQW5YS1RZIiwiajZ6ZFg1OUJYZHlTNFFaTGJITWJ0MzJpenRzWXdkZzRjNkpzWUxNc3ZaMCIsInhKR3Radm41cFM4VEhqVFlJZ3MwS1N5VC1uR3BSR3hDVnp6c1ZEbmMyWkUiXX0sImxpY2Vuc2UiOnsibnVtYmVyIjoxMH0sImNuZiI6eyJqd2siOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VHZW1jIiwieSI6Ilp4amlXV2JaTVFHSFZXS1ZRNGhiU0lpcnNWZnVlY0NFNnQ0alQ5RjJIWlEifX0sIl9zZF9hbGciOiJzaGEtMjU2IiwiX3NkIjpbIl90YnpMeHBaeDBQVHVzV2hPOHRUZlVYU2ZzQjVlLUtrbzl3dmZaaFJrYVkiLCJ1WmNQaHdUTmN4LXpNQU1zemlYMkFfOXlJTGpQSEhobDhEd2pvVXJLVVdZIl19.HAcudVInhNpXkTPQGNosjKTFRJWgKj90NpfloRaDQchGd4zxc1ChWTCCPXzUXTBypASKrzgjZCiXlTr0bzmLAg~WyJHeDZHRUZvR2t6WUpWLVNRMWlDREdBIiwiZGF0ZU9mQmlydGgiLCIyMDAwMDEwMSJd~WyJ1LUt3cmJvMkZfTExQekdSZE1XLUtBIiwibmFtZSIsIkpvaG4iXQ~WyJNV1ZieGJqVFZxUXdLS3h2UGVZdWlnIiwibGFzdE5hbWUiLCJEb2UiXQ~'; + +const presentationSignCallback: PresentationSignCallback = async (_args) => { + const kbJwt = (_args.presentation as SdJwtDecodedVerifiableCredentialWithKbJwtInput).kbJwt; + + // In real life scenario, the KB-JWT must be signed + // As the KB-JWT is a normal JWT, the user does not need an sd-jwt implementation in the presentation sign callback + // NOTE: should the presentation just be the KB-JWT header + payload instead of the whole decoded SD JWT? + expect(kbJwt).toEqual({ + header: { + typ: 'kb+jwt', + }, + payload: { + _sd_hash: expect.any(String), + iat: expect.any(Number), + nonce: undefined, + }, + }); + + return SD_JWT_VC; +}; + +function getPresentationDefinition(): IPresentationDefinition { + return { + id: '32f54163-7166-48f1-93d8-ff217bdb0653', + name: 'Conference Entry Requirements', + purpose: 'We can only allow people associated with Washington State business representatives into conference areas', + format: { + 'vc+sd-jwt': {}, + }, + input_descriptors: [ + { + id: 'wa_driver_license', + name: 'Washington State Business License', + purpose: 'We can only allow licensed Washington State business representatives into the WA Business Conference', + constraints: { + limit_disclosure: 'required', + fields: [ + { + path: ['$.vct'], + filter: { + type: 'string', + const: 'https://high-assurance.com/StateBusinessLicense', + }, + }, + { + path: ['$.license.number'], + filter: { + type: 'number', + }, + }, + { + path: ['$.user.name'], + filter: { + type: 'string', + }, + }, + ], + }, + }, + ], + }; +} + +function getVCs(): OriginalVerifiableCredential[] { + return [SD_JWT_VC]; +} + +describe('RP and OP interaction should', () => { + it('succeed when calling with presentation definitions and right verifiable presentation', async () => { + 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', + }; + + // 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 rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) + .withClientId(rpMockEntity.did) + .withScope('test') + .withHasher(hasher) + .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) + .withHasher(hasher) + .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 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(); + + 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(), hasher }); + 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, + }, + }); + expect(authenticationResponseWithJWT.response.payload).toBeDefined(); + expect(authenticationResponseWithJWT.response.idToken).toBeDefined(); + + const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { + presentationDefinitions: [{ definition: pd[0].definition, location: pd[0].location }], + }); + + expect(verifiedAuthResponseWithJWT.idToken.jwt).toBeDefined(); + expect(verifiedAuthResponseWithJWT.idToken.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg'); + }); +}); diff --git a/packages/siopv2/test/TestUtils.ts b/packages/siopv2/test/TestUtils.ts new file mode 100644 index 00000000..e4d7b8d0 --- /dev/null +++ b/packages/siopv2/test/TestUtils.ts @@ -0,0 +1,283 @@ +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 moment from 'moment'; +import { v4 as uuidv4 } from 'uuid'; + +import { + assertValidMetadata, + base64ToHexString, + DiscoveryMetadataPayload, + KeyCurve, + KeyType, + ResponseIss, + ResponseType, + RPRegistrationMetadataPayload, + Scope, + SigningAlgo, + SubjectSyntaxTypesSupportedValues, + SubjectType, +} from '../src'; +import SIOPErrors from '../src/types/Errors'; + +import { + DID_DOCUMENT_PUBKEY_B58, + DID_DOCUMENT_PUBKEY_JWK, + VERIFIER_LOGO_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT, + VERIFIER_NAME_FOR_CLIENT_NL, + VERIFIERZ_PURPOSE_TO_VERIFY, + VERIFIERZ_PURPOSE_TO_VERIFY_NL, +} from './data/mockedData'; + +export interface TESTKEY { + key: JWK; + did: string; + didDoc?: DIDDocument; +} + +export async function generateTestKey(kty: string): Promise { + if (kty !== KeyType.EC) throw new Error(SIOPErrors.NO_ALG_SUPPORTED); + const key = crypto.generateKeyPairSync('ec', { + namedCurve: KeyCurve.SECP256k1, + }); + const privateJwk = await exportJWK(key.privateKey); + + const did = getDIDFromKey(privateJwk); + + return { + key: privateJwk, + did, + }; +} + +function getDIDFromKey(key: JWK): string { + return `did:ethr:${getEthAddress(key)}`; +} + +function getEthAddress(key: JWK): string { + return getEthWallet(key).address; +} + +function getEthWallet(key: JWK): ethers.Wallet { + return new ethers.Wallet(prefixWith0x(base64ToHexString(key.d))); +} + +export const prefixWith0x = (key: string): string => (key.startsWith('0x') ? key : `0x${key}`); + +export interface IEnterpriseAuthZToken extends JWTPayload { + sub?: string; + did: string; + aud: string; + nonce: string; +} + +export interface LegalEntityTestAuthN { + iss: string; // legal entity name identifier + aud: string; // RP Application Name. + iat: number; + exp: number; + nonce: string; + callbackUrl?: string; // Entity url to send notifications + image?: string; // base64 encoded image data + icon?: string; // base64 encoded image icon data +} + +export const mockedKeyAndDid = async (): Promise<{ + hexPrivateKey: string; + did: string; + jwk: JWK; + hexPublicKey: string; +}> => { + // generate a new keypair + const key = crypto.generateKeyPairSync('ec', { + namedCurve: KeyCurve.SECP256k1, + }); + const privateJwk = await exportJWK(key.privateKey); + const hexPrivateKey = base64ToHexString(privateJwk.d); + const wallet: ethers.Wallet = new ethers.Wallet(prefixWith0x(hexPrivateKey)); + const did = `did:ethr:${wallet.address}`; + const hexPublicKey = wallet.signingKey.publicKey; + + return { + hexPrivateKey, + did, + jwk: privateJwk, + hexPublicKey, + }; +}; + +const mockedEntityAuthNToken = async ( + enterpiseName?: string, +): Promise<{ + jwt: string; + jwk: JWK; + did: string; + hexPrivateKey: string; + hexPublicKey: string; +}> => { + // generate a new keypair + const { did, jwk, hexPrivateKey, hexPublicKey } = await mockedKeyAndDid(); + + const payload: LegalEntityTestAuthN = { + iss: enterpiseName || 'Test Entity', + aud: 'test', + iat: moment().unix(), + exp: moment().add(15, 'minutes').unix(), + nonce: uuidv4(), + }; + + const privateKey = await importJWK(jwk, SigningAlgo.ES256K); + const jwt = await new SignJWT(payload as unknown as JWTPayload) + .setProtectedHeader({ + alg: 'ES256K', + typ: 'JWT', + }) + .sign(privateKey); + return { jwt, jwk, did, hexPrivateKey, hexPublicKey }; +}; + +export async function mockedGetEnterpriseAuthToken(enterpriseName?: string): Promise<{ + jwt: string; + did: string; + jwk: JWK; + hexPrivateKey: string; + hexPublicKey: string; +}> { + const testAuth = await mockedEntityAuthNToken(enterpriseName); + const payload = jwt_decode(testAuth.jwt); + + const inputPayload: IEnterpriseAuthZToken = { + did: testAuth.did, + + 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 + iat: moment().unix(), + exp: moment().add(15, 'minutes').unix(), + aud: 'test', + }, + }; + + const privateKey = await importJWK(testAuth.jwk, SigningAlgo.ES256K); + const jwt = await new SignJWT(testApiPayload) + .setProtectedHeader({ + alg: 'ES256K', + typ: 'JWT', + }) + .sign(privateKey); + + return { + jwt, + did: testAuth.did, + jwk: testAuth.jwk, + hexPrivateKey: testAuth.hexPrivateKey, + hexPublicKey: testAuth.hexPublicKey, + }; +} + +export interface DidKey { + did: string; + publicKeyHex?: string; + jwk?: JWK; +} + +interface FixJwk extends JWK { + kty: string; +} + +export const getParsedDidDocument = (didKey: DidKey): DIDDocument => { + if (didKey.publicKeyHex) { + const didDocB58 = DID_DOCUMENT_PUBKEY_B58; + didDocB58.id = didKey.did; + didDocB58.controller = didKey.did; + didDocB58.verificationMethod[0].id = `${didKey.did}#keys-1`; + didDocB58.verificationMethod[0].controller = didKey.did; + didDocB58.verificationMethod[0].publicKeyBase58 = base58.encode(Buffer.from(didKey.publicKeyHex.replace('0x', ''), 'hex')); + return didDocB58; + } + // then didKey jws public key + const didDocJwk = DID_DOCUMENT_PUBKEY_JWK; + const { jwk } = didKey; + jwk.kty = didKey.jwk.kty || 'EC'; + didDocJwk.id = didKey.did; + didDocJwk.controller = didKey.did; + didDocJwk.verificationMethod[0].id = `${didKey.did}#keys-1`; + didDocJwk.verificationMethod[0].controller = didKey.did; + didDocJwk.verificationMethod[0].publicKeyJwk = jwk as FixJwk; + 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; + rpMetadata: RPRegistrationMetadataPayload; + verify(): unknown; +} = { + opMetadata: { + issuer: ResponseIss.SELF_ISSUED_V2, + authorization_endpoint: 'http://test.com', + subject_syntax_types_supported: ['did:web'], + id_token_signing_alg_values_supported: undefined, + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA], + response_types_supported: ResponseType.ID_TOKEN, + scopes_supported: [Scope.OPENID_DIDAUTHN], + subject_types_supported: [SubjectType.PAIRWISE], + vp_formats: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + jwt_vc: { + alg: [SigningAlgo.ES256, SigningAlgo.ES256K], + }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT + ' 2022-09-29 02', + client_name: VERIFIER_NAME_FOR_CLIENT + ' 2022-09-29 02', + 'client_name#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + ' 2022-09-29 02', + client_purpose: VERIFIERZ_PURPOSE_TO_VERIFY + ' 2022-09-29 02', + 'client_purpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL + ' 2022-09-29 02', + }, + rpMetadata: { + client_id: WELL_KNOWN_OPENID_FEDERATION, + id_token_signing_alg_values_supported: [], + request_object_signing_alg_values_supported: [SigningAlgo.EDDSA], + response_types_supported: [ResponseType.ID_TOKEN], + scopes_supported: [Scope.OPENID, Scope.OPENID_DIDAUTHN], + subject_syntax_types_supported: [SubjectSyntaxTypesSupportedValues.DID.valueOf(), 'did:web', 'did:key'], + subject_types_supported: [SubjectType.PAIRWISE], + vp_formats: { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + jwt_vc: { + alg: [SigningAlgo.ES256, SigningAlgo.ES256K], + }, + }, + logo_uri: VERIFIER_LOGO_FOR_CLIENT + ' 2022-09-29 03', + client_name: VERIFIER_NAME_FOR_CLIENT + ' 2022-09-29 03', + 'client_name#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + ' 2022-09-29 03', + client_purpose: VERIFIERZ_PURPOSE_TO_VERIFY + ' 2022-09-29 03', + 'client_purpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL + ' 2022-09-29 03', + }, + verify() { + return assertValidMetadata(this.opMetadata, this.rpMetadata); + }, +}; diff --git a/packages/siopv2/test/data/mockedData.ts b/packages/siopv2/test/data/mockedData.ts new file mode 100644 index 00000000..173eee0d --- /dev/null +++ b/packages/siopv2/test/data/mockedData.ts @@ -0,0 +1,147 @@ +import { ServiceTypesEnum } from '@sphereon/wellknown-dids-client'; +import { JWTHeader } from 'did-jwt'; +import { DIDDocument } from 'did-resolver'; + +export const UNIT_TEST_TIMEOUT = 90000; + +export const DIDAUTH_HEADER: JWTHeader = { + typ: 'JWT', + alg: 'ES256K', + kid: 'did:ethr:0x416e6e6162656c2e4c65652e452d412d506f652e#key1', +}; + +export const VERIFIER_LOGO_FOR_CLIENT = 'https://sphereon.com/content/themes/sphereon/assets/favicons/safari-pinned-tab.svg'; + +export const VERIFIER_NAME_FOR_CLIENT = 'Client Verifier Relying Party Sphereon INC'; +export const VERIFIER_NAME_FOR_CLIENT_NL = ' *** dutch *** Client Verifier Relying Party Sphereon B.V.'; + +export const VERIFIERZ_PURPOSE_TO_VERIFY = 'To request, receive and verify your credential about the the valid subject.'; +export const VERIFIERZ_PURPOSE_TO_VERIFY_NL = ' *** Dutch *** To request, receive and verify your credential about the the valid subject.'; + +export const DID_DOCUMENT_PUBKEY_B58: DIDDocument = { + assertionMethod: [], + capabilityDelegation: [], + capabilityInvocation: [], + keyAgreement: [], + '@context': 'https://w3id.org/did/v1', + id: 'did:ethr:0xE3f80bcbb360F04865AfA795B7507d384154216C', + controller: 'did:ethr:0xE3f80bcbb360F04865AfA795B7507d384154216C', + authentication: ['did:ethr:0xE3f80bcbb360F04865AfA795B7507d384154216C#key-1'], + verificationMethod: [ + { + id: 'did:ethr:0xE3f80bcbb360F04865AfA795B7507d384154216C#key-1', + type: 'EcdsaSecp256k1VerificationKey2019', + controller: 'did:ethr:0xE3f80bcbb360F04865AfA795B7507d384154216C', + publicKeyBase58: 'PSPfR29Snu5yxJcLHf2t6SyJ9mttet19ECkDHr4HY3FD5YC8ZenjvspPSAGSpaQ8B8kXADV97WSd7JqaNAUTn8YG', + }, + ], +}; + +export const DID_DOCUMENT_PUBKEY_JWK: DIDDocument = { + assertionMethod: [], + capabilityDelegation: [], + capabilityInvocation: [], + keyAgreement: [], + '@context': 'https://w3id.org/did/v1', + id: 'did:ethr:0x96e9A346905a8F8D5ee0e6BA5D13456965e74513', + controller: 'did:ethr:0x96e9A346905a8F8D5ee0e6BA5D13456965e74513', + authentication: ['did:ethr:0x96e9A346905a8F8D5ee0e6BA5D13456965e74513#JTa8+HgHPyId90xmMFw6KRD4YUYLosBuWJw33nAuRS0='], + verificationMethod: [ + { + id: 'did:ethr:0x96e9A346905a8F8D5ee0e6BA5D13456965e74513#JTa8+HgHPyId90xmMFw6KRD4YUYLosBuWJw33nAuRS0=', + type: 'EcdsaSecp256k1VerificationKey2019', + controller: 'did:ethr:0x96e9A346905a8F8D5ee0e6BA5D13456965e74513', + publicKeyJwk: { + kty: 'EC', + crv: 'secp256k1', + x: '62451c7a3e0c6e2276960834b79ae491ba0a366cd6a1dd814571212ffaeaaf5a', + y: '1ede3d754090437db67eca78c1659498c9cf275d2becc19cdc8f1ef76b9d8159', + kid: 'JTa8+HgHPyId90xmMFw6KRD4YUYLosBuWJw33nAuRS0=', + }, + }, + ], +}; + +export const DID_KEY = 'did:key:z6MktwS79rvBjzRX8a8PPiURqG7HMJAfACTiozFkPJeJHRxS'; + +export const DID_KEY_ORIGIN = 'https://example.com'; + +export const DID_KEY_DOCUMENT = { + '@context': [ + 'https://www.w3.org/ns/did/v1', + { + Ed25519VerificationKey2018: 'https://w3id.org/security#Ed25519VerificationKey2018', + publicKeyJwk: { + '@id': 'https://w3id.org/security#publicKeyJwk', + '@type': '@json', + }, + }, + ], + id: DID_KEY, + verificationMethod: [ + { + id: `${DID_KEY}#z6MktwS79rvBjzRX8a8PPiURqG7HMJAfACTiozFkPJeJHRxS`, + type: 'Ed25519VerificationKey2018', + controller: DID_KEY, + publicKeyJwk: { + kty: 'OKP', + crv: 'Ed25519', + x: '1ztBkC3x-8Eu8uPNTkTgH1Q0tkuO8v8RJDqfqWFl1N8', + }, + }, + ], + authentication: [`${DID_KEY}#z6MktwS79rvBjzRX8a8PPiURqG7HMJAfACTiozFkPJeJHRxS`], + assertionMethod: [`${DID_KEY}#z6MktwS79rvBjzRX8a8PPiURqG7HMJAfACTiozFkPJeJHRxS`], + service: [ + { + id: `${DID_KEY}#z6MktwS79rvBjzRX8a8PPiURqG7HMJAfACTiozFkPJeJHRxS`, + type: ServiceTypesEnum.LINKED_DOMAINS, + serviceEndpoint: DID_KEY_ORIGIN, + }, + ], +}; + +export const VC_KEY_PAIR = { + type: 'Ed25519VerificationKey2020', + id: `${DID_KEY}#z6MktwS79rvBjzRX8a8PPiURqG7HMJAfACTiozFkPJeJHRxS`, + controller: `${DID_KEY_ORIGIN}/1234`, + publicKeyMultibase: 'z6MktwS79rvBjzRX8a8PPiURqG7HMJAfACTiozFkPJeJHRxS', + privateKeyMultibase: 'zrv4UTisGEUxoZr1enXeC7NMVapzq48KkS1rLSpBvpTyg1v3cLo7g5SnprD1eD4bdKdYHHMu5feATzatSAkbhgXgtZU', +}; + +export const DID_ION = + 'did:ion:EiCMvVdXv6iL3W8i4n-LmqUhE614kX4TYxVR5kTY2QGOjg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXkxIiwicHVibGljS2V5SndrIjp7ImNydiI6InNlY3AyNTZrMSIsImt0eSI6IkVDIiwieCI6Ii1MbHNpQVk5b3JmMXpKQlJOV0NuN0RpNUpoYl8tY2xhNlY5R3pHa3FmSFUiLCJ5IjoiRXBIU25GZHQ2ZU5lRkJEZzNVNVFIVDE0TVRsNHZIc0h5NWRpWU9DWEs1TSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiIsImFzc2VydGlvbk1ldGhvZCJdLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFWZXJpZmljYXRpb25LZXkyMDE5In1dLCJzZXJ2aWNlcyI6W3siaWQiOiJsZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHBzOi8vbGR0ZXN0LnNwaGVyZW9uLmNvbSIsInR5cGUiOiJMaW5rZWREb21haW5zIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlBem8wTVVZUW5HNWM0VFJKZVFsNFR5WVRrSmRyeTJoeXlQUlpENzdFQm1CdyJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQUwtaEtrLUVsODNsRVJiZkFDUk1kSWNQVjRXWGJqZ3dsZ1ZDWTNwbDhhMGciLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUItT2NSbTlTNXdhU3QxbU4zSG4zM2RnMzJKN25MOEdBVHpGQ2ZXaWdIXzh3In19'; + +export const DID_ION_ORIGIN = 'https://ldtest.sphereon.com'; + +export const DID_ION_DOCUMENT = { + id: DID_ION, + '@context': [ + 'https://www.w3.org/ns/did/v1', + { + '@base': DID_ION, + }, + ], + service: [ + { + id: '#ld', + type: 'LinkedDomains', + serviceEndpoint: DID_ION_ORIGIN, + }, + ], + verificationMethod: [ + { + id: '#key1', + controller: DID_ION, + type: 'EcdsaSecp256k1VerificationKey2019', + publicKeyJwk: { + kty: 'EC', + crv: 'secp256k1', + x: '-LlsiAY9orf1zJBRNWCn7Di5Jhb_-cla6V9GzGkqfHU', + y: 'EpHSnFdt6eNeFBDg3U5QHT14MTl4vHsHy5diYOCXK5M', + }, + }, + ], + authentication: ['#key1'], + assertionMethod: ['#key1'], +}; diff --git a/packages/siopv2/test/e2e/mattr.launchpad.spec.ts b/packages/siopv2/test/e2e/mattr.launchpad.spec.ts new file mode 100644 index 00000000..ded7ab68 --- /dev/null +++ b/packages/siopv2/test/e2e/mattr.launchpad.spec.ts @@ -0,0 +1,252 @@ +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'; + +export interface InitiateOfferRequest { + types: string[]; +} + +export interface InitiateOfferResponse { + authorizeRequestUri: string; + state: string; + nonce: string; +} + +export const UNIT_TEST_TIMEOUT = 30000; + +export const VP_CREATE_URL = 'https://launchpad.mattrlabs.com/api/vp/create'; + +export const OPENBADGE_JWT_VC = + 'eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDp3ZWI6bGF1bmNocGFkLnZpaS5lbGVjdHJvbi5tYXR0cmxhYnMuaW8jNkJoRk1DR1RKZyJ9.eyJpc3MiOiJkaWQ6d2ViOmxhdW5jaHBhZC52aWkuZWxlY3Ryb24ubWF0dHJsYWJzLmlvIiwic3ViIjoiZGlkOmtleTp6Nk1raXRHVmduTGRORlpqbUE5WEpwQThrM29lakVudU1GN205NkJEN3BaTGprWTIiLCJuYmYiOjE2OTYzNjA1MTEsImV4cCI6MTcyNzk4MjkxMSwidmMiOnsibmFtZSI6IkV4YW1wbGUgVW5pdmVyc2l0eSBEZWdyZWUiLCJkZXNjcmlwdGlvbiI6IkpGRiBQbHVnZmVzdCAzIE9wZW5CYWRnZSBDcmVkZW50aWFsIiwiY3JlZGVudGlhbEJyYW5kaW5nIjp7ImJhY2tncm91bmRDb2xvciI6IiM0NjRjNDkifSwiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL21hdHRyLmdsb2JhbC9jb250ZXh0cy92Yy1leHRlbnNpb25zL3YyIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQtMy4wLjIuanNvbiIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvb2IvdjNwMC9leHRlbnNpb25zLmpzb24iLCJodHRwczovL3czaWQub3JnL3ZjLXJldm9jYXRpb24tbGlzdC0yMDIwL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJPcGVuQmFkZ2VDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1raXRHVmduTGRORlpqbUE5WEpwQThrM29lakVudU1GN205NkJEN3BaTGprWTIiLCJ0eXBlIjpbIkFjaGlldmVtZW50U3ViamVjdCJdLCJhY2hpZXZlbWVudCI6eyJpZCI6Imh0dHBzOi8vZXhhbXBsZS5jb20vYWNoaWV2ZW1lbnRzLzIxc3QtY2VudHVyeS1za2lsbHMvdGVhbXdvcmsiLCJuYW1lIjoiVGVhbXdvcmsiLCJ0eXBlIjpbIkFjaGlldmVtZW50Il0sImltYWdlIjp7ImlkIjoiaHR0cHM6Ly93M2MtY2NnLmdpdGh1Yi5pby92Yy1lZC9wbHVnZmVzdC0zLTIwMjMvaW1hZ2VzL0pGRi1WQy1FRFUtUExVR0ZFU1QzLWJhZGdlLWltYWdlLnBuZyIsInR5cGUiOiJJbWFnZSJ9LCJjcml0ZXJpYSI6eyJuYXJyYXRpdmUiOiJUZWFtIG1lbWJlcnMgYXJlIG5vbWluYXRlZCBmb3IgdGhpcyBiYWRnZSBieSB0aGVpciBwZWVycyBhbmQgcmVjb2duaXplZCB1cG9uIHJldmlldyBieSBFeGFtcGxlIENvcnAgbWFuYWdlbWVudC4ifSwiZGVzY3JpcHRpb24iOiJUaGlzIGJhZGdlIHJlY29nbml6ZXMgdGhlIGRldmVsb3BtZW50IG9mIHRoZSBjYXBhY2l0eSB0byBjb2xsYWJvcmF0ZSB3aXRoaW4gYSBncm91cCBlbnZpcm9ubWVudC4ifX0sImlzc3VlciI6eyJpZCI6ImRpZDp3ZWI6bGF1bmNocGFkLnZpaS5lbGVjdHJvbi5tYXR0cmxhYnMuaW8iLCJuYW1lIjoiRXhhbXBsZSBVbml2ZXJzaXR5IiwiaWNvblVybCI6Imh0dHBzOi8vdzNjLWNjZy5naXRodWIuaW8vdmMtZWQvcGx1Z2Zlc3QtMS0yMDIyL2ltYWdlcy9KRkZfTG9nb0xvY2t1cC5wbmciLCJpbWFnZSI6Imh0dHBzOi8vdzNjLWNjZy5naXRodWIuaW8vdmMtZWQvcGx1Z2Zlc3QtMS0yMDIyL2ltYWdlcy9KRkZfTG9nb0xvY2t1cC5wbmcifX19.JDQ5kp_nvqJbL9Q8o2xIdt_r_WG0cB1o-Boy1RiDZhXRlVTgwAxvCa41OiL97VnbovN98tL7VtXbM6slAt6TBg'; + +export const jwk: JWK = { + crv: 'Ed25519', + d: 'kTRm0aONHYwNPA-w_DtjMHUIWjE3K70qgCIhWojZ0eU', + x: 'NeA0d8sp86xRh3DczU4m5wPNIbl0HCSwOBcMN3sNmdk', + kty: 'OKP', +}; + +// pub hex: 35e03477cb29f3ac518770dccd4e26e703cd21b9741c24b038170c377b0d99d9 +const hexPrivateKey = '913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5'; + +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( + { + secureRandom: () => { + return opts?.seed ?? '913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5'; + }, + }, + { accept: 'application/did+json' }, + ); + + return { keys, didDocument }; +}; + +const resolve = async (didUrl: string, options?: DIDResolutionOptions): Promise => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return await didKeyResolve(didUrl, options); +}; + +describe('OID4VCI-Client using Mattr issuer should', () => { + async function testWithOp(format: string | string[]) { + const did = await generateDid({ seed: u8a.fromString(hexPrivateKey, 'base16') }); + expect(did).toBeDefined(); + expect(did.didDocument).toBeDefined(); + + const offer = await getOffer(format); + const { authorizeRequestUri, state, nonce } = offer; + expect(authorizeRequestUri).toBeDefined(); + expect(state).toBeDefined(); + expect(nonce).toBeDefined(); + + const correlationId = 'test'; + + const op: OP = OP.builder() + .addResolver('key', { resolve }) + .withCheckLinkedDomain(CheckLinkedDomain.NEVER) + .withPresentationSignCallback(presentationSignCalback) + .withSignature({ alg: SigningAlgo.EDDSA, kid, did: didStr, hexPrivateKey }) + .build(); + + const verifiedAuthRequest = await op.verifyAuthorizationRequest(authorizeRequestUri, { correlationId }); + expect(verifiedAuthRequest).toBeDefined(); + expect(verifiedAuthRequest.presentationDefinitions).toHaveLength(1); + + const pex = new PresentationExchange({ allDIDs: [didStr], allVerifiableCredentials: [OPENBADGE_JWT_VC] }); + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( + verifiedAuthRequest.authorizationRequestPayload, + ); + await pex.selectVerifiableCredentialsForSubmission(pd[0].definition); + const verifiablePresentationResult = await pex.createVerifiablePresentation(pd[0].definition, [OPENBADGE_JWT_VC], presentationSignCalback, { + presentationSubmissionLocation: PresentationSubmissionLocation.EXTERNAL, + proofOptions: { nonce }, + holderDID: didStr, + }); + + const authResponse = await op.createAuthorizationResponse(verifiedAuthRequest, { + issuer: didStr, + presentationExchange: { + verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], + presentationSubmission: verifiablePresentationResult.presentationSubmission, + }, + correlationId, + }); + + 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); + } + + async function testWithPayloads(format: string | string[]) { + const did = await generateDid({ seed: u8a.fromString(hexPrivateKey, 'base16') }); + expect(did).toBeDefined(); + expect(did.didDocument).toBeDefined(); + + const offer = await getOffer(format); + const { authorizeRequestUri, state, nonce } = offer; + expect(authorizeRequestUri).toBeDefined(); + expect(state).toBeDefined(); + expect(nonce).toBeDefined(); + + const correlationId = 'test'; + + const verifiedAuthRequest = await AuthorizationRequest.verify(authorizeRequestUri, { + correlationId, + verification: { + mode: VerificationMode.INTERNAL, + resolveOpts: {}, + }, + }); + expect(verifiedAuthRequest).toBeDefined(); + expect(verifiedAuthRequest.presentationDefinitions).toHaveLength(1); + + const pex = new PresentationExchange({ allDIDs: [didStr], allVerifiableCredentials: [OPENBADGE_JWT_VC] }); + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( + verifiedAuthRequest.authorizationRequestPayload, + ); + await pex.selectVerifiableCredentialsForSubmission(pd[0].definition); + const verifiablePresentationResult = await pex.createVerifiablePresentation(pd[0].definition, [OPENBADGE_JWT_VC], presentationSignCalback, { + presentationSubmissionLocation: PresentationSubmissionLocation.EXTERNAL, + proofOptions: { nonce }, + holderDID: didStr, + }); + + const authResponse = await AuthorizationResponse.fromVerifiedAuthorizationRequest( + verifiedAuthRequest, + { + presentationExchange: { + verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], + presentationSubmission: verifiablePresentationResult.presentationSubmission, + }, + signature: { hexPrivateKey: '913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5', did: didStr, kid, alg: SigningAlgo.EDDSA }, + }, + { 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( + 'succeed using OpenID4VCI version 11 and ldp_vc request/responses', + async () => { + await testWithPayloads('OpenBadgeCredential'); + }, + UNIT_TEST_TIMEOUT, + ); + it( + 'succeed in a full flow with the client using OpenID4VCI version 11 and jwt_vc_json', + async () => { + await testWithOp('OpenBadgeCredential'); + }, + UNIT_TEST_TIMEOUT, + ); +}); + +async function getOffer(types: string | string[]): Promise { + const credentialOffer = await fetch(VP_CREATE_URL, { + method: 'post', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + + //make sure to serialize your JSON body + body: JSON.stringify({ + types: Array.isArray(types) ? types : [types], + }), + }); + + return (await credentialOffer.json()) as InitiateOfferResponse; +} + +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({ + correlationId: 'test', + verification: { + mode: VerificationMode.INTERNAL, + resolveOpts: {}, + }, + }); + + console.log(JSON.stringify(verification)); + expect(verification).toBeDefined(); + expect(verification.versions).toEqual([SupportedVersion.SIOPv2_D12_OID4VP_D18]); + + /** + * pd value: {"id":"dae5d9b6-8145-4297-99b2-b8fcc5abb5ad","input_descriptors":[{"id":"OpenBadgeCredential","format":{"jwt_vc_json":{"alg":["EdDSA"]},"jwt_vc":{"alg":["EdDSA"]}},"constraints":{"fields":[{"path":["$.vc.type"],"filter":{"type":"array","items":{"type":"string"},"contains":{"const":"OpenBadgeCredential"}}}]}}]} + */ + }); +}); + +async function presentationSignCalback(args: PresentationSignCallBackParams): Promise { + const importedJwk = await importJWK(jwk, 'EdDSA'); + const jwt = await new SignJWT({ vp: { ...args.presentation }, nonce: args.options.proofOptions.nonce, iss: args.options.holderDID }) + .setProtectedHeader({ + typ: 'JWT', + alg: 'EdDSA', + kid, + }) + .setIssuedAt() + .setExpirationTime('2h') + .sign(importedJwk); + + console.log(`VP: ${jwt}`); + return jwt; +} diff --git a/packages/siopv2/test/functions/DidJWT.spec.ts b/packages/siopv2/test/functions/DidJWT.spec.ts new file mode 100644 index 00000000..400e04f2 --- /dev/null +++ b/packages/siopv2/test/functions/DidJWT.spec.ts @@ -0,0 +1,134 @@ +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/packages/siopv2/test/functions/DidSiopMetadata.spec.ts b/packages/siopv2/test/functions/DidSiopMetadata.spec.ts new file mode 100644 index 00000000..9d07a960 --- /dev/null +++ b/packages/siopv2/test/functions/DidSiopMetadata.spec.ts @@ -0,0 +1,64 @@ +import { Format } from '@sphereon/pex-models'; +import { IProofType } from '@sphereon/ssi-types'; + +import { SigningAlgo, SIOPErrors, supportedCredentialsFormats } from '../../src'; + +describe('DidSiopMetadata should ', () => { + it('find supportedCredentialsFormats correctly', async function () { + const rpFormat: Format = { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + jwt_vc: { + alg: [SigningAlgo.ES256, SigningAlgo.ES256K], + }, + }; + const opFormat: Format = { + jwt_vc: { + alg: [SigningAlgo.ES256, SigningAlgo.ES256K], + }, + }; + expect(supportedCredentialsFormats(rpFormat, opFormat)).toStrictEqual({ jwt_vc: { alg: ['ES256', 'ES256K'] } }); + }); + + it('throw CREDENTIAL_FORMATS_NOT_SUPPORTED for algs not matching', async function () { + const rpFormat: Format = { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + jwt_vc: { + alg: [SigningAlgo.ES256K], + }, + }; + const opFormat: Format = { + jwt_vc: { + alg: [SigningAlgo.ES256], + }, + }; + expect(() => supportedCredentialsFormats(rpFormat, opFormat)).toThrow(SIOPErrors.CREDENTIAL_FORMATS_NOT_SUPPORTED); + }); + + it('throw CREDENTIAL_FORMATS_NOT_SUPPORTED for types not matching', async function () { + const rpFormat: Format = { + ldp_vc: { + proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019], + }, + }; + const opFormat: Format = { + jwt_vc: { + alg: [SigningAlgo.ES256], + }, + }; + expect(() => supportedCredentialsFormats(rpFormat, opFormat)).toThrow(SIOPErrors.CREDENTIAL_FORMATS_NOT_SUPPORTED); + }); + + it('throw CREDENTIALS_FORMATS_NOT_PROVIDED', async function () { + const rpFormat: Format = {}; + const opFormat: Format = { + jwt_vc: { + alg: [SigningAlgo.ES256, SigningAlgo.ES256K], + }, + }; + expect(() => supportedCredentialsFormats(rpFormat, opFormat)).toThrow(SIOPErrors.CREDENTIALS_FORMATS_NOT_PROVIDED); + }); +}); diff --git a/packages/siopv2/test/functions/Encodings.spec.ts b/packages/siopv2/test/functions/Encodings.spec.ts new file mode 100644 index 00000000..851e4578 --- /dev/null +++ b/packages/siopv2/test/functions/Encodings.spec.ts @@ -0,0 +1,51 @@ +import { encodeJsonAsURI } from '../../src'; + +describe('Encodings', () => { + /*test('encodeAsUriValue', () => { + expect(encodeAsUriValue(undefined, { a: { b: { c: 'd', e: 'f' } } })).toBe('a%5Bb%5D%5Bc%5D=d&a%5Bb%5D%5Be%5D=f'); + + expect(encodeAsUriValue(undefined, { a: ['b', 'c', 'd'] })).toBe('a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d'); + + expect( + encodeAsUriValue(undefined, { + a: { + b: { + 'a$s939very-2eweird-==key': { + c: 'd', + }, + }, + }, + }) + ).toBe('a%5Bb%5D%5Ba%24s939very-2eweird-%3D%3Dkey%5D%5Bc%5D=d'); + });*/ + + test('encodeJsonAsURI', () => { + const encoded = encodeJsonAsURI( + { + presentation_submission: { + id: 'bbYJTQe7YPvVx-3rLl4Aq', + definition_id: '000fc41b-2859-4fc3-b797-510492a9479a', + descriptor_map: [ + { + id: 'OpenBadgeCredential', + format: 'jwt_vp', + path: '$', + path_nested: { + id: 'OpenBadgeCredential', + format: 'jwt_vc_json', + path: '$.vp.verifiableCredential[0]', + }, + }, + ], + }, + vp_token: ['ey...1', 'ey...2'], + vp_token_single: 'ey...3', + }, + /*{ arraysWithIndex: ['presentation_submission', 'vp_token', 'vp_token_single'] }*/ + ); + + expect(encoded).toBe( + `presentation_submission=%7B%22id%22%3A%22bbYJTQe7YPvVx-3rLl4Aq%22%2C%22definition_id%22%3A%22000fc41b-2859-4fc3-b797-510492a9479a%22%2C%22descriptor_map%22%3A%5B%7B%22id%22%3A%22OpenBadgeCredential%22%2C%22format%22%3A%22jwt_vp%22%2C%22path%22%3A%22%24%22%2C%22path_nested%22%3A%7B%22id%22%3A%22OpenBadgeCredential%22%2C%22format%22%3A%22jwt_vc_json%22%2C%22path%22%3A%22%24.vp.verifiableCredential%5B0%5D%22%7D%7D%5D%7D&vp_token=%5B%22ey...1%22%2C%22ey...2%22%5D&vp_token_single=ey...3`, + ); + }); +}); diff --git a/packages/siopv2/test/functions/LanguageTagUtils.spec.ts b/packages/siopv2/test/functions/LanguageTagUtils.spec.ts new file mode 100644 index 00000000..8966d4fe --- /dev/null +++ b/packages/siopv2/test/functions/LanguageTagUtils.spec.ts @@ -0,0 +1,257 @@ +import { LanguageTagUtils } from '../../src'; + +describe('Language tag util should', () => { + it('return no lingually tagged fields if there are no lingually tagged fields in the source object', async () => { + expect.assertions(1); + const source = { nonLanguageTaggedFieldName: 'value' }; + expect(LanguageTagUtils.getAllLanguageTaggedProperties(source)).toEqual(new Map()); + }); + + it('return all lingually tagged fields if there are lingually tagged fields in the source object', async () => { + expect.assertions(1); + const source = { + FieldNameWithoutLanguageTag: 'value', + 'FieldNameWithLanguageTag#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag#en-US': 'englishValue', + }; + + const expectedTaggedFields = new Map(); + expectedTaggedFields.set('FieldNameWithLanguageTag#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag#en-US', 'englishValue'); + + const allLanguageTaggedProperties = LanguageTagUtils.getAllLanguageTaggedProperties(source); + expect(allLanguageTaggedProperties).toEqual(expectedTaggedFields); + }); + + it('return all lingually tagged fields regardless of capitalization if there are lingually tagged fields in the source object', async () => { + expect.assertions(1); + const source = { + FieldNameWithoutLanguageTag: 'value', + 'FieldNameWithLanguageTag#nl-nl': 'dutchValue', + 'FieldNameWithLanguageTag#en-US': 'englishValue', + }; + + const expectedTaggedFields = new Map(); + expectedTaggedFields.set('FieldNameWithLanguageTag#nl-nl', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag#en-US', 'englishValue'); + + const allLanguageTaggedProperties = LanguageTagUtils.getAllLanguageTaggedProperties(source); + expect(allLanguageTaggedProperties).toEqual(expectedTaggedFields); + }); + + it('return all lingually tagged fields if there are only lingually tagged fields in the source object', async () => { + expect.assertions(1); + const source = { + 'FieldNameWithLanguageTag#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag#en-US': 'englishValue', + }; + const expectedTaggedFields = new Map(); + expectedTaggedFields.set('FieldNameWithLanguageTag#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag#en-US', 'englishValue'); + + const allLanguageTaggedProperties = LanguageTagUtils.getAllLanguageTaggedProperties(source); + expect(allLanguageTaggedProperties).toEqual(expectedTaggedFields); + }); + + it('return all lingually tagged fields if there are multiple lingually tagged fields in the source object but no non-lingually tagged fields', async () => { + expect.assertions(1); + const source = { + 'FieldNameWithLanguageTag1#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag2#en-US': 'englishValue', + }; + const expectedTaggedFields = new Map(); + expectedTaggedFields.set('FieldNameWithLanguageTag1#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag2#en-US', 'englishValue'); + + const allLanguageTaggedProperties = LanguageTagUtils.getAllLanguageTaggedProperties(source); + expect(allLanguageTaggedProperties).toEqual(expectedTaggedFields); + }); + + it('return all lingually tagged fields if there are multiple lingually tagged fields in multiple languages in the source object but no non-lingually tagged fields', async () => { + expect.assertions(1); + const source = { + 'FieldNameWithLanguageTag1#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag1#en-US': 'englishValue', + 'FieldNameWithLanguageTag2#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag2#en-US': 'englishValue', + }; + const expectedTaggedFields = new Map(); + expectedTaggedFields.set('FieldNameWithLanguageTag1#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag1#en-US', 'englishValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag2#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag2#en-US', 'englishValue'); + + const allLanguageTaggedProperties = LanguageTagUtils.getAllLanguageTaggedProperties(source); + expect(allLanguageTaggedProperties).toEqual(expectedTaggedFields); + }); + + it('return all lingually tagged fields if there are multiple lingually tagged fields in multiple languages in the source object but there is a non-lingually tagged field', async () => { + expect.assertions(1); + const source = { + nonLanguageTaggedFieldName: 'value', + 'FieldNameWithLanguageTag1#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag1#en-US': 'englishValue', + 'FieldNameWithLanguageTag2#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag2#en-US': 'englishValue', + }; + const expectedTaggedFields = new Map(); + expectedTaggedFields.set('FieldNameWithLanguageTag1#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag1#en-US', 'englishValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag2#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag2#en-US', 'englishValue'); + + const allLanguageTaggedProperties = LanguageTagUtils.getAllLanguageTaggedProperties(source); + expect(allLanguageTaggedProperties).toEqual(expectedTaggedFields); + }); + + it('return all lingually tagged fields if there are multiple lingually tagged fields in multiple languages in the source object but there are non-lingually tagged fields', async () => { + expect.assertions(1); + const source = { + nonLanguageTaggedFieldName: 'value', + nonLanguageTaggedFieldName2: 'value', + 'FieldNameWithLanguageTag1#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag1#en-US': 'englishValue', + 'FieldNameWithLanguageTag2#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag2#en-US': 'englishValue', + }; + + const expectedTaggedFields = new Map(); + expectedTaggedFields.set('FieldNameWithLanguageTag1#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag1#en-US', 'englishValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag2#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag2#en-US', 'englishValue'); + + const allLanguageTaggedProperties = LanguageTagUtils.getAllLanguageTaggedProperties(source); + expect(allLanguageTaggedProperties).toEqual(expectedTaggedFields); + }); + + it('return no lingually tagged fields if there are incorrect lingually tagged fields in the source object', async () => { + expect.assertions(1); + const source = { + 'FieldNameWithLanguageTag2#en-EN': 'englishValue', + }; + + const allLanguageTaggedProperties = LanguageTagUtils.getAllLanguageTaggedProperties(source); + expect(allLanguageTaggedProperties).toEqual(new Map()); + }); + + it('return non-mapped lingually tagged fields if there are multiple lingually tagged fields in multiple languages in the source object but there are non-lingually tagged fields as well', async () => { + expect.assertions(1); + const source = { + nonLanguageTaggedFieldName: 'value', + nonLanguageTaggedFieldName2: 'value', + 'FieldNameWithLanguageTag1#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag1#en-US': 'englishValue', + 'FieldNameWithLanguageTag2#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag2#en-US': 'englishValue', + }; + const expectedTaggedFields = new Map(); + expectedTaggedFields.set('FieldNameWithLanguageTag1#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag1#en-US', 'englishValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag2#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag2#en-US', 'englishValue'); + + const allLanguageTaggedProperties = LanguageTagUtils.getLanguageTaggedProperties(source, [ + 'FieldNameWithLanguageTag1', + 'FieldNameWithLanguageTag2', + ]); + expect(allLanguageTaggedProperties).toEqual(expectedTaggedFields); + }); + + it('return only desired non-mapped lingually tagged fields if there are multiple lingually tagged fields in multiple languages in the source object but there are non-lingually tagged fields as well', async () => { + expect.assertions(1); + const source = { + nonLanguageTaggedFieldName: 'value', + nonLanguageTaggedFieldName2: 'value', + 'FieldNameWithLanguageTag1#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag1#en-US': 'englishValue', + 'FieldNameWithLanguageTag2#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag2#en-US': 'englishValue', + }; + const expectedTaggedFields = new Map(); + expectedTaggedFields.set('FieldNameWithLanguageTag1#nl-NL', 'dutchValue'); + expectedTaggedFields.set('FieldNameWithLanguageTag1#en-US', 'englishValue'); + + const allLanguageTaggedProperties = LanguageTagUtils.getLanguageTaggedProperties(source, ['FieldNameWithLanguageTag1']); + expect(allLanguageTaggedProperties).toEqual(expectedTaggedFields); + }); + + it('return only desired mapped lingually tagged fields if there are multiple lingually tagged fields in multiple languages in the source object but there are non-lingually tagged fields as well', async () => { + expect.assertions(1); + const source = { + nonLanguageTaggedFieldName: 'value', + nonLanguageTaggedFieldName2: 'value', + 'FieldNameWithLanguageTag1#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag1#en-US': 'englishValue', + 'FieldNameWithLanguageTag2#nl-NL': 'dutchValue', + 'FieldNameWithLanguageTag2#en-US': 'englishValue', + }; + const expectedTaggedFields = new Map(); + expectedTaggedFields.set('field_name_with_Language_tag_1#nl-NL', 'dutchValue'); + expectedTaggedFields.set('field_name_with_Language_tag_1#en-US', 'englishValue'); + + const languageTagEnabledFieldsNamesMapping = new Map(); + languageTagEnabledFieldsNamesMapping.set('FieldNameWithLanguageTag1', 'field_name_with_Language_tag_1'); + + const allLanguageTaggedProperties = LanguageTagUtils.getLanguageTaggedPropertiesMapped(source, languageTagEnabledFieldsNamesMapping); + expect(allLanguageTaggedProperties).toEqual(expectedTaggedFields); + }); + + it('throw error if source is null', async () => { + expect.assertions(1); + await expect(() => LanguageTagUtils.getAllLanguageTaggedProperties(null)).toThrowError(); + }); + + it('throw error if list is null', async () => { + expect.assertions(1); + await expect(() => LanguageTagUtils.getLanguageTaggedProperties({}, null)).toThrowError(); + }); + + it('throw error if list is given but not effective', async () => { + expect.assertions(1); + await expect(() => LanguageTagUtils.getLanguageTaggedProperties({}, [])).toThrowError(); + }); + + it('throw error if list is given but no proper field names', async () => { + expect.assertions(1); + await expect(() => LanguageTagUtils.getLanguageTaggedProperties({}, [''])).toThrowError(); + }); + + it('do not throw error if mapping is null', async () => { + expect.assertions(1); + expect(LanguageTagUtils.getLanguageTaggedPropertiesMapped({}, null)).toEqual(new Map()); + }); + + it('throw error if mapping is given but not effective', async () => { + expect.assertions(1); + await expect(() => LanguageTagUtils.getLanguageTaggedPropertiesMapped({}, new Map())).toThrowError(); + }); + + it('throw error if mapping is given but no proper names', async () => { + expect.assertions(1); + const languageTagEnabledFieldsNamesMapping: Map = new Map(); + languageTagEnabledFieldsNamesMapping.set(null, 'valid'); + await expect(() => LanguageTagUtils.getLanguageTaggedPropertiesMapped({}, languageTagEnabledFieldsNamesMapping)).toThrowError(); + }); + + it('throw error if mapping is given but no proper field names', async () => { + expect.assertions(1); + const languageTagEnabledFieldsNamesMapping: Map = new Map(); + languageTagEnabledFieldsNamesMapping.set('', 'valid'); + await expect(() => LanguageTagUtils.getLanguageTaggedPropertiesMapped({}, languageTagEnabledFieldsNamesMapping)).toThrowError(); + }); + + it('throw error if mapping is given but no mapped names', async () => { + expect.assertions(1); + const languageTagEnabledFieldsNamesMapping: Map = new Map(); + languageTagEnabledFieldsNamesMapping.set('valid', null); + await expect(() => LanguageTagUtils.getLanguageTaggedPropertiesMapped({}, languageTagEnabledFieldsNamesMapping)).toThrowError(); + }); + + it('throw error if mapping is given but no proper mapped names', async () => { + expect.assertions(1); + const languageTagEnabledFieldsNamesMapping: Map = new Map(); + languageTagEnabledFieldsNamesMapping.set('valid', ''); + await expect(() => LanguageTagUtils.getLanguageTaggedPropertiesMapped({}, languageTagEnabledFieldsNamesMapping)).toThrowError(); + }); +}); diff --git a/packages/siopv2/test/functions/LinkedDomainValidations.spec.ts b/packages/siopv2/test/functions/LinkedDomainValidations.spec.ts new file mode 100644 index 00000000..81f477c4 --- /dev/null +++ b/packages/siopv2/test/functions/LinkedDomainValidations.spec.ts @@ -0,0 +1,367 @@ +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/packages/siopv2/test/interop/auth0/auth0.spec.ts b/packages/siopv2/test/interop/auth0/auth0.spec.ts new file mode 100644 index 00000000..ab1c790c --- /dev/null +++ b/packages/siopv2/test/interop/auth0/auth0.spec.ts @@ -0,0 +1,12 @@ +import { PEX } from '@sphereon/pex'; + +import { anyDef, VCs } from './fixtures'; + +describe('auth0 presentation tool', () => { + it('any match definition should return all credentials', async () => { + const pex = new PEX(); + expect(VCs).toHaveLength(5); + const selectResult = await pex.selectFrom(anyDef, VCs); + expect(selectResult.matches).toHaveLength(5); + }); +}); diff --git a/packages/siopv2/test/interop/auth0/fixtures.ts b/packages/siopv2/test/interop/auth0/fixtures.ts new file mode 100644 index 00000000..17f6a426 --- /dev/null +++ b/packages/siopv2/test/interop/auth0/fixtures.ts @@ -0,0 +1,110 @@ +import { PresentationDefinitionV1 } from '@sphereon/pex-models'; + +export const anyDef: PresentationDefinitionV1 = { + id: '1', + input_descriptors: [ + { + id: '1', + name: 'A specific type of VC', + purpose: 'We want a VC of this type', + schema: [{ uri: 'VerifiableCredential' }], + }, + ], +}; + +export const multiple = { + id: '00000000-0000-0000-0000-000000000000', + input_descriptors: [ + { + id: '1', + name: 'A specific type of VC', + purpose: 'We want a VC of this type', + schema: [ + { + uri: '', + }, + ], + }, + ], +}; +export const VCs = [ + { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://sphereon-opensource.github.io/ssi-mobile-wallet/context/sphereon-wallet-identity-v1.jsonld', + ], + id: 'urn:uuid:69f05612-a6f4-415f-90588f91819aa2c1', + type: ['VerifiableCredential', 'SphereonWalletIdentityCredential'], + issuer: 'did:key:z6MkkVP8oAK9wSpE5pX5A8u5pXZzdkYxpzEUrgGyQD11DsAM', + issuanceDate: '2023-04-20T15:10:19.356Z', + credentialSubject: { + id: 'did:key:z6MkkVP8oAK9wSpE5pX5A8u5pXZzdkYxpzEUrgGyQD11DsAM', + firstName: 'Niels', + lastName: 'Klomp', + emailAddress: 'nklomp@sphereon.com', + }, + proof: { + type: 'Ed25519Signature2018', + created: '2023-04-20T15:10:19Z', + verificationMethod: 'did:key:z6MkkVP8oAK9wSpE5pX5A8u5pXZzdkYxpzEUrgGyQD11DsAM#z6MkkVP8oAK9wSpE5pX5A8u5pXZzdkYxpzEUrgGyQD11DsAM', + proofPurpose: 'assertionMethod', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..-IwvA3_naB-p6lz20T8wp5PVPmrUm47HgICMlJ9Z6yfoRkupGvGtEHfzY6fZOr9r0QmAj_6nRlf-MKhwVxv4BQ', + }, + }, + 'eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0oxYzJVaU9pSnphV2NpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lOMlEyWTJKbU1qUTRPV0l6TkRJM05tSXhOekl4T1RBMU5EbGtNak01TVRnaUxDSjRJam9pUm01RlZWVmhkV1J0T1RsT016QmlPREJxY3poV2REUkJiazk0ZGxKM1dIUm5VbU5MY1ROblFrbDFPQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMCMwIiwidHlwIjoiSldUIiwiYWxnIjoiRWREU0EifQ.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0oxYzJVaU9pSnphV2NpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lOMlEyWTJKbU1qUTRPV0l6TkRJM05tSXhOekl4T1RBMU5EbGtNak01TVRnaUxDSjRJam9pUm01RlZWVmhkV1J0T1RsT016QmlPREJxY3poV2REUkJiazk0ZGxKM1dIUm5VbU5MY1ROblFrbDFPQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMCIsInN1YiI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGVXpJMU5rc2lMQ0oxYzJVaU9pSnphV2NpTENKcmRIa2lPaUpGUXlJc0ltTnlkaUk2SW5ObFkzQXlOVFpyTVNJc0luZ2lPaUkyTm1kR1VqZDNhVjl1VFU1VlZIQmxVM3BEY0hKNmFWRllORXBDUzFOT1ptcG1lbmN6VVRWaFZITTRJaXdpZVNJNklqSktkRVpwVTJOdmIzRlhXV1EyVVZoT1pYVlJUSGhQU1MxMFpXSnVNSEZTWmxoNlRYWXlTM1UwY0VVaWZRIiwibmJmIjoxNjgyMDA0NjA1LCJpYXQiOjE2ODIwMDQ2MDUsInZjIjp7InR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJPcGVuQmFkZ2VDcmVkZW50aWFsIl0sIkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQuanNvbiJdLCJpZCI6InVybjp1dWlkOjUwMGI1NWU0LWQxNmItNDkxOS05NWQ0LTJhNmFjNDhhM2M2ZiIsImlzc3VlciI6eyJpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSjFjMlVpT2lKemFXY2lMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaU4yUTJZMkptTWpRNE9XSXpOREkzTm1JeE56SXhPVEExTkRsa01qTTVNVGdpTENKNElqb2lSbTVGVlZWaGRXUnRPVGxPTXpCaU9EQnFjemhXZERSQmJrOTRkbEozV0hSblVtTkxjVE5uUWtsMU9DSXNJbUZzWnlJNklrVmtSRk5CSW4wIiwiaW1hZ2UiOnsiaWQiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTItMjAyMi9pbWFnZXMvSkZGLVZDLUVEVS1QTFVHRkVTVDItYmFkZ2UtaW1hZ2UucG5nIiwidHlwZSI6IkltYWdlIn0sIm5hbWUiOiJKb2JzIGZvciB0aGUgRnV0dXJlIChKRkYpIiwidHlwZSI6IlByb2ZpbGUiLCJ1cmwiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTItMjAyMi9pbWFnZXMvSkZGLVZDLUVEVS1QTFVHRkVTVDItYmFkZ2UtaW1hZ2UucG5nIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDQtMjBUMTU6MzA6MDVaIiwiaXNzdWVkIjoiMjAyMy0wNC0yMFQxNTozMDowNVoiLCJ2YWxpZEZyb20iOiIyMDIzLTA0LTIwVDE1OjMwOjA1WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpoYkdjaU9pSkZVekkxTmtzaUxDSjFjMlVpT2lKemFXY2lMQ0pyZEhraU9pSkZReUlzSW1OeWRpSTZJbk5sWTNBeU5UWnJNU0lzSW5naU9pSTJObWRHVWpkM2FWOXVUVTVWVkhCbFUzcERjSEo2YVZGWU5FcENTMU5PWm1wbWVuY3pVVFZoVkhNNElpd2llU0k2SWpKS2RFWnBVMk52YjNGWFdXUTJVVmhPWlhWUlRIaFBTUzEwWldKdU1IRlNabGg2VFhZeVMzVTBjRVVpZlEiLCJhY2hpZXZlbWVudCI6eyJjcml0ZXJpYSI6eyJuYXJyYXRpdmUiOiJUaGUgY29ob3J0IG9mIHRoZSBKRkYgUGx1Z2Zlc3QgMiBpbiBBdWd1c3QtTm92ZW1iZXIgb2YgMjAyMiBjb2xsYWJvcmF0ZWQgdG8gcHVzaCBpbnRlcm9wZXJhYmlsaXR5IG9mIFZDcyBpbiBlZHVjYXRpb24gZm9yd2FyZC4iLCJ0eXBlIjoiQ3JpdGVyaWEifSwiZGVzY3JpcHRpb24iOiJUaGlzIHdhbGxldCBjYW4gZGlzcGxheSB0aGlzIE9wZW4gQmFkZ2UgMy4wIiwiaWQiOiIwIiwiaW1hZ2UiOnsiaWQiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTItMjAyMi9pbWFnZXMvSkZGLVZDLUVEVS1QTFVHRkVTVDItYmFkZ2UtaW1hZ2UucG5nIiwidHlwZSI6IkltYWdlIn0sIm5hbWUiOiJPdXIgV2FsbGV0IFBhc3NlZCBKRkYgUGx1Z2Zlc3QgIzIgMjAyMiIsInR5cGUiOiJBY2hpZXZlbWVudCJ9LCJ0eXBlIjoiQWNoaWV2ZW1lbnRTdWJqZWN0In0sIm5hbWUiOiJBY2hpZXZlbWVudCBDcmVkZW50aWFsIn0sImp0aSI6InVybjp1dWlkOjUwMGI1NWU0LWQxNmItNDkxOS05NWQ0LTJhNmFjNDhhM2M2ZiJ9.Is1GlvsyjScNWOqldkV_dt1jRaF9FHO3EK1IBgaE4ey7d-zs535yZY0YvH9gWnzDPisGbN2L4xBFRcZWxaHwDg', + 'eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0oxYzJVaU9pSnphV2NpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lOMlEyWTJKbU1qUTRPV0l6TkRJM05tSXhOekl4T1RBMU5EbGtNak01TVRnaUxDSjRJam9pUm01RlZWVmhkV1J0T1RsT016QmlPREJxY3poV2REUkJiazk0ZGxKM1dIUm5VbU5MY1ROblFrbDFPQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMCMwIiwidHlwIjoiSldUIiwiYWxnIjoiRWREU0EifQ.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0oxYzJVaU9pSnphV2NpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lOMlEyWTJKbU1qUTRPV0l6TkRJM05tSXhOekl4T1RBMU5EbGtNak01TVRnaUxDSjRJam9pUm01RlZWVmhkV1J0T1RsT016QmlPREJxY3poV2REUkJiazk0ZGxKM1dIUm5VbU5MY1ROblFrbDFPQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMCIsInN1YiI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGVXpJMU5rc2lMQ0oxYzJVaU9pSnphV2NpTENKcmRIa2lPaUpGUXlJc0ltTnlkaUk2SW5ObFkzQXlOVFpyTVNJc0luZ2lPaUkyTm1kR1VqZDNhVjl1VFU1VlZIQmxVM3BEY0hKNmFWRllORXBDUzFOT1ptcG1lbmN6VVRWaFZITTRJaXdpZVNJNklqSktkRVpwVTJOdmIzRlhXV1EyVVZoT1pYVlJUSGhQU1MxMFpXSnVNSEZTWmxoNlRYWXlTM1UwY0VVaWZRIiwibmJmIjoxNjgyMDA0NjI3LCJpYXQiOjE2ODIwMDQ2MjcsInZjIjp7InR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlSWQiXSwiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDpiYjA1MmRiYi0xYTY5LTQ1N2MtYWM4Zi0zMzc3NWE0MzY0MGYiLCJpc3N1ZXIiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0oxYzJVaU9pSnphV2NpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lOMlEyWTJKbU1qUTRPV0l6TkRJM05tSXhOekl4T1RBMU5EbGtNak01TVRnaUxDSjRJam9pUm01RlZWVmhkV1J0T1RsT016QmlPREJxY3poV2REUkJiazk0ZGxKM1dIUm5VbU5MY1ROblFrbDFPQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMCIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDQtMjBUMTU6MzA6MjdaIiwiaXNzdWVkIjoiMjAyMy0wNC0yMFQxNTozMDoyN1oiLCJ2YWxpZEZyb20iOiIyMDIzLTA0LTIwVDE1OjMwOjI3WiIsImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vd2FsdC1pZC93YWx0aWQtc3Npa2l0LXZjbGliL21hc3Rlci9zcmMvdGVzdC9yZXNvdXJjZXMvc2NoZW1hcy9WZXJpZmlhYmxlSWQuanNvbiIsInR5cGUiOiJGdWxsSnNvblNjaGVtYVZhbGlkYXRvcjIwMjEifSwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOa3NpTENKMWMyVWlPaUp6YVdjaUxDSnJkSGtpT2lKRlF5SXNJbU55ZGlJNkluTmxZM0F5TlRack1TSXNJbmdpT2lJMk5tZEdVamQzYVY5dVRVNVZWSEJsVTNwRGNISjZhVkZZTkVwQ1MxTk9abXBtZW5jelVUVmhWSE00SWl3aWVTSTZJakpLZEVacFUyTnZiM0ZYV1dRMlVWaE9aWFZSVEhoUFNTMTBaV0p1TUhGU1psaDZUWFl5UzNVMGNFVWlmUSIsImN1cnJlbnRBZGRyZXNzIjpbIjEgQm91bGV2YXJkIGRlIGxhIExpYmVydMOpLCA1OTgwMCBMaWxsZSJdLCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZmlyc3ROYW1lIjoiSmFuZSIsImdlbmRlciI6IkZFTUFMRSIsIm5hbWVBbmRGYW1pbHlOYW1lQXRCaXJ0aCI6IkphbmUgRE9FIiwicGVyc29uYWxJZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJwbGFjZU9mQmlydGgiOiJMSUxMRSwgRlJBTkNFIn0sImV2aWRlbmNlIjpbeyJkb2N1bWVudFByZXNlbmNlIjpbIlBoeXNpY2FsIl0sImV2aWRlbmNlRG9jdW1lbnQiOlsiUGFzc3BvcnQiXSwic3ViamVjdFByZXNlbmNlIjoiUGh5c2ljYWwiLCJ0eXBlIjpbIkRvY3VtZW50VmVyaWZpY2F0aW9uIl0sInZlcmlmaWVyIjoiZGlkOmVic2k6MkE5Qlo5U1VlNkJhdGFjU3B2czFWNUNkakh2THBRN2JFc2kySmI2TGRIS25ReGFOIn1dfSwianRpIjoidXJuOnV1aWQ6YmIwNTJkYmItMWE2OS00NTdjLWFjOGYtMzM3NzVhNDM2NDBmIn0.7p-Bi5zX-5LJIJV-xYhJHdpiivbKcG7TldirI5cDL-RhKohO58zzAa964oYM_03s4AA8SboIxPT47LT6MO9FBQ', + { + type: ['VerifiableCredential', 'VerifiableCredentialExtension', 'PermanentResidentCard'], + issuer: { + id: 'did:web:launchpad.vii.electron.mattrlabs.io', + name: 'Government of Kakapo', + logoUrl: 'https://static.mattr.global/credential-assets/government-of-kakapo/web/logo.svg', + iconUrl: 'https://static.mattr.global/credential-assets/government-of-kakapo/web/icon.svg', + image: 'https://static.mattr.global/credential-assets/government-of-kakapo/web/icon.svg', + }, + name: 'Permanent Resident Card', + description: 'Government of Kakapo PRC.', + credentialBranding: { + backgroundColor: '#3a2d2d', + watermarkImageUrl: 'https://static.mattr.global/credential-assets/government-of-kakapo/web/watermark@2x.png', + }, + issuanceDate: '2023-04-24T18:13:50.848Z', + credentialSubject: { + id: 'did:key:z6MkkVP8oAK9wSpE5pX5A8u5pXZzdkYxpzEUrgGyQD11DsAM', + image: + 'data:image/png;base64,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', + gender: 'Male', + birthDate: '1958-08-17', + givenName: 'Louis', + lprNumber: '1958-08-17', + familyName: 'Pasteur', + lprCategory: 'C09', + birthCountry: 'France', + residentSince: '2015-01-01', + commuterClassification: 'C1', + }, + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + { + '@vocab': 'https://w3id.org/security/undefinedTerm#', + }, + 'https://mattr.global/contexts/vc-extensions/v1', + 'https://schema.org', + 'https://w3id.org/vc-revocation-list-2020/v1', + ], + credentialStatus: { + id: 'https://launchpad.vii.electron.mattrlabs.io/core/v1/revocation-lists/25ce0f22-975a-43f8-8936-b93983b3e8f0#79', + type: 'RevocationList2020Status', + revocationListIndex: '79', + revocationListCredential: 'https://launchpad.vii.electron.mattrlabs.io/core/v1/revocation-lists/25ce0f22-975a-43f8-8936-b93983b3e8f0', + }, + proof: { + type: 'Ed25519Signature2018', + created: '2023-04-24T18:13:51Z', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..6QxpmD1apUezx-_0zNNBEuRDCohh0EDuSQyWPPG_VgFx-eDtDfgpfm7-JcErW2xn0FihqMzxCxgMvRL2lzJJDQ', + proofPurpose: 'assertionMethod', + verificationMethod: 'did:web:launchpad.vii.electron.mattrlabs.io#6BhFMCGTJg', + }, + }, + 'eyJraWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0oxYzJVaU9pSnphV2NpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lOMlEyWTJKbU1qUTRPV0l6TkRJM05tSXhOekl4T1RBMU5EbGtNak01TVRnaUxDSjRJam9pUm01RlZWVmhkV1J0T1RsT016QmlPREJxY3poV2REUkJiazk0ZGxKM1dIUm5VbU5MY1ROblFrbDFPQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMCMwIiwidHlwIjoiSldUIiwiYWxnIjoiRWREU0EifQ.eyJpc3MiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0oxYzJVaU9pSnphV2NpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpYTJsa0lqb2lOMlEyWTJKbU1qUTRPV0l6TkRJM05tSXhOekl4T1RBMU5EbGtNak01TVRnaUxDSjRJam9pUm01RlZWVmhkV1J0T1RsT016QmlPREJxY3poV2REUkJiazk0ZGxKM1dIUm5VbU5MY1ROblFrbDFPQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMCIsInN1YiI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGVXpJMU5rc2lMQ0oxYzJVaU9pSnphV2NpTENKcmRIa2lPaUpGUXlJc0ltTnlkaUk2SW5ObFkzQXlOVFpyTVNJc0luZ2lPaUkyTm1kR1VqZDNhVjl1VFU1VlZIQmxVM3BEY0hKNmFWRllORXBDUzFOT1ptcG1lbmN6VVRWaFZITTRJaXdpZVNJNklqSktkRVpwVTJOdmIzRlhXV1EyVVZoT1pYVlJUSGhQU1MxMFpXSnVNSEZTWmxoNlRYWXlTM1UwY0VVaWZRIiwibmJmIjoxNjgyNjIzNzY5LCJpYXQiOjE2ODI2MjM3NjksInZjIjp7InR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJPcGVuQmFkZ2VDcmVkZW50aWFsIl0sIkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQuanNvbiJdLCJpZCI6InVybjp1dWlkOjU3OWE1YjljLTI2Y2MtNDgwNi1hMDYwLTIyOWMwYjE5ZmI2OCIsImlzc3VlciI6eyJpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSjFjMlVpT2lKemFXY2lMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2lhMmxrSWpvaU4yUTJZMkptTWpRNE9XSXpOREkzTm1JeE56SXhPVEExTkRsa01qTTVNVGdpTENKNElqb2lSbTVGVlZWaGRXUnRPVGxPTXpCaU9EQnFjemhXZERSQmJrOTRkbEozV0hSblVtTkxjVE5uUWtsMU9DSXNJbUZzWnlJNklrVmtSRk5CSW4wIiwiaW1hZ2UiOnsiaWQiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTItMjAyMi9pbWFnZXMvSkZGLVZDLUVEVS1QTFVHRkVTVDItYmFkZ2UtaW1hZ2UucG5nIiwidHlwZSI6IkltYWdlIn0sIm5hbWUiOiJKb2JzIGZvciB0aGUgRnV0dXJlIChKRkYpIiwidHlwZSI6IlByb2ZpbGUiLCJ1cmwiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTItMjAyMi9pbWFnZXMvSkZGLVZDLUVEVS1QTFVHRkVTVDItYmFkZ2UtaW1hZ2UucG5nIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDQtMjdUMTk6Mjk6MjlaIiwiaXNzdWVkIjoiMjAyMy0wNC0yN1QxOToyOToyOVoiLCJ2YWxpZEZyb20iOiIyMDIzLTA0LTI3VDE5OjI5OjI5WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmp3azpleUpoYkdjaU9pSkZVekkxTmtzaUxDSjFjMlVpT2lKemFXY2lMQ0pyZEhraU9pSkZReUlzSW1OeWRpSTZJbk5sWTNBeU5UWnJNU0lzSW5naU9pSTJObWRHVWpkM2FWOXVUVTVWVkhCbFUzcERjSEo2YVZGWU5FcENTMU5PWm1wbWVuY3pVVFZoVkhNNElpd2llU0k2SWpKS2RFWnBVMk52YjNGWFdXUTJVVmhPWlhWUlRIaFBTUzEwWldKdU1IRlNabGg2VFhZeVMzVTBjRVVpZlEiLCJhY2hpZXZlbWVudCI6eyJjcml0ZXJpYSI6eyJuYXJyYXRpdmUiOiJUaGUgY29ob3J0IG9mIHRoZSBKRkYgUGx1Z2Zlc3QgMiBpbiBBdWd1c3QtTm92ZW1iZXIgb2YgMjAyMiBjb2xsYWJvcmF0ZWQgdG8gcHVzaCBpbnRlcm9wZXJhYmlsaXR5IG9mIFZDcyBpbiBlZHVjYXRpb24gZm9yd2FyZC4iLCJ0eXBlIjoiQ3JpdGVyaWEifSwiZGVzY3JpcHRpb24iOiJUaGlzIHdhbGxldCBjYW4gZGlzcGxheSB0aGlzIE9wZW4gQmFkZ2UgMy4wIiwiaWQiOiIwIiwiaW1hZ2UiOnsiaWQiOiJodHRwczovL3czYy1jY2cuZ2l0aHViLmlvL3ZjLWVkL3BsdWdmZXN0LTItMjAyMi9pbWFnZXMvSkZGLVZDLUVEVS1QTFVHRkVTVDItYmFkZ2UtaW1hZ2UucG5nIiwidHlwZSI6IkltYWdlIn0sIm5hbWUiOiJPdXIgV2FsbGV0IFBhc3NlZCBKRkYgUGx1Z2Zlc3QgIzIgMjAyMiIsInR5cGUiOiJBY2hpZXZlbWVudCJ9LCJ0eXBlIjoiQWNoaWV2ZW1lbnRTdWJqZWN0In0sIm5hbWUiOiJBY2hpZXZlbWVudCBDcmVkZW50aWFsIn0sImp0aSI6InVybjp1dWlkOjU3OWE1YjljLTI2Y2MtNDgwNi1hMDYwLTIyOWMwYjE5ZmI2OCJ9.UazGcYVGIW0xKeqdP6cW-TFgkk9in2pXz5v4LPRUmHshj5O1m_i91O5XsMAZP0WC5n1bClKlXcMGrBqlrrH2Aw', +]; diff --git a/packages/siopv2/test/interop/mattr/fixtures.ts b/packages/siopv2/test/interop/mattr/fixtures.ts new file mode 100644 index 00000000..8668a548 --- /dev/null +++ b/packages/siopv2/test/interop/mattr/fixtures.ts @@ -0,0 +1,53 @@ +export const MattrPRCVC = { + type: ['VerifiableCredential', 'VerifiableCredentialExtension', 'PermanentResidentCard'], + issuer: { + id: 'did:web:launchpad.vii.electron.mattrlabs.io', + name: 'Government of Kakapo', + logoUrl: 'https://static.mattr.global/credential-assets/government-of-kakapo/web/logo.svg', + iconUrl: 'https://static.mattr.global/credential-assets/government-of-kakapo/web/icon.svg', + image: 'https://static.mattr.global/credential-assets/government-of-kakapo/web/icon.svg', + }, + name: 'Permanent Resident Card', + description: 'Government of Kakapo PRC.', + credentialBranding: { + backgroundColor: '#3a2d2d', + watermarkImageUrl: 'https://static.mattr.global/credential-assets/government-of-kakapo/web/watermark@2x.png', + }, + issuanceDate: '2023-04-24T18:13:50.848Z', + credentialSubject: { + id: 'did:key:z6MkkVP8oAK9wSpE5pX5A8u5pXZzdkYxpzEUrgGyQD11DsAM', + image: + 'data:image/png;base64,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', + gender: 'Male', + birthDate: '1958-08-17', + givenName: 'Louis', + lprNumber: '1958-08-17', + familyName: 'Pasteur', + lprCategory: 'C09', + birthCountry: 'France', + residentSince: '2015-01-01', + commuterClassification: 'C1', + }, + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + { + '@vocab': 'https://w3id.org/security/undefinedTerm#', + }, + 'https://mattr.global/contexts/vc-extensions/v1', + 'https://schema.org', + 'https://w3id.org/vc-revocation-list-2020/v1', + ], + credentialStatus: { + id: 'https://launchpad.vii.electron.mattrlabs.io/core/v1/revocation-lists/25ce0f22-975a-43f8-8936-b93983b3e8f0#79', + type: 'RevocationList2020Status', + revocationListIndex: '79', + revocationListCredential: 'https://launchpad.vii.electron.mattrlabs.io/core/v1/revocation-lists/25ce0f22-975a-43f8-8936-b93983b3e8f0', + }, + proof: { + type: 'Ed25519Signature2018', + created: '2023-04-24T18:13:51Z', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..6QxpmD1apUezx-_0zNNBEuRDCohh0EDuSQyWPPG_VgFx-eDtDfgpfm7-JcErW2xn0FihqMzxCxgMvRL2lzJJDQ', + proofPurpose: 'assertionMethod', + verificationMethod: 'did:web:launchpad.vii.electron.mattrlabs.io#6BhFMCGTJg', + }, +}; diff --git a/packages/siopv2/test/modules.d.ts b/packages/siopv2/test/modules.d.ts new file mode 100644 index 00000000..b6caddcb --- /dev/null +++ b/packages/siopv2/test/modules.d.ts @@ -0,0 +1,3 @@ +declare module '@digitalcredentials/vc'; +declare module '@digitalcredentials/jsonld-signatures'; +declare module '@digitalcredentials/ed25519-signature-2020'; diff --git a/packages/siopv2/test/regressions/ClientIdIsObject.spec.ts b/packages/siopv2/test/regressions/ClientIdIsObject.spec.ts new file mode 100644 index 00000000..9f0f3bae --- /dev/null +++ b/packages/siopv2/test/regressions/ClientIdIsObject.spec.ts @@ -0,0 +1,59 @@ +import { ResponseType } from '@sphereon/oid4vc-common'; + +import { PassBy, RevocationVerification, RP, Scope, SigningAlgo, SubjectType, SupportedVersion } from '../../src'; +const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello'; +// const EXAMPLE_REFERENCE_URL = 'https://rp.acme.com/siop/jwts'; +const HEX_KEY = 'f857544a9d1097e242ff0b287a7e6e90f19cf973efe2317f2a4678739664420f'; +const DID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0'; +const KID = 'did:ethr:0x0106a2e985b1E1De9B5ddb4aF6dC9e928F4e99D0#keys-1'; + +const rp = RP.builder() + // .withClientId('test') + .withRedirectUri(EXAMPLE_REDIRECT_URL) + .withRequestByValue() + .withRevocationVerification(RevocationVerification.NEVER) + .withInternalSignature(HEX_KEY, DID, KID, SigningAlgo.ES256K) + .addDidMethod('ethr') + .addDidMethod('key') + .withSupportedVersions([SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1]) + .withClientMetadata({ + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], + passBy: PassBy.VALUE, + 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:ethr:', 'did:key:', 'did'], + }) + .withPresentationDefinition({ + definition: { + id: '1234-1234-1234-1234', + input_descriptors: [ + { + id: 'ExampleInputDescriptor', + schema: [ + { + uri: 'https://did.itsourweb.org:3000/smartcredential/Ontario-Health-Insurance-Plan', + }, + ], + }, + ], + }, + }) + .build(); + +describe('Creating an AuthRequest with an RP from builder', () => { + it('should have a client_id that is a string when not explicitly provided', async () => { + // see: https://github.com/Sphereon-Opensource/SIOP-OID4VP/issues/54 + // When not supplying a clientId to the builder, the request object creates an object of the clientId + const authRequest = await rp.createAuthorizationRequest({ + correlationId: '1', + nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', + state: 'b32f0087fc9816eb813fd11f', + }); + + const requestObjectPayload = await authRequest.requestObject.getPayload(); + await expect(requestObjectPayload.client_id).toEqual(DID); + }); +}); diff --git a/packages/siopv2/test/spec-compliance/jwtVCPresentationProfile.spec.ts b/packages/siopv2/test/spec-compliance/jwtVCPresentationProfile.spec.ts new file mode 100644 index 00000000..603db343 --- /dev/null +++ b/packages/siopv2/test/spec-compliance/jwtVCPresentationProfile.spec.ts @@ -0,0 +1,624 @@ +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'; +import * as u8a from 'uint8arrays'; + +import { + AuthorizationRequest, + AuthorizationResponse, + CheckLinkedDomain, + IDToken, + OP, + PassBy, + PresentationDefinitionLocation, + PresentationExchange, + PresentationSignCallback, + PresentationVerificationCallback, + PropertyTarget, + ResponseMode, + ResponseType, + RevocationVerification, + RP, + SigningAlgo, + SupportedVersion, + VerificationMode, + VPTokenLocation, +} from '../../src'; + +let rp: RP; +let op: OP; + +afterEach(() => { + nock.cleanAll(); +}); +beforeEach(async () => { + await TestVectors.init(); + + TestVectors.mockDID(TestVectors.issuerDID, TestVectors.issuerKID, TestVectors.issuerJwk); + TestVectors.mockDID(TestVectors.holderDID, TestVectors.holderKID, TestVectors.holderJwk); + TestVectors.mockDID(TestVectors.verifierDID, TestVectors.verifierKID, TestVectors.verifierJwk); + + rp = RP.builder({ requestVersion: SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1 }) + .withResponseType(ResponseType.ID_TOKEN, PropertyTarget.REQUEST_OBJECT) + .withClientId(TestVectors.issuerDID, PropertyTarget.REQUEST_OBJECT) + .withScope('openid', PropertyTarget.REQUEST_OBJECT) + .withResponseMode(ResponseMode.POST, PropertyTarget.REQUEST_OBJECT) + .withClientMetadata( + { + passBy: PassBy.VALUE, + // targets: [PropertyTarget.REQUEST_OBJECT], + logo_uri: 'https://example.com/verifier-icon.png', + tos_uri: 'https://example.com/verifier-info', + clientName: 'Example Verifier', + vpFormatsSupported: { + jwt_vc: { + alg: ['EdDSA', 'ES256K'], + }, + jwt_vp: { + alg: ['EdDSA', 'ES256K'], + }, + }, + subject_syntax_types_supported: ['did:ion'], + }, + PropertyTarget.REQUEST_OBJECT, + ) + .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) + .build(); + + op = OP.builder() + .addDidMethod('ion') + .withInternalSignature(TestVectors.holderHexPrivateKey, TestVectors.holderDID, TestVectors.holderKID, SigningAlgo.EDDSA) + .withCheckLinkedDomain(CheckLinkedDomain.IF_PRESENT) + .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(); + expect(await authRequest.requestObject.getPayload()).toMatchObject({ + response_type: 'id_token', + nonce: '40252afc-6a82-4a2e-905f-e41f122ef575', + client_id: + 'did:ion:EiBAA99TAezxKRc2wuuBnr4zzGsS2YcsOA4IPQV0KY64Xg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkdnWkdUZzhlQ2E3bFYyOE1MOUpUbUJVdms3RFlCYmZSS1dMaHc2NUpvMXMiLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaURKV0Z2WUJ5Qzd2azA2MXAzdHYwd29WSTk5MTFQTGgwUVp4cWpZM2Y4MVFRIn0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlBX1RvVlNBZDBTRWxOU2VrQ1k1UDVHZ01KQy1MTVpFY2ZSV2ZqZGNaYXJFQSIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpRDN0ZTV4eFliemJod0pYdEUwZ2tZV3Z3MlZ2VFB4MU9la0RTcXduZzRTWmcifX0', + response_mode: 'post', + // "nbf" : 1674772063, + scope: 'openid', + claims: { + vp_token: { + presentation_definition: { + input_descriptors: [ + { + schema: [ + { + uri: 'VerifiedEmployee', + }, + ], + purpose: 'We need to verify that you have a valid VerifiedEmployee Verifiable Credential.', + name: 'VerifiedEmployeeVC', + id: 'VerifiedEmployeeVC', + }, + ], + id: '649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9', + }, + }, + }, + registration: { + logo_uri: 'https://example.com/verifier-icon.png', + tos_uri: 'https://example.com/verifier-info', + client_name: 'Example Verifier', + vp_formats: { + jwt_vc: { + alg: ['EdDSA', 'ES256K'], + }, + jwt_vp: { + alg: ['EdDSA', 'ES256K'], + }, + }, + subject_syntax_types_supported: ['did:ion'], + }, + state: '649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9', + redirect_uri: 'https://example.com/siop-response' /*, + "exp" : 1674775663, + "iat" : 1674772063, + "jti" : "f0e6dcf5-3fe6-4507-adc9-b496daf34512"*/, + }); + }); + it('should re-create uri', async () => { + const authRequest = await createAuthRequest(); + const uri = await authRequest.uri(); + expect(uri.encodedUri).toEqual( + 'openid-vc://?request_uri=https%3A%2F%2Fexample%2Fservice%2Fapi%2Fv1%2Fpresentation-request%2F649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9', + ); + }); + it('should get presentation definition', async () => { + const authRequest = await createAuthRequest(); + const defs = await authRequest.getPresentationDefinitions(); + expect(defs).toBeDefined(); + expect(defs).toHaveLength(1); + }); + it('should decode id token jwt', async () => { + const idToken = await IDToken.fromIDToken(TestVectors.idTokenJwt); + expect(idToken).toBeDefined(); + const payload = await idToken.payload(); + expect(payload).toEqual(TestVectors.idTokenPayload); + expect( + await idToken.verify({ + 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.IF_PRESENT, + presentationVerificationCallback, + wellknownDIDVerifyCallback: verifyCallback, + }, + }), + ).toBeTruthy(); + }); + + it('should decode auth response', async () => { + const authorizationResponse = await AuthorizationResponse.fromPayload(TestVectors.authorizationResponsePayload); + expect(authorizationResponse).toBeDefined(); + expect(authorizationResponse.payload).toEqual(TestVectors.authorizationResponsePayload); + expect(await authorizationResponse.idToken.payload()).toEqual(TestVectors.idTokenPayload); + expect( + await authorizationResponse.idToken.verify({ + 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, + }, + }, + }), + ).toBeTruthy(); + + const authRequest = await createAuthRequest(); + const presentationDefinitions = await authRequest.getPresentationDefinitions(); + + const verified = await authorizationResponse.verify({ + 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, + }, + }, + presentationDefinitions, + }); + expect(verified).toBeDefined(); + // console.log(verified); + }); +}); + +describe('OP using test vectors', () => { + it('should import auth request and be able to provide the auth request unaltered', async () => { + 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 } }, + }, + }, + }); + 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); + + expect(authorizationResponse.payload.vp_token).toBeDefined(); + expect(authorizationResponse.payload.id_token).toBeDefined(); + expect(await authorizationResponse.idToken.payload()).toEqual({ + _vp_token: { + presentation_submission: { + definition_id: '649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9', + descriptor_map: [ + { + format: 'jwt_vp', + id: 'VerifiedEmployeeVC', + path: '$', + path_nested: { + format: 'jwt_vc', + id: 'VerifiedEmployeeVC', + path: '$.verifiableCredential[0]', + }, + }, + ], + id: '9af24e8a-c8f3-4b9a-9161-b715e77a6010', + }, + }, + aud: 'did:ion:EiBWe9RtHT7VZ-Juff8OnnJAyFJtCokcYHx1CQkFtpl7pw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkNfT1VKeEg2aUljQzZYZE5oN0ptQy1USFhBVmFYbnZ1OU9FRVo4dHE5TkkiLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUNYTkJqSWZMVGZOV0NHMFQ2M2VaYmJEZFZoSmJUTjgtSmZlaUx4dW1oZW53In0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlCZVZ5RXBDb0NPeXJ6VDhDSHlvQW1acU1CT1o0VTZqcm1sdUt1SjlxS0pkZyIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQnhkcHlyamlVSFZ1akNRWTBKMkhBUFFYZnNwWFBKYWluV21mV3RNcFhneFEifX0', + exp: 1674786463, + iat: 1674772063, + iss: 'https://self-issued.me/v2/openid-vc', + jti: '0f5dafed-0d82-43b1-af79-40440e3f1366', + nonce: '40252afc-6a82-4a2e-905f-e41f122ef575', + sub: 'did:ion:EiAeM6No9kdpos6_ehBUDh4RINY4USDMh-QdWksmsI3WkA:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IncwNk9WN2U2blR1cnQ2RzlWcFZYeEl3WW55amZ1cHhlR3lLQlMtYmxxdmciLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUFSNGRVQmxqNWNGa3dMdkpTWUYzVExjLV81MWhDX2xZaGxXZkxWZ29seTRRIn0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlEcVJyWU5fV3JTakFQdnlFYlJQRVk4WVhPRmNvT0RTZExUTWItM2FKVElGQSIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQUwyMFdYakpQQW54WWdQY1U5RV9POE1OdHNpQk00QktpaVNwT3ZFTWpVOUEifX0', + }); + await rp.verifyAuthorizationResponse(TestVectors.authorizationResponsePayload, { + 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: [ + { + definition: TestVectors.presentationDef, + location: PresentationDefinitionLocation.CLAIMS_VP_TOKEN, + }, + ], + }); + + nock('https://example', {}).post('/resp').reply(200, {}); + await op.submitAuthorizationResponse({ + response: authorizationResponse, + correlationId: '12345', + responseURI: 'https://example/resp', + }); + }); + it('should create auth response', async () => { + nock('https://example') + .get('/service/api/v1/presentation-request/649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9') + .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 } }, + }, + }, + }); + const presentationExchange = new PresentationExchange({ + allDIDs: [TestVectors.holderDID], + allVerifiableCredentials: [TestVectors.jwtVCFromVPToken], + }); + const verifiablePresentationResult = await presentationExchange.createVerifiablePresentation( + TestVectors.presentationDef, + [TestVectors.jwtVCFromVPToken], + presentationSignCallback, + { + holderDID: TestVectors.holderDID, + signatureOptions: {}, + proofOptions: { + type: IProofType.JwtProof2020, + }, + }, + ); + const response = 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)); + }); +}); + +async function createAuthRequest(): Promise { + return await rp.createAuthorizationRequest({ + correlationId: '1234', + nonce: { propertyValue: '40252afc-6a82-4a2e-905f-e41f122ef575', targets: PropertyTarget.REQUEST_OBJECT }, + state: { propertyValue: '649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9', targets: PropertyTarget.REQUEST_OBJECT }, + claims: { + propertyValue: { + vp_token: { + presentation_definition: { + input_descriptors: [ + { + schema: [ + { + uri: 'VerifiedEmployee', + }, + ], + purpose: 'We need to verify that you have a valid VerifiedEmployee Verifiable Credential.', + name: 'VerifiedEmployeeVC', + id: 'VerifiedEmployeeVC', + }, + ], + id: '649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9', + }, + }, + }, + targets: PropertyTarget.REQUEST_OBJECT, + }, + }); +} + +class TestVectors { + public static issuerDID = + 'did:ion:EiBAA99TAezxKRc2wuuBnr4zzGsS2YcsOA4IPQV0KY64Xg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkdnWkdUZzhlQ2E3bFYyOE1MOUpUbUJVdms3RFlCYmZSS1dMaHc2NUpvMXMiLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaURKV0Z2WUJ5Qzd2azA2MXAzdHYwd29WSTk5MTFQTGgwUVp4cWpZM2Y4MVFRIn0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlBX1RvVlNBZDBTRWxOU2VrQ1k1UDVHZ01KQy1MTVpFY2ZSV2ZqZGNaYXJFQSIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpRDN0ZTV4eFliemJod0pYdEUwZ2tZV3Z3MlZ2VFB4MU9la0RTcXduZzRTWmcifX0'; + public static issuerKID = `${TestVectors.issuerDID}#key-1`; + public static issuerJwk = { + kty: 'OKP', + d: 'jGLXxgOFN5DuQQFrRBN58Xll5SRizDXyVL5uiDY60_4', + crv: 'Ed25519', + kid: 'key-1', + x: 'GgZGTg8eCa7lV28ML9JTmBUvk7DYBbfRKWLhw65Jo1s', + }; + public static issuerKey; + public static issuerPrivateKey; + public static issuerPublicKey; + public static issuerHexPrivateKey; + + public static holderJwk = { + kty: 'OKP', + d: 'ZeDOVmemqzPAK0R2F1BHVfRYC7g65p_UpyXhEaX03N4', + crv: 'Ed25519', + kid: 'key-1', + x: 'w06OV7e6nTurt6G9VpVXxIwYnyjfupxeGyKBS-blqvg', + }; + public static holderDID = + 'did:ion:EiAeM6No9kdpos6_ehBUDh4RINY4USDMh-QdWksmsI3WkA:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IncwNk9WN2U2blR1cnQ2RzlWcFZYeEl3WW55amZ1cHhlR3lLQlMtYmxxdmciLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUFSNGRVQmxqNWNGa3dMdkpTWUYzVExjLV81MWhDX2xZaGxXZkxWZ29seTRRIn0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlEcVJyWU5fV3JTakFQdnlFYlJQRVk4WVhPRmNvT0RTZExUTWItM2FKVElGQSIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQUwyMFdYakpQQW54WWdQY1U5RV9POE1OdHNpQk00QktpaVNwT3ZFTWpVOUEifX0'; + public static holderKID = `${TestVectors.holderDID}#key-1`; + public static holderKey; + public static holderPrivateKey; + public static holderPublicKey; + public static holderHexPrivateKey; + + public static verifierJwk = { + kty: 'OKP', + d: 'SP18SnbU9f-Rph0GwulyvmLFyCXDHqZVKWDo2E41llQ', + crv: 'Ed25519', + kid: 'key-1', + x: 'C_OUJxH6iIcC6XdNh7JmC-THXAVaXnvu9OEEZ8tq9NI', + }; + public static verifierDID = + 'did:ion:EiBWe9RtHT7VZ-Juff8OnnJAyFJtCokcYHx1CQkFtpl7pw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkNfT1VKeEg2aUljQzZYZE5oN0ptQy1USFhBVmFYbnZ1OU9FRVo4dHE5TkkiLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUNYTkJqSWZMVGZOV0NHMFQ2M2VaYmJEZFZoSmJUTjgtSmZlaUx4dW1oZW53In0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlCZVZ5RXBDb0NPeXJ6VDhDSHlvQW1acU1CT1o0VTZqcm1sdUt1SjlxS0pkZyIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQnhkcHlyamlVSFZ1akNRWTBKMkhBUFFYZnNwWFBKYWluV21mV3RNcFhneFEifX0'; + + public static verifierKID = `${TestVectors.verifierDID}#key-1`; + public static verifierKey; + public static verifierPrivateKey; + public static verifierPublicKey; + public static verifierHexPrivateKey; + + public static async init() { + TestVectors.issuerKey = (await jose.importJWK(TestVectors.issuerJwk, 'EdDSA', true)) as KeyLike; + TestVectors.issuerPrivateKey = u8a.toString(u8a.fromString(TestVectors.issuerJwk.d, 'base64url'), 'hex'); + TestVectors.issuerPublicKey = u8a.toString(u8a.fromString(TestVectors.issuerJwk.x, 'base64url'), 'hex'); + TestVectors.issuerHexPrivateKey = `${TestVectors.issuerPrivateKey}${TestVectors.issuerPublicKey}`; + + TestVectors.holderKey = (await jose.importJWK(TestVectors.holderJwk, 'EdDSA', true)) as KeyLike; + TestVectors.holderPrivateKey = u8a.toString(u8a.fromString(TestVectors.holderJwk.d, 'base64url'), 'hex'); + TestVectors.holderPublicKey = u8a.toString(u8a.fromString(TestVectors.holderJwk.x, 'base64url'), 'hex'); + TestVectors.holderHexPrivateKey = `${TestVectors.holderPrivateKey}${TestVectors.holderPublicKey}`; + + TestVectors.verifierKey = (await jose.importJWK(TestVectors.verifierJwk, 'EdDSA', true)) as KeyLike; + TestVectors.verifierPrivateKey = u8a.toString(u8a.fromString(TestVectors.verifierJwk.d, 'base64url'), 'hex'); + TestVectors.verifierPublicKey = u8a.toString(u8a.fromString(TestVectors.verifierJwk.x, 'base64url'), 'hex'); + TestVectors.verifierHexPrivateKey = `${TestVectors.verifierPrivateKey}${TestVectors.verifierPublicKey}`; + } + + public static auth_request = 'openid-vc://?request_uri=https://example/service/api/v1/presentation-request/649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9'; + public static request_uri = 'https://example/service/api/v1/presentation-request/649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9'; + + public static idTokenJwt = + 'eyJraWQiOiJkaWQ6aW9uOkVpQWVNNk5vOWtkcG9zNl9laEJVRGg0UklOWTRVU0RNaC1RZFdrc21zSTNXa0E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKclpYa3RNU0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpqY25ZaU9pSkZaREkxTlRFNUlpd2lhM1I1SWpvaVQwdFFJaXdpZUNJNkluY3dOazlXTjJVMmJsUjFjblEyUnpsV2NGWlllRWwzV1c1NWFtWjFjSGhsUjNsTFFsTXRZbXh4ZG1jaUxDSnJhV1FpT2lKclpYa3RNU0o5TENKd2RYSndiM05sY3lJNld5SmhkWFJvWlc1MGFXTmhkR2x2YmlKZExDSjBlWEJsSWpvaVNuTnZibGRsWWt0bGVUSXdNakFpZlYxOWZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVGU05HUlZRbXhxTldOR2EzZE1ka3BUV1VZelZFeGpMVjgxTVdoRFgyeFphR3hYWmt4V1oyOXNlVFJSSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEVjVkp5V1U1ZlYzSlRha0ZRZG5sRllsSlFSVms0V1ZoUFJtTnZUMFJUWkV4VVRXSXRNMkZLVkVsR1FTSXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFVd3lNRmRZYWtwUVFXNTRXV2RRWTFVNVJWOVBPRTFPZEhOcFFrMDBRa3RwYVZOd1QzWkZUV3BWT1VFaWZYMCNrZXktMSIsImFsZyI6IkVkRFNBIn0.eyJzdWIiOiJkaWQ6aW9uOkVpQWVNNk5vOWtkcG9zNl9laEJVRGg0UklOWTRVU0RNaC1RZFdrc21zSTNXa0E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKclpYa3RNU0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpqY25ZaU9pSkZaREkxTlRFNUlpd2lhM1I1SWpvaVQwdFFJaXdpZUNJNkluY3dOazlXTjJVMmJsUjFjblEyUnpsV2NGWlllRWwzV1c1NWFtWjFjSGhsUjNsTFFsTXRZbXh4ZG1jaUxDSnJhV1FpT2lKclpYa3RNU0o5TENKd2RYSndiM05sY3lJNld5SmhkWFJvWlc1MGFXTmhkR2x2YmlKZExDSjBlWEJsSWpvaVNuTnZibGRsWWt0bGVUSXdNakFpZlYxOWZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVGU05HUlZRbXhxTldOR2EzZE1ka3BUV1VZelZFeGpMVjgxTVdoRFgyeFphR3hYWmt4V1oyOXNlVFJSSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEVjVkp5V1U1ZlYzSlRha0ZRZG5sRllsSlFSVms0V1ZoUFJtTnZUMFJUWkV4VVRXSXRNMkZLVkVsR1FTSXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFVd3lNRmRZYWtwUVFXNTRXV2RRWTFVNVJWOVBPRTFPZEhOcFFrMDBRa3RwYVZOd1QzWkZUV3BWT1VFaWZYMCIsImF1ZCI6ImRpZDppb246RWlCV2U5UnRIVDdWWi1KdWZmOE9ubkpBeUZKdENva2NZSHgxQ1FrRnRwbDdwdzpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUpyWlhrdE1TSXNJbkIxWW14cFkwdGxlVXAzYXlJNmV5SmpjbllpT2lKRlpESTFOVEU1SWl3aWEzUjVJam9pVDB0UUlpd2llQ0k2SWtOZlQxVktlRWcyYVVsalF6WllaRTVvTjBwdFF5MVVTRmhCVm1GWWJuWjFPVTlGUlZvNGRIRTVUa2tpTENKcmFXUWlPaUpyWlhrdE1TSjlMQ0p3ZFhKd2IzTmxjeUk2V3lKaGRYUm9aVzUwYVdOaGRHbHZiaUpkTENKMGVYQmxJam9pU25OdmJsZGxZa3RsZVRJd01qQWlmVjE5ZlYwc0luVndaR0YwWlVOdmJXMXBkRzFsYm5RaU9pSkZhVU5ZVGtKcVNXWk1WR1pPVjBOSE1GUTJNMlZhWW1KRVpGWm9TbUpVVGpndFNtWmxhVXg0ZFcxb1pXNTNJbjBzSW5OMVptWnBlRVJoZEdFaU9uc2laR1ZzZEdGSVlYTm9Jam9pUldsQ1pWWjVSWEJEYjBOUGVYSjZWRGhEU0hsdlFXMWFjVTFDVDFvMFZUWnFjbTFzZFV0MVNqbHhTMHBrWnlJc0luSmxZMjkyWlhKNVEyOXRiV2wwYldWdWRDSTZJa1ZwUW5oa2NIbHlhbWxWU0ZaMWFrTlJXVEJLTWtoQlVGRllabk53V0ZCS1lXbHVWMjFtVjNSTmNGaG5lRkVpZlgwIiwiaXNzIjoiaHR0cHM6XC9cL3NlbGYtaXNzdWVkLm1lXC92Mlwvb3BlbmlkLXZjIiwiZXhwIjoxNjc0Nzg2NDYzLCJpYXQiOjE2NzQ3NzIwNjMsIm5vbmNlIjoiNDAyNTJhZmMtNmE4Mi00YTJlLTkwNWYtZTQxZjEyMmVmNTc1IiwianRpIjoiMGY1ZGFmZWQtMGQ4Mi00M2IxLWFmNzktNDA0NDBlM2YxMzY2IiwiX3ZwX3Rva2VuIjp7InByZXNlbnRhdGlvbl9zdWJtaXNzaW9uIjp7ImlkIjoiOWFmMjRlOGEtYzhmMy00YjlhLTkxNjEtYjcxNWU3N2E2MDEwIiwiZGVmaW5pdGlvbl9pZCI6IjY0OWQ4YzNjLWY1YWMtNDFiZC05YzE5LTU4MDRlYTFiOGZlOSIsImRlc2NyaXB0b3JfbWFwIjpbeyJpZCI6IlZlcmlmaWVkRW1wbG95ZWVWQyIsImZvcm1hdCI6Imp3dF92cCIsInBhdGgiOiIkIiwicGF0aF9uZXN0ZWQiOnsiaWQiOiJWZXJpZmllZEVtcGxveWVlVkMiLCJmb3JtYXQiOiJqd3RfdmMiLCJwYXRoIjoiJC52ZXJpZmlhYmxlQ3JlZGVudGlhbFswXSJ9fV19fX0.jh-SnpQcYPGEb_N5mqKUKCi9pA2OqxXw7BbAYuQwQat69KqpHA0sEZ1tOTOwsVP9UCfjmVg_8z0I_TvKkEkCBA'; + + public static presentation_submission = { + descriptor_map: [ + { + path: '$', + format: 'jwt_vp', + path_nested: { + path: '$.verifiableCredential[0]', + format: 'jwt_vc', + id: 'VerifiedEmployeeVC', + }, + id: 'VerifiedEmployeeVC', + }, + ], + definition_id: '649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9', + id: '9af24e8a-c8f3-4b9a-9161-b715e77a6010', + }; + + public static idTokenPayload = { + sub: 'did:ion:EiAeM6No9kdpos6_ehBUDh4RINY4USDMh-QdWksmsI3WkA:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IncwNk9WN2U2blR1cnQ2RzlWcFZYeEl3WW55amZ1cHhlR3lLQlMtYmxxdmciLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUFSNGRVQmxqNWNGa3dMdkpTWUYzVExjLV81MWhDX2xZaGxXZkxWZ29seTRRIn0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlEcVJyWU5fV3JTakFQdnlFYlJQRVk4WVhPRmNvT0RTZExUTWItM2FKVElGQSIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQUwyMFdYakpQQW54WWdQY1U5RV9POE1OdHNpQk00QktpaVNwT3ZFTWpVOUEifX0', + aud: 'did:ion:EiBWe9RtHT7VZ-Juff8OnnJAyFJtCokcYHx1CQkFtpl7pw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkNfT1VKeEg2aUljQzZYZE5oN0ptQy1USFhBVmFYbnZ1OU9FRVo4dHE5TkkiLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUNYTkJqSWZMVGZOV0NHMFQ2M2VaYmJEZFZoSmJUTjgtSmZlaUx4dW1oZW53In0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlCZVZ5RXBDb0NPeXJ6VDhDSHlvQW1acU1CT1o0VTZqcm1sdUt1SjlxS0pkZyIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQnhkcHlyamlVSFZ1akNRWTBKMkhBUFFYZnNwWFBKYWluV21mV3RNcFhneFEifX0', + iss: 'https://self-issued.me/v2/openid-vc', + exp: 1674786463, + iat: 1674772063, + nonce: '40252afc-6a82-4a2e-905f-e41f122ef575', + jti: '0f5dafed-0d82-43b1-af79-40440e3f1366', + _vp_token: { + presentation_submission: { + descriptor_map: [ + { + path: '$', + format: 'jwt_vp', + path_nested: { + path: '$.verifiableCredential[0]', + format: 'jwt_vc', + id: 'VerifiedEmployeeVC', + }, + id: 'VerifiedEmployeeVC', + }, + ], + definition_id: '649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9', + id: '9af24e8a-c8f3-4b9a-9161-b715e77a6010', + }, + }, + }; + public static presentationDef = { + input_descriptors: [ + { + schema: [ + { + uri: 'VerifiedEmployee', + }, + ], + purpose: 'We need to verify that you have a valid VerifiedEmployee Verifiable Credential.', + name: 'VerifiedEmployeeVC', + id: 'VerifiedEmployeeVC', + }, + ], + id: '649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9', + }; + + public static requestObjectJwt = + 'eyJraWQiOiJkaWQ6aW9uOkVpQldlOVJ0SFQ3VlotSnVmZjhPbm5KQXlGSnRDb2tjWUh4MUNRa0Z0cGw3cHc6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKclpYa3RNU0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpqY25ZaU9pSkZaREkxTlRFNUlpd2lhM1I1SWpvaVQwdFFJaXdpZUNJNklrTmZUMVZLZUVnMmFVbGpRelpZWkU1b04wcHRReTFVU0ZoQlZtRllibloxT1U5RlJWbzRkSEU1VGtraUxDSnJhV1FpT2lKclpYa3RNU0o5TENKd2RYSndiM05sY3lJNld5SmhkWFJvWlc1MGFXTmhkR2x2YmlKZExDSjBlWEJsSWpvaVNuTnZibGRsWWt0bGVUSXdNakFpZlYxOWZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVOWVRrSnFTV1pNVkdaT1YwTkhNRlEyTTJWYVltSkVaRlpvU21KVVRqZ3RTbVpsYVV4NGRXMW9aVzUzSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbENaVlo1UlhCRGIwTlBlWEo2VkRoRFNIbHZRVzFhY1UxQ1QxbzBWVFpxY20xc2RVdDFTamx4UzBwa1p5SXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFuaGtjSGx5YW1sVlNGWjFha05SV1RCS01raEJVRkZZWm5Od1dGQktZV2x1VjIxbVYzUk5jRmhuZUZFaWZYMCNrZXktMSIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJyZXNwb25zZV90eXBlIjoiaWRfdG9rZW4iLCJub25jZSI6IjQwMjUyYWZjLTZhODItNGEyZS05MDVmLWU0MWYxMjJlZjU3NSIsImNsaWVudF9pZCI6ImRpZDppb246RWlCV2U5UnRIVDdWWi1KdWZmOE9ubkpBeUZKdENva2NZSHgxQ1FrRnRwbDdwdzpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUpyWlhrdE1TSXNJbkIxWW14cFkwdGxlVXAzYXlJNmV5SmpjbllpT2lKRlpESTFOVEU1SWl3aWEzUjVJam9pVDB0UUlpd2llQ0k2SWtOZlQxVktlRWcyYVVsalF6WllaRTVvTjBwdFF5MVVTRmhCVm1GWWJuWjFPVTlGUlZvNGRIRTVUa2tpTENKcmFXUWlPaUpyWlhrdE1TSjlMQ0p3ZFhKd2IzTmxjeUk2V3lKaGRYUm9aVzUwYVdOaGRHbHZiaUpkTENKMGVYQmxJam9pU25OdmJsZGxZa3RsZVRJd01qQWlmVjE5ZlYwc0luVndaR0YwWlVOdmJXMXBkRzFsYm5RaU9pSkZhVU5ZVGtKcVNXWk1WR1pPVjBOSE1GUTJNMlZhWW1KRVpGWm9TbUpVVGpndFNtWmxhVXg0ZFcxb1pXNTNJbjBzSW5OMVptWnBlRVJoZEdFaU9uc2laR1ZzZEdGSVlYTm9Jam9pUldsQ1pWWjVSWEJEYjBOUGVYSjZWRGhEU0hsdlFXMWFjVTFDVDFvMFZUWnFjbTFzZFV0MVNqbHhTMHBrWnlJc0luSmxZMjkyWlhKNVEyOXRiV2wwYldWdWRDSTZJa1ZwUW5oa2NIbHlhbWxWU0ZaMWFrTlJXVEJLTWtoQlVGRllabk53V0ZCS1lXbHVWMjFtVjNSTmNGaG5lRkVpZlgwIiwicmVzcG9uc2VfbW9kZSI6InBvc3QiLCJuYmYiOjE2NzQ3NzIwNjMsInNjb3BlIjoib3BlbmlkIiwiY2xhaW1zIjp7InZwX3Rva2VuIjp7InByZXNlbnRhdGlvbl9kZWZpbml0aW9uIjp7ImlkIjoiNjQ5ZDhjM2MtZjVhYy00MWJkLTljMTktNTgwNGVhMWI4ZmU5IiwiaW5wdXRfZGVzY3JpcHRvcnMiOlt7ImlkIjoiVmVyaWZpZWRFbXBsb3llZVZDIiwibmFtZSI6IlZlcmlmaWVkRW1wbG95ZWVWQyIsInB1cnBvc2UiOiJXZSBuZWVkIHRvIHZlcmlmeSB0aGF0IHlvdSBoYXZlIGEgVmVyaWZpZWRFbXBsb3llZSBWZXJpZmlhYmxlIENyZWRlbnRpYWwuIiwic2NoZW1hIjpbeyJ1cmkiOiJWZXJpZmllZEVtcGxveWVlIn1dfV19fX0sInJlZ2lzdHJhdGlvbiI6eyJjbGllbnRfbmFtZSI6IkV4YW1wbGUgVmVyaWZpZXIiLCJ0b3NfdXJpIjoiaHR0cHM6XC9cL2V4YW1wbGUuY29tXC92ZXJpZmllci1pbmZvIiwibG9nb191cmkiOiJodHRwczpcL1wvZXhhbXBsZS5jb21cL3ZlcmlmaWVyLWljb24ucG5nIiwic3ViamVjdF9zeW50YXhfdHlwZXNfc3VwcG9ydGVkIjpbImRpZDppb24iXSwidnBfZm9ybWF0cyI6eyJqd3RfdnAiOnsiYWxnIjpbIkVkRFNBIiwiRVMyNTZLIl19LCJqd3RfdmMiOnsiYWxnIjpbIkVkRFNBIiwiRVMyNTZLIl19fX0sInN0YXRlIjoiNjQ5ZDhjM2MtZjVhYy00MWJkLTljMTktNTgwNGVhMWI4ZmU5IiwicmVkaXJlY3RfdXJpIjoiaHR0cHM6XC9cL2V4YW1wbGUuY29tXC9zaW9wLXJlc3BvbnNlIiwiZXhwIjoxNjc0Nzc1NjYzLCJpYXQiOjE2NzQ3NzIwNjMsImp0aSI6ImYwZTZkY2Y1LTNmZTYtNDUwNy1hZGM5LWI0OTZkYWYzNDUxMiJ9.znX9h8l8JYoy8BHlnZzRDBEpaAv3hkb_XfUEzG-9eZID3tJjJdrO7PAr4kTay-nxvMhkzNsQg1rCZsjOMbKbBg'; + public static requestObjectPayload = + '\n' + + ' {\n' + + ' "kid" : "did:ion:EiBWe9RtHT7VZ-Juff8OnnJAyFJtCokcYHx1CQkFtpl7pw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkNfT1VKeEg2aUljQzZYZE5oN0ptQy1USFhBVmFYbnZ1OU9FRVo4dHE5TkkiLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUNYTkJqSWZMVGZOV0NHMFQ2M2VaYmJEZFZoSmJUTjgtSmZlaUx4dW1oZW53In0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlCZVZ5RXBDb0NPeXJ6VDhDSHlvQW1acU1CT1o0VTZqcm1sdUt1SjlxS0pkZyIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQnhkcHlyamlVSFZ1akNRWTBKMkhBUFFYZnNwWFBKYWluV21mV3RNcFhneFEifX0#key-1",\n' + + ' "typ" : "JWT",\n' + + ' "alg" : "EdDSA"\n' + + ' }.\n' + + ' {\n' + + ' "response_type" : "id_token",\n' + + ' "nonce" : "40252afc-6a82-4a2e-905f-e41f122ef575",\n' + + ' "client_id" : "did:ion:EiBWe9RtHT7VZ-Juff8OnnJAyFJtCokcYHx1CQkFtpl7pw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkNfT1VKeEg2aUljQzZYZE5oN0ptQy1USFhBVmFYbnZ1OU9FRVo4dHE5TkkiLCJraWQiOiJrZXktMSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUNYTkJqSWZMVGZOV0NHMFQ2M2VaYmJEZFZoSmJUTjgtSmZlaUx4dW1oZW53In0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlCZVZ5RXBDb0NPeXJ6VDhDSHlvQW1acU1CT1o0VTZqcm1sdUt1SjlxS0pkZyIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQnhkcHlyamlVSFZ1akNRWTBKMkhBUFFYZnNwWFBKYWluV21mV3RNcFhneFEifX0",\n' + + ' "response_mode" : "post",\n' + + ' "nbf" : 1674772063,\n' + + ' "scope" : "openid",\n' + + ' "claims" : {\n' + + ' "vp_token" : {\n' + + ' "presentation_definition" : {\n' + + ' "input_descriptors" : [ {\n' + + ' "schema" : [ {\n' + + ' "uri" : "VerifiedEmployee"\n' + + ' } ],\n' + + ' "purpose" : "We need to verify that you have a valid VerifiedEmployee Verifiable Credential.",\n' + + ' "name" : "VerifiedEmployeeVC",\n' + + ' "id" : "VerifiedEmployeeVC"\n' + + ' } ],\n' + + ' "id" : "649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9"\n' + + ' }\n' + + ' }\n' + + ' },\n' + + ' "registration" : {\n' + + ' "logo_uri" : "https://example.com/verifier-icon.png",\n' + + ' "tos_uri" : "https://example.com/verifier-info",\n' + + ' "client_name" : "Example Verifier",\n' + + ' "vp_formats" : {\n' + + ' "jwt_vc" : {\n' + + ' "alg" : [ "EdDSA", "ES256K" ]\n' + + ' },\n' + + ' "jwt_vp" : {\n' + + ' "alg" : [ "EdDSA", "ES256K" ]\n' + + ' }\n' + + ' },\n' + + ' "subject_syntax_types_supported" : [ "did:ion" ]\n' + + ' },\n' + + ' "state" : "649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9",\n' + + ' "redirect_uri" : "https://example.com/siop-response",\n' + + ' "exp" : 1674775663,\n' + + ' "iat" : 1674772063,\n' + + ' "jti" : "f0e6dcf5-3fe6-4507-adc9-b496daf34512"\n' + + ' }.\n' + + ' [withSignature]\n'; + + public static authorizationResponsePayload = { + state: '649d8c3c-f5ac-41bd-9c19-5804ea1b8fe9', + id_token: + 'eyJraWQiOiJkaWQ6aW9uOkVpQWVNNk5vOWtkcG9zNl9laEJVRGg0UklOWTRVU0RNaC1RZFdrc21zSTNXa0E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKclpYa3RNU0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpqY25ZaU9pSkZaREkxTlRFNUlpd2lhM1I1SWpvaVQwdFFJaXdpZUNJNkluY3dOazlXTjJVMmJsUjFjblEyUnpsV2NGWlllRWwzV1c1NWFtWjFjSGhsUjNsTFFsTXRZbXh4ZG1jaUxDSnJhV1FpT2lKclpYa3RNU0o5TENKd2RYSndiM05sY3lJNld5SmhkWFJvWlc1MGFXTmhkR2x2YmlKZExDSjBlWEJsSWpvaVNuTnZibGRsWWt0bGVUSXdNakFpZlYxOWZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVGU05HUlZRbXhxTldOR2EzZE1ka3BUV1VZelZFeGpMVjgxTVdoRFgyeFphR3hYWmt4V1oyOXNlVFJSSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEVjVkp5V1U1ZlYzSlRha0ZRZG5sRllsSlFSVms0V1ZoUFJtTnZUMFJUWkV4VVRXSXRNMkZLVkVsR1FTSXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFVd3lNRmRZYWtwUVFXNTRXV2RRWTFVNVJWOVBPRTFPZEhOcFFrMDBRa3RwYVZOd1QzWkZUV3BWT1VFaWZYMCNrZXktMSIsImFsZyI6IkVkRFNBIn0.eyJzdWIiOiJkaWQ6aW9uOkVpQWVNNk5vOWtkcG9zNl9laEJVRGg0UklOWTRVU0RNaC1RZFdrc21zSTNXa0E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKclpYa3RNU0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpqY25ZaU9pSkZaREkxTlRFNUlpd2lhM1I1SWpvaVQwdFFJaXdpZUNJNkluY3dOazlXTjJVMmJsUjFjblEyUnpsV2NGWlllRWwzV1c1NWFtWjFjSGhsUjNsTFFsTXRZbXh4ZG1jaUxDSnJhV1FpT2lKclpYa3RNU0o5TENKd2RYSndiM05sY3lJNld5SmhkWFJvWlc1MGFXTmhkR2x2YmlKZExDSjBlWEJsSWpvaVNuTnZibGRsWWt0bGVUSXdNakFpZlYxOWZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVGU05HUlZRbXhxTldOR2EzZE1ka3BUV1VZelZFeGpMVjgxTVdoRFgyeFphR3hYWmt4V1oyOXNlVFJSSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEVjVkp5V1U1ZlYzSlRha0ZRZG5sRllsSlFSVms0V1ZoUFJtTnZUMFJUWkV4VVRXSXRNMkZLVkVsR1FTSXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFVd3lNRmRZYWtwUVFXNTRXV2RRWTFVNVJWOVBPRTFPZEhOcFFrMDBRa3RwYVZOd1QzWkZUV3BWT1VFaWZYMCIsImF1ZCI6ImRpZDppb246RWlCV2U5UnRIVDdWWi1KdWZmOE9ubkpBeUZKdENva2NZSHgxQ1FrRnRwbDdwdzpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUpyWlhrdE1TSXNJbkIxWW14cFkwdGxlVXAzYXlJNmV5SmpjbllpT2lKRlpESTFOVEU1SWl3aWEzUjVJam9pVDB0UUlpd2llQ0k2SWtOZlQxVktlRWcyYVVsalF6WllaRTVvTjBwdFF5MVVTRmhCVm1GWWJuWjFPVTlGUlZvNGRIRTVUa2tpTENKcmFXUWlPaUpyWlhrdE1TSjlMQ0p3ZFhKd2IzTmxjeUk2V3lKaGRYUm9aVzUwYVdOaGRHbHZiaUpkTENKMGVYQmxJam9pU25OdmJsZGxZa3RsZVRJd01qQWlmVjE5ZlYwc0luVndaR0YwWlVOdmJXMXBkRzFsYm5RaU9pSkZhVU5ZVGtKcVNXWk1WR1pPVjBOSE1GUTJNMlZhWW1KRVpGWm9TbUpVVGpndFNtWmxhVXg0ZFcxb1pXNTNJbjBzSW5OMVptWnBlRVJoZEdFaU9uc2laR1ZzZEdGSVlYTm9Jam9pUldsQ1pWWjVSWEJEYjBOUGVYSjZWRGhEU0hsdlFXMWFjVTFDVDFvMFZUWnFjbTFzZFV0MVNqbHhTMHBrWnlJc0luSmxZMjkyWlhKNVEyOXRiV2wwYldWdWRDSTZJa1ZwUW5oa2NIbHlhbWxWU0ZaMWFrTlJXVEJLTWtoQlVGRllabk53V0ZCS1lXbHVWMjFtVjNSTmNGaG5lRkVpZlgwIiwiaXNzIjoiaHR0cHM6XC9cL3NlbGYtaXNzdWVkLm1lXC92Mlwvb3BlbmlkLXZjIiwiZXhwIjoxNjc0Nzg2NDYzLCJpYXQiOjE2NzQ3NzIwNjMsIm5vbmNlIjoiNDAyNTJhZmMtNmE4Mi00YTJlLTkwNWYtZTQxZjEyMmVmNTc1IiwianRpIjoiMGY1ZGFmZWQtMGQ4Mi00M2IxLWFmNzktNDA0NDBlM2YxMzY2IiwiX3ZwX3Rva2VuIjp7InByZXNlbnRhdGlvbl9zdWJtaXNzaW9uIjp7ImlkIjoiOWFmMjRlOGEtYzhmMy00YjlhLTkxNjEtYjcxNWU3N2E2MDEwIiwiZGVmaW5pdGlvbl9pZCI6IjY0OWQ4YzNjLWY1YWMtNDFiZC05YzE5LTU4MDRlYTFiOGZlOSIsImRlc2NyaXB0b3JfbWFwIjpbeyJpZCI6IlZlcmlmaWVkRW1wbG95ZWVWQyIsImZvcm1hdCI6Imp3dF92cCIsInBhdGgiOiIkIiwicGF0aF9uZXN0ZWQiOnsiaWQiOiJWZXJpZmllZEVtcGxveWVlVkMiLCJmb3JtYXQiOiJqd3RfdmMiLCJwYXRoIjoiJC52ZXJpZmlhYmxlQ3JlZGVudGlhbFswXSJ9fV19fX0.jh-SnpQcYPGEb_N5mqKUKCi9pA2OqxXw7BbAYuQwQat69KqpHA0sEZ1tOTOwsVP9UCfjmVg_8z0I_TvKkEkCBA', + vp_token: + 'eyJraWQiOiJkaWQ6aW9uOkVpQWVNNk5vOWtkcG9zNl9laEJVRGg0UklOWTRVU0RNaC1RZFdrc21zSTNXa0E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKclpYa3RNU0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpqY25ZaU9pSkZaREkxTlRFNUlpd2lhM1I1SWpvaVQwdFFJaXdpZUNJNkluY3dOazlXTjJVMmJsUjFjblEyUnpsV2NGWlllRWwzV1c1NWFtWjFjSGhsUjNsTFFsTXRZbXh4ZG1jaUxDSnJhV1FpT2lKclpYa3RNU0o5TENKd2RYSndiM05sY3lJNld5SmhkWFJvWlc1MGFXTmhkR2x2YmlKZExDSjBlWEJsSWpvaVNuTnZibGRsWWt0bGVUSXdNakFpZlYxOWZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVGU05HUlZRbXhxTldOR2EzZE1ka3BUV1VZelZFeGpMVjgxTVdoRFgyeFphR3hYWmt4V1oyOXNlVFJSSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEVjVkp5V1U1ZlYzSlRha0ZRZG5sRllsSlFSVms0V1ZoUFJtTnZUMFJUWkV4VVRXSXRNMkZLVkVsR1FTSXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFVd3lNRmRZYWtwUVFXNTRXV2RRWTFVNVJWOVBPRTFPZEhOcFFrMDBRa3RwYVZOd1QzWkZUV3BWT1VFaWZYMCNrZXktMSIsImFsZyI6IkVkRFNBIn0.eyJhdWQiOiJkaWQ6aW9uOkVpQldlOVJ0SFQ3VlotSnVmZjhPbm5KQXlGSnRDb2tjWUh4MUNRa0Z0cGw3cHc6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKclpYa3RNU0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpqY25ZaU9pSkZaREkxTlRFNUlpd2lhM1I1SWpvaVQwdFFJaXdpZUNJNklrTmZUMVZLZUVnMmFVbGpRelpZWkU1b04wcHRReTFVU0ZoQlZtRllibloxT1U5RlJWbzRkSEU1VGtraUxDSnJhV1FpT2lKclpYa3RNU0o5TENKd2RYSndiM05sY3lJNld5SmhkWFJvWlc1MGFXTmhkR2x2YmlKZExDSjBlWEJsSWpvaVNuTnZibGRsWWt0bGVUSXdNakFpZlYxOWZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVOWVRrSnFTV1pNVkdaT1YwTkhNRlEyTTJWYVltSkVaRlpvU21KVVRqZ3RTbVpsYVV4NGRXMW9aVzUzSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbENaVlo1UlhCRGIwTlBlWEo2VkRoRFNIbHZRVzFhY1UxQ1QxbzBWVFpxY20xc2RVdDFTamx4UzBwa1p5SXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFuaGtjSGx5YW1sVlNGWjFha05SV1RCS01raEJVRkZZWm5Od1dGQktZV2x1VjIxbVYzUk5jRmhuZUZFaWZYMCIsImlzcyI6ImRpZDppb246RWlBZU02Tm85a2Rwb3M2X2VoQlVEaDRSSU5ZNFVTRE1oLVFkV2tzbXNJM1drQTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUpyWlhrdE1TSXNJbkIxWW14cFkwdGxlVXAzYXlJNmV5SmpjbllpT2lKRlpESTFOVEU1SWl3aWEzUjVJam9pVDB0UUlpd2llQ0k2SW5jd05rOVdOMlUyYmxSMWNuUTJSemxXY0ZaWWVFbDNXVzU1YW1aMWNIaGxSM2xMUWxNdFlteHhkbWNpTENKcmFXUWlPaUpyWlhrdE1TSjlMQ0p3ZFhKd2IzTmxjeUk2V3lKaGRYUm9aVzUwYVdOaGRHbHZiaUpkTENKMGVYQmxJam9pU25OdmJsZGxZa3RsZVRJd01qQWlmVjE5ZlYwc0luVndaR0YwWlVOdmJXMXBkRzFsYm5RaU9pSkZhVUZTTkdSVlFteHFOV05HYTNkTWRrcFRXVVl6VkV4akxWODFNV2hEWDJ4WmFHeFhaa3hXWjI5c2VUUlJJbjBzSW5OMVptWnBlRVJoZEdFaU9uc2laR1ZzZEdGSVlYTm9Jam9pUldsRWNWSnlXVTVmVjNKVGFrRlFkbmxGWWxKUVJWazRXVmhQUm1OdlQwUlRaRXhVVFdJdE0yRktWRWxHUVNJc0luSmxZMjkyWlhKNVEyOXRiV2wwYldWdWRDSTZJa1ZwUVV3eU1GZFlha3BRUVc1NFdXZFFZMVU1UlY5UE9FMU9kSE5wUWswMFFrdHBhVk53VDNaRlRXcFZPVUVpZlgwIiwidnAiOnsiQGNvbnRleHQiOlsiaHR0cHM6XC9cL3d3dy53My5vcmdcLzIwMThcL2NyZWRlbnRpYWxzXC92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwidmVyaWZpYWJsZUNyZWRlbnRpYWwiOlsiZXlKcmFXUWlPaUprYVdRNmFXOXVPa1ZwUWtGQk9UbFVRV1Y2ZUV0U1l6SjNkWFZDYm5JMGVucEhjMU15V1dOelQwRTBTVkJSVmpCTFdUWTBXR2M2WlhsS2ExcFhlREJaVTBrMlpYbEtkMWxZVW1waFIxWjZTV3B3WW1WNVNtaFpNMUp3WWpJMGFVOXBTbmxhV0VKeldWZE9iRWxwZDJsYVJ6bHFaRmN4YkdKdVVXbFBibk5wWTBoV2FXSkhiR3BUTWxZMVkzbEpObGN6YzJsaFYxRnBUMmxLY2xwWWEzUk5VMGx6U1c1Q01WbHRlSEJaTUhSc1pWVndNMkY1U1RabGVVcHFZMjVaYVU5cFNrWmFSRWt4VGxSRk5VbHBkMmxoTTFJMVNXcHZhVlF3ZEZGSmFYZHBaVU5KTmtsclpHNVhhMlJWV25wb2JGRXlSVE5pUmxsNVQwVXhUVTlWY0ZWaVZVcFdaRzF6TTFKR2JFTlpiVnBUVXpGa1RXRklZekpPVlhCMlRWaE5hVXhEU25KaFYxRnBUMmxLY2xwWWEzUk5VMG81VEVOS2QyUllTbmRpTTA1c1kzbEpObGQ1U21oa1dGSnZXbGMxTUdGWFRtaGtSMngyWW1sS1pFeERTakJsV0VKc1NXcHZhVk51VG5aaWJHUnNXV3QwYkdWVVNYZE5ha0ZwWmxZeE9XWldNSE5KYmxaM1drZEdNRnBWVG5aaVZ6RndaRWN4YkdKdVVXbFBhVXBHWVZWU1MxWXdXakpYVlVvMVVYcGtNbUY2UVRKTldFRjZaRWhaZDJReU9WZFRWR3MxVFZSR1VWUkhaM2RWVm5BMFkxZHdXazB5V1RSTlZrWlNTVzR3YzBsdVRqRmFiVnB3WlVWU2FHUkhSV2xQYm5OcFdrZFdjMlJIUmtsWldFNXZTV3B2YVZKWGJFSllNVkoyVm14T1FscEVRbFJTVjNoUFZUSldjbEV4YXpGVlJGWklXakF4UzFGNU1VMVVWbkJHV1RKYVUxWXlXbkZhUjA1aFdWaEtSbEZUU1hOSmJrcHNXVEk1TWxwWVNqVlJNamwwWWxkc01HSlhWblZrUTBrMlNXdFdjRkpFVGpCYVZGWTBaVVpzYVdWdFNtOWtNSEJaWkVWVmQxb3lkRnBXTTFvelRXeGFNbFpHUWpSTlZUbHNZVEJTVkdOWVpIVmFlbEpVVjIxamFXWllNQ05yWlhrdE1TSXNJblI1Y0NJNklrcFhWQ0lzSW1Gc1p5STZJa1ZrUkZOQkluMC5leUp6ZFdJaU9pSmthV1E2YVc5dU9rVnBRV1ZOTms1dk9XdGtjRzl6Tmw5bGFFSlZSR2cwVWtsT1dUUlZVMFJOYUMxUlpGZHJjMjF6U1ROWGEwRTZaWGxLYTFwWGVEQlpVMGsyWlhsS2QxbFlVbXBoUjFaNlNXcHdZbVY1U21oWk0xSndZakkwYVU5cFNubGFXRUp6V1ZkT2JFbHBkMmxhUnpscVpGY3hiR0p1VVdsUGJuTnBZMGhXYVdKSGJHcFRNbFkxWTNsSk5sY3pjMmxoVjFGcFQybEtjbHBZYTNSTlUwbHpTVzVDTVZsdGVIQlpNSFJzWlZWd00yRjVTVFpsZVVwcVkyNVphVTlwU2taYVJFa3hUbFJGTlVscGQybGhNMUkxU1dwdmFWUXdkRkZKYVhkcFpVTkpOa2x1WTNkT2F6bFhUakpWTW1Kc1VqRmpibEV5VW5wc1YyTkdXbGxsUld3elYxYzFOV0Z0V2pGalNHaHNVak5zVEZGc1RYUlpiWGg0WkcxamFVeERTbkpoVjFGcFQybEtjbHBZYTNSTlUwbzVURU5LZDJSWVNuZGlNMDVzWTNsSk5sZDVTbWhrV0ZKdldsYzFNR0ZYVG1oa1IyeDJZbWxLWkV4RFNqQmxXRUpzU1dwdmFWTnVUblppYkdSc1dXdDBiR1ZVU1hkTmFrRnBabFl4T1daV01ITkpibFozV2tkR01GcFZUblppVnpGd1pFY3hiR0p1VVdsUGFVcEdZVlZHVTA1SFVsWlJiWGh4VGxkT1IyRXpaRTFrYTNCVVYxVlplbFpGZUdwTVZqZ3hUVmRvUkZneWVGcGhSM2hZV210NFYxb3lPWE5sVkZKU1NXNHdjMGx1VGpGYWJWcHdaVVZTYUdSSFJXbFBibk5wV2tkV2MyUkhSa2xaV0U1dlNXcHZhVkpYYkVWalZrcDVWMVUxWmxZelNsUmhhMFpSWkc1c1JsbHNTbEZTVm1zMFYxWm9VRkp0VG5aVU1GSlVXa1Y0VlZSWFNYUk5Na1pMVmtWc1IxRlRTWE5KYmtwc1dUSTVNbHBZU2pWUk1qbDBZbGRzTUdKWFZuVmtRMGsyU1d0V2NGRlZkM2xOUm1SWllXdHdVVkZYTlRSWFYyUlJXVEZWTlZKV09WQlBSVEZQWkVoT2NGRnJNREJSYTNSd1lWWk9kMVF6V2taVVYzQldUMVZGYVdaWU1DSXNJbTVpWmlJNk1UWTNORGMzTWpBMk15d2lhWE56SWpvaVpHbGtPbWx2YmpwRmFVSkJRVGs1VkVGbGVuaExVbU15ZDNWMVFtNXlOSHA2UjNOVE1sbGpjMDlCTkVsUVVWWXdTMWsyTkZobk9tVjVTbXRhVjNnd1dWTkpObVY1U25kWldGSnFZVWRXZWtscWNHSmxlVXBvV1ROU2NHSXlOR2xQYVVwNVdsaENjMWxYVG14SmFYZHBXa2M1YW1SWE1XeGlibEZwVDI1emFXTklWbWxpUjJ4cVV6SldOV041U1RaWE0zTnBZVmRSYVU5cFNuSmFXR3QwVFZOSmMwbHVRakZaYlhod1dUQjBiR1ZWY0ROaGVVazJaWGxLYW1OdVdXbFBhVXBHV2tSSk1VNVVSVFZKYVhkcFlUTlNOVWxxYjJsVU1IUlJTV2wzYVdWRFNUWkphMlJ1VjJ0a1ZWcDZhR3hSTWtVellrWlplVTlGTVUxUFZYQlZZbFZLVm1SdGN6TlNSbXhEV1cxYVUxTXhaRTFoU0dNeVRsVndkazFZVFdsTVEwcHlZVmRSYVU5cFNuSmFXR3QwVFZOS09VeERTbmRrV0VwM1lqTk9iR041U1RaWGVVcG9aRmhTYjFwWE5UQmhWMDVvWkVkc2RtSnBTbVJNUTBvd1pWaENiRWxxYjJsVGJrNTJZbXhrYkZscmRHeGxWRWwzVFdwQmFXWldNVGxtVmpCelNXNVdkMXBIUmpCYVZVNTJZbGN4Y0dSSE1XeGlibEZwVDJsS1JtRlZVa3RXTUZveVYxVktOVkY2WkRKaGVrRXlUVmhCZW1SSVdYZGtNamxYVTFSck5VMVVSbEZVUjJkM1ZWWndOR05YY0ZwTk1sazBUVlpHVWtsdU1ITkpiazR4V20xYWNHVkZVbWhrUjBWcFQyNXphVnBIVm5Oa1IwWkpXVmhPYjBscWIybFNWMnhDV0RGU2RsWnNUa0phUkVKVVVsZDRUMVV5Vm5KUk1Xc3hWVVJXU0Zvd01VdFJlVEZOVkZad1Jsa3lXbE5XTWxweFdrZE9ZVmxZU2taUlUwbHpTVzVLYkZreU9USmFXRW8xVVRJNWRHSlhiREJpVjFaMVpFTkpOa2xyVm5CU1JFNHdXbFJXTkdWR2JHbGxiVXB2WkRCd1dXUkZWWGRhTW5SYVZqTmFNMDFzV2pKV1JrSTBUVlU1YkdFd1VsUmpXR1IxV25wU1ZGZHRZMmxtV0RBaUxDSnBZWFFpT2pFMk56UTNOekl3TmpNc0luWmpJanA3SWtCamIyNTBaWGgwSWpwYkltaDBkSEJ6T2x3dlhDOTNkM2N1ZHpNdWIzSm5YQzh5TURFNFhDOWpjbVZrWlc1MGFXRnNjMXd2ZGpFaVhTd2lkSGx3WlNJNld5SldaWEpwWm1saFlteGxRM0psWkdWdWRHbGhiQ0lzSWxabGNtbG1hV1ZrUlcxd2JHOTVaV1VpWFN3aVkzSmxaR1Z1ZEdsaGJGTjFZbXBsWTNRaU9uc2laR2x6Y0d4aGVVNWhiV1VpT2lKUVlYUWdVMjFwZEdnaUxDSm5hWFpsYms1aGJXVWlPaUpRWVhRaUxDSnFiMkpVYVhSc1pTSTZJbGR2Y210bGNpSXNJbk4xY201aGJXVWlPaUpUYldsMGFDSXNJbkJ5WldabGNuSmxaRXhoYm1kMVlXZGxJam9pWlc0dFZWTWlMQ0p0WVdsc0lqb2ljR0YwTG5OdGFYUm9RR1Y0WVcxd2JHVXVZMjl0SW4wc0ltTnlaV1JsYm5ScFlXeFRkR0YwZFhNaU9uc2lhV1FpT2lKb2RIUndjenBjTDF3dlpYaGhiWEJzWlM1amIyMWNMMkZ3YVZ3dllYTjBZWFIxYzJ4cGMzUmNMMlJwWkRwcGIyNDZSV2xDUVVFNU9WUkJaWHA0UzFKak1uZDFkVUp1Y2pSNmVrZHpVekpaWTNOUFFUUkpVRkZXTUV0Wk5qUllaMXd2TVNNd0lpd2lkSGx3WlNJNklsSmxkbTlqWVhScGIyNU1hWE4wTWpBeU1WTjBZWFIxY3lJc0luTjBZWFIxYzB4cGMzUkpibVJsZUNJNklqQWlMQ0p6ZEdGMGRYTk1hWE4wUTNKbFpHVnVkR2xoYkNJNkltaDBkSEJ6T2x3dlhDOWxlR0Z0Y0d4bExtTnZiVnd2WVhCcFhDOWhjM1JoZEhWemJHbHpkRnd2Wkdsa09tbHZianBGYVVKQlFUazVWRUZsZW5oTFVtTXlkM1YxUW01eU5IcDZSM05UTWxsamMwOUJORWxRVVZZd1MxazJORmhuWEM4eEluMTlMQ0pxZEdraU9pSmlPREExTW1ZNVl5MDBaamhqTFRRek16QXRZbUpqTVMwME1ETXpZamhsWlRWa05tSWlmUS5WRWlLQ3IzUlZTY1VNRjgxRnhnckdDbGRZeEtJSmM0dWNMWDN6MHhha21sX0dPeG5udndrbzNDNlFxajdKTVVJOUs3dlFVVU1Wakk4MUt4a3RZdDBBUSJdfSwiZXhwIjoxNjc0Nzg2NDYzLCJpYXQiOjE2NzQ3NzIwNjMsIm5vbmNlIjoiNDAyNTJhZmMtNmE4Mi00YTJlLTkwNWYtZTQxZjEyMmVmNTc1IiwianRpIjoiOWNlZGFjODYtYWU1MS00MWQwLWFlNmYtOTI5NjZhNjFlMWY1In0.X9q1amromvW0WuA7bkanc-8BC9axhXh8RhN9i87FluTBzK3SRtKBS0O0alHU3Ii5HixENljCnncTKxi5_rbvDg', + }; + + public static jwtVCFromVPToken = + 'eyJraWQiOiJkaWQ6aW9uOkVpQkFBOTlUQWV6eEtSYzJ3dXVCbnI0enpHc1MyWWNzT0E0SVBRVjBLWTY0WGc6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKclpYa3RNU0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpqY25ZaU9pSkZaREkxTlRFNUlpd2lhM1I1SWpvaVQwdFFJaXdpZUNJNklrZG5Xa2RVWnpobFEyRTNiRll5T0UxTU9VcFViVUpWZG1zM1JGbENZbVpTUzFkTWFIYzJOVXB2TVhNaUxDSnJhV1FpT2lKclpYa3RNU0o5TENKd2RYSndiM05sY3lJNld5SmhkWFJvWlc1MGFXTmhkR2x2YmlKZExDSjBlWEJsSWpvaVNuTnZibGRsWWt0bGVUSXdNakFpZlYxOWZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVSS1YwWjJXVUo1UXpkMmF6QTJNWEF6ZEhZd2QyOVdTVGs1TVRGUVRHZ3dVVnA0Y1dwWk0yWTRNVkZSSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEJYMVJ2VmxOQlpEQlRSV3hPVTJWclExazFVRFZIWjAxS1F5MU1UVnBGWTJaU1YyWnFaR05hWVhKRlFTSXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFJETjBaVFY0ZUZsaWVtSm9kMHBZZEVVd1oydFpWM1ozTWxaMlZGQjRNVTlsYTBSVGNYZHVaelJUV21jaWZYMCNrZXktMSIsInR5cCI6IkpXVCIsImFsZyI6IkVkRFNBIn0.eyJzdWIiOiJkaWQ6aW9uOkVpQWVNNk5vOWtkcG9zNl9laEJVRGg0UklOWTRVU0RNaC1RZFdrc21zSTNXa0E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKclpYa3RNU0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpqY25ZaU9pSkZaREkxTlRFNUlpd2lhM1I1SWpvaVQwdFFJaXdpZUNJNkluY3dOazlXTjJVMmJsUjFjblEyUnpsV2NGWlllRWwzV1c1NWFtWjFjSGhsUjNsTFFsTXRZbXh4ZG1jaUxDSnJhV1FpT2lKclpYa3RNU0o5TENKd2RYSndiM05sY3lJNld5SmhkWFJvWlc1MGFXTmhkR2x2YmlKZExDSjBlWEJsSWpvaVNuTnZibGRsWWt0bGVUSXdNakFpZlYxOWZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVGU05HUlZRbXhxTldOR2EzZE1ka3BUV1VZelZFeGpMVjgxTVdoRFgyeFphR3hYWmt4V1oyOXNlVFJSSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEVjVkp5V1U1ZlYzSlRha0ZRZG5sRllsSlFSVms0V1ZoUFJtTnZUMFJUWkV4VVRXSXRNMkZLVkVsR1FTSXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFVd3lNRmRZYWtwUVFXNTRXV2RRWTFVNVJWOVBPRTFPZEhOcFFrMDBRa3RwYVZOd1QzWkZUV3BWT1VFaWZYMCIsIm5iZiI6MTY3NDc3MjA2MywiaXNzIjoiZGlkOmlvbjpFaUJBQTk5VEFlenhLUmMyd3V1Qm5yNHp6R3NTMlljc09BNElQUVYwS1k2NFhnOmV5SmtaV3gwWVNJNmV5SndZWFJqYUdWeklqcGJleUpoWTNScGIyNGlPaUp5WlhCc1lXTmxJaXdpWkc5amRXMWxiblFpT25zaWNIVmliR2xqUzJWNWN5STZXM3NpYVdRaU9pSnJaWGt0TVNJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUpGWkRJMU5URTVJaXdpYTNSNUlqb2lUMHRRSWl3aWVDSTZJa2RuV2tkVVp6aGxRMkUzYkZZeU9FMU1PVXBVYlVKVmRtczNSRmxDWW1aU1MxZE1hSGMyTlVwdk1YTWlMQ0pyYVdRaU9pSnJaWGt0TVNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZWMTlmVjBzSW5Wd1pHRjBaVU52YlcxcGRHMWxiblFpT2lKRmFVUktWMFoyV1VKNVF6ZDJhekEyTVhBemRIWXdkMjlXU1RrNU1URlFUR2d3VVZwNGNXcFpNMlk0TVZGUkluMHNJbk4xWm1acGVFUmhkR0VpT25zaVpHVnNkR0ZJWVhOb0lqb2lSV2xCWDFSdlZsTkJaREJUUld4T1UyVnJRMWsxVURWSFowMUtReTFNVFZwRlkyWlNWMlpxWkdOYVlYSkZRU0lzSW5KbFkyOTJaWEo1UTI5dGJXbDBiV1Z1ZENJNklrVnBSRE4wWlRWNGVGbGllbUpvZDBwWWRFVXdaMnRaVjNaM01sWjJWRkI0TVU5bGEwUlRjWGR1WnpSVFdtY2lmWDAiLCJpYXQiOjE2NzQ3NzIwNjMsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOlwvXC93d3cudzMub3JnXC8yMDE4XC9jcmVkZW50aWFsc1wvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlZlcmlmaWVkRW1wbG95ZWUiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGlzcGxheU5hbWUiOiJQYXQgU21pdGgiLCJnaXZlbk5hbWUiOiJQYXQiLCJqb2JUaXRsZSI6IldvcmtlciIsInN1cm5hbWUiOiJTbWl0aCIsInByZWZlcnJlZExhbmd1YWdlIjoiZW4tVVMiLCJtYWlsIjoicGF0LnNtaXRoQGV4YW1wbGUuY29tIn0sImNyZWRlbnRpYWxTdGF0dXMiOnsiaWQiOiJodHRwczpcL1wvZXhhbXBsZS5jb21cL2FwaVwvYXN0YXR1c2xpc3RcL2RpZDppb246RWlCQUE5OVRBZXp4S1JjMnd1dUJucjR6ekdzUzJZY3NPQTRJUFFWMEtZNjRYZ1wvMSMwIiwidHlwZSI6IlJldm9jYXRpb25MaXN0MjAyMVN0YXR1cyIsInN0YXR1c0xpc3RJbmRleCI6IjAiLCJzdGF0dXNMaXN0Q3JlZGVudGlhbCI6Imh0dHBzOlwvXC9leGFtcGxlLmNvbVwvYXBpXC9hc3RhdHVzbGlzdFwvZGlkOmlvbjpFaUJBQTk5VEFlenhLUmMyd3V1Qm5yNHp6R3NTMlljc09BNElQUVYwS1k2NFhnXC8xIn19LCJqdGkiOiJiODA1MmY5Yy00ZjhjLTQzMzAtYmJjMS00MDMzYjhlZTVkNmIifQ.VEiKCr3RVScUMF81FxgrGCldYxKIJc4ucLX3z0xakml_GOxnnvwko3C6Qqj7JMUI9K7vQUUMVjI81KxktYt0AQ'; + + public static didDocument(did: string, vm: string, publicKeyJwk: JsonWebKey) { + return { + id: did, + '@context': [ + 'https://www.w3.org/ns/did/v1', + { + '@base': did, + }, + ], + service: [ + { + id: '#linkedin', + type: 'linkedin', + serviceEndpoint: 'linkedin.com/in/henry-tsai-6b884014', + }, + { + id: '#github', + type: 'github', + serviceEndpoint: 'github.com/thehenrytsai', + }, + ], + verificationMethod: [ + { + id: vm, + controller: did, + type: 'JsonWebKey2020', + publicKeyJwk, + }, + ], + authentication: [vm], + assertionMethod: [vm], + }; + } + + public static mockDID(did: string, vm: string, publickKeyJwk: JsonWebKey) { + nock('https://dev.uniresolver.io') + .get(`/1.0/identifiers/${did}`) + .times(100) + .reply(200, TestVectors.didDocument(did, vm, publickKeyJwk)); + } +} diff --git a/packages/siopv2/tsconfig.build.json b/packages/siopv2/tsconfig.build.json new file mode 100644 index 00000000..57c88787 --- /dev/null +++ b/packages/siopv2/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["test/**/*.ts", "generator/**/*.ts"], + "ts-node": { + "esm": false + } +} diff --git a/packages/siopv2/tsconfig.json b/packages/siopv2/tsconfig.json new file mode 100644 index 00000000..3cef7c88 --- /dev/null +++ b/packages/siopv2/tsconfig.json @@ -0,0 +1,38 @@ +{ + "extends": "../tsconfig-base.json", + "compilerOptions": { + "incremental": true, + "target": "ES6", + "outDir": "dist", + "moduleResolution": "node", + "module": "commonjs", + "declaration": true, + "inlineSourceMap": true, + "allowJs": true, + "esModuleInterop": true, + /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "resolveJsonModule": true, + /* Include modules imported with .json extension. */ // "strict": true /* Enable all strict type-checking options. */, + /* Additional Checks */ + "noUnusedLocals": true, + /* Report errors on unused locals. */ "noUnusedParameters": true, + /* Report errors on unused parameters. */ "noImplicitReturns": true, + /* Report error when not all code paths in function return a value. */ "noFallthroughCasesInSwitch": true, + /* Report errors for fallthrough cases in switch statement. */ /* Debugging Options */ "strict": false, + "traceResolution": false, + /* Report module resolution log messages. */ "listEmittedFiles": false, + "skipLibCheck": true, + /* Print names of generated files part of the compilation. */ "listFiles": false, + /* Print names of files part of the compilation. */ "pretty": true, + + /*"lib": [ + "ES2017" + ],*/ + "types": ["jest", "node"] + }, + "include": ["index.ts", "src/**/*.ts", "src/**/*.js", "test/**/*.ts", "generator/*.ts"], + "exclude": ["node_modules/**", "dist/**"], + "ts-node": { + "esm": true + }, + "compileOnSave": false +} diff --git a/packages/tsconfig-base.json b/packages/tsconfig-base.json index 4f45ea3d..bb555f0c 100644 --- a/packages/tsconfig-base.json +++ b/packages/tsconfig-base.json @@ -12,7 +12,8 @@ // "checkJs": true, /* Report errors in .js files. */ "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, "declaration": true /* Generates corresponding '.d.ts' file. */, - "sourceMap": true /* Generates corresponding '.map' file. */, + "sourceMap": false /* Generates corresponding '.map' file. */, + "inlineSourceMap": true, // "outFile": "./", /* Concatenate and emit output to single file. */ // "outDir": "./", /* Redirect output structure to the directory. */ // "rootDir": "./" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, diff --git a/packages/tsconfig.json b/packages/tsconfig.json index 5fa5bce5..30aa1d3e 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -16,6 +16,9 @@ }, { "path": "callback-example" + }, + { + "path": "siopv2" } ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index daa77d7b..5e32d033 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,8 @@ settings: overrides: node-fetch: 2.6.12 + isomorphic-webcrypto: npm:@sphereon/isomorphic-webcrypto@^2.4.0-unstable.4 + eslint>strip-ansi: 6.0.1 importers: @@ -55,7 +57,7 @@ importers: version: 2.0.3 '@digitalcredentials/ed25519-signature-2020': specifier: ^3.0.2 - version: 3.0.2(expo@48.0.20)(react-native@0.71.13) + version: 3.0.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@digitalcredentials/ed25519-verification-key-2020': specifier: ^4.0.0 version: 4.0.0 @@ -64,19 +66,19 @@ importers: version: 1.0.0(react-native@0.71.13) '@digitalcredentials/vc': specifier: ^5.0.0 - version: 5.0.0(expo@48.0.20)(react-native@0.71.13) + version: 5.0.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/oid4vc-common': + specifier: workspace:* + version: link:../common '@sphereon/oid4vci-client': specifier: workspace:* version: link:../client - '@sphereon/oid4vci-common': - specifier: workspace:* - version: link:../common '@sphereon/oid4vci-issuer': specifier: workspace:* version: link:../issuer '@sphereon/ssi-types': - specifier: 0.17.6-unstable.69 - version: 0.17.6-unstable.69 + specifier: 0.18.1 + version: 0.18.1 jose: specifier: ^4.10.0 version: 4.14.6 @@ -114,12 +116,12 @@ importers: packages/client: dependencies: - '@sphereon/oid4vci-common': + '@sphereon/oid4vc-common': specifier: workspace:* version: link:../common '@sphereon/ssi-types': - specifier: 0.17.6-unstable.69 - version: 0.17.6-unstable.69 + specifier: 0.18.1 + version: 0.18.1 cross-fetch: specifier: ^3.1.8 version: 3.1.8 @@ -206,8 +208,8 @@ importers: packages/common: dependencies: '@sphereon/ssi-types': - specifier: 0.17.6-unstable.69 - version: 0.17.6-unstable.69 + specifier: 0.18.1 + version: 0.18.1 cross-fetch: specifier: ^3.1.8 version: 3.1.8 @@ -219,17 +221,17 @@ importers: specifier: ^29.5.3 version: 29.5.5 typescript: - specifier: 5.0.4 - version: 5.0.4 + specifier: 5.3.3 + version: 5.3.3 packages/issuer: dependencies: - '@sphereon/oid4vci-common': + '@sphereon/oid4vc-common': specifier: workspace:* version: link:../common '@sphereon/ssi-types': - specifier: 0.17.6-unstable.69 - version: 0.17.6-unstable.69 + specifier: 0.18.1 + version: 0.18.1 awesome-qr: specifier: ^2.1.5-rc.0 version: 2.1.5-rc.0 @@ -255,18 +257,18 @@ importers: packages/issuer-rest: dependencies: - '@sphereon/oid4vci-common': + '@sphereon/oid4vc-common': specifier: workspace:* version: link:../common '@sphereon/oid4vci-issuer': specifier: workspace:* version: link:../issuer '@sphereon/ssi-express-support': - specifier: 0.17.6-unstable.69 - version: 0.17.6-unstable.69 + specifier: 0.18.1 + version: 0.18.1 '@sphereon/ssi-types': - specifier: 0.17.6-unstable.69 - version: 0.17.6-unstable.69 + specifier: 0.18.1 + version: 0.18.1 body-parser: specifier: ^1.20.2 version: 1.20.2 @@ -336,7 +338,173 @@ importers: version: 6.3.3 ts-jest: specifier: ^29.1.0 - version: 29.1.1(@babel/core@7.23.0)(jest@29.7.0)(typescript@5.0.4) + version: 29.1.1(@babel/core@7.23.0)(jest@29.7.0)(typescript@5.3.3) + + packages/siopv2: + dependencies: + '@astronautlabs/jsonpath': + specifier: ^1.1.2 + version: 1.1.2 + '@sphereon/did-uni-client': + specifier: ^0.6.1 + version: 0.6.1 + '@sphereon/oid4vc-common': + specifier: workspace:* + version: link:../common + '@sphereon/pex': + specifier: ^3.0.1 + version: 3.0.1 + '@sphereon/pex-models': + specifier: ^2.1.5 + version: 2.1.5 + '@sphereon/ssi-types': + specifier: 0.18.1 + version: 0.18.1 + '@sphereon/wellknown-dids-client': + specifier: ^0.1.3 + version: 0.1.3 + cross-fetch: + specifier: ^4.0.0 + version: 4.0.0 + did-jwt: + specifier: 6.11.6 + version: 6.11.6 + did-resolver: + specifier: ^4.1.0 + version: 4.1.0 + events: + specifier: ^3.3.0 + version: 3.3.0 + language-tags: + specifier: ^1.0.9 + version: 1.0.9 + multiformats: + specifier: ^11.0.2 + version: 11.0.2 + qs: + specifier: ^6.11.2 + version: 6.11.2 + sha.js: + specifier: ^2.4.11 + version: 2.4.11 + uint8arrays: + specifier: ^3.1.1 + version: 3.1.1 + uuid: + specifier: ^9.0.0 + version: 9.0.1 + devDependencies: + '@digitalcredentials/did-method-key': + specifier: ^2.0.3 + version: 2.0.3 + '@digitalcredentials/ed25519-signature-2020': + specifier: ^3.0.2 + version: 3.0.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@digitalcredentials/jsonld-signatures': + specifier: ^9.3.2 + version: 9.3.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@digitalcredentials/vc': + specifier: ^6.0.0 + version: 6.0.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@transmute/did-key-ed25519': + specifier: ^0.3.0-unstable.10 + version: 0.3.0-unstable.10 + '@transmute/ed25519-key-pair': + specifier: 0.7.0-unstable.82 + version: 0.7.0-unstable.82 + '@transmute/ed25519-signature-2018': + specifier: ^0.7.0-unstable.82 + version: 0.7.0-unstable.82 + '@types/jest': + specifier: ^29.5.11 + version: 29.5.11 + '@types/language-tags': + specifier: ^1.0.4 + version: 1.0.4 + '@types/qs': + specifier: ^6.9.11 + version: 6.9.11 + '@types/uuid': + specifier: ^9.0.7 + version: 9.0.7 + '@typescript-eslint/eslint-plugin': + specifier: ^5.52.0 + version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.50.0)(typescript@5.3.3) + '@typescript-eslint/parser': + specifier: ^5.52.0 + version: 5.62.0(eslint@8.50.0)(typescript@5.3.3) + ajv: + specifier: ^8.12.0 + version: 8.12.0 + bs58: + specifier: ^5.0.0 + version: 5.0.0 + codecov: + specifier: ^3.8.3 + version: 3.8.3 + cspell: + specifier: ^6.26.3 + version: 6.31.3 + dotenv: + specifier: ^16.3.1 + version: 16.3.1 + eslint: + specifier: ^8.34.0 + version: 8.50.0 + eslint-config-prettier: + specifier: ^8.6.0 + version: 8.10.0(eslint@8.50.0) + eslint-plugin-eslint-comments: + specifier: ^3.2.0 + version: 3.2.0(eslint@8.50.0) + eslint-plugin-import: + specifier: ^2.27.5 + version: 2.28.1(@typescript-eslint/parser@5.62.0)(eslint@8.50.0) + ethers: + specifier: ^6.10.0 + version: 6.10.0 + jest: + specifier: ^29.4.3 + version: 29.7.0(@types/node@18.18.0)(ts-node@10.9.1) + jest-junit: + specifier: ^15.0.0 + version: 15.0.0 + jose: + specifier: ^4.12.0 + version: 4.14.6 + jwt-decode: + specifier: ^3.1.2 + version: 3.1.2 + moment: + specifier: ^2.30.1 + version: 2.30.1 + nock: + specifier: ^13.4.0 + version: 13.5.0 + npm-run-all: + specifier: ^4.1.5 + version: 4.1.5 + open-cli: + specifier: ^7.1.0 + version: 7.2.0 + prettier: + specifier: ^2.8.8 + version: 2.8.8 + ts-interface-checker: + specifier: ^1.0.2 + version: 1.0.2 + ts-jest: + specifier: ^29.1.1 + version: 29.1.1(@babel/core@7.23.0)(jest@29.7.0)(typescript@5.3.3) + ts-json-schema-generator: + specifier: 1.5.0 + version: 1.5.0 + tsimp: + specifier: ^2.0.10 + version: 2.0.10(typescript@5.3.3) + typescript: + specifier: ^5.3.3 + version: 5.3.3 packages: @@ -345,6 +513,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /@adraffy/ens-normalize@1.10.0: + resolution: {integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==} + dev: true + /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} @@ -352,6 +524,12 @@ packages: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.19 + /@astronautlabs/jsonpath@1.1.2: + resolution: {integrity: sha512-FqL/muoreH7iltYC1EB5Tvox5E8NSOOPGkgns4G+qxRKl6k5dxEVljUjB5NcKESzkqwnUqWjSZkL61XGYOuV+A==} + dependencies: + static-eval: 2.0.2 + dev: false + /@babel/code-frame@7.10.4: resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==} dependencies: @@ -667,6 +845,18 @@ packages: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-export-default-from': 7.22.5(@babel/core@7.23.0) + /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.23.0): + resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.0 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.0) + dev: true + /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.23.0): resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} engines: {node: '>=6.9.0'} @@ -1678,6 +1868,282 @@ packages: engines: {node: '>=8.9'} dev: true + /@cspell/cspell-bundled-dicts@6.31.3: + resolution: {integrity: sha512-KXy3qKWYzXOGYwqOGMCXHem3fV39iEmoKLiNhoWWry/SFdvAafmeY+LIDcQTXAcOQLkMDCwP2/rY/NadcWnrjg==} + engines: {node: '>=14'} + dependencies: + '@cspell/dict-ada': 4.0.2 + '@cspell/dict-aws': 3.0.0 + '@cspell/dict-bash': 4.1.3 + '@cspell/dict-companies': 3.0.31 + '@cspell/dict-cpp': 5.1.1 + '@cspell/dict-cryptocurrencies': 3.0.1 + '@cspell/dict-csharp': 4.0.2 + '@cspell/dict-css': 4.0.12 + '@cspell/dict-dart': 2.0.3 + '@cspell/dict-django': 4.1.0 + '@cspell/dict-docker': 1.1.7 + '@cspell/dict-dotnet': 5.0.0 + '@cspell/dict-elixir': 4.0.3 + '@cspell/dict-en-common-misspellings': 1.0.2 + '@cspell/dict-en-gb': 1.1.33 + '@cspell/dict-en_us': 4.3.14 + '@cspell/dict-filetypes': 3.0.3 + '@cspell/dict-fonts': 3.0.2 + '@cspell/dict-fullstack': 3.1.5 + '@cspell/dict-gaming-terms': 1.0.5 + '@cspell/dict-git': 2.0.0 + '@cspell/dict-golang': 6.0.5 + '@cspell/dict-haskell': 4.0.1 + '@cspell/dict-html': 4.0.5 + '@cspell/dict-html-symbol-entities': 4.0.0 + '@cspell/dict-java': 5.0.6 + '@cspell/dict-k8s': 1.0.2 + '@cspell/dict-latex': 4.0.0 + '@cspell/dict-lorem-ipsum': 3.0.0 + '@cspell/dict-lua': 4.0.3 + '@cspell/dict-node': 4.0.3 + '@cspell/dict-npm': 5.0.15 + '@cspell/dict-php': 4.0.5 + '@cspell/dict-powershell': 5.0.3 + '@cspell/dict-public-licenses': 2.0.5 + '@cspell/dict-python': 4.1.11 + '@cspell/dict-r': 2.0.1 + '@cspell/dict-ruby': 5.0.2 + '@cspell/dict-rust': 4.0.2 + '@cspell/dict-scala': 5.0.0 + '@cspell/dict-software-terms': 3.3.16 + '@cspell/dict-sql': 2.1.3 + '@cspell/dict-svelte': 1.0.2 + '@cspell/dict-swift': 2.0.1 + '@cspell/dict-typescript': 3.1.2 + '@cspell/dict-vue': 3.0.0 + dev: true + + /@cspell/cspell-json-reporter@6.31.3: + resolution: {integrity: sha512-ZJwj2vT4lxncYxduXcxy0dCvjjMvXIfphbLSCN5CXvufrtupB4KlcjZUnOofCi4pfpp8qocCSn1lf2DU9xgUXA==} + engines: {node: '>=14'} + dependencies: + '@cspell/cspell-types': 6.31.3 + dev: true + + /@cspell/cspell-pipe@6.31.3: + resolution: {integrity: sha512-Lv/y4Ya/TJyU1pf66yl1te7LneFZd3lZg1bN5oe1cPrKSmfWdiX48v7plTRecWd/OWyLGd0yN807v79A+/0W7A==} + engines: {node: '>=14'} + dev: true + + /@cspell/cspell-service-bus@6.31.3: + resolution: {integrity: sha512-x5j8j3n39KN8EXOAlv75CpircdpF5WEMCC5pcO916o6GBmJBy8SrdzdsBGJhVcYGGilqy6pf8R9RCZ3yAmG8gQ==} + engines: {node: '>=14'} + dev: true + + /@cspell/cspell-types@6.31.3: + resolution: {integrity: sha512-wZ+t+lUsQJB65M31btZM4fH3K1CkRgE8pSeTiCwxYcnCL19pi4TMcEEMKdO8yFZMdocW4B7VRwzxNoQMw2ewBg==} + engines: {node: '>=14'} + dev: true + + /@cspell/dict-ada@4.0.2: + resolution: {integrity: sha512-0kENOWQeHjUlfyId/aCM/mKXtkEgV0Zu2RhUXCBr4hHo9F9vph+Uu8Ww2b0i5a4ZixoIkudGA+eJvyxrG1jUpA==} + dev: true + + /@cspell/dict-aws@3.0.0: + resolution: {integrity: sha512-O1W6nd5y3Z00AMXQMzfiYrIJ1sTd9fB1oLr+xf/UD7b3xeHeMeYE2OtcWbt9uyeHim4tk+vkSTcmYEBKJgS5bQ==} + dev: true + + /@cspell/dict-bash@4.1.3: + resolution: {integrity: sha512-tOdI3QVJDbQSwPjUkOiQFhYcu2eedmX/PtEpVWg0aFps/r6AyjUQINtTgpqMYnYuq8O1QUIQqnpx21aovcgZCw==} + dev: true + + /@cspell/dict-companies@3.0.31: + resolution: {integrity: sha512-hKVpV/lcGKP4/DpEPS8P4osPvFH/YVLJaDn9cBIOH6/HSmL5LbFgJNKpMGaYRbhm2FEX56MKE3yn/MNeNYuesQ==} + dev: true + + /@cspell/dict-cpp@5.1.1: + resolution: {integrity: sha512-Qy9fNsR/5RcQ6G85gDKFjvzh0AdgAilLQeSXPtqY21Fx1kCjUqdVVJYMmHUREgcxH6ptAxtn5knTWU4PIhQtOw==} + dev: true + + /@cspell/dict-cryptocurrencies@3.0.1: + resolution: {integrity: sha512-Tdlr0Ahpp5yxtwM0ukC13V6+uYCI0p9fCRGMGZt36rWv8JQZHIuHfehNl7FB/Qc09NCF7p5ep0GXbL+sVTd/+w==} + dev: true + + /@cspell/dict-csharp@4.0.2: + resolution: {integrity: sha512-1JMofhLK+4p4KairF75D3A924m5ERMgd1GvzhwK2geuYgd2ZKuGW72gvXpIV7aGf52E3Uu1kDXxxGAiZ5uVG7g==} + dev: true + + /@cspell/dict-css@4.0.12: + resolution: {integrity: sha512-vGBgPM92MkHQF5/2jsWcnaahOZ+C6OE/fPvd5ScBP72oFY9tn5GLuomcyO0z8vWCr2e0nUSX1OGimPtcQAlvSw==} + dev: true + + /@cspell/dict-dart@2.0.3: + resolution: {integrity: sha512-cLkwo1KT5CJY5N5RJVHks2genFkNCl/WLfj+0fFjqNR+tk3tBI1LY7ldr9piCtSFSm4x9pO1x6IV3kRUY1lLiw==} + dev: true + + /@cspell/dict-data-science@1.0.11: + resolution: {integrity: sha512-TaHAZRVe0Zlcc3C23StZqqbzC0NrodRwoSAc8dis+5qLeLLnOCtagYQeROQvDlcDg3X/VVEO9Whh4W/z4PAmYQ==} + dev: true + + /@cspell/dict-django@4.1.0: + resolution: {integrity: sha512-bKJ4gPyrf+1c78Z0Oc4trEB9MuhcB+Yg+uTTWsvhY6O2ncFYbB/LbEZfqhfmmuK/XJJixXfI1laF2zicyf+l0w==} + dev: true + + /@cspell/dict-docker@1.1.7: + resolution: {integrity: sha512-XlXHAr822euV36GGsl2J1CkBIVg3fZ6879ZOg5dxTIssuhUOCiV2BuzKZmt6aIFmcdPmR14+9i9Xq+3zuxeX0A==} + dev: true + + /@cspell/dict-dotnet@5.0.0: + resolution: {integrity: sha512-EOwGd533v47aP5QYV8GlSSKkmM9Eq8P3G/eBzSpH3Nl2+IneDOYOBLEUraHuiCtnOkNsz0xtZHArYhAB2bHWAw==} + dev: true + + /@cspell/dict-elixir@4.0.3: + resolution: {integrity: sha512-g+uKLWvOp9IEZvrIvBPTr/oaO6619uH/wyqypqvwpmnmpjcfi8+/hqZH8YNKt15oviK8k4CkINIqNhyndG9d9Q==} + dev: true + + /@cspell/dict-en-common-misspellings@1.0.2: + resolution: {integrity: sha512-jg7ZQZpZH7+aAxNBlcAG4tGhYF6Ksy+QS5Df73Oo+XyckBjC9QS+PrRwLTeYoFIgXy5j3ICParK5r3MSSoL4gw==} + dev: true + + /@cspell/dict-en-gb@1.1.33: + resolution: {integrity: sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==} + dev: true + + /@cspell/dict-en_us@4.3.14: + resolution: {integrity: sha512-Od7vPVNN4td0Fild5BcCPikx+lBJ2L809zWeO3lThYHqtZXqsbaBNzfv9qlB1bXW199Ru461vu02CrklU1oD+Q==} + dev: true + + /@cspell/dict-filetypes@3.0.3: + resolution: {integrity: sha512-J9UP+qwwBLfOQ8Qg9tAsKtSY/WWmjj21uj6zXTI9hRLD1eG1uUOLcfVovAmtmVqUWziPSKMr87F6SXI3xmJXgw==} + dev: true + + /@cspell/dict-fonts@3.0.2: + resolution: {integrity: sha512-Z5QdbgEI7DV+KPXrAeDA6dDm/vTzyaW53SGlKqz6PI5VhkOjgkBXv3YtZjnxMZ4dY2ZIqq+RUK6qa9Pi8rQdGQ==} + dev: true + + /@cspell/dict-fullstack@3.1.5: + resolution: {integrity: sha512-6ppvo1dkXUZ3fbYn/wwzERxCa76RtDDl5Afzv2lijLoijGGUw5yYdLBKJnx8PJBGNLh829X352ftE7BElG4leA==} + dev: true + + /@cspell/dict-gaming-terms@1.0.5: + resolution: {integrity: sha512-C3riccZDD3d9caJQQs1+MPfrUrQ+0KHdlj9iUR1QD92FgTOF6UxoBpvHUUZ9YSezslcmpFQK4xQQ5FUGS7uWfw==} + dev: true + + /@cspell/dict-git@2.0.0: + resolution: {integrity: sha512-n1AxyX5Kgxij/sZFkxFJlzn3K9y/sCcgVPg/vz4WNJ4K9YeTsUmyGLA2OQI7d10GJeiuAo2AP1iZf2A8j9aj2w==} + dev: true + + /@cspell/dict-golang@6.0.5: + resolution: {integrity: sha512-w4mEqGz4/wV+BBljLxduFNkMrd3rstBNDXmoX5kD4UTzIb4Sy0QybWCtg2iVT+R0KWiRRA56QKOvBsgXiddksA==} + dev: true + + /@cspell/dict-haskell@4.0.1: + resolution: {integrity: sha512-uRrl65mGrOmwT7NxspB4xKXFUenNC7IikmpRZW8Uzqbqcu7ZRCUfstuVH7T1rmjRgRkjcIjE4PC11luDou4wEQ==} + dev: true + + /@cspell/dict-html-symbol-entities@4.0.0: + resolution: {integrity: sha512-HGRu+48ErJjoweR5IbcixxETRewrBb0uxQBd6xFGcxbEYCX8CnQFTAmKI5xNaIt2PKaZiJH3ijodGSqbKdsxhw==} + dev: true + + /@cspell/dict-html@4.0.5: + resolution: {integrity: sha512-p0brEnRybzSSWi8sGbuVEf7jSTDmXPx7XhQUb5bgG6b54uj+Z0Qf0V2n8b/LWwIPJNd1GygaO9l8k3HTCy1h4w==} + dev: true + + /@cspell/dict-java@5.0.6: + resolution: {integrity: sha512-kdE4AHHHrixyZ5p6zyms1SLoYpaJarPxrz8Tveo6gddszBVVwIUZ+JkQE1bWNLK740GWzIXdkznpUfw1hP9nXw==} + dev: true + + /@cspell/dict-k8s@1.0.2: + resolution: {integrity: sha512-tLT7gZpNPnGa+IIFvK9SP1LrSpPpJ94a/DulzAPOb1Q2UBFwdpFd82UWhio0RNShduvKG/WiMZf/wGl98pn+VQ==} + dev: true + + /@cspell/dict-latex@4.0.0: + resolution: {integrity: sha512-LPY4y6D5oI7D3d+5JMJHK/wxYTQa2lJMSNxps2JtuF8hbAnBQb3igoWEjEbIbRRH1XBM0X8dQqemnjQNCiAtxQ==} + dev: true + + /@cspell/dict-lorem-ipsum@3.0.0: + resolution: {integrity: sha512-msEV24qEpzWZs2kcEicqYlhyBpR0amfDkJOs+iffC07si9ftqtQ+yP3lf1VFLpgqw3SQh1M1vtU7RD4sPrNlcQ==} + dev: true + + /@cspell/dict-lua@4.0.3: + resolution: {integrity: sha512-lDHKjsrrbqPaea13+G9s0rtXjMO06gPXPYRjRYawbNmo4E/e3XFfVzeci3OQDQNDmf2cPOwt9Ef5lu2lDmwfJg==} + dev: true + + /@cspell/dict-node@4.0.3: + resolution: {integrity: sha512-sFlUNI5kOogy49KtPg8SMQYirDGIAoKBO3+cDLIwD4MLdsWy1q0upc7pzGht3mrjuyMiPRUV14Bb0rkVLrxOhg==} + dev: true + + /@cspell/dict-npm@5.0.15: + resolution: {integrity: sha512-sX0X5YWNW54F4baW7b5JJB6705OCBIZtUqjOghlJNORS5No7QY1IX1zc5FxNNu4gsaCZITAmfMi4ityXEsEThA==} + dev: true + + /@cspell/dict-php@4.0.5: + resolution: {integrity: sha512-9r8ao7Z/mH9Z8pSB7yLtyvcCJWw+/MnQpj7xGVYzIV7V2ZWDRjXZAMgteHMJ37m8oYz64q5d4tiipD300QSetQ==} + dev: true + + /@cspell/dict-powershell@5.0.3: + resolution: {integrity: sha512-lEdzrcyau6mgzu1ie98GjOEegwVHvoaWtzQnm1ie4DyZgMr+N6D0Iyj1lzvtmt0snvsDFa5F2bsYzf3IMKcpcA==} + dev: true + + /@cspell/dict-public-licenses@2.0.5: + resolution: {integrity: sha512-91HK4dSRri/HqzAypHgduRMarJAleOX5NugoI8SjDLPzWYkwZ1ftuCXSk+fy8DLc3wK7iOaFcZAvbjmnLhVs4A==} + dev: true + + /@cspell/dict-python@4.1.11: + resolution: {integrity: sha512-XG+v3PumfzUW38huSbfT15Vqt3ihNb462ulfXifpQllPok5OWynhszCLCRQjQReV+dgz784ST4ggRxW452/kVg==} + dependencies: + '@cspell/dict-data-science': 1.0.11 + dev: true + + /@cspell/dict-r@2.0.1: + resolution: {integrity: sha512-KCmKaeYMLm2Ip79mlYPc8p+B2uzwBp4KMkzeLd5E6jUlCL93Y5Nvq68wV5fRLDRTf7N1LvofkVFWfDcednFOgA==} + dev: true + + /@cspell/dict-ruby@5.0.2: + resolution: {integrity: sha512-cIh8KTjpldzFzKGgrqUX4bFyav5lC52hXDKo4LbRuMVncs3zg4hcSf4HtURY+f2AfEZzN6ZKzXafQpThq3dl2g==} + dev: true + + /@cspell/dict-rust@4.0.2: + resolution: {integrity: sha512-RhziKDrklzOntxAbY3AvNR58wnFGIo3YS8+dNeLY36GFuWOvXDHFStYw5Pod4f/VXbO/+1tXtywCC4zWfB2p1w==} + dev: true + + /@cspell/dict-scala@5.0.0: + resolution: {integrity: sha512-ph0twaRoV+ylui022clEO1dZ35QbeEQaKTaV2sPOsdwIokABPIiK09oWwGK9qg7jRGQwVaRPEq0Vp+IG1GpqSQ==} + dev: true + + /@cspell/dict-software-terms@3.3.16: + resolution: {integrity: sha512-ixorEP80LGxAU+ODVSn/CYIDjV0XAlZ2VrBu7CT+PwUFJ7h8o3JX1ywKB4qnt0hHru3JjWFtBoBThmZdrXnREQ==} + dev: true + + /@cspell/dict-sql@2.1.3: + resolution: {integrity: sha512-SEyTNKJrjqD6PAzZ9WpdSu6P7wgdNtGV2RV8Kpuw1x6bV+YsSptuClYG+JSdRExBTE6LwIe1bTklejUp3ZP8TQ==} + dev: true + + /@cspell/dict-svelte@1.0.2: + resolution: {integrity: sha512-rPJmnn/GsDs0btNvrRBciOhngKV98yZ9SHmg8qI6HLS8hZKvcXc0LMsf9LLuMK1TmS2+WQFAan6qeqg6bBxL2Q==} + dev: true + + /@cspell/dict-swift@2.0.1: + resolution: {integrity: sha512-gxrCMUOndOk7xZFmXNtkCEeroZRnS2VbeaIPiymGRHj5H+qfTAzAKxtv7jJbVA3YYvEzWcVE2oKDP4wcbhIERw==} + dev: true + + /@cspell/dict-typescript@3.1.2: + resolution: {integrity: sha512-lcNOYWjLUvDZdLa0UMNd/LwfVdxhE9rKA+agZBGjL3lTA3uNvH7IUqSJM/IXhJoBpLLMVEOk8v1N9xi+vDuCdA==} + dev: true + + /@cspell/dict-vue@3.0.0: + resolution: {integrity: sha512-niiEMPWPV9IeRBRzZ0TBZmNnkK3olkOPYxC1Ny2AX4TGlYRajcW0WUtoSHmvvjZNfWLSg2L6ruiBeuPSbjnG6A==} + dev: true + + /@cspell/dynamic-import@6.31.3: + resolution: {integrity: sha512-A6sT00+6UNGFksQ5SxW2ohNl6vUutai8F4jwJMHTjZL/9vivQpU7y5V4PpsfoPZtx3WZcbrzuTvJ+tLfdbWc4A==} + engines: {node: '>=14'} + dependencies: + import-meta-resolve: 2.2.2 + dev: true + + /@cspell/strong-weak-map@6.31.3: + resolution: {integrity: sha512-znwc9IlgGUPioHGshP/zyM8HsuYg1OY5S7HSiVXARh5H8RqcyBsnyn8abc0PPhqPrfDy9Fh5xHsAEPZ55dl1vQ==} + engines: {node: '>=14.6'} + dev: true + /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -1703,6 +2169,14 @@ packages: - web-streams-polyfill dev: true + /@digitalbazaar/bitstring@3.1.0: + resolution: {integrity: sha512-Cii+Sl++qaexOvv3vchhgZFfSmtHPNIPzGegaq4ffPnflVXFu+V2qrJ17aL2+gfLxrlC/zazZFuAltyKTPq7eg==} + engines: {node: '>=16'} + dependencies: + base64url-universal: 2.0.0 + pako: 2.1.0 + dev: true + /@digitalbazaar/http-client@1.2.0: resolution: {integrity: sha512-W9KQQ5pUJcaR0I4c2HPJC0a7kRbZApIorZgPnEDwMBgj16iQzutGLrCXYaZOmxqVLVNqqlQ4aUJh+HBQZy4W6Q==} engines: {node: '>=10.0.0'} @@ -1715,18 +2189,68 @@ packages: - web-streams-polyfill dev: true + /@digitalbazaar/http-client@3.4.1: + resolution: {integrity: sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==} + engines: {node: '>=14.0'} + dependencies: + ky: 0.33.3 + ky-universal: 0.11.0(ky@0.33.3) + undici: 5.28.2 + transitivePeerDependencies: + - encoding + - web-streams-polyfill + dev: true + /@digitalbazaar/security-context@1.0.1: resolution: {integrity: sha512-0WZa6tPiTZZF8leBtQgYAfXQePFQp2z5ivpCEN/iZguYYZ0TB9qRmWtan5XH6mNFuusHtMcyIzAcReyE6rZPhA==} - dev: false /@digitalbazaar/vc-status-list-context@3.0.1: resolution: {integrity: sha512-vQsqQXpmSXKNy/C0xxFUOBzz60dHh6oupQam1xRC8IspVC11hYJiX9SAhmbI0ulHvX1R2JfqZaJHZjmAyMZ/aA==} - dev: false + + /@digitalbazaar/vc-status-list@7.1.0: + resolution: {integrity: sha512-p5uxKJlX13N8TcTuv9qFDeej+6bndU+Rh1Cez2MT+bXQE6Jpn5t336FBSHmcECB4yUfZQpkmV/LOcYU4lW8Ojw==} + engines: {node: '>=16'} + dependencies: + '@digitalbazaar/bitstring': 3.1.0 + '@digitalbazaar/vc': 5.0.0 + '@digitalbazaar/vc-status-list-context': 3.0.1 + credentials-context: 2.0.0 + transitivePeerDependencies: + - encoding + - web-streams-polyfill + dev: true + + /@digitalbazaar/vc@5.0.0: + resolution: {integrity: sha512-XmLM7Ag5W+XidGnFuxFIyUFSMnHnWEMJlHei602GG94+WzFJ6Ik8txzPQL8T18egSoiTsd1VekymbIlSimhuaQ==} + engines: {node: '>=14'} + dependencies: + credentials-context: 2.0.0 + jsonld: 8.3.2 + jsonld-signatures: 11.2.1 + transitivePeerDependencies: + - encoding + - web-streams-polyfill + dev: true /@digitalcredentials/base58-universal@1.0.1: resolution: {integrity: sha512-1xKdJnfITMvrF/sCgwBx2C4p7qcNAARyIvrAOZGqIHmBaT/hAenpC8bf44qVY+UIMuCYP23kqpIfJQebQDThDQ==} engines: {node: '>=12'} + /@digitalcredentials/base64url-universal@2.0.6: + resolution: {integrity: sha512-QJyK6xS8BYNnkKLhEAgQc6Tb9DMe+GkHnBAWJKITCxVRXJAFLhJnr+FsJnCThS3x2Y0UiiDAXoWjwMqtUrp4Kg==} + engines: {node: '>=14'} + dependencies: + base64url: 3.0.1 + dev: true + + /@digitalcredentials/bitstring@2.0.1: + resolution: {integrity: sha512-9priXvsEJGI4LYHPwLqf5jv9HtQGlG0MgeuY8Q4NHN+xWz5rYMylh1TYTVThKa3XI6xF2pR2oEfKZD21eWXveQ==} + engines: {node: '>=14'} + dependencies: + '@digitalcredentials/base64url-universal': 2.0.6 + pako: 2.1.0 + dev: true + /@digitalcredentials/bnid@2.1.2(react-native@0.71.13): resolution: {integrity: sha512-pSQSZ7OH9dCVV/0CNoS5+7UHGekUjyJBOt4uTOLpCJWMINjD8RDVEpJLPvcV/kT9dq+S4DPA4ZyNDb/NmXCHGA==} engines: {node: '>=14'} @@ -1762,21 +2286,22 @@ packages: '@digitalcredentials/ed25519-verification-key-2020': 3.2.2 '@digitalcredentials/x25519-key-agreement-key-2020': 2.0.2 - /@digitalcredentials/ed25519-signature-2020@3.0.2(expo@48.0.20)(react-native@0.71.13): + /@digitalcredentials/ed25519-signature-2020@3.0.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): resolution: {integrity: sha512-R8IrR21Dh+75CYriQov3nVHKaOVusbxfk9gyi6eCAwLHKn6fllUt+2LQfuUrL7Ts/sGIJqQcev7YvkX9GvyYRA==} engines: {node: '>=14'} dependencies: '@digitalcredentials/base58-universal': 1.0.1 '@digitalcredentials/ed25519-verification-key-2020': 3.2.2 - '@digitalcredentials/jsonld-signatures': 9.3.2(expo@48.0.20)(react-native@0.71.13) + '@digitalcredentials/jsonld-signatures': 9.3.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) ed25519-signature-2018-context: 1.1.0 ed25519-signature-2020-context: 1.1.0 transitivePeerDependencies: - encoding - expo - - react-native + - expo-crypto + - msrcrypto + - react-native-securerandom - web-streams-polyfill - dev: false /@digitalcredentials/ed25519-verification-key-2020@3.2.2: resolution: {integrity: sha512-ZfxNFZlA379MZpf+gV2tUYyiZ15eGVgjtCQLWlyu3frWxsumUgv++o0OJlMnrDsWGwzFMRrsXcosd5+752rLOA==} @@ -1805,53 +2330,55 @@ packages: transitivePeerDependencies: - encoding - web-streams-polyfill - dev: false - /@digitalcredentials/jsonld-signatures@9.3.2(expo@48.0.20)(react-native@0.71.13): + /@digitalcredentials/jsonld-signatures@9.3.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): resolution: {integrity: sha512-auubZrr3D7et5O6zCdqoXsLhI8/F26HqneE94gIoZYVuxNHBNaFoDQ1Z71RfddRqwJonHkfkWgeZSzqjv6aUmg==} engines: {node: '>=12'} dependencies: '@digitalbazaar/security-context': 1.0.1 - '@digitalcredentials/jsonld': 6.0.0(expo@48.0.20)(react-native@0.71.13) + '@digitalcredentials/jsonld': 6.0.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) fast-text-encoding: 1.0.6 - isomorphic-webcrypto: 2.3.8(expo@48.0.20)(react-native@0.71.13) + isomorphic-webcrypto: /@sphereon/isomorphic-webcrypto@2.4.0-unstable.4(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) serialize-error: 8.1.0 transitivePeerDependencies: - encoding - expo - - react-native + - expo-crypto + - msrcrypto + - react-native-securerandom - web-streams-polyfill - dev: false - /@digitalcredentials/jsonld@5.2.2(expo@48.0.20)(react-native@0.71.13): + /@digitalcredentials/jsonld@5.2.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): resolution: {integrity: sha512-hz7YR3kv6+8UUdgMyTGl1o8NjVKKwnMry/Rh/rWeAvwL+NqgoUHorWzI3rM+PW+MPFyDC0ieXStClt9n9D9SGA==} engines: {node: '>=12'} dependencies: '@digitalcredentials/http-client': 1.2.2 - '@digitalcredentials/rdf-canonize': 1.0.0(expo@48.0.20)(react-native@0.71.13) + '@digitalcredentials/rdf-canonize': 1.0.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) canonicalize: 1.0.8 lru-cache: 6.0.0 transitivePeerDependencies: - encoding - expo - - react-native + - expo-crypto + - msrcrypto + - react-native-securerandom - web-streams-polyfill - dev: false - /@digitalcredentials/jsonld@6.0.0(expo@48.0.20)(react-native@0.71.13): + /@digitalcredentials/jsonld@6.0.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): resolution: {integrity: sha512-5tTakj0/GsqAJi8beQFVMQ97wUJZnuxViW9xRuAATL6eOBIefGBwHkVryAgEq2I4J/xKgb/nEyw1ZXX0G8wQJQ==} engines: {node: '>=12'} dependencies: '@digitalcredentials/http-client': 1.2.2 - '@digitalcredentials/rdf-canonize': 1.0.0(expo@48.0.20)(react-native@0.71.13) + '@digitalcredentials/rdf-canonize': 1.0.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) canonicalize: 1.0.8 lru-cache: 6.0.0 transitivePeerDependencies: - encoding - expo - - react-native + - expo-crypto + - msrcrypto + - react-native-securerandom - web-streams-polyfill - dev: false /@digitalcredentials/keypair@1.0.5: resolution: {integrity: sha512-g0QvhJMTSFCoUkEvSeggwVTJa2jFkQXjf/mpTn9sePkz+5OouMEDfXUWL61juTaxK5JWPEFc0PKlolXzHaHHHQ==} @@ -1868,16 +2395,21 @@ packages: resolution: {integrity: sha512-uaaL1htmUsJESfb3v7bAXKA4xu/xc7bzmK2R7BlYJEKY1QZpYOH9PZBrGqUEWfcMHfu1V2ILErIU4vMDz20lHQ==} dev: false - /@digitalcredentials/rdf-canonize@1.0.0(expo@48.0.20)(react-native@0.71.13): + /@digitalcredentials/open-badges-context@2.0.1: + resolution: {integrity: sha512-cMS+biUjJYwq60xeop6iHPC3Cxrv77jbdS2hPY/IkZfXIZlt2rvB7dz7rP/iGWwRiT5SQBLVdX+ZiDZc8xee/Q==} + dev: true + + /@digitalcredentials/rdf-canonize@1.0.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): resolution: {integrity: sha512-z8St0Ex2doecsExCFK1uI4gJC+a5EqYYu1xpRH1pKmqSS9l/nxfuVxexNFyaeEum4dUdg1EetIC2rTwLIFhPRA==} engines: {node: '>=12'} dependencies: fast-text-encoding: 1.0.6 - isomorphic-webcrypto: 2.3.8(expo@48.0.20)(react-native@0.71.13) + isomorphic-webcrypto: /@sphereon/isomorphic-webcrypto@2.4.0-unstable.4(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) transitivePeerDependencies: - expo - - react-native - dev: false + - expo-crypto + - msrcrypto + - react-native-securerandom /@digitalcredentials/security-document-loader@1.0.0(react-native@0.71.13): resolution: {integrity: sha512-J7vbTgI5k1kdvaAgzPan30laJUTMcRdKBQ11b7a1viSWlHXOwFGIRFeVOcV8gZ+iOIaDHRo/uRHlyyLMapaS9w==} @@ -1905,20 +2437,77 @@ packages: - web-streams-polyfill dev: false - /@digitalcredentials/vc@5.0.0(expo@48.0.20)(react-native@0.71.13): + /@digitalcredentials/vc-status-list@5.0.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): + resolution: {integrity: sha512-PI0N7SM0tXpaNLelbCNsMAi34AjOeuhUzMSYTkHdeqRPX7oT2F3ukyOssgr4koEqDxw9shHtxHu3fSJzrzcPMQ==} + engines: {node: '>=14'} + dependencies: + '@digitalbazaar/vc-status-list-context': 3.0.1 + '@digitalcredentials/bitstring': 2.0.1 + '@digitalcredentials/vc': 4.2.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + credentials-context: 2.0.0 + transitivePeerDependencies: + - encoding + - expo + - expo-crypto + - msrcrypto + - react-native-securerandom + - web-streams-polyfill + dev: true + + /@digitalcredentials/vc@4.2.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): + resolution: {integrity: sha512-8Rxpn77JghJN7noBQdcMuzm/tB8vhDwPoFepr3oGd5w+CyJxOk2RnBlgIGlAAGA+mALFWECPv1rANfXno+hdjA==} + engines: {node: '>=12'} + dependencies: + '@digitalcredentials/jsonld': 5.2.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@digitalcredentials/jsonld-signatures': 9.3.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + credentials-context: 2.0.0 + transitivePeerDependencies: + - encoding + - expo + - expo-crypto + - msrcrypto + - react-native-securerandom + - web-streams-polyfill + dev: true + + /@digitalcredentials/vc@5.0.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): resolution: {integrity: sha512-87ARRxlAdIuUPArbMYJ8vUY7QqkIvJGFrBwfTH1PcB8Wz1E/M4q3oc/WLrDyJNg4o/irVVB5gkA9iIntTYSpoA==} engines: {node: '>=12'} dependencies: - '@digitalcredentials/jsonld': 5.2.2(expo@48.0.20)(react-native@0.71.13) - '@digitalcredentials/jsonld-signatures': 9.3.2(expo@48.0.20)(react-native@0.71.13) + '@digitalcredentials/jsonld': 5.2.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@digitalcredentials/jsonld-signatures': 9.3.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) credentials-context: 2.0.0 transitivePeerDependencies: - encoding - expo - - react-native + - expo-crypto + - msrcrypto + - react-native-securerandom - web-streams-polyfill dev: false + /@digitalcredentials/vc@6.0.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): + resolution: {integrity: sha512-RNCkNAKEnkU7/8OiKbS3sM3qePQpH4ZGAXSwaQ0XrRQumPbLEJz8AMpxXmH28sFnmxUrCyvuCGKUq8CBjS1+cQ==} + engines: {node: '>=12'} + dependencies: + '@digitalbazaar/vc-status-list': 7.1.0 + '@digitalcredentials/ed25519-signature-2020': 3.0.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@digitalcredentials/jsonld': 6.0.0(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@digitalcredentials/jsonld-signatures': 9.3.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@digitalcredentials/open-badges-context': 2.0.1 + '@digitalcredentials/vc-status-list': 5.0.2(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + credentials-context: 2.0.0 + fix-esm: 1.0.1 + transitivePeerDependencies: + - encoding + - expo + - expo-crypto + - msrcrypto + - react-native-securerandom + - supports-color + - web-streams-polyfill + dev: true + /@digitalcredentials/x25519-key-agreement-key-2020@2.0.2: resolution: {integrity: sha512-7Ay5AkGfIEWBRJiHl6PhrpFrjAqCZ/+G4rV6sqTUGK8fBnkxqlJ/XiD7NouUF6uTalVm7mJWJXHuCN5FAuXGsg==} engines: {node: '>=12'} @@ -2284,6 +2873,11 @@ packages: find-up: 5.0.0 js-yaml: 4.1.0 + /@fastify/busboy@2.1.0: + resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} + engines: {node: '>=14'} + dev: true + /@fastify/deepmerge@1.3.0: resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} dev: false @@ -2348,6 +2942,16 @@ packages: - web-streams-polyfill dev: false + /@isaacs/cached@1.0.1: + resolution: {integrity: sha512-7kGcJ9Hc1f4qpTApWz3swxbF9Qv1NF/GxuPtXeTptbsgvJIoufSd0h854Nq/2bw80F5C1onsFgEI05l+q0e4vw==} + dependencies: + '@isaacs/catcher': 1.0.4 + dev: true + + /@isaacs/catcher@1.0.4: + resolution: {integrity: sha512-g2klMwbnguClWNnCeQ1zYaDJsvPbIbnjdJPDE0z09MqoejJDZSLK5vIKiClq2Bkg5ubuI8vaN6wfIUi5GYzMVA==} + dev: true + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -2801,6 +3405,12 @@ packages: resolution: {integrity: sha512-QCOA9cgf3Rc33owG0AYBB9wszz+Ul2kramWN8tXG44Gyciud/tbkEqvxRF/IpqQaBpRBNi9f4jdNxqB2CQCIXg==} dev: true + /@noble/curves@1.2.0: + resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} + dependencies: + '@noble/hashes': 1.3.2 + dev: true + /@noble/curves@1.3.0: resolution: {integrity: sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==} dependencies: @@ -2811,6 +3421,11 @@ packages: resolution: {integrity: sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==} dev: false + /@noble/hashes@1.3.2: + resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} + engines: {node: '>= 16'} + dev: true + /@noble/hashes@1.3.3: resolution: {integrity: sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==} engines: {node: '>= 16'} @@ -3395,9 +4010,28 @@ packages: resolution: {integrity: sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==} dev: true - /@sd-jwt/core@0.1.2-alpha.0: - resolution: {integrity: sha512-x4MVXar6WmPauZDRJ3aHwaY8o/bHzN77Ts7o43JKuuqIBFjPgAcSlRtd/Xk1rWhazFai4MCIwJDSQ1OQRJtNug==} + /@sd-jwt/decode@0.2.0: + resolution: {integrity: sha512-nmiZN3SQ4ApapEu+rS1h/YAkDIq3exgN7swSCsEkrxSEwnBSbXtISIY/sv+EmwnehF1rcKbivHfHNxOWYtlxvg==} + dependencies: + '@sd-jwt/types': 0.2.0 + '@sd-jwt/utils': 0.2.0 + dev: false + + /@sd-jwt/present@0.2.0: + resolution: {integrity: sha512-6xDBiB+UqCwW8k7O7OUJ7BgC/8zcO+AD5ZX1k4I6yjDM9vscgPulSVxT/yUH+Aov3cZ/BKvfKC0qDEZkHmP/kg==} dependencies: + '@sd-jwt/types': 0.2.0 + '@sd-jwt/utils': 0.2.0 + dev: false + + /@sd-jwt/types@0.2.0: + resolution: {integrity: sha512-16WFRcL/maG0/JxN9UCSx07/vJ2SDbGscv9gDLmFLgJzhJcGPer41XfI6aDfVARYP430wHFixChfY/n7qC1L/Q==} + dev: false + + /@sd-jwt/utils@0.2.0: + resolution: {integrity: sha512-oHCfRYVHCb5RNwdq3eHAt7P9d7TsEaSM1TTux+xl1I9PeQGLtZETnto9Gchtzn8FlTrMdVsLlcuAcK6Viwj1Qw==} + dependencies: + '@sd-jwt/types': 0.2.0 buffer: 6.0.3 dev: false @@ -3464,6 +4098,15 @@ packages: dependencies: '@sinonjs/commons': 3.0.0 + /@sphereon/did-uni-client@0.6.1: + resolution: {integrity: sha512-ryIPq9fAp8UuaN0ZQ16Gong5n5SX8G+SjNQ3x3Uy/pmd6syxh97kkmrfbna7a8dTmbP8YdNtgPLpcNbhLPMClQ==} + dependencies: + cross-fetch: 4.0.0 + did-resolver: 4.1.0 + transitivePeerDependencies: + - encoding + dev: false + /@sphereon/isomorphic-webcrypto@2.4.0-unstable.4(expo-crypto@12.6.0)(expo@48.0.20)(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): resolution: {integrity: sha512-7i9GBta0yji3Z5ocyk82fXpqrV/swe7hXZVfVzOXRaGtTUNd+y8W/3cpHRQC2S4UEO/5N3lX7+B6qUunK9wS/Q==} peerDependencies: @@ -3489,10 +4132,30 @@ packages: sha.js: 2.4.11 str2buf: 1.3.0 webcrypto-shim: 0.1.7 - dev: true - /@sphereon/ssi-express-support@0.17.6-unstable.69: - resolution: {integrity: sha512-IpiiW6KPv5zjvlCCxyw4S093WL4na6zvJjoWI26te04Y4T1yYF3FfaGdcA+BAQl4URvvcp09mzay2Z8RwmDLSA==} + /@sphereon/pex-models@2.1.5: + resolution: {integrity: sha512-7THexvdYUK/Dh8olBB46ErT9q/RnecnMdb5r2iwZ6be0Dt4vQLAUN7QU80H0HZBok4jRTb8ydt12x0raBSTHOg==} + dev: false + + /@sphereon/pex@3.0.1: + resolution: {integrity: sha512-rj+GhFfV5JLyo7dTIA3htWlrT+f6tayF9JRAGxdsIYBfYictLi9BirQ++JRBXsiq7T5zMnfermz4RGi3cvt13Q==} + engines: {node: '>=18'} + dependencies: + '@astronautlabs/jsonpath': 1.1.2 + '@sd-jwt/decode': 0.2.0 + '@sd-jwt/present': 0.2.0 + '@sd-jwt/utils': 0.2.0 + '@sphereon/pex-models': 2.1.5 + '@sphereon/ssi-types': 0.18.1 + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + jwt-decode: 3.1.2 + nanoid: 3.3.7 + string.prototype.matchall: 4.0.10 + dev: false + + /@sphereon/ssi-express-support@0.18.1: + resolution: {integrity: sha512-AiRvSs9hkMnYZYM1Vre+VZfZ2EzxuWxKHenF1ReLQn4dTCFpITbG/dl86jQ8RP/APPt5KHWIKylfu6DAv9JHuQ==} peerDependencies: '@noble/hashes': 1.2.0 passport-azure-ad: ^4.3.5 @@ -3548,15 +4211,48 @@ packages: - supports-color dev: true - /@sphereon/ssi-types@0.17.6-unstable.69: - resolution: {integrity: sha512-VwjVd6XhoV5QecWcRh0RpBf5N324tfhYcQrVk9se3brqMNMauZTBbhUhk+QLdwFd+u/1+IfEWnS28HyIU7lXHQ==} + /@sphereon/ssi-types@0.18.1: + resolution: {integrity: sha512-uM0gb1woyc0R+p+qh8tVDi15ZWmpzo9BP0iBp/yRkJar7gAfgwox/yvtEToaH9jROKnDCwL3DDQCDeNucpMkwg==} + dependencies: + '@sd-jwt/decode': 0.2.0 + jwt-decode: 3.1.2 + dev: false + + /@sphereon/ssi-types@0.9.0: + resolution: {integrity: sha512-umCr/syNcmvMMbQ+i/r/mwjI1Qw2aFPp9AwBTvTo1ailAVaaJjJGPkkVz1K9/2NZATNdDiQ3A8yGzdVJoKh9pA==} + dependencies: + jwt-decode: 3.1.2 + dev: false + + /@sphereon/wellknown-dids-client@0.1.3: + resolution: {integrity: sha512-TAT24L3RoXD8ocrkTcsz7HuJmgjNjdoV6IXP1p3DdaI/GqkynytXE3J1+F7vUFMRYwY5nW2RaXSgDQhrFJemaA==} dependencies: - '@sd-jwt/core': 0.1.2-alpha.0 + '@sphereon/ssi-types': 0.9.0 + cross-fetch: 3.1.8 jwt-decode: 3.1.2 + transitivePeerDependencies: + - encoding dev: false /@stablelib/aead@1.0.1: resolution: {integrity: sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg==} + + /@stablelib/aes-kw@1.0.1: + resolution: {integrity: sha512-KrOkiRex1tQTbWk+hFB5fFw4vqKhNnTUtlCRf1bhUEOFp7hadWe49/sLa/P4X4FBQVoh3Z9Lj0zS1OWu/AHA1w==} + dependencies: + '@stablelib/aes': 1.0.1 + '@stablelib/binary': 1.0.1 + '@stablelib/blockcipher': 1.0.1 + '@stablelib/constant-time': 1.0.1 + '@stablelib/wipe': 1.0.1 + dev: true + + /@stablelib/aes@1.0.1: + resolution: {integrity: sha512-bMiezJDeFONDHbMEa+Kic26962+bwkZfsHPAmcqTjLaHCAhEQuK3i1H0POPOkcHCdj75oVRIqFCraCA0cyHPvw==} + dependencies: + '@stablelib/binary': 1.0.1 + '@stablelib/blockcipher': 1.0.1 + '@stablelib/wipe': 1.0.1 dev: true /@stablelib/binary@1.0.1: @@ -3564,9 +4260,12 @@ packages: dependencies: '@stablelib/int': 1.0.1 + /@stablelib/blockcipher@1.0.1: + resolution: {integrity: sha512-4bkpV8HUAv0CgI1fUqkPUEEvv3RXQ3qBkuZaSWhshXGAz1JCpriesgiO9Qs4f0KzBJkCtvcho5n7d/RKvnHbew==} + dev: true + /@stablelib/bytes@1.0.1: resolution: {integrity: sha512-Kre4Y4kdwuqL8BR2E9hV/R5sOrUj6NanZaZis0V6lX5yzqC3hBuVSDXUIBqQv/sCpmuWRiHLwqiT1pqqjuBXoQ==} - dev: true /@stablelib/chacha20poly1305@1.0.1: resolution: {integrity: sha512-MmViqnqHd1ymwjOQfghRKw2R/jMIGT3wySN7cthjXCBdO+qErNPUBnRzqNpnvIwg7JBCg3LdeCZZO4de/yEhVA==} @@ -3577,18 +4276,15 @@ packages: '@stablelib/constant-time': 1.0.1 '@stablelib/poly1305': 1.0.1 '@stablelib/wipe': 1.0.1 - dev: true /@stablelib/chacha@1.0.1: resolution: {integrity: sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg==} dependencies: '@stablelib/binary': 1.0.1 '@stablelib/wipe': 1.0.1 - dev: true /@stablelib/constant-time@1.0.1: resolution: {integrity: sha512-tNOs3uD0vSJcK6z1fvef4Y+buN7DXhzHDPqRLSXUel1UfqMB1PWNsnnAezrKfEwTLpN0cGH2p9NNjs6IqeD0eg==} - dev: true /@stablelib/ed25519@1.0.3: resolution: {integrity: sha512-puIMWaX9QlRsbhxfDc5i+mNPMY+0TmQEskunY1rZEBPi1acBCVQAhnsk/1Hk50DGPtVsZtAWQg4NHGlVaO9Hqg==} @@ -3607,14 +4303,12 @@ packages: resolution: {integrity: sha512-VKL6xBwgJnI6l1jKrBAfn265cspaWBPAPEc62VBQrWHLqVgNRE09gQ/AnOEyKUWrrqfD+xSQ3u42gJjLDdMDQg==} dependencies: '@stablelib/bytes': 1.0.1 - dev: true /@stablelib/poly1305@1.0.1: resolution: {integrity: sha512-1HlG3oTSuQDOhSnLwJRKeTRSAdFNVB/1djy2ZbS35rBSJ/PFqx9cf9qatinWghC2UbfOYD8AcrtbUQl8WoxabA==} dependencies: '@stablelib/constant-time': 1.0.1 '@stablelib/wipe': 1.0.1 - dev: true /@stablelib/random@1.0.0: resolution: {integrity: sha512-G9vwwKrNCGMI/uHL6XeWe2Nk4BuxkYyWZagGaDU9wrsuV+9hUwNI1lok2WVo8uJDa2zx7ahNwN7Ij983hOUFEw==} @@ -3635,7 +4329,6 @@ packages: '@stablelib/binary': 1.0.1 '@stablelib/hash': 1.0.1 '@stablelib/wipe': 1.0.1 - dev: true /@stablelib/sha512@1.0.1: resolution: {integrity: sha512-13gl/iawHV9zvDKciLo1fQ8Bgn2Pvf7OV6amaRVKiq3pjQ3UmEpXxWiAfV8tYjUpeZroBxtyrwtdooQT/i3hzw==} @@ -3653,7 +4346,6 @@ packages: '@stablelib/keyagreement': 1.0.1 '@stablelib/random': 1.0.2 '@stablelib/wipe': 1.0.1 - dev: true /@stablelib/xchacha20@1.0.1: resolution: {integrity: sha512-1YkiZnFF4veUwBVhDnDYwo6EHeKzQK4FnLiO7ezCl/zu64uG0bCCAUROJaBkaLH+5BEsO3W7BTXTguMbSLlWSw==} @@ -3661,7 +4353,6 @@ packages: '@stablelib/binary': 1.0.1 '@stablelib/chacha': 1.0.1 '@stablelib/wipe': 1.0.1 - dev: true /@stablelib/xchacha20poly1305@1.0.1: resolution: {integrity: sha512-B1Abj0sMJ8h3HNmGnJ7vHBrAvxuNka6cJJoZ1ILN7iuacXp7sUYcgOVEOTLWj+rtQMpspY9tXSCRLPmN1mQNWg==} @@ -3671,7 +4362,6 @@ packages: '@stablelib/constant-time': 1.0.1 '@stablelib/wipe': 1.0.1 '@stablelib/xchacha20': 1.0.1 - dev: true /@tokenizer/token@0.3.0: resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} @@ -3698,6 +4388,10 @@ packages: - supports-color dev: true + /@transmute/credentials-context@0.7.0-unstable.82: + resolution: {integrity: sha512-2cB6UcMKeEK6kqvl5Uhpoe5iUUAcVURlRHl9nVa/Xx2JymNHyBvyXi+CGjIwf/eEk7hsgMIwDfGqq5Mcnbk5cw==} + dev: true + /@transmute/did-context@0.6.1-unstable.37: resolution: {integrity: sha512-p/QnG3QKS4218hjIDgdvJOFATCXsAnZKgy4egqRrJLlo3Y6OaDBg7cA73dixOwUPoEKob0K6rLIGcsCI/L1acw==} dev: true @@ -3803,6 +4497,61 @@ packages: '@transmute/x25519-key-pair': 0.6.1-unstable.37 dev: true + /@transmute/ed25519-key-pair@0.7.0-unstable.2: + resolution: {integrity: sha512-B0jg348Z8F0+lGWQic28xVxBZiXOJYbisWp6EfP4fQdMV3G4sES9YubpdiuoZHjesDZrf6xZ7cEB81mjGJMUkA==} + engines: {node: '>=10'} + dependencies: + '@stablelib/ed25519': 1.0.3 + '@transmute/ld-key-pair': 0.7.0-unstable.81 + '@transmute/x25519-key-pair': 0.7.0-unstable.81 + dev: true + + /@transmute/ed25519-key-pair@0.7.0-unstable.82: + resolution: {integrity: sha512-ZPMlPXAzQ59ImUP5j0EPp05ZA7H3voM23+zWINZawd4tehTaUpyCXVBPyAyHscJ4isS/l+XZnnOnYcvl9+YrXg==} + engines: {node: '>=16'} + dependencies: + '@stablelib/ed25519': 1.0.3 + '@transmute/ld-key-pair': 0.7.0-unstable.82 + '@transmute/x25519-key-pair': 0.7.0-unstable.82 + dev: true + + /@transmute/ed25519-signature-2018@0.7.0-unstable.82: + resolution: {integrity: sha512-WvD+x7EpeacXEtOTmOQltSNdevwHJZ3Y53Yj8SZJ0CGzVKyqj3/F7wGvagbEUWxALe2rXrby5F6FPVS7mJwgCg==} + engines: {node: '>=16'} + dependencies: + '@transmute/credentials-context': 0.7.0-unstable.82 + '@transmute/ed25519-key-pair': 0.7.0-unstable.2 + '@transmute/jose-ld': 0.7.0-unstable.82 + '@transmute/jsonld': 0.0.4 + '@transmute/security-context': 0.7.0-unstable.82 + transitivePeerDependencies: + - encoding + - web-streams-polyfill + dev: true + + /@transmute/jose-ld@0.7.0-unstable.82: + resolution: {integrity: sha512-FBDbb0bGs7Ssd1H6NXEXqzfF2cnIGRW2ggR13MaTeQR51CEX2lfWlf2fdioOZa0Bk1GZlmUtyEvhPTEjp302WQ==} + engines: {node: '>=16'} + dependencies: + '@peculiar/webcrypto': 1.4.3 + '@stablelib/aes-kw': 1.0.1 + '@stablelib/xchacha20poly1305': 1.0.1 + base64url: 3.0.1 + jose: 4.14.6 + web-streams-polyfill: 3.3.2 + dev: true + + /@transmute/jsonld@0.0.4: + resolution: {integrity: sha512-6G++8imMYW9dtTvATPHNfrV3lLeX5E57DOmlgIDfO0A0yjkBCss1usB80NfONS26ynyveb8vTbp4nQDW9Ki4Rw==} + engines: {node: '>=16'} + dependencies: + json-pointer: 0.6.2 + jsonld: 5.2.0 + transitivePeerDependencies: + - encoding + - web-streams-polyfill + dev: true + /@transmute/ld-key-pair@0.6.1-unstable.37: resolution: {integrity: sha512-DcTpEruAQBfOd2laZkg3uCQ+67Y7dw2hsvo42NAQ5tItCIx5AClP7zccri7T2JUcfDUFaE32z/BLTMEKYt3XZQ==} dev: true @@ -3812,6 +4561,11 @@ packages: engines: {node: '>=16'} dev: true + /@transmute/ld-key-pair@0.7.0-unstable.82: + resolution: {integrity: sha512-XWnVNCL1LeohldBLu7O12tc53rzdCYjZiaMrWvEH/sNpqnZBiNWAsdLWengXhF67LqAXWMwstfbCLNTPCD+EGg==} + engines: {node: '>=16'} + dev: true + /@transmute/secp256k1-key-pair@0.7.0-unstable.81: resolution: {integrity: sha512-kofomMOOLkdTOAV2bQAEZAC0REuiI/RDqxYJJg/qpXnguyGTtv5DVHD8UXmUDKJLJkAql1lbksfs/roYYVBN7g==} engines: {node: '>=16'} @@ -3825,6 +4579,10 @@ packages: resolution: {integrity: sha512-GtLmG65qlORrz/2S4I74DT+vA4+qXsFxrMr0cNOXjUqZBd/AW1PTrFnryLF9907BfoiD58HC9qb1WVGWjSlBYw==} dev: true + /@transmute/security-context@0.7.0-unstable.82: + resolution: {integrity: sha512-Hih4A3iatK8daSREtuF/y9hGnrLZGRTfBYBUlUeaGEoCrcnhNcZrn8EQmW2dqj/7VZ2W5ResxQLPljA9pVJt5w==} + dev: true + /@transmute/web-crypto-key-pair@0.7.0-unstable.81: resolution: {integrity: sha512-oTHub0iFdwJdugQxohcuG1CZaxfuSUPisDkPsxaEHGEOU9+hBBym2Ugr3ZX9H+nT29UNXPlTKNKsSxV4UCtc5w==} engines: {node: '>=16'} @@ -3850,6 +4608,14 @@ packages: '@transmute/ld-key-pair': 0.7.0-unstable.81 dev: true + /@transmute/x25519-key-pair@0.7.0-unstable.82: + resolution: {integrity: sha512-y4lPzk/SY/Cy1dUCa17ES3kqvShNQwevTO16dvbuevu6YcTYBAdSCYvW9JL+ppFqPYI5NSDPUwT6kkd4wNWmsA==} + engines: {node: '>=16'} + dependencies: + '@stablelib/x25519': 1.0.3 + '@transmute/ld-key-pair': 0.7.0-unstable.82 + dev: true + /@trust/keyto@2.0.0-alpha1: resolution: {integrity: sha512-VmlOa+nOaDzhEUfprnVp7RxFQyuEwA4fJ5+smnsud5WM01gU16yQnO/ejZnDVMGXuq/sUwTa5pCej4JhkKA5Sg==} dependencies: @@ -3955,7 +4721,7 @@ packages: resolution: {integrity: sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==} dependencies: '@types/node': 18.18.0 - '@types/qs': 6.9.8 + '@types/qs': 6.9.11 '@types/range-parser': 1.2.5 '@types/send': 0.17.2 dev: true @@ -3998,6 +4764,13 @@ packages: dependencies: '@types/istanbul-lib-report': 3.0.1 + /@types/jest@29.5.11: + resolution: {integrity: sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==} + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + dev: true + /@types/jest@29.5.5: resolution: {integrity: sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==} dependencies: @@ -4013,6 +4786,10 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/language-tags@1.0.4: + resolution: {integrity: sha512-20PQbifv3v/djCT+KlXybv0KqO5ofoR1qD1wkinN59kfggTPVTWGmPFgL/1yWuDyRcsQP/POvkqK+fnl5nOwTg==} + dev: true + /@types/mime@1.3.3: resolution: {integrity: sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==} dev: true @@ -4033,6 +4810,10 @@ packages: resolution: {integrity: sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==} dev: true + /@types/node@18.15.13: + resolution: {integrity: sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==} + dev: true + /@types/node@18.18.0: resolution: {integrity: sha512-3xA4X31gHT1F1l38ATDIL9GpRLdwVhnEFC8Uikv5ZLlXATwrCYyPq7ZWHxzxc3J/30SUiwiYT+bQe0/XvKlWbw==} @@ -4040,6 +4821,10 @@ packages: resolution: {integrity: sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==} dev: true + /@types/qs@6.9.11: + resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==} + dev: true + /@types/qs@6.9.8: resolution: {integrity: sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==} dev: true @@ -4078,6 +4863,10 @@ packages: resolution: {integrity: sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==} dev: true + /@types/uuid@9.0.7: + resolution: {integrity: sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==} + dev: true + /@types/yargs-parser@21.0.1: resolution: {integrity: sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==} @@ -4124,6 +4913,34 @@ packages: - supports-color dev: true + /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.50.0)(typescript@5.3.3): + resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.9.0 + '@typescript-eslint/parser': 5.62.0(eslint@8.50.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/type-utils': 5.62.0(eslint@8.50.0)(typescript@5.3.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.50.0)(typescript@5.3.3) + debug: 4.3.4 + eslint: 8.50.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare-lite: 1.4.0 + semver: 7.5.4 + tsutils: 3.21.0(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@5.62.0(eslint@8.50.0)(typescript@4.9.5): resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4144,6 +4961,26 @@ packages: - supports-color dev: true + /@typescript-eslint/parser@5.62.0(eslint@8.50.0)(typescript@5.3.3): + resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) + debug: 4.3.4 + eslint: 8.50.0 + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/scope-manager@5.62.0: resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4172,12 +5009,53 @@ packages: - supports-color dev: true - /@typescript-eslint/types@5.62.0: - resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@typescript-eslint/typescript-estree@5.62.0(typescript@4.9.5): + /@typescript-eslint/type-utils@5.62.0(eslint@8.50.0)(typescript@5.3.3): + resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.50.0)(typescript@5.3.3) + debug: 4.3.4 + eslint: 8.50.0 + tsutils: 3.21.0(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@5.62.0: + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/typescript-estree@5.62.0(typescript@4.9.5): + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + tsutils: 3.21.0(typescript@4.9.5) + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.3): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -4192,8 +5070,8 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 - tsutils: 3.21.0(typescript@4.9.5) - typescript: 4.9.5 + tsutils: 3.21.0(typescript@5.3.3) + typescript: 5.3.3 transitivePeerDependencies: - supports-color dev: true @@ -4218,6 +5096,26 @@ packages: - typescript dev: true + /@typescript-eslint/utils@5.62.0(eslint@8.50.0)(typescript@5.3.3): + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0) + '@types/json-schema': 7.0.13 + '@types/semver': 7.5.3 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) + eslint: 8.50.0 + eslint-scope: 5.1.1 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys@5.62.0: resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4226,25 +5124,6 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@unimodules/core@7.1.2: - resolution: {integrity: sha512-lY+e2TAFuebD3vshHMIRqru3X4+k7Xkba4Wa7QsDBd+ex4c4N2dHAO61E2SrGD9+TRBD8w/o7mzK6ljbqRnbyg==} - deprecated: 'replaced by the ''expo'' package, learn more: https://blog.expo.dev/whats-new-in-expo-modules-infrastructure-7a7cdda81ebc' - requiresBuild: true - dependencies: - compare-versions: 3.6.0 - dev: false - optional: true - - /@unimodules/react-native-adapter@6.3.9: - resolution: {integrity: sha512-i9/9Si4AQ8awls+YGAKkByFbeAsOPgUNeLoYeh2SQ3ddjxJ5ZJDtq/I74clDnpDcn8zS9pYlcDJ9fgVJa39Glw==} - deprecated: 'replaced by the ''expo'' package, learn more: https://blog.expo.dev/whats-new-in-expo-modules-infrastructure-7a7cdda81ebc' - requiresBuild: true - dependencies: - expo-modules-autolinking: 0.0.3 - invariant: 2.2.4 - dev: false - optional: true - /@urql/core@2.3.6(graphql@15.8.0): resolution: {integrity: sha512-PUxhtBh7/8167HJK6WqBv6Z0piuiaZHQGYbhwpNL9aIQmLROPEdaUYkY4wh45wPQXcTpnd11l0q3Pw+TI11pdw==} peerDependencies: @@ -4358,6 +5237,10 @@ packages: resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} dev: true + /aes-js@4.0.0-beta.5: + resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} + dev: true + /agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -4407,7 +5290,6 @@ packages: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 uri-js: 4.4.1 - dev: false /anser@1.4.10: resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} @@ -4539,7 +5421,6 @@ packages: dependencies: call-bind: 1.0.2 is-array-buffer: 3.0.2 - dev: true /array-differ@3.0.0: resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} @@ -4565,6 +5446,10 @@ packages: is-string: 1.0.7 dev: true + /array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + dev: true + /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -4611,7 +5496,6 @@ packages: get-intrinsic: 1.2.1 is-array-buffer: 3.0.2 is-shared-array-buffer: 1.0.2 - dev: true /arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} @@ -4626,13 +5510,8 @@ packages: /asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - /asmcrypto.js@0.22.0: - resolution: {integrity: sha512-usgMoyXjMbx/ZPdzTSXExhMPur2FTdz/Vo5PVx2gIaBcdAAJNOFlsdgqveM8Cff7W0v+xrf9BwjOV26JSAF9qA==} - dev: false - /asmcrypto.js@2.3.2: resolution: {integrity: sha512-3FgFARf7RupsZETQ1nHnhLUUvpcttcCq1iZCaVAbJZbCZ5VNRrNyvpDyHTOb0KC3llFcsyOT/a99NZcCbeiEsA==} - dev: true /asn1.js@5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} @@ -4677,7 +5556,6 @@ packages: /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} - dev: true /await-lock@2.2.2: resolution: {integrity: sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==} @@ -4904,7 +5782,6 @@ packages: /base-x@4.0.0: resolution: {integrity: sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==} - dev: false /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -4915,6 +5792,13 @@ packages: dependencies: base64url: 3.0.1 + /base64url-universal@2.0.0: + resolution: {integrity: sha512-6Hpg7EBf3t148C3+fMzjf+CHnADVDafWzlJUXAqqqbm4MKNXbsoPdOkWeRTjNlkYG7TpyjIpRO1Gk0SnsFD1rw==} + engines: {node: '>=14'} + dependencies: + base64url: 3.0.1 + dev: true + /base64url@3.0.1: resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==} engines: {node: '>=6.0.0'} @@ -4928,7 +5812,6 @@ packages: /bech32@2.0.0: resolution: {integrity: sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==} - dev: true /before-after-hook@2.2.3: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} @@ -4956,7 +5839,6 @@ packages: /bn.js@4.12.0: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} - dev: true /body-parser@1.20.1: resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} @@ -5044,7 +5926,6 @@ packages: /brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - dev: true /browserslist@4.22.0: resolution: {integrity: sha512-v+Jcv64L2LbfTC6OnRcaxtqJNJuQAVhZKSJfR/6hn7lhnChUXl4amwVviqN1k411BB+3rRoKMitELRn1CojeRA==} @@ -5069,6 +5950,12 @@ packages: base-x: 3.0.9 dev: true + /bs58@5.0.0: + resolution: {integrity: sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==} + dependencies: + base-x: 4.0.0 + dev: true + /bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: @@ -5241,7 +6128,6 @@ packages: /canonicalize@2.0.0: resolution: {integrity: sha512-ulDEYPv7asdKvqahuAY35c1selLdzDwHqugK92hfkzvlDCwXRRelDkR+Er33md/PtnpqHemgkuDPanZ4fiYZ8w==} - dev: true /canvas@2.11.2: resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} @@ -5317,7 +6203,6 @@ packages: dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 - dev: true /cjs-module-lexer@1.2.3: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} @@ -5327,6 +6212,14 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + /clear-module@4.1.2: + resolution: {integrity: sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==} + engines: {node: '>=8'} + dependencies: + parent-module: 2.0.0 + resolve-from: 5.0.0 + dev: true + /cli-cursor@2.1.0: resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} engines: {node: '>=4'} @@ -5509,6 +6402,16 @@ packages: dev: true optional: true + /commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + dev: true + + /commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + dev: true + /commander@2.13.0: resolution: {integrity: sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==} @@ -5527,6 +6430,17 @@ packages: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} + /comment-json@4.2.3: + resolution: {integrity: sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==} + engines: {node: '>= 6'} + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + has-own-prop: 2.0.0 + repeat-string: 1.6.1 + dev: true + /commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -5580,6 +6494,18 @@ packages: typedarray: 0.0.6 dev: true + /configstore@5.0.1: + resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==} + engines: {node: '>=8'} + dependencies: + dot-prop: 5.3.0 + graceful-fs: 4.2.11 + make-dir: 3.1.0 + unique-string: 2.0.0 + write-file-atomic: 3.0.3 + xdg-basedir: 4.0.0 + dev: true + /connect@3.7.0: resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} engines: {node: '>= 0.10.0'} @@ -5764,6 +6690,16 @@ packages: js-yaml: 3.14.1 parse-json: 4.0.0 + /cosmiconfig@8.0.0: + resolution: {integrity: sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==} + engines: {node: '>=14'} + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + dev: true + /cosmiconfig@8.3.6(typescript@5.0.4): resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} @@ -5788,7 +6724,6 @@ packages: md5.js: 1.3.5 ripemd160: 2.0.2 sha.js: 2.4.11 - dev: true /create-jest@29.7.0(@types/node@18.18.0)(ts-node@10.9.1): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} @@ -5822,7 +6757,6 @@ packages: /credentials-context@2.0.0: resolution: {integrity: sha512-/mFKax6FK26KjgV2KW2D4YqKgoJ5DVJpNt87X2Jc9IxT2HBMy7nEIlc+n7pEi+YFFe721XqrvZPd+jbyyBjsvQ==} - dev: false /cross-fetch@3.1.8: resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} @@ -5831,6 +6765,14 @@ packages: transitivePeerDependencies: - encoding + /cross-fetch@4.0.0: + resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} + dependencies: + node-fetch: 2.6.12 + transitivePeerDependencies: + - encoding + dev: false + /cross-spawn@6.0.5: resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} engines: {node: '>=4.8'} @@ -5876,6 +6818,117 @@ packages: type-fest: 1.4.0 dev: true + /cspell-dictionary@6.31.3: + resolution: {integrity: sha512-3w5P3Md/tbHLVGPKVL0ePl1ObmNwhdDiEuZ2TXfm2oAIwg4aqeIrw42A2qmhaKLcuAIywpqGZsrGg8TviNNhig==} + engines: {node: '>=14'} + dependencies: + '@cspell/cspell-pipe': 6.31.3 + '@cspell/cspell-types': 6.31.3 + cspell-trie-lib: 6.31.3 + fast-equals: 4.0.3 + gensequence: 5.0.2 + dev: true + + /cspell-gitignore@6.31.3: + resolution: {integrity: sha512-vCfVG4ZrdwJnsZHl/cdp8AY+YNPL3Ga+0KR9XJsaz69EkQpgI6porEqehuwle7hiXw5e3L7xFwNEbpCBlxgLRA==} + engines: {node: '>=14'} + hasBin: true + dependencies: + cspell-glob: 6.31.3 + find-up: 5.0.0 + dev: true + + /cspell-glob@6.31.3: + resolution: {integrity: sha512-+koUJPSCOittQwhR0T1mj4xXT3N+ZnY2qQ53W6Gz9HY3hVfEEy0NpbwE/Uy7sIvFMbc426fK0tGXjXyIj72uhQ==} + engines: {node: '>=14'} + dependencies: + micromatch: 4.0.5 + dev: true + + /cspell-grammar@6.31.3: + resolution: {integrity: sha512-TZYaOLIGAumyHlm4w7HYKKKcR1ZgEMKt7WNjCFqq7yGVW7U+qyjQqR8jqnLiUTZl7c2Tque4mca7n0CFsjVv5A==} + engines: {node: '>=14'} + hasBin: true + dependencies: + '@cspell/cspell-pipe': 6.31.3 + '@cspell/cspell-types': 6.31.3 + dev: true + + /cspell-io@6.31.3: + resolution: {integrity: sha512-yCnnQ5bTbngUuIAaT5yNSdI1P0Kc38uvC8aynNi7tfrCYOQbDu1F9/DcTpbdhrsCv+xUn2TB1YjuCmm0STfJlA==} + engines: {node: '>=14'} + dependencies: + '@cspell/cspell-service-bus': 6.31.3 + node-fetch: 2.6.12 + transitivePeerDependencies: + - encoding + dev: true + + /cspell-lib@6.31.3: + resolution: {integrity: sha512-Dv55aecaMvT/5VbNryKo0Zos8dtHon7e1K0z8DR4/kGZdQVT0bOFWeotSLhuaIqoNFdEt8ypfKbrIHIdbgt1Hg==} + engines: {node: '>=14.6'} + dependencies: + '@cspell/cspell-bundled-dicts': 6.31.3 + '@cspell/cspell-pipe': 6.31.3 + '@cspell/cspell-types': 6.31.3 + '@cspell/strong-weak-map': 6.31.3 + clear-module: 4.1.2 + comment-json: 4.2.3 + configstore: 5.0.1 + cosmiconfig: 8.0.0 + cspell-dictionary: 6.31.3 + cspell-glob: 6.31.3 + cspell-grammar: 6.31.3 + cspell-io: 6.31.3 + cspell-trie-lib: 6.31.3 + fast-equals: 4.0.3 + find-up: 5.0.0 + gensequence: 5.0.2 + import-fresh: 3.3.0 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + vscode-languageserver-textdocument: 1.0.11 + vscode-uri: 3.0.8 + transitivePeerDependencies: + - encoding + dev: true + + /cspell-trie-lib@6.31.3: + resolution: {integrity: sha512-HNUcLWOZAvtM3E34U+7/mSSpO0F6nLd/kFlRIcvSvPb9taqKe8bnSa0Yyb3dsdMq9rMxUmuDQtF+J6arZK343g==} + engines: {node: '>=14'} + dependencies: + '@cspell/cspell-pipe': 6.31.3 + '@cspell/cspell-types': 6.31.3 + gensequence: 5.0.2 + dev: true + + /cspell@6.31.3: + resolution: {integrity: sha512-VeeShDLWVM6YPiU/imeGy0lmg6ki63tbLEa6hz20BExhzzpmINOP5nSTYtpY0H9zX9TrF/dLbI38TuuYnyG3Uw==} + engines: {node: '>=14'} + hasBin: true + dependencies: + '@cspell/cspell-json-reporter': 6.31.3 + '@cspell/cspell-pipe': 6.31.3 + '@cspell/cspell-types': 6.31.3 + '@cspell/dynamic-import': 6.31.3 + chalk: 4.1.2 + commander: 10.0.1 + cspell-gitignore: 6.31.3 + cspell-glob: 6.31.3 + cspell-io: 6.31.3 + cspell-lib: 6.31.3 + fast-glob: 3.3.1 + fast-json-stable-stringify: 2.1.0 + file-entry-cache: 6.0.1 + get-stdin: 8.0.0 + imurmurhash: 0.1.4 + semver: 7.5.4 + strip-ansi: 6.0.1 + vscode-uri: 3.0.8 + transitivePeerDependencies: + - encoding + dev: true + /csv-parse@5.5.0: resolution: {integrity: sha512-RxruSK3M4XgzcD7Trm2wEN+SJ26ChIb903+IWxNOcB5q4jT2Cs+hFr6QP39J05EohshRFEvyzEBoZ/466S2sbw==} dev: false @@ -5969,7 +7022,6 @@ packages: /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true /deepmerge@3.3.0: resolution: {integrity: sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==} @@ -6131,7 +7183,6 @@ packages: js-sha3: 0.8.0 multiformats: 9.9.0 uint8arrays: 3.1.1 - dev: true /did-jwt@7.4.7: resolution: {integrity: sha512-Apz7nIfIHSKWIMaEP5L/K8xkwByvjezjTG0xiqwKdnNj1x8M0+Yasury5Dm/KPltxi2PlGfRPf3IejRKZrT8mQ==} @@ -6149,7 +7200,6 @@ packages: /did-resolver@4.1.0: resolution: {integrity: sha512-S6fWHvCXkZg2IhS4RcVHxwuyVejPR7c+a4Go0xbQ9ps5kILa8viiYQgrM4gfTyeTjJ0ekgJH9gk/BawTpmkbZA==} - dev: true /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} @@ -6220,11 +7270,9 @@ packages: /ed25519-signature-2018-context@1.1.0: resolution: {integrity: sha512-ppDWYMNwwp9bploq0fS4l048vHIq41nWsAbPq6H4mNVx9G/GxW3fwg4Ln0mqctP13MoEpREK7Biz8TbVVdYXqA==} - dev: false /ed25519-signature-2020-context@1.1.0: resolution: {integrity: sha512-dBGSmoUIK6h2vadDctrDnhhTO01PR2hJk0mRNEfrRDPCjaIwrfy4J+eziEQ9Q1m8By4f/CSRgKM1h53ydKfdNg==} - dev: false /ed2curve@0.3.0: resolution: {integrity: sha512-8w2fmmq3hv9rCrcI7g9hms2pMunQr1JINfcjwR9tAyZqhtyaMN991lF/ZfHfr5tzZQ8c7y7aBgZbjfbd0fjFwQ==} @@ -6255,7 +7303,6 @@ packages: inherits: 2.0.4 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - dev: true /emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -6380,7 +7427,6 @@ packages: typed-array-length: 1.0.4 unbox-primitive: 1.0.2 which-typed-array: 1.1.11 - dev: true /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} @@ -6389,7 +7435,6 @@ packages: get-intrinsic: 1.2.1 has: 1.0.3 has-tostringtag: 1.0.0 - dev: true /es-shim-unscopables@1.0.0: resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} @@ -6404,7 +7449,6 @@ packages: is-callable: 1.2.7 is-date-object: 1.0.5 is-symbol: 1.0.4 - dev: true /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -6426,6 +7470,28 @@ packages: engines: {node: '>=10'} dev: true + /escodegen@1.14.3: + resolution: {integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==} + engines: {node: '>=4.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 4.3.0 + esutils: 2.0.3 + optionator: 0.8.3 + optionalDependencies: + source-map: 0.6.1 + dev: false + + /eslint-config-prettier@8.10.0(eslint@8.50.0): + resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.50.0 + dev: true + /eslint-config-prettier@9.0.0(eslint@8.50.0): resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} hasBin: true @@ -6623,7 +7689,6 @@ packages: /estraverse@4.3.0: resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} engines: {node: '>=4.0'} - dev: true /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} @@ -6638,6 +7703,22 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + /ethers@6.10.0: + resolution: {integrity: sha512-nMNwYHzs6V1FR3Y4cdfxSQmNgZsRj1RiTU25JwvnJLmyzw9z3SKxNc2XKDuiXXo/v9ds5Mp9m6HBabgYQQ26tA==} + engines: {node: '>=14.0.0'} + dependencies: + '@adraffy/ens-normalize': 1.10.0 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@types/node': 18.15.13 + aes-js: 4.0.0-beta.5 + tslib: 2.4.0 + ws: 8.5.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -6649,7 +7730,6 @@ packages: /events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - dev: true /exec-async@2.2.0: resolution: {integrity: sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==} @@ -6777,7 +7857,6 @@ packages: dependencies: base64-js: 1.5.1 expo: 48.0.20(@babel/core@7.23.0) - dev: true /expo-file-system@15.2.2(expo@48.0.20): resolution: {integrity: sha512-LFkOLcWwlmnjkURxZ3/0ukS35OswX8iuQknLHRHeyk8mUA8fpRPPelD/a1lS+yclqfqavMJmTXVKM1Nsq5XVMA==} @@ -6810,19 +7889,6 @@ packages: dependencies: expo: 48.0.20(@babel/core@7.23.0) - /expo-modules-autolinking@0.0.3: - resolution: {integrity: sha512-azkCRYj/DxbK4udDuDxA9beYzQTwpJ5a9QA0bBgha2jHtWdFGF4ZZWSY+zNA5mtU3KqzYt8jWHfoqgSvKyu1Aw==} - hasBin: true - requiresBuild: true - dependencies: - chalk: 4.1.2 - commander: 7.2.0 - fast-glob: 3.3.1 - find-up: 5.0.0 - fs-extra: 9.1.0 - dev: false - optional: true - /expo-modules-autolinking@1.2.0: resolution: {integrity: sha512-QOPh/iXykNDCAzUual1imSrn2aDakzCGUp2QmxVREr0llajXygroUWlT9sQXh1zKzbNp+a+i/xK375ZeBFiNJA==} hasBin: true @@ -6839,17 +7905,6 @@ packages: compare-versions: 3.6.0 invariant: 2.2.4 - /expo-random@13.4.0(expo@48.0.20): - resolution: {integrity: sha512-Z/Bbd+1MbkK8/4ukspgA3oMlcu0q3YTCu//7q2xHwy35huN6WCv4/Uw2OGyCiOQjAbU02zwq6swA+VgVmJRCEw==} - requiresBuild: true - peerDependencies: - expo: '*' - dependencies: - base64-js: 1.5.1 - expo: 48.0.20(@babel/core@7.23.0) - dev: false - optional: true - /expo@48.0.20(@babel/core@7.23.0): resolution: {integrity: sha512-SDRlLRINWWqf/OIPaUr/BsFZLhR5oEj1u9Cn06h1mPeo8pqv6ei/QTSZql4e0ixHIu3PWMPrUx9k/47nnTyTpg==} hasBin: true @@ -6970,6 +8025,10 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + /fast-equals@4.0.3: + resolution: {integrity: sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==} + dev: true + /fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} @@ -6997,7 +8056,6 @@ packages: /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true /fast-printf@1.6.9: resolution: {integrity: sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==} @@ -7012,7 +8070,6 @@ packages: /fast-text-encoding@1.0.6: resolution: {integrity: sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==} - dev: false /fast-uri@2.2.0: resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} @@ -7194,6 +8251,16 @@ packages: dependencies: micromatch: 4.0.5 + /fix-esm@1.0.1: + resolution: {integrity: sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==} + dependencies: + '@babel/core': 7.23.0 + '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.23.0) + '@babel/plugin-transform-modules-commonjs': 7.23.0(@babel/core@7.23.0) + transitivePeerDependencies: + - supports-color + dev: true + /flat-cache@3.1.0: resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==} engines: {node: '>=12.0.0'} @@ -7233,6 +8300,9 @@ packages: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 + + /foreach@2.0.6: + resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==} dev: true /foreground-child@3.1.1: @@ -7355,11 +8425,9 @@ packages: define-properties: 1.2.1 es-abstract: 1.22.2 functions-have-names: 1.2.3 - dev: true /functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: true /gauge@3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} @@ -7390,6 +8458,11 @@ packages: wide-align: 1.1.5 dev: true + /gensequence@5.0.2: + resolution: {integrity: sha512-JlKEZnFc6neaeSVlkzBGGgkIoIaSxMgvdamRoPN8r3ozm2r9dusqxeKqYQ7lhzmj2UhFQP8nkyfCaiLQxiLrDA==} + engines: {node: '>=14'} + dev: true + /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -7431,6 +8504,11 @@ packages: engines: {node: '>=8'} dev: true + /get-stdin@8.0.0: + resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} + engines: {node: '>=10'} + dev: true + /get-stdin@9.0.0: resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==} engines: {node: '>=12'} @@ -7458,7 +8536,6 @@ packages: dependencies: call-bind: 1.0.2 get-intrinsic: 1.2.1 - dev: true /getenv@1.0.0: resolution: {integrity: sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==} @@ -7560,7 +8637,7 @@ packages: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.0.5 + minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 dev: true @@ -7606,6 +8683,13 @@ packages: path-scurry: 1.10.1 dev: true + /global-dirs@0.1.1: + resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} + engines: {node: '>=4'} + dependencies: + ini: 1.3.8 + dev: true + /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -7679,7 +8763,6 @@ packages: /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} @@ -7689,6 +8772,11 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + /has-own-prop@2.0.0: + resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} + engines: {node: '>=8'} + dev: true + /has-property-descriptors@1.0.0: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} dependencies: @@ -7707,7 +8795,6 @@ packages: engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 - dev: true /has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} @@ -7725,14 +8812,12 @@ packages: inherits: 2.0.4 readable-stream: 3.6.2 safe-buffer: 5.2.1 - dev: true /hash.js@1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} dependencies: inherits: 2.0.4 minimalistic-assert: 1.0.1 - dev: true /hermes-estree@0.8.0: resolution: {integrity: sha512-W6JDAOLZ5pMPMjEiQGLCXSSV7pIBEgRR5zGkxgmzGSXHOxqV5dC/M1Zevqpbm9TZDE5tu358qZf8Vkzmsc+u7Q==} @@ -7763,7 +8848,6 @@ packages: hash.js: 1.1.7 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - dev: true /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -7946,6 +9030,10 @@ packages: resolve-cwd: 3.0.0 dev: true + /import-meta-resolve@2.2.2: + resolution: {integrity: sha512-f8KcQ1D80V7RnqVm+/lirO9zkOxjGxhaTC1IPrBGd3MEfNgmNG67tSUO9gTi2F3Blr2Az6g1vocaxzkVnWl9MA==} + dev: true + /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -8049,7 +9137,6 @@ packages: get-intrinsic: 1.2.1 has: 1.0.3 side-channel: 1.0.4 - dev: true /invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -8085,7 +9172,6 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.2.1 is-typed-array: 1.1.12 - dev: true /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -8094,7 +9180,6 @@ packages: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: has-bigints: 1.0.2 - dev: true /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} @@ -8102,7 +9187,6 @@ packages: dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 - dev: true /is-buffer@1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} @@ -8110,7 +9194,6 @@ packages: /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - dev: true /is-ci@3.0.1: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} @@ -8129,7 +9212,6 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 - dev: true /is-directory@0.3.1: resolution: {integrity: sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==} @@ -8211,14 +9293,12 @@ packages: /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} - dev: true /is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 - dev: true /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} @@ -8259,7 +9339,6 @@ packages: dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 - dev: true /is-root@2.1.0: resolution: {integrity: sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==} @@ -8269,7 +9348,6 @@ packages: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: call-bind: 1.0.2 - dev: true /is-ssh@1.4.0: resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} @@ -8300,14 +9378,12 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 - dev: true /is-symbol@1.0.4: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 - dev: true /is-text-path@1.0.1: resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} @@ -8321,6 +9397,9 @@ packages: engines: {node: '>= 0.4'} dependencies: which-typed-array: 1.1.11 + + /is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} dev: true /is-unicode-supported@0.1.0: @@ -8337,7 +9416,6 @@ packages: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 - dev: true /is-wsl@1.1.0: resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} @@ -8354,7 +9432,6 @@ packages: /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: true /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -8363,26 +9440,6 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} - /isomorphic-webcrypto@2.3.8(expo@48.0.20)(react-native@0.71.13): - resolution: {integrity: sha512-XddQSI0WYlSCjxtm1AI8kWQOulf7hAN3k3DclF1sxDJZqOe0pcsOt675zvWW91cZH9hYs3nlA3Ev8QK5i80SxQ==} - dependencies: - '@peculiar/webcrypto': 1.4.3 - asmcrypto.js: 0.22.0 - b64-lite: 1.4.0 - b64u-lite: 1.1.0 - msrcrypto: 1.5.8 - str2buf: 1.3.0 - webcrypto-shim: 0.1.7 - optionalDependencies: - '@unimodules/core': 7.1.2 - '@unimodules/react-native-adapter': 6.3.9 - expo-random: 13.4.0(expo@48.0.20) - react-native-securerandom: 0.1.1(react-native@0.71.13) - transitivePeerDependencies: - - expo - - react-native - dev: false - /istanbul-lib-coverage@3.2.0: resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} engines: {node: '>=8'} @@ -8636,6 +9693,16 @@ packages: fsevents: 2.3.3 dev: true + /jest-junit@15.0.0: + resolution: {integrity: sha512-Z5sVX0Ag3HZdMUnD5DFlG+1gciIFSy7yIVPhOdGUi8YJaI9iLvvBb530gtQL2CHmv0JJeiwRZenr0VrSR7frvg==} + engines: {node: '>=10.12.0'} + dependencies: + mkdirp: 1.0.4 + strip-ansi: 6.0.1 + uuid: 8.3.2 + xml: 1.0.1 + dev: true + /jest-junit@16.0.0: resolution: {integrity: sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==} engines: {node: '>=10.12.0'} @@ -8948,7 +10015,6 @@ packages: /js-sha3@0.8.0: resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} - dev: true /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -9031,6 +10097,12 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true + /json-pointer@0.6.2: + resolution: {integrity: sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==} + dependencies: + foreach: 2.0.6 + dev: true + /json-schema-deref-sync@0.13.0: resolution: {integrity: sha512-YBOEogm5w9Op337yb6pAT6ZXDqlxAsQCanM3grid8lMWNxRJO/zWEJi3ZzqDL8boWfwhTFym5EFrNgWwpqcBRg==} engines: {node: '>=6.0.0'} @@ -9050,7 +10122,6 @@ packages: /json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: false /json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -9108,6 +10179,18 @@ packages: engines: {node: '>=12'} dev: false + /jsonld-signatures@11.2.1: + resolution: {integrity: sha512-RNaHTEeRrX0jWeidPCwxMq/E/Ze94zFyEZz/v267ObbCHQlXhPO7GtkY6N5PSHQfQhZPXa8NlMBg5LiDF4dNbA==} + engines: {node: '>=14'} + dependencies: + '@digitalbazaar/security-context': 1.0.1 + jsonld: 8.3.2 + serialize-error: 8.1.0 + transitivePeerDependencies: + - encoding + - web-streams-polyfill + dev: true + /jsonld@5.2.0: resolution: {integrity: sha512-JymgT6Xzk5CHEmHuEyvoTNviEPxv6ihLWSPu1gFdtjSAyM6cFqNrv02yS/SIur3BBIkCf0HjizRc24d8/FfQKw==} engines: {node: '>=12'} @@ -9121,6 +10204,19 @@ packages: - web-streams-polyfill dev: true + /jsonld@8.3.2: + resolution: {integrity: sha512-MwBbq95szLwt8eVQ1Bcfwmgju/Y5P2GdtlHE2ncyfuYjIdEhluUVyj1eudacf1mOkWIoS9GpDBTECqhmq7EOaA==} + engines: {node: '>=14'} + dependencies: + '@digitalbazaar/http-client': 3.4.1 + canonicalize: 1.0.8 + lru-cache: 6.0.0 + rdf-canonize: 3.4.0 + transitivePeerDependencies: + - encoding + - web-streams-polyfill + dev: true + /jsonparse@1.3.1: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} @@ -9128,7 +10224,6 @@ packages: /jwt-decode@3.1.2: resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==} - dev: false /keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} @@ -9151,6 +10246,23 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} + /ky-universal@0.11.0(ky@0.33.3): + resolution: {integrity: sha512-65KyweaWvk+uKKkCrfAf+xqN2/epw1IJDtlyCPxYffFCMR8u1sp2U65NtWpnozYfZxQ6IUzIlvUcw+hQ82U2Xw==} + engines: {node: '>=14.16'} + peerDependencies: + ky: '>=0.31.4' + web-streams-polyfill: '>=3.2.1' + peerDependenciesMeta: + web-streams-polyfill: + optional: true + dependencies: + abort-controller: 3.0.0 + ky: 0.33.3 + node-fetch: 2.6.12 + transitivePeerDependencies: + - encoding + dev: true + /ky-universal@0.8.2(ky@0.25.1): resolution: {integrity: sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ==} engines: {node: '>=10.17'} @@ -9171,6 +10283,22 @@ packages: resolution: {integrity: sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA==} engines: {node: '>=10'} + /ky@0.33.3: + resolution: {integrity: sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==} + engines: {node: '>=14.16'} + dev: true + + /language-subtag-registry@0.3.22: + resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} + dev: false + + /language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + dependencies: + language-subtag-registry: 0.3.22 + dev: false + /lerna-changelog@2.2.0: resolution: {integrity: sha512-yjYNAHrbnw8xYFKmYWJEP52Tk4xSdlNmzpYr26+3glbSGDmpe8UMo8f9DlEntjGufL+opup421oVTXcLshwAaQ==} engines: {node: 12.* || 14.* || >= 16} @@ -9282,6 +10410,14 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + /levn@0.3.0: + resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + type-check: 0.3.2 + dev: false + /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -9572,7 +10708,6 @@ packages: hash-base: 3.1.0 inherits: 2.0.4 safe-buffer: 5.2.1 - dev: true /md5@2.2.1: resolution: {integrity: sha512-PlGG4z5mBANDGCKsYQe0CaUYHdZYZt8ZPZLmEt+Urf0W4GlpTX4HescwHU+dc9+Z/G/vZKYZYFrwgm9VxK6QOQ==} @@ -10035,11 +11170,9 @@ packages: /minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - dev: true /minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - dev: true /minimatch@3.0.5: resolution: {integrity: sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==} @@ -10190,11 +11323,21 @@ packages: engines: {node: '>=10'} hasBin: true + /mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + dev: true + /modify-values@1.0.1: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} dev: true + /moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + dev: true + /morgan@1.10.0: resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} engines: {node: '>= 0.8.0'} @@ -10228,6 +11371,11 @@ packages: '@multiformats/base-x': 4.0.1 dev: true + /multiformats@11.0.2: + resolution: {integrity: sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==} + engines: {node: '>=16.0.0', npm: '>=7.0.0'} + dev: false + /multiformats@9.9.0: resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} @@ -10272,6 +11420,12 @@ packages: resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==} dev: false + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true @@ -10338,6 +11492,17 @@ packages: - supports-color dev: true + /nock@13.5.0: + resolution: {integrity: sha512-9hc1eCS2HtOz+sE9W7JQw/tXJktg0zoPSu48s/pYe73e25JW9ywiowbqnUSd7iZPeVawLcVpPZeZS312fwSY+g==} + engines: {node: '>= 10.13'} + dependencies: + debug: 4.3.4 + json-stringify-safe: 5.0.1 + propagate: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: true + /node-addon-api@2.0.2: resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} dev: true @@ -10713,7 +11878,6 @@ packages: define-properties: 1.2.1 has-symbols: 1.0.3 object-keys: 1.1.1 - dev: true /object.fromentries@2.0.7: resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} @@ -10832,6 +11996,18 @@ packages: oidc-token-hash: 5.0.3 dev: false + /optionator@0.8.3: + resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.3.0 + prelude-ls: 1.1.2 + type-check: 0.3.2 + word-wrap: 1.2.5 + dev: false + /optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} @@ -11038,6 +12214,10 @@ packages: - supports-color dev: true + /pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + dev: true + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -11045,6 +12225,13 @@ packages: callsites: 3.1.0 dev: true + /parent-module@2.0.0: + resolution: {integrity: sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==} + engines: {node: '>=8'} + dependencies: + callsites: 3.1.0 + dev: true + /parse-json@4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} @@ -11253,11 +12440,22 @@ packages: resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} engines: {node: '>=4.0.0'} + /prelude-ls@1.1.2: + resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} + engines: {node: '>= 0.8.0'} + dev: false + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} dev: true + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + /prettier@3.0.3: resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} engines: {node: '>=14'} @@ -11433,14 +12631,12 @@ packages: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: safe-buffer: 5.2.1 - dev: true /randomfill@1.0.4: resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==} dependencies: randombytes: 2.1.0 safe-buffer: 5.2.1 - dev: true /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} @@ -11513,17 +12709,6 @@ packages: /react-native-gradle-plugin@0.71.19: resolution: {integrity: sha512-1dVk9NwhoyKHCSxcrM6vY6cxmojeATsBobDicX0ZKr7DgUF2cBQRTKsimQFvzH8XhOVXyH8p4HyDSZNIFI8OlQ==} - /react-native-securerandom@0.1.1(react-native@0.71.13): - resolution: {integrity: sha512-CozcCx0lpBLevxiXEb86kwLRalBCHNjiGPlw3P7Fi27U6ZLdfjOCNRHD1LtBKcvPvI3TvkBXB3GOtLvqaYJLGw==} - requiresBuild: true - peerDependencies: - react-native: '*' - dependencies: - base64-js: 1.5.1 - react-native: 0.71.13(@babel/core@7.23.0)(@babel/preset-env@7.22.20)(react@18.2.0) - dev: false - optional: true - /react-native-securerandom@1.0.1(react-native@0.71.13): resolution: {integrity: sha512-ibuDnd3xi17HyD5CkilOXGPFpS9Z1oifjyHFwUl8NMzcQcpruM0ZX8ytr3A4rCeAsaBHjz69r78Xgd6vUswv1Q==} peerDependencies: @@ -11775,7 +12960,6 @@ packages: call-bind: 1.0.2 define-properties: 1.2.1 set-function-name: 2.0.1 - dev: true /regexpu-core@5.3.2: resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} @@ -11797,6 +12981,11 @@ packages: /remove-trailing-slash@0.1.1: resolution: {integrity: sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==} + /repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + dev: true + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -11842,6 +13031,13 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + /resolve-global@1.0.0: + resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} + engines: {node: '>=8'} + dependencies: + global-dirs: 0.1.1 + dev: true + /resolve.exports@2.0.2: resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} engines: {node: '>=10'} @@ -11942,7 +13138,6 @@ packages: dependencies: hash-base: 3.1.0 inherits: 2.0.4 - dev: true /roarr@7.15.1: resolution: {integrity: sha512-0ExL9rjOXeQPvQvQo8IcV8SR2GTXmDr1FQFlY2HiAV+gdVQjaVZNOx9d4FI2RqFFsd0sNsiw2TRS/8RU9g0ZfA==} @@ -11996,7 +13191,6 @@ packages: get-intrinsic: 1.2.1 has-symbols: 1.0.3 isarray: 2.0.5 - dev: true /safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -12015,12 +13209,10 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.2.1 is-regex: 1.1.4 - dev: true /safe-stable-stringify@2.4.3: resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} engines: {node: '>=10'} - dev: false /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -12110,7 +13302,6 @@ packages: engines: {node: '>=10'} dependencies: type-fest: 0.20.2 - dev: false /serve-static@1.15.0: resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} @@ -12133,7 +13324,6 @@ packages: define-data-property: 1.1.0 functions-have-names: 1.2.3 has-property-descriptors: 1.0.0 - dev: true /setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -12147,7 +13337,6 @@ packages: dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 - dev: true /shallow-clone@3.0.1: resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} @@ -12250,6 +13439,19 @@ packages: engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} dev: true + /sock-daemon@1.4.1: + resolution: {integrity: sha512-KaiK8hLNEMKydOsdveudcruwzps/j83v4C1JQ58Bs8skIiMe1pAYSqHcNoxmU1McIaPTRIrhskB7g+TuzLzfjg==} + dependencies: + mkdirp: 3.0.1 + rimraf: 5.0.5 + signal-exit: 4.1.0 + socket-post-message: 1.0.3 + dev: true + + /socket-post-message@1.0.3: + resolution: {integrity: sha512-UhJaB3xR2oF+HvddFOq2cBZi4zVKOHvdiBo+BaScNxsEUg3TLWSP8BkweKfe07kfH1thjn1hJR0af/w1EtBFjg==} + dev: true + /socks-proxy-agent@6.2.1: resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} engines: {node: '>= 10'} @@ -12383,6 +13585,12 @@ packages: dependencies: type-fest: 0.7.1 + /static-eval@2.0.2: + resolution: {integrity: sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==} + dependencies: + escodegen: 1.14.3 + dev: false + /statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -12429,6 +13637,20 @@ packages: strip-ansi: 7.1.0 dev: true + /string.prototype.matchall@4.0.10: + resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + get-intrinsic: 1.2.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + regexp.prototype.flags: 1.5.1 + set-function-name: 2.0.1 + side-channel: 1.0.4 + dev: false + /string.prototype.padend@3.1.5: resolution: {integrity: sha512-DOB27b/2UTTD+4myKUFh+/fXWcu/UDyASIXfg+7VzoCNNGOfWvoyU/x5pvVHr++ztyt/oSYI1BcWBBG/hmlNjA==} engines: {node: '>= 0.4'} @@ -12445,7 +13667,6 @@ packages: call-bind: 1.0.2 define-properties: 1.2.1 es-abstract: 1.22.2 - dev: true /string.prototype.trimend@1.0.7: resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} @@ -12453,7 +13674,6 @@ packages: call-bind: 1.0.2 define-properties: 1.2.1 es-abstract: 1.22.2 - dev: true /string.prototype.trimstart@1.0.7: resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} @@ -12461,7 +13681,6 @@ packages: call-bind: 1.0.2 define-properties: 1.2.1 es-abstract: 1.22.2 - dev: true /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -12892,6 +14111,10 @@ packages: /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + /ts-interface-checker@1.0.2: + resolution: {integrity: sha512-4IKKvhZRXhvtYF/mtu+OCfBqJKV6LczUq4kQYcpT+iSB7++R9+giWnp2ecwWMIcnG16btVOkXFnoxLSYMN1Q1g==} + dev: true + /ts-jest@29.1.1(@babel/core@7.23.0)(jest@29.7.0)(typescript@4.9.5): resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -12926,7 +14149,7 @@ packages: yargs-parser: 21.1.1 dev: true - /ts-jest@29.1.1(@babel/core@7.23.0)(jest@29.7.0)(typescript@5.0.4): + /ts-jest@29.1.1(@babel/core@7.23.0)(jest@29.7.0)(typescript@5.3.3): resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -12956,10 +14179,24 @@ packages: lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.5.4 - typescript: 5.0.4 + typescript: 5.3.3 yargs-parser: 21.1.1 dev: true + /ts-json-schema-generator@1.5.0: + resolution: {integrity: sha512-RkiaJ6YxGc5EWVPfyHxszTmpGxX8HC2XBvcFlAl1zcvpOG4tjjh+eXioStXJQYTvr9MoK8zCOWzAUlko3K0DiA==} + engines: {node: '>=10.0.0'} + hasBin: true + dependencies: + '@types/json-schema': 7.0.13 + commander: 11.1.0 + glob: 8.1.0 + json5: 2.2.3 + normalize-path: 3.0.0 + safe-stable-stringify: 2.4.3 + typescript: 5.3.3 + dev: true + /ts-node@10.9.1(@types/node@18.18.0)(typescript@4.9.5): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true @@ -13015,10 +14252,33 @@ packages: strip-bom: 3.0.0 dev: true + /tsimp@2.0.10(typescript@5.3.3): + resolution: {integrity: sha512-FLcP/VOvIROt+w2qBrTcUAuHHeMoKI+pTJ1JDt2ADhMgyKYS87M28BXoBFOdVuewpl8030/33gNXolbbshW5Xw==} + engines: {node: 16 >=16.17.0 || 18 >= 18.6.0 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.1.0 + dependencies: + '@isaacs/cached': 1.0.1 + '@isaacs/catcher': 1.0.4 + foreground-child: 3.1.1 + mkdirp: 3.0.1 + pirates: 4.0.6 + rimraf: 5.0.5 + signal-exit: 4.1.0 + sock-daemon: 1.4.1 + typescript: 5.3.3 + walk-up-path: 3.0.1 + dev: true + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true + /tslib@2.4.0: + resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + dev: true + /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -13037,6 +14297,16 @@ packages: typescript: 4.9.5 dev: true + /tsutils@3.21.0(typescript@5.3.3): + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 5.3.3 + dev: true + /tuf-js@1.1.7: resolution: {integrity: sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -13051,6 +14321,13 @@ packages: /tweetnacl@1.0.3: resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + /type-check@0.3.2: + resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + dev: false + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -13134,7 +14411,6 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.2.1 is-typed-array: 1.1.12 - dev: true /typed-array-byte-length@1.0.0: resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} @@ -13144,7 +14420,6 @@ packages: for-each: 0.3.3 has-proto: 1.0.1 is-typed-array: 1.1.12 - dev: true /typed-array-byte-offset@1.0.0: resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} @@ -13155,7 +14430,6 @@ packages: for-each: 0.3.3 has-proto: 1.0.1 is-typed-array: 1.1.12 - dev: true /typed-array-length@1.0.4: resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} @@ -13163,6 +14437,11 @@ packages: call-bind: 1.0.2 for-each: 0.3.3 is-typed-array: 1.1.12 + + /typedarray-to-buffer@3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + dependencies: + is-typedarray: 1.0.0 dev: true /typedarray@0.0.6: @@ -13181,6 +14460,12 @@ packages: hasBin: true dev: true + /typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + /typical@4.0.0: resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} engines: {node: '>=8'} @@ -13234,6 +14519,12 @@ packages: has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + + /undici@5.28.2: + resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==} + engines: {node: '>=14.0'} + dependencies: + '@fastify/busboy': 2.1.0 dev: true /unicode-canonical-property-names-ecmascript@2.0.0: @@ -13454,6 +14745,18 @@ packages: /vlq@1.0.1: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + /vscode-languageserver-textdocument@1.0.11: + resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==} + dev: true + + /vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + dev: true + + /walk-up-path@3.0.1: + resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} + dev: true + /walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: @@ -13472,6 +14775,11 @@ packages: '@zxing/text-encoding': 0.9.0 dev: true + /web-streams-polyfill@3.3.2: + resolution: {integrity: sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==} + engines: {node: '>= 8'} + dev: true + /webcrypto-core@1.7.7: resolution: {integrity: sha512-7FjigXNsBfopEj+5DV2nhNpfic2vumtjjgPmeDKk45z+MJwXKKfhPB7118Pfzrmh4jqOMST6Ch37iPAHoImg5g==} dependencies: @@ -13517,7 +14825,6 @@ packages: is-number-object: 1.0.7 is-string: 1.0.7 is-symbol: 1.0.4 - dev: true /which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} @@ -13531,7 +14838,6 @@ packages: for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.0 - dev: true /which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} @@ -13562,6 +14868,11 @@ packages: /wonka@4.0.15: resolution: {integrity: sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==} + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + dev: false + /wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} dev: true @@ -13611,6 +14922,15 @@ packages: imurmurhash: 0.1.4 signal-exit: 3.0.7 + /write-file-atomic@3.0.3: + resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + dependencies: + imurmurhash: 0.1.4 + is-typedarray: 1.0.0 + signal-exit: 3.0.7 + typedarray-to-buffer: 3.1.5 + dev: true + /write-file-atomic@4.0.2: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -13685,6 +15005,19 @@ packages: utf-8-validate: optional: true + /ws@8.5.0: + resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /x25519-key-agreement-2020-context@1.0.0: resolution: {integrity: sha512-zblYd8oSg6hNAD+fA9X7ek1hJQRircl3jVlEVCaBTNN9Mv9b4G32uJvRZFMQEMmda8iaTtYo9i2dRMdXX8pjpA==} dev: false @@ -13696,6 +15029,11 @@ packages: simple-plist: 1.3.1 uuid: 7.0.3 + /xdg-basedir@4.0.0: + resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} + engines: {node: '>=8'} + dev: true + /xml2js@0.4.23: resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} engines: {node: '>=4.0.0'}