@sphereon/oid4vci-common 0.10.4-unstable.81 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/events/index.d.ts +17 -0
  2. package/dist/events/index.d.ts.map +1 -0
  3. package/dist/events/index.js +21 -0
  4. package/dist/events/index.js.map +1 -0
  5. package/dist/experimental/holder-vci.d.ts +13 -0
  6. package/dist/experimental/holder-vci.d.ts.map +1 -0
  7. package/dist/experimental/holder-vci.js +9 -0
  8. package/dist/experimental/holder-vci.js.map +1 -0
  9. package/dist/functions/CredentialOfferUtil.d.ts +3 -7
  10. package/dist/functions/CredentialOfferUtil.d.ts.map +1 -1
  11. package/dist/functions/CredentialOfferUtil.js +63 -64
  12. package/dist/functions/CredentialOfferUtil.js.map +1 -1
  13. package/dist/functions/CredentialRequestUtil.d.ts.map +1 -1
  14. package/dist/functions/CredentialRequestUtil.js +7 -10
  15. package/dist/functions/CredentialRequestUtil.js.map +1 -1
  16. package/dist/functions/CredentialResponseUtil.d.ts +3 -1
  17. package/dist/functions/CredentialResponseUtil.d.ts.map +1 -1
  18. package/dist/functions/CredentialResponseUtil.js +1 -1
  19. package/dist/functions/CredentialResponseUtil.js.map +1 -1
  20. package/dist/functions/IssuerMetadataUtils.d.ts +10 -3
  21. package/dist/functions/IssuerMetadataUtils.d.ts.map +1 -1
  22. package/dist/functions/IssuerMetadataUtils.js +147 -30
  23. package/dist/functions/IssuerMetadataUtils.js.map +1 -1
  24. package/dist/functions/ProofUtil.d.ts +38 -0
  25. package/dist/functions/ProofUtil.d.ts.map +1 -0
  26. package/dist/functions/ProofUtil.js +141 -0
  27. package/dist/functions/ProofUtil.js.map +1 -0
  28. package/dist/functions/index.d.ts +1 -0
  29. package/dist/functions/index.d.ts.map +1 -1
  30. package/dist/functions/index.js +1 -0
  31. package/dist/functions/index.js.map +1 -1
  32. package/dist/index.d.ts +5 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +6 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/types/Authorization.types.d.ts +15 -1
  37. package/dist/types/Authorization.types.d.ts.map +1 -1
  38. package/dist/types/Authorization.types.js +7 -1
  39. package/dist/types/Authorization.types.js.map +1 -1
  40. package/dist/types/CredentialIssuance.types.d.ts +7 -3
  41. package/dist/types/CredentialIssuance.types.d.ts.map +1 -1
  42. package/dist/types/CredentialIssuance.types.js.map +1 -1
  43. package/dist/types/Generic.types.d.ts +21 -3
  44. package/dist/types/Generic.types.d.ts.map +1 -1
  45. package/dist/types/Generic.types.js.map +1 -1
  46. package/dist/types/StateManager.types.d.ts +1 -0
  47. package/dist/types/StateManager.types.d.ts.map +1 -1
  48. package/dist/types/StateManager.types.js.map +1 -1
  49. package/dist/types/v1_0_11.types.d.ts +2 -1
  50. package/dist/types/v1_0_11.types.d.ts.map +1 -1
  51. package/dist/types/v1_0_11.types.js.map +1 -1
  52. package/dist/types/v1_0_13.types.d.ts +6 -9
  53. package/dist/types/v1_0_13.types.d.ts.map +1 -1
  54. package/lib/__tests__/CredentialOfferUtil.spec.ts +19 -3
  55. package/lib/events/index.ts +20 -0
  56. package/lib/experimental/holder-vci.ts +19 -0
  57. package/lib/functions/CredentialOfferUtil.ts +57 -60
  58. package/lib/functions/CredentialRequestUtil.ts +8 -11
  59. package/lib/functions/CredentialResponseUtil.ts +4 -4
  60. package/lib/functions/IssuerMetadataUtils.ts +141 -38
  61. package/lib/functions/ProofUtil.ts +185 -0
  62. package/lib/functions/index.ts +1 -0
  63. package/lib/index.ts +7 -0
  64. package/lib/types/Authorization.types.ts +17 -1
  65. package/lib/types/CredentialIssuance.types.ts +8 -3
  66. package/lib/types/Generic.types.ts +38 -12
  67. package/lib/types/StateManager.types.ts +1 -0
  68. package/lib/types/v1_0_11.types.ts +3 -0
  69. package/lib/types/v1_0_13.types.ts +19 -14
  70. package/package.json +3 -11
@@ -12,14 +12,9 @@ import { getFormatForVersion } from './FormatUtils';
12
12
  export function getTypesFromRequest(credentialRequest: CredentialRequest, opts?: { filterVerifiableCredential: boolean }) {
13
13
  let types: string[] = [];
14
14
  if ('credential_identifier' in credentialRequest && credentialRequest.credential_identifier) {
15
- types = [credentialRequest.credential_identifier];
15
+ throw Error(`Cannot get types from request when it contains a credential_identifier`);
16
16
  } else if (credentialRequest.format === 'jwt_vc_json' || credentialRequest.format === 'jwt_vc') {
17
- types =
18
- 'types' in credentialRequest
19
- ? credentialRequest.types
20
- : 'credential_identifier' in credentialRequest
21
- ? [credentialRequest.credential_identifier]
22
- : [];
17
+ types = 'types' in credentialRequest ? credentialRequest.types : [];
23
18
  } else if (credentialRequest.format === 'jwt_vc_json-ld' || credentialRequest.format === 'ldp_vc') {
24
19
  types =
25
20
  'credential_definition' in credentialRequest && credentialRequest.credential_definition
@@ -28,9 +23,7 @@ export function getTypesFromRequest(credentialRequest: CredentialRequest, opts?:
28
23
  // @ts-ignore
29
24
  'types' in credentialRequest.types
30
25
  ? (credentialRequest['types' as keyof CredentialRequest] as unknown as string[])
31
- : 'credential_identifier' in credentialRequest
32
- ? [credentialRequest.credential_identifier]
33
- : [];
26
+ : [];
34
27
  } else if (credentialRequest.format === 'vc+sd-jwt') {
35
28
  types = 'vct' in credentialRequest ? [credentialRequest.vct as string] : [];
36
29
  }
@@ -49,9 +42,13 @@ export function getCredentialRequestForVersion(
49
42
  version: OpenId4VCIVersion,
50
43
  ): UniformCredentialRequest | CredentialRequestV1_0_08 | CredentialRequestV1_0_11 | CredentialRequestV1_0_13 {
51
44
  if (version === OpenId4VCIVersion.VER_1_0_08) {
52
- const draft8Format = getFormatForVersion(credentialRequest.format, version);
45
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
46
+ const draft8Format = getFormatForVersion(credentialRequest.format!, version);
53
47
  const types = getTypesFromRequest(credentialRequest, { filterVerifiableCredential: true });
54
48
 
49
+ if (credentialRequest.credential_subject_issuance) {
50
+ throw Error('Experimental subject issuance is not supported for older versions of the spec');
51
+ }
55
52
  return {
56
53
  format: draft8Format,
57
54
  proof: credentialRequest.proof,
@@ -47,8 +47,8 @@ export async function acquireDeferredCredential({
47
47
  deferredCredentialIntervalInMS?: number;
48
48
  deferredCredentialAwait?: boolean;
49
49
  deferredCredentialEndpoint: string;
50
- }): Promise<OpenIDResponse<CredentialResponse>> {
51
- let credentialResponse: OpenIDResponse<CredentialResponse> = await acquireDeferredCredentialImpl({
50
+ }): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
51
+ let credentialResponse: OpenIDResponse<CredentialResponse> & { access_token: string } = await acquireDeferredCredentialImpl({
52
52
  bearerToken,
53
53
  transactionId,
54
54
  deferredCredentialEndpoint,
@@ -77,7 +77,7 @@ async function acquireDeferredCredentialImpl({
77
77
  bearerToken: string;
78
78
  transactionId?: string;
79
79
  deferredCredentialEndpoint: string;
80
- }): Promise<OpenIDResponse<CredentialResponse>> {
80
+ }): Promise<OpenIDResponse<CredentialResponse> & { access_token: string }> {
81
81
  const response: OpenIDResponse<CredentialResponse> = await post(
82
82
  deferredCredentialEndpoint,
83
83
  JSON.stringify(transactionId ? { transaction_id: transactionId } : ''),
@@ -86,5 +86,5 @@ async function acquireDeferredCredentialImpl({
86
86
  console.log(JSON.stringify(response, null, 2));
87
87
  assertNonFatalError(response);
88
88
 
89
- return response;
89
+ return { ...response, access_token: bearerToken };
90
90
  }
@@ -1,3 +1,4 @@
1
+ import { CredentialDefinitionV1_0_13, CredentialOfferFormat, JsonLdIssuerCredentialDefinition, VCI_LOG_COMMON } from '../index';
1
2
  import {
2
3
  AuthorizationServerMetadata,
3
4
  CredentialConfigurationSupported,
@@ -11,27 +12,58 @@ import {
11
12
  OpenId4VCIVersion,
12
13
  } from '../types';
13
14
 
14
- export function getSupportedCredentials(options?: {
15
+ export function getSupportedCredentials(opts?: {
15
16
  issuerMetadata?: CredentialIssuerMetadata | IssuerMetadata;
16
17
  version: OpenId4VCIVersion;
17
18
  types?: string[][];
18
19
  format?: OID4VCICredentialFormat | string | (OID4VCICredentialFormat | string)[];
19
- }): Record<string, CredentialConfigurationSupportedV1_0_13> {
20
- if (options?.types && Array.isArray(options.types)) {
21
- return options.types
22
- .map((typeSet) => {
23
- return getSupportedCredential({ ...options, types: typeSet });
24
- })
25
- .reduce(
26
- (acc, result) => {
27
- Object.assign(acc, result);
28
- return acc;
29
- },
30
- {} as Record<string, CredentialConfigurationSupportedV1_0_13>,
31
- );
20
+ }): Record<string, CredentialConfigurationSupportedV1_0_13> | Array<CredentialConfigurationSupported> {
21
+ const { version = OpenId4VCIVersion.VER_1_0_13, types } = opts ?? {};
22
+ if (types && Array.isArray(types)) {
23
+ if (version < OpenId4VCIVersion.VER_1_0_13) {
24
+ return types.flatMap((typeSet) => getSupportedCredential({ ...opts, version, types: typeSet }) as Array<CredentialConfigurationSupported>);
25
+ } else {
26
+ return types
27
+ .map((typeSet) => {
28
+ return getSupportedCredential({ ...opts, version, types: typeSet });
29
+ })
30
+ .reduce(
31
+ (acc, result) => {
32
+ Object.assign(acc, result);
33
+ return acc;
34
+ },
35
+ {} as Record<string, CredentialConfigurationSupportedV1_0_13>,
36
+ );
37
+ }
38
+ }
39
+
40
+ return getSupportedCredential(opts ? { ...opts, types: undefined } : undefined);
41
+ }
42
+
43
+ export function determineVersionsFromIssuerMetadata(issuerMetadata: CredentialIssuerMetadata | IssuerMetadata): Array<OpenId4VCIVersion> {
44
+ const versions = new Set<OpenId4VCIVersion>();
45
+ if ('authorization_server' in issuerMetadata) {
46
+ versions.add(OpenId4VCIVersion.VER_1_0_11);
47
+ } else if ('authorization_servers' in issuerMetadata) {
48
+ versions.add(OpenId4VCIVersion.VER_1_0_13);
49
+ }
50
+ if (versions.size === 0) {
51
+ // The above checks where already very specific and only applicable to single versions we support, so let's skip if we encounter them
52
+ if ('credential_configurations_supported' in issuerMetadata) {
53
+ versions.add(OpenId4VCIVersion.VER_1_0_13);
54
+ } else if ('credentials_supported' in issuerMetadata) {
55
+ if (typeof issuerMetadata.credentials_supported === 'object') {
56
+ versions.add(OpenId4VCIVersion.VER_1_0_08);
57
+ } else {
58
+ versions.add(OpenId4VCIVersion.VER_1_0_09).add(OpenId4VCIVersion.VER_1_0_11);
59
+ }
60
+ }
61
+ }
62
+ if (versions.size === 0) {
63
+ versions.add(OpenId4VCIVersion.VER_UNKNOWN);
32
64
  }
33
65
 
34
- return getSupportedCredential(options ? { ...options, types: undefined } : undefined);
66
+ return Array.from(versions).sort().reverse(); // highest version first
35
67
  }
36
68
 
37
69
  export function getSupportedCredential(opts?: {
@@ -39,31 +71,82 @@ export function getSupportedCredential(opts?: {
39
71
  version: OpenId4VCIVersion;
40
72
  types?: string | string[];
41
73
  format?: OID4VCICredentialFormat | string | (OID4VCICredentialFormat | string)[];
42
- }): Record<string, CredentialConfigurationSupportedV1_0_13> {
43
- const { issuerMetadata, types, format } = opts ?? {};
74
+ }): Record<string, CredentialConfigurationSupportedV1_0_13> | Array<CredentialConfigurationSupported> {
75
+ const { issuerMetadata, types, format, version = OpenId4VCIVersion.VER_1_0_13 } = opts ?? {};
44
76
 
45
- if (!issuerMetadata || !issuerMetadata.credential_configurations_supported) {
46
- return {};
77
+ let credentialConfigurationsV11: Array<CredentialConfigurationSupported> | undefined = undefined;
78
+ let credentialConfigurationsV13: Record<string, CredentialConfigurationSupportedV1_0_13> | undefined = undefined;
79
+ if (version < OpenId4VCIVersion.VER_1_0_12 || issuerMetadata?.credentials_supported) {
80
+ if (issuerMetadata?.credentials_supported && !Array.isArray(issuerMetadata?.credentials_supported)) {
81
+ // The current code duplication and logic is such a mess, that we re-adjust the object to the proper type again
82
+ credentialConfigurationsV11 = [];
83
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
84
+ Object.entries(issuerMetadata.credentials_supported!).forEach(([id, supported]) => {
85
+ if (!supported.id) {
86
+ supported.id = id;
87
+ }
88
+ credentialConfigurationsV11?.push(supported as CredentialConfigurationSupported);
89
+ });
90
+ } else {
91
+ credentialConfigurationsV11 = (issuerMetadata?.credentials_supported as Array<CredentialConfigurationSupported>) ?? [];
92
+ }
93
+ } else {
94
+ credentialConfigurationsV13 =
95
+ (issuerMetadata?.credential_configurations_supported as Record<string, CredentialConfigurationSupportedV1_0_13>) ?? {};
96
+ }
97
+ if (!issuerMetadata || (!issuerMetadata.credential_configurations_supported && !issuerMetadata.credentials_supported)) {
98
+ VCI_LOG_COMMON.warning(`No credential issuer metadata or supported credentials found for issuer}`);
99
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
100
+ return version < OpenId4VCIVersion.VER_1_0_13 ? credentialConfigurationsV11! : credentialConfigurationsV13!;
47
101
  }
48
102
 
49
- const credentialConfigurations: Record<string, CredentialConfigurationSupportedV1_0_13> =
50
- issuerMetadata.credential_configurations_supported as Record<string, CredentialConfigurationSupportedV1_0_13>;
51
103
  const normalizedTypes: string[] = Array.isArray(types) ? types : types ? [types] : [];
52
104
  const normalizedFormats: string[] = Array.isArray(format) ? format : format ? [format] : [];
53
105
 
54
- return Object.entries(credentialConfigurations).reduce(
55
- (filteredConfigs, [id, config]) => {
56
- const isTypeMatch = normalizedTypes.length === 0 || normalizedTypes.some((type) => config.credential_definition.type?.includes(type));
57
- const isFormatMatch = normalizedFormats.length === 0 || normalizedFormats.includes(config.format);
58
-
59
- if (isTypeMatch && isFormatMatch) {
60
- filteredConfigs[id] = config;
106
+ function filterMatchingConfig(config: CredentialConfigurationSupported): CredentialConfigurationSupported | undefined {
107
+ let isTypeMatch = normalizedTypes.length === 0;
108
+ const types = getTypesFromObject(config);
109
+ if (!isTypeMatch) {
110
+ if (normalizedTypes.length === 1 && config.id === normalizedTypes[0]) {
111
+ isTypeMatch = true;
112
+ } else if (types) {
113
+ isTypeMatch = normalizedTypes.every((type) => types.includes(type));
114
+ } else {
115
+ if ('credential_definition' in config) {
116
+ isTypeMatch = normalizedTypes.every((type) => config.credential_definition.type?.includes(type));
117
+ } else if ('type' in config && Array.isArray(config.type)) {
118
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
119
+ // @ts-ignore
120
+ isTypeMatch = normalizedTypes.every((type) => config.type.includes(type));
121
+ } else if ('types' in config) {
122
+ isTypeMatch = normalizedTypes.every((type) => config.types?.includes(type));
123
+ }
61
124
  }
125
+ }
126
+
127
+ const isFormatMatch = normalizedFormats.length === 0 || normalizedFormats.includes(config.format);
128
+
129
+ return isTypeMatch && isFormatMatch ? config : undefined;
130
+ }
62
131
 
63
- return filteredConfigs;
64
- },
65
- {} as Record<string, CredentialConfigurationSupportedV1_0_13>,
66
- );
132
+ if (credentialConfigurationsV13) {
133
+ return Object.entries(credentialConfigurationsV13).reduce(
134
+ (filteredConfigs, [id, config]) => {
135
+ if (filterMatchingConfig(config)) {
136
+ filteredConfigs[id] = config;
137
+ // Added to enable support < 13. We basically assign the
138
+ if (!config.id) {
139
+ config.id = id;
140
+ }
141
+ }
142
+ return filteredConfigs;
143
+ },
144
+ {} as Record<string, CredentialConfigurationSupportedV1_0_13>,
145
+ );
146
+ } else if (credentialConfigurationsV11) {
147
+ return credentialConfigurationsV11.filter((config) => filterMatchingConfig(config));
148
+ }
149
+ throw Error(`Either < v11 configurations or V13 configurations should have been filtered at this point`);
67
150
  }
68
151
 
69
152
  export function getTypesFromCredentialSupported(
@@ -77,12 +160,7 @@ export function getTypesFromCredentialSupported(
77
160
  credentialSupported.format === 'jwt_vc_json-ld' ||
78
161
  credentialSupported.format === 'ldp_vc'
79
162
  ) {
80
- types =
81
- (credentialSupported.types
82
- ? (credentialSupported.types as string[])
83
- : 'credential_definition' in credentialSupported
84
- ? credentialSupported.credential_definition?.type
85
- : []) ?? [];
163
+ types = getTypesFromObject(credentialSupported) ?? [];
86
164
  } else if (credentialSupported.format === 'vc+sd-jwt') {
87
165
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
88
166
  // @ts-ignore
@@ -152,3 +230,28 @@ export function getIssuerName(
152
230
  }
153
231
  return url;
154
232
  }
233
+
234
+ /**
235
+ * The specs had many places where types could be expressed. This method ensures we get them in any way possible
236
+ * @param subject
237
+ */
238
+ export function getTypesFromObject(
239
+ subject: CredentialConfigurationSupported | CredentialOfferFormat | CredentialDefinitionV1_0_13 | JsonLdIssuerCredentialDefinition | string,
240
+ ): string[] | undefined {
241
+ if (subject === undefined) {
242
+ return undefined;
243
+ } else if (typeof subject === 'string') {
244
+ return [subject];
245
+ } else if ('credential_definition' in subject && subject.credential_definition) {
246
+ return getTypesFromObject(subject.credential_definition);
247
+ } else if ('types' in subject && subject.types) {
248
+ return Array.isArray(subject.types) ? subject.types : [subject.types];
249
+ } else if ('type' in subject && subject.type) {
250
+ return Array.isArray(subject.type) ? subject.type : [subject.type];
251
+ } else if ('vct' in subject && subject.vct) {
252
+ return [subject.vct];
253
+ }
254
+
255
+ VCI_LOG_COMMON.warning('Could not deduce credential types. Probably a failure down the line will happen!');
256
+ return undefined;
257
+ }
@@ -0,0 +1,185 @@
1
+ import Debug from 'debug';
2
+ import jwtDecode from 'jwt-decode';
3
+
4
+ import { PoPMode, VCI_LOG_COMMON } from '..';
5
+ import {
6
+ BAD_PARAMS,
7
+ BaseJWK,
8
+ JWK,
9
+ JWS_NOT_VALID,
10
+ Jwt,
11
+ JWTHeader,
12
+ JWTPayload,
13
+ JWTVerifyCallback,
14
+ JwtVerifyResult,
15
+ ProofOfPossession,
16
+ ProofOfPossessionCallbacks,
17
+ Typ,
18
+ } from '../types';
19
+
20
+ const debug = Debug('sphereon:openid4vci:common');
21
+
22
+ /**
23
+ *
24
+ * - proofOfPossessionCallback: JWTSignerCallback
25
+ * Mandatory if you want to create (sign) ProofOfPossession
26
+ * - proofOfPossessionVerifierCallback?: JWTVerifyCallback
27
+ * If exists, verifies the ProofOfPossession
28
+ * - proofOfPossessionCallbackArgs: ProofOfPossessionCallbackArgs
29
+ * arguments needed for signing ProofOfPossession
30
+ * @param callbacks:
31
+ * - proofOfPossessionCallback: JWTSignerCallback
32
+ * Mandatory to create (sign) ProofOfPossession
33
+ * - proofOfPossessionVerifierCallback?: JWTVerifyCallback
34
+ * If exists, verifies the ProofOfPossession
35
+ * @param jwtProps
36
+ * @param existingJwt
37
+ * - Optional, clientId of the party requesting the credential
38
+ */
39
+ export const createProofOfPossession = async <DIDDoc>(
40
+ popMode: PoPMode,
41
+ callbacks: ProofOfPossessionCallbacks<DIDDoc>,
42
+ jwtProps?: JwtProps,
43
+ existingJwt?: Jwt,
44
+ ): Promise<ProofOfPossession> => {
45
+ if (!callbacks.signCallback) {
46
+ debug(`no jwt signer callback or arguments supplied!`);
47
+ throw new Error(BAD_PARAMS);
48
+ }
49
+
50
+ const signerArgs = createJWT(popMode, jwtProps, existingJwt);
51
+ const jwt = await callbacks.signCallback(signerArgs, signerArgs.header.kid);
52
+ const proof = {
53
+ proof_type: 'jwt',
54
+ jwt,
55
+ } as ProofOfPossession;
56
+
57
+ try {
58
+ partiallyValidateJWS(jwt);
59
+ if (callbacks.verifyCallback) {
60
+ debug(`Calling supplied verify callback....`);
61
+ await callbacks.verifyCallback({ jwt, kid: signerArgs.header.kid });
62
+ debug(`Supplied verify callback return success result`);
63
+ }
64
+ } catch {
65
+ debug(`JWS was not valid`);
66
+ throw new Error(JWS_NOT_VALID);
67
+ }
68
+ debug(`Proof of Possession JWT:\r\n${jwt}`);
69
+ return proof;
70
+ };
71
+
72
+ const partiallyValidateJWS = (jws: string): void => {
73
+ if (jws.split('.').length !== 3 || !jws.startsWith('ey')) {
74
+ throw new Error(JWS_NOT_VALID);
75
+ }
76
+ };
77
+
78
+ export const isJWS = (token: string): boolean => {
79
+ try {
80
+ partiallyValidateJWS(token);
81
+ return true;
82
+ } catch (e) {
83
+ return false;
84
+ }
85
+ };
86
+
87
+ export const extractBearerToken = (authorizationHeader?: string): string | undefined => {
88
+ return authorizationHeader ? /Bearer (.*)/i.exec(authorizationHeader)?.[1] : undefined;
89
+ };
90
+
91
+ export const validateJWT = async (
92
+ jwt?: string,
93
+ opts?: { kid?: string; accessTokenVerificationCallback?: JWTVerifyCallback<never> },
94
+ ): Promise<JwtVerifyResult<any>> => {
95
+ if (!jwt) {
96
+ throw Error('No JWT was supplied');
97
+ }
98
+
99
+ if (!opts?.accessTokenVerificationCallback) {
100
+ VCI_LOG_COMMON.warning(`No access token verification callback supplied. Access tokens will not be verified, except for a very basic check`);
101
+ partiallyValidateJWS(jwt);
102
+ const header = jwtDecode<JWTHeader>(jwt, { header: true });
103
+ const payload = jwtDecode<JWTPayload>(jwt, { header: false });
104
+ return {
105
+ jwt: { header, payload } satisfies Jwt,
106
+ ...header,
107
+ ...payload,
108
+ };
109
+ } else {
110
+ return await opts.accessTokenVerificationCallback({ jwt, kid: opts.kid });
111
+ }
112
+ };
113
+
114
+ export interface JwtProps {
115
+ typ?: Typ;
116
+ kid?: string;
117
+ jwk?: JWK;
118
+ aud?: string | string[];
119
+ issuer?: string;
120
+ clientId?: string;
121
+ alg?: string;
122
+ jti?: string;
123
+ nonce?: string;
124
+ }
125
+
126
+ const createJWT = (mode: PoPMode, jwtProps?: JwtProps, existingJwt?: Jwt): Jwt => {
127
+ const aud =
128
+ mode === 'pop'
129
+ ? getJwtProperty<string | string[]>('aud', true, jwtProps?.issuer, existingJwt?.payload?.aud)
130
+ : getJwtProperty<string | string[]>('aud', false, jwtProps?.aud, existingJwt?.payload?.aud);
131
+ const iss =
132
+ // mode === 'pop'
133
+ getJwtProperty<string>('iss', false, jwtProps?.clientId, existingJwt?.payload?.iss)
134
+ // : getJwtProperty<string>('iss', false, jwtProps?.issuer, existingJwt?.payload?.iss);
135
+ const client_id = mode === 'jwt' ? getJwtProperty<string>('client_id', false, jwtProps?.clientId, existingJwt?.payload?.client_id) : undefined;
136
+ const jti = getJwtProperty<string>('jti', false, jwtProps?.jti, existingJwt?.payload?.jti);
137
+ const typ = getJwtProperty<string>('typ', true, jwtProps?.typ, existingJwt?.header?.typ, 'jwt');
138
+ const nonce = getJwtProperty<string>('nonce', false, jwtProps?.nonce, existingJwt?.payload?.nonce); // Officially this is required, but some implementations don't have it
139
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
140
+ const alg = getJwtProperty<string>('alg', false, jwtProps?.alg, existingJwt?.header?.alg, 'ES256')!;
141
+ const kid = getJwtProperty<string>('kid', false, jwtProps?.kid, existingJwt?.header?.kid);
142
+ const jwk = getJwtProperty<BaseJWK>('jwk', false, jwtProps?.jwk, existingJwt?.header?.jwk);
143
+ const jwt: Partial<Jwt> = existingJwt ? existingJwt : {};
144
+ const now = +new Date();
145
+ const jwtPayload: Partial<JWTPayload> = {
146
+ ...(aud && { aud }),
147
+ iat: jwt.payload?.iat ?? Math.round(now / 1000 - 60), // Let's ensure we subtract 60 seconds for potential time offsets
148
+ exp: jwt.payload?.exp ?? Math.round(now / 1000 + 10 * 60),
149
+ nonce,
150
+ ...(client_id && { client_id }),
151
+ ...(iss && { iss }),
152
+ ...(jti && { jti }),
153
+ };
154
+
155
+ const jwtHeader: JWTHeader = {
156
+ typ,
157
+ alg,
158
+ ...(kid && { kid }),
159
+ ...(jwk && { jwk }),
160
+ };
161
+ return {
162
+ payload: { ...jwt.payload, ...jwtPayload },
163
+ header: { ...jwt.header, ...jwtHeader },
164
+ };
165
+ };
166
+
167
+ const getJwtProperty = <T>(
168
+ propertyName: string,
169
+ required: boolean,
170
+ option?: string | string[] | JWK,
171
+ jwtProperty?: T,
172
+ defaultValue?: T,
173
+ ): T | undefined => {
174
+ if (typeof option === 'string' && option && jwtProperty && option !== jwtProperty) {
175
+ throw Error(`Cannot have a property '${propertyName}' with value '${option}' and different JWT value '${jwtProperty}' at the same time`);
176
+ }
177
+ let result = (jwtProperty ? jwtProperty : option) as T | undefined;
178
+ if (!result) {
179
+ if (required) {
180
+ throw Error(`No ${propertyName} property provided either in a JWT or as option`);
181
+ }
182
+ result = defaultValue;
183
+ }
184
+ return result;
185
+ };
@@ -7,6 +7,7 @@ export * from './TypeConversionUtils';
7
7
  export * from './IssuerMetadataUtils';
8
8
  export * from './FormatUtils';
9
9
  export * from './HttpUtils';
10
+ export * from './ProofUtil';
10
11
  export * from './AuthorizationResponseUtil';
11
12
  export { randomBytes };
12
13
  export * from './RandomUtils';
package/lib/index.ts CHANGED
@@ -1,2 +1,9 @@
1
+ import { Loggers } from '@sphereon/ssi-types';
2
+
3
+ export const VCI_LOGGERS = Loggers.DEFAULT;
4
+ export const VCI_LOG_COMMON = VCI_LOGGERS.get('sphereon:oid4vci:common');
5
+
1
6
  export * from './functions';
2
7
  export * from './types';
8
+ export * from './experimental/holder-vci';
9
+ export * from './events';
@@ -1,4 +1,4 @@
1
- import { CredentialOfferPayload, UniformCredentialOffer } from './CredentialIssuance.types';
1
+ import { CredentialOfferPayload, ProofOfPossessionCallbacks, UniformCredentialOffer } from './CredentialIssuance.types';
2
2
  import {
3
3
  ErrorResponse,
4
4
  IssuerCredentialSubject,
@@ -260,6 +260,21 @@ export interface PKCEOpts {
260
260
  codeVerifier?: string;
261
261
  }
262
262
 
263
+ export enum CreateRequestObjectMode {
264
+ NONE,
265
+ REQUEST_OBJECT,
266
+ REQUEST_URI,
267
+ }
268
+
269
+ export type RequestObjectOpts = {
270
+ requestObjectMode?: CreateRequestObjectMode;
271
+ signCallbacks?: ProofOfPossessionCallbacks<never>;
272
+ clientMetadata?: Record<string, any>; // TODO: Merge SIOP/OID4VP
273
+ iss?: string;
274
+ jwksUri?: string;
275
+ kid?: string;
276
+ };
277
+
263
278
  export interface AuthorizationRequestOpts {
264
279
  clientId?: string;
265
280
  pkce?: PKCEOpts;
@@ -267,6 +282,7 @@ export interface AuthorizationRequestOpts {
267
282
  authorizationDetails?: AuthorizationDetails | AuthorizationDetails[];
268
283
  redirectUri?: string;
269
284
  scope?: string;
285
+ requestObjectOpts?: RequestObjectOpts;
270
286
  }
271
287
 
272
288
  export interface AuthorizationResponse {
@@ -1,5 +1,7 @@
1
1
  import { W3CVerifiableCredential } from '@sphereon/ssi-types';
2
2
 
3
+ import { ExperimentalSubjectIssuance } from '../experimental/holder-vci';
4
+
3
5
  import { AuthzFlowType } from './Authorization.types';
4
6
  import { OID4VCICredentialFormat, TxCode, UniformCredentialRequest } from './Generic.types';
5
7
  import { OpenId4VCIVersion } from './OpenID4VCIVersions.types';
@@ -8,13 +10,14 @@ import { CredentialOfferPayloadV1_0_09, CredentialOfferV1_0_09 } from './v1_0_09
8
10
  import { CredentialOfferPayloadV1_0_11, CredentialOfferV1_0_11, CredentialRequestV1_0_11 } from './v1_0_11.types';
9
11
  import { CredentialOfferPayloadV1_0_13, CredentialOfferV1_0_13, CredentialRequestV1_0_13 } from './v1_0_13.types';
10
12
 
11
- export interface CredentialResponse {
13
+ export interface CredentialResponse extends ExperimentalSubjectIssuance {
12
14
  credential?: W3CVerifiableCredential; // OPTIONAL. Contains issued Credential. MUST be present when acceptance_token is not returned. MAY be a JSON string or a JSON object, depending on the Credential format. See Appendix E for the Credential format specific encoding requirements
13
- format: OID4VCICredentialFormat /* | OID4VCICredentialFormat[]*/; // REQUIRED. JSON string denoting the format of the issued Credential TODO: remove when cleaning <v13
15
+ format?: OID4VCICredentialFormat /* | OID4VCICredentialFormat[]*/; // REQUIRED. JSON string denoting the format of the issued Credential TODO: remove when cleaning <v13
14
16
  transaction_id?: string; //OPTIONAL. A string identifying a Deferred Issuance transaction. This claim is contained in the response if the Credential Issuer was unable to immediately issue the credential. The value is subsequently used to obtain the respective Credential with the Deferred Credential Endpoint (see Section 9). It MUST be present when the credential parameter is not returned. It MUST be invalidated after the credential for which it was meant has been obtained by the Wallet.
15
17
  acceptance_token?: string; //deprecated // OPTIONAL. A JSON string containing a security token subsequently used to obtain a Credential. MUST be present when credential is not returned
16
18
  c_nonce?: string; // OPTIONAL. JSON string containing a nonce to be used to create a proof of possession of key material when requesting a Credential (see Section 7.2). When received, the Wallet MUST use this nonce value for its subsequent credential requests until the Credential Issuer provides a fresh nonce
17
19
  c_nonce_expires_in?: number; // OPTIONAL. JSON integer denoting the lifetime in seconds of the c_nonce
20
+ notification_id?: string;
18
21
  }
19
22
 
20
23
  export interface CredentialOfferRequestWithBaseUrl extends UniformCredentialOfferRequest {
@@ -24,6 +27,7 @@ export interface CredentialOfferRequestWithBaseUrl extends UniformCredentialOffe
24
27
  txCode?: TxCode;
25
28
  issuerState?: string;
26
29
  preAuthorizedCode?: string;
30
+ userPinRequired: boolean;
27
31
  }
28
32
 
29
33
  export type CredentialOffer = CredentialOfferV1_0_09 | CredentialOfferV1_0_11 | CredentialOfferV1_0_13;
@@ -195,6 +199,7 @@ export interface JWTPayload {
195
199
  nonce?: string; // REQUIRED (string). The value type of this claim MUST be a string, where the value is a c_nonce provided by the credential issuer. //TODO: Marked as required not present in NGI flow
196
200
  jti?: string; // A new nonce chosen by the wallet. Used to prevent replay
197
201
  exp?: number; // Not longer than 5 minutes
202
+ client_id?: string; // (string). The value of this claim MUST be the client_id of the client making the credential request.
198
203
  [s: string]: unknown;
199
204
  }
200
205
 
@@ -207,6 +212,6 @@ export interface JwtVerifyResult<DIDDoc> {
207
212
  alg: string;
208
213
  did?: string;
209
214
  didDocument?: DIDDoc;
210
- x5c?: string;
215
+ x5c?: string[];
211
216
  jwk?: BaseJWK;
212
217
  }