@sphereon/oid4vci-common 0.10.3 → 0.10.4-next.119

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 (101) 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 -3
  10. package/dist/functions/CredentialOfferUtil.d.ts.map +1 -1
  11. package/dist/functions/CredentialOfferUtil.js +71 -28
  12. package/dist/functions/CredentialOfferUtil.js.map +1 -1
  13. package/dist/functions/CredentialRequestUtil.d.ts +3 -3
  14. package/dist/functions/CredentialRequestUtil.d.ts.map +1 -1
  15. package/dist/functions/CredentialRequestUtil.js +13 -4
  16. package/dist/functions/CredentialRequestUtil.js.map +1 -1
  17. package/dist/functions/CredentialResponseUtil.d.ts +3 -1
  18. package/dist/functions/CredentialResponseUtil.d.ts.map +1 -1
  19. package/dist/functions/CredentialResponseUtil.js +1 -1
  20. package/dist/functions/CredentialResponseUtil.js.map +1 -1
  21. package/dist/functions/Encoding.d.ts +7 -6
  22. package/dist/functions/Encoding.d.ts.map +1 -1
  23. package/dist/functions/Encoding.js +9 -8
  24. package/dist/functions/Encoding.js.map +1 -1
  25. package/dist/functions/IssuerMetadataUtils.d.ts +19 -12
  26. package/dist/functions/IssuerMetadataUtils.d.ts.map +1 -1
  27. package/dist/functions/IssuerMetadataUtils.js +155 -88
  28. package/dist/functions/IssuerMetadataUtils.js.map +1 -1
  29. package/dist/functions/ProofUtil.d.ts +38 -0
  30. package/dist/functions/ProofUtil.d.ts.map +1 -0
  31. package/dist/functions/ProofUtil.js +141 -0
  32. package/dist/functions/ProofUtil.js.map +1 -0
  33. package/dist/functions/index.d.ts +1 -0
  34. package/dist/functions/index.d.ts.map +1 -1
  35. package/dist/functions/index.js +1 -0
  36. package/dist/functions/index.js.map +1 -1
  37. package/dist/index.d.ts +5 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +6 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/types/Authorization.types.d.ts +26 -4
  42. package/dist/types/Authorization.types.d.ts.map +1 -1
  43. package/dist/types/Authorization.types.js +7 -1
  44. package/dist/types/Authorization.types.js.map +1 -1
  45. package/dist/types/CredentialIssuance.types.d.ts +17 -10
  46. package/dist/types/CredentialIssuance.types.d.ts.map +1 -1
  47. package/dist/types/CredentialIssuance.types.js.map +1 -1
  48. package/dist/types/Generic.types.d.ts +104 -17
  49. package/dist/types/Generic.types.d.ts.map +1 -1
  50. package/dist/types/Generic.types.js.map +1 -1
  51. package/dist/types/OpenID4VCIErrors.d.ts +1 -0
  52. package/dist/types/OpenID4VCIErrors.d.ts.map +1 -1
  53. package/dist/types/OpenID4VCIErrors.js +2 -1
  54. package/dist/types/OpenID4VCIErrors.js.map +1 -1
  55. package/dist/types/OpenID4VCIVersions.types.d.ts +1 -0
  56. package/dist/types/OpenID4VCIVersions.types.d.ts.map +1 -1
  57. package/dist/types/OpenID4VCIVersions.types.js +1 -0
  58. package/dist/types/OpenID4VCIVersions.types.js.map +1 -1
  59. package/dist/types/ServerMetadata.d.ts +0 -7
  60. package/dist/types/ServerMetadata.d.ts.map +1 -1
  61. package/dist/types/ServerMetadata.js.map +1 -1
  62. package/dist/types/StateManager.types.d.ts +1 -0
  63. package/dist/types/StateManager.types.d.ts.map +1 -1
  64. package/dist/types/StateManager.types.js.map +1 -1
  65. package/dist/types/index.d.ts +1 -0
  66. package/dist/types/index.d.ts.map +1 -1
  67. package/dist/types/index.js +1 -0
  68. package/dist/types/index.js.map +1 -1
  69. package/dist/types/v1_0_11.types.d.ts +35 -5
  70. package/dist/types/v1_0_11.types.d.ts.map +1 -1
  71. package/dist/types/v1_0_11.types.js.map +1 -1
  72. package/dist/types/v1_0_12.types.d.ts +30 -1
  73. package/dist/types/v1_0_12.types.d.ts.map +1 -1
  74. package/dist/types/v1_0_13.types.d.ts +120 -0
  75. package/dist/types/v1_0_13.types.d.ts.map +1 -0
  76. package/dist/types/v1_0_13.types.js +3 -0
  77. package/dist/types/v1_0_13.types.js.map +1 -0
  78. package/lib/__tests__/CredentialOfferUtil.spec.ts +25 -9
  79. package/lib/__tests__/IssuerMetadataUtils.spec.ts +38 -0
  80. package/lib/events/index.ts +20 -0
  81. package/lib/experimental/holder-vci.ts +19 -0
  82. package/lib/functions/CredentialOfferUtil.ts +78 -26
  83. package/lib/functions/CredentialRequestUtil.ts +23 -8
  84. package/lib/functions/CredentialResponseUtil.ts +4 -4
  85. package/lib/functions/Encoding.ts +10 -8
  86. package/lib/functions/IssuerMetadataUtils.ts +164 -93
  87. package/lib/functions/ProofUtil.ts +185 -0
  88. package/lib/functions/index.ts +1 -0
  89. package/lib/index.ts +7 -0
  90. package/lib/types/Authorization.types.ts +31 -4
  91. package/lib/types/CredentialIssuance.types.ts +26 -10
  92. package/lib/types/Generic.types.ts +150 -27
  93. package/lib/types/OpenID4VCIErrors.ts +1 -0
  94. package/lib/types/OpenID4VCIVersions.types.ts +1 -0
  95. package/lib/types/ServerMetadata.ts +0 -10
  96. package/lib/types/StateManager.types.ts +1 -0
  97. package/lib/types/index.ts +1 -0
  98. package/lib/types/v1_0_11.types.ts +41 -4
  99. package/lib/types/v1_0_12.types.ts +39 -1
  100. package/lib/types/v1_0_13.types.ts +158 -0
  101. package/package.json +3 -11
@@ -1,106 +1,158 @@
1
+ import { CredentialDefinitionV1_0_13, CredentialOfferFormat, JsonLdIssuerCredentialDefinition, VCI_LOG_COMMON } from '../index';
1
2
  import {
2
3
  AuthorizationServerMetadata,
4
+ CredentialConfigurationSupported,
5
+ CredentialConfigurationSupportedV1_0_13,
3
6
  CredentialIssuerMetadata,
4
- CredentialOfferFormat,
5
- CredentialSupported,
6
7
  CredentialSupportedTypeV1_0_08,
7
8
  CredentialSupportedV1_0_08,
8
- IssuerMetadataV1_0_08,
9
+ IssuerMetadata,
9
10
  MetadataDisplay,
10
11
  OID4VCICredentialFormat,
11
12
  OpenId4VCIVersion,
12
13
  } from '../types';
13
14
 
14
15
  export function getSupportedCredentials(opts?: {
15
- issuerMetadata?: CredentialIssuerMetadata | IssuerMetadataV1_0_08;
16
+ issuerMetadata?: CredentialIssuerMetadata | IssuerMetadata;
16
17
  version: OpenId4VCIVersion;
17
18
  types?: string[][];
18
- format?: (OID4VCICredentialFormat | string) | (OID4VCICredentialFormat | string)[];
19
- }): CredentialSupported[] {
20
- if (opts?.types && Array.isArray(opts?.types)) {
21
- return opts.types.flatMap((types) => getSupportedCredential({ ...opts, types }));
19
+ format?: OID4VCICredentialFormat | string | (OID4VCICredentialFormat | string)[];
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
+ }
22
38
  }
39
+
23
40
  return getSupportedCredential(opts ? { ...opts, types: undefined } : undefined);
24
41
  }
25
42
 
26
- export function getSupportedCredential(opts?: {
27
- issuerMetadata?: CredentialIssuerMetadata | IssuerMetadataV1_0_08;
28
- version: OpenId4VCIVersion;
29
- types?: string | string[];
30
- format?: (OID4VCICredentialFormat | string) | (OID4VCICredentialFormat | string)[];
31
- }): CredentialSupported[] {
32
- const { issuerMetadata } = opts ?? {};
33
- let formats: (OID4VCICredentialFormat | string)[];
34
- if (opts?.format && Array.isArray(opts.format)) {
35
- formats = opts.format;
36
- } else if (opts?.format && !Array.isArray(opts.format)) {
37
- formats = [opts.format];
38
- } else {
39
- formats = [];
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);
40
49
  }
41
- let credentialsSupported: CredentialSupported[];
42
- if (!issuerMetadata) {
43
- return [];
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
+ }
44
61
  }
45
- const { version, types } = opts ?? { version: OpenId4VCIVersion.VER_1_0_11 };
46
- if (version === OpenId4VCIVersion.VER_1_0_08 || !Array.isArray(issuerMetadata.credentials_supported)) {
47
- credentialsSupported = credentialsSupportedV8ToV11((issuerMetadata as IssuerMetadataV1_0_08).credentials_supported ?? {});
48
- } else {
49
- credentialsSupported = (issuerMetadata as CredentialIssuerMetadata).credentials_supported;
62
+ if (versions.size === 0) {
63
+ versions.add(OpenId4VCIVersion.VER_UNKNOWN);
50
64
  }
51
65
 
52
- if (credentialsSupported === undefined || credentialsSupported.length === 0) {
53
- return [];
54
- } else if (!types || types.length === 0) {
55
- return credentialsSupported;
56
- }
57
- /**
58
- * the following (not array part is a legacy code from version 1_0-08 which JFF plugfest 2 implementors used)
59
- */
60
- let initiationTypes: string[] | undefined;
61
- if (opts?.types) {
62
- if (typeof opts.types === 'string') {
63
- initiationTypes = [opts.types];
66
+ return Array.from(versions).sort().reverse(); // highest version first
67
+ }
68
+
69
+ export function getSupportedCredential(opts?: {
70
+ issuerMetadata?: CredentialIssuerMetadata | IssuerMetadata;
71
+ version: OpenId4VCIVersion;
72
+ types?: string | string[];
73
+ format?: OID4VCICredentialFormat | string | (OID4VCICredentialFormat | string)[];
74
+ }): Record<string, CredentialConfigurationSupportedV1_0_13> | Array<CredentialConfigurationSupported> {
75
+ const { issuerMetadata, types, format, version = OpenId4VCIVersion.VER_1_0_13 } = opts ?? {};
76
+
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
+ });
64
90
  } else {
65
- initiationTypes = opts.types;
91
+ credentialConfigurationsV11 = (issuerMetadata?.credentials_supported as Array<CredentialConfigurationSupported>) ?? [];
66
92
  }
93
+ } else {
94
+ credentialConfigurationsV13 =
95
+ (issuerMetadata?.credential_configurations_supported as Record<string, CredentialConfigurationSupportedV1_0_13>) ?? {};
67
96
  }
68
- if (version === OpenId4VCIVersion.VER_1_0_08 && (!initiationTypes || initiationTypes?.length === 0)) {
69
- initiationTypes = formats;
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!;
70
101
  }
71
- const supportedFormats: (CredentialOfferFormat | string)[] = formats && formats.length > 0 ? formats : ['jwt_vc_json', 'jwt_vc_json-ld', 'ldp_vc'];
72
102
 
73
- const credentialSupportedOverlap: CredentialSupported[] = [];
74
- if ((opts?.types && typeof opts?.types === 'string') || opts?.types?.length === 1) {
75
- const types = Array.isArray(opts.types) ? opts.types[0] : opts.types;
76
- const supported = credentialsSupported.filter(
77
- (sup) => sup.id === types || (initiationTypes && arrayEqualsIgnoreOrder(getTypesFromCredentialSupported(sup), initiationTypes)),
78
- );
79
- if (supported) {
80
- credentialSupportedOverlap.push(...supported);
81
- }
82
- }
103
+ const normalizedTypes: string[] = Array.isArray(types) ? types : types ? [types] : [];
104
+ const normalizedFormats: string[] = Array.isArray(format) ? format : format ? [format] : [];
83
105
 
84
- if (credentialSupportedOverlap.length === 0) {
85
- // Make sure we include Verifiable Credential both on the offer side as well as in the metadata side, to ensure consistency of the issuer does not.
86
- if (initiationTypes && !initiationTypes.includes('VerifiableCredential')) {
87
- initiationTypes.push('VerifiableCredential');
88
- }
89
- const supported = credentialsSupported.filter((sup) => {
90
- const supTypes = getTypesFromCredentialSupported(sup);
91
- if (!supTypes.includes('VerifiableCredential')) {
92
- supTypes.push('VerifiableCredential');
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
+ }
93
124
  }
94
- return (!initiationTypes || arrayEqualsIgnoreOrder(supTypes, initiationTypes)) && supportedFormats.includes(sup.format);
95
- });
96
- if (supported) {
97
- credentialSupportedOverlap.push(...supported);
98
125
  }
126
+
127
+ const isFormatMatch = normalizedFormats.length === 0 || normalizedFormats.includes(config.format);
128
+
129
+ return isTypeMatch && isFormatMatch ? config : undefined;
130
+ }
131
+
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));
99
148
  }
100
- return credentialSupportedOverlap;
149
+ throw Error(`Either < v11 configurations or V13 configurations should have been filtered at this point`);
101
150
  }
102
151
 
103
- export function getTypesFromCredentialSupported(credentialSupported: CredentialSupported, opts?: { filterVerifiableCredential: boolean }) {
152
+ export function getTypesFromCredentialSupported(
153
+ credentialSupported: CredentialConfigurationSupported,
154
+ opts?: { filterVerifiableCredential: boolean },
155
+ ) {
104
156
  let types: string[] = [];
105
157
  if (
106
158
  credentialSupported.format === 'jwt_vc_json' ||
@@ -108,8 +160,10 @@ export function getTypesFromCredentialSupported(credentialSupported: CredentialS
108
160
  credentialSupported.format === 'jwt_vc_json-ld' ||
109
161
  credentialSupported.format === 'ldp_vc'
110
162
  ) {
111
- types = credentialSupported.types;
163
+ types = getTypesFromObject(credentialSupported) ?? [];
112
164
  } else if (credentialSupported.format === 'vc+sd-jwt') {
165
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
166
+ // @ts-ignore
113
167
  types = [credentialSupported.vct];
114
168
  }
115
169
 
@@ -122,44 +176,36 @@ export function getTypesFromCredentialSupported(credentialSupported: CredentialS
122
176
  return types;
123
177
  }
124
178
 
125
- function arrayEqualsIgnoreOrder(a: string[], b: string[]) {
126
- if (a.length !== b.length) return false;
127
- const uniqueValues = new Set([...a, ...b]);
128
- for (const v of uniqueValues) {
129
- const aCount = a.filter((e) => e === v).length;
130
- const bCount = b.filter((e) => e === v).length;
131
- if (aCount !== bCount) return false;
132
- }
133
- return true;
134
- }
135
-
136
- export function credentialsSupportedV8ToV11(supportedV8: CredentialSupportedTypeV1_0_08): CredentialSupported[] {
137
- return Object.entries(supportedV8).flatMap((entry) => {
179
+ export function credentialsSupportedV8ToV13(supportedV8: CredentialSupportedTypeV1_0_08): Record<string, CredentialConfigurationSupported> {
180
+ const credentialConfigsSupported: Record<string, CredentialConfigurationSupported> = {};
181
+ Object.entries(supportedV8).flatMap((entry) => {
138
182
  const type = entry[0];
139
183
  const supportedV8 = entry[1];
140
- return credentialSupportedV8ToV11(type, supportedV8);
184
+ Object.assign(credentialConfigsSupported, credentialSupportedV8ToV13(type, supportedV8));
141
185
  });
186
+ return credentialConfigsSupported;
142
187
  }
143
188
 
144
- export function credentialSupportedV8ToV11(key: string, supportedV8: CredentialSupportedV1_0_08): CredentialSupported[] {
145
- return Object.entries(supportedV8.formats).map((entry) => {
189
+ export function credentialSupportedV8ToV13(key: string, supportedV8: CredentialSupportedV1_0_08): Record<string, CredentialConfigurationSupported> {
190
+ const credentialConfigsSupported: Record<string, CredentialConfigurationSupported> = {};
191
+ Object.entries(supportedV8.formats).map((entry) => {
146
192
  const format = entry[0];
147
193
  const credentialSupportBrief = entry[1];
148
194
  if (typeof format !== 'string') {
149
195
  throw Error(`Unknown format received ${JSON.stringify(format)}`);
150
196
  }
151
- let credentialSupport: Partial<CredentialSupported> = {};
152
- credentialSupport = {
197
+ const credentialConfigSupported: Partial<CredentialConfigurationSupported> = {
153
198
  format: format as OID4VCICredentialFormat,
154
199
  display: supportedV8.display,
155
200
  ...credentialSupportBrief,
156
201
  credentialSubject: supportedV8.claims,
157
202
  };
158
- return credentialSupport as CredentialSupported;
203
+ credentialConfigsSupported[key] = credentialConfigSupported as CredentialConfigurationSupported;
159
204
  });
205
+ return credentialConfigsSupported;
160
206
  }
161
207
 
162
- export function getIssuerDisplays(metadata: CredentialIssuerMetadata | IssuerMetadataV1_0_08, opts?: { prefLocales: string[] }): MetadataDisplay[] {
208
+ export function getIssuerDisplays(metadata: CredentialIssuerMetadata | IssuerMetadata, opts?: { prefLocales: string[] }): MetadataDisplay[] {
163
209
  const matchedDisplays =
164
210
  metadata.display?.filter(
165
211
  (item) => !opts?.prefLocales || opts.prefLocales.length === 0 || (item.locale && opts.prefLocales.includes(item.locale)) || !item.locale,
@@ -172,7 +218,7 @@ export function getIssuerDisplays(metadata: CredentialIssuerMetadata | IssuerMet
172
218
  */
173
219
  export function getIssuerName(
174
220
  url: string,
175
- credentialIssuerMetadata?: Partial<AuthorizationServerMetadata> & (CredentialIssuerMetadata | IssuerMetadataV1_0_08),
221
+ credentialIssuerMetadata?: Partial<AuthorizationServerMetadata> & (CredentialIssuerMetadata | IssuerMetadata),
176
222
  ): string {
177
223
  if (credentialIssuerMetadata) {
178
224
  const displays: Array<MetadataDisplay> = credentialIssuerMetadata ? getIssuerDisplays(credentialIssuerMetadata) : [];
@@ -184,3 +230,28 @@ export function getIssuerName(
184
230
  }
185
231
  return url;
186
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,10 +1,11 @@
1
- import { CredentialOfferPayload, UniformCredentialOffer } from './CredentialIssuance.types';
1
+ import { CredentialOfferPayload, ProofOfPossessionCallbacks, UniformCredentialOffer } from './CredentialIssuance.types';
2
2
  import {
3
3
  ErrorResponse,
4
4
  IssuerCredentialSubject,
5
5
  JsonLdIssuerCredentialDefinition,
6
6
  OID4VCICredentialFormat,
7
7
  PRE_AUTH_CODE_LITERAL,
8
+ TxCode,
8
9
  } from './Generic.types';
9
10
  import { EndpointMetadata } from './ServerMetadata';
10
11
 
@@ -104,21 +105,27 @@ export interface CommonAuthorizationDetails {
104
105
  * MUST be set to openid_credential for the purpose of this specification.
105
106
  */
106
107
  type: 'openid_credential' | string;
108
+
109
+ /**
110
+ * REQUIRED when format parameter is not present. String specifying a unique identifier of the Credential being described in the credential_configurations_supported map in the Credential Issuer Metadata as defined in Section 11.2.3. The referenced object in the credential_configurations_supported map conveys the details, such as the format, for issuance of the requested Credential. This specification defines Credential Format specific Issuer Metadata in Appendix A. It MUST NOT be present if format parameter is present.
111
+ */
112
+ credential_configuration_id?: string; // FIXME maybe split up and make this & format required again
113
+
107
114
  /**
108
115
  * REQUIRED. JSON string representing the format in which the Credential is requested to be issued.
109
116
  * This Credential format identifier determines further claims in the authorization details object
110
117
  * specifically used to identify the Credential type to be issued. This specification defines
111
118
  * Credential Format Profiles in Appendix E.
112
119
  */
113
- format: OID4VCICredentialFormat;
120
+ format?: OID4VCICredentialFormat;
114
121
  /**
115
122
  * If the Credential Issuer metadata contains an authorization_server parameter,
116
123
  * the authorization detail's locations common data field MUST be set to the Credential Issuer Identifier value.
117
124
  */
118
125
  locations?: string[];
119
126
 
120
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
121
- [key: string]: any;
127
+ /* // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ // [key: string]: any;*/
122
129
  }
123
130
 
124
131
  export interface AuthorizationDetailsJwtVcJson extends CommonAuthorizationDetails {
@@ -191,6 +198,9 @@ export interface IssuerOpts {
191
198
  export interface AccessTokenFromAuthorizationResponseOpts extends AccessTokenRequestOpts {
192
199
  authorizationResponse: AuthorizationResponse;
193
200
  }
201
+
202
+ export type TxCodeAndPinRequired = { isPinRequired?: boolean; txCode?: TxCode };
203
+
194
204
  export interface AccessTokenRequestOpts {
195
205
  credentialOffer?: UniformCredentialOffer;
196
206
  credentialIssuer?: string;
@@ -200,6 +210,7 @@ export interface AccessTokenRequestOpts {
200
210
  code?: string; // only required for authorization flow
201
211
  redirectUri?: string; // only required for authorization flow
202
212
  pin?: string; // Pin-number. Only used when required
213
+ pinMetadata?: TxCodeAndPinRequired; // OPTIONAL. String value containing a Transaction Code. This value MUST be present if a tx_code object was present in the Credential Offer (including if the object was empty). This parameter MUST only be used if the grant_type is urn:ietf:params:oauth:grant-type:pre-authorized_code.
203
214
  }
204
215
 
205
216
  /*export interface AuthorizationRequestOpts {
@@ -249,6 +260,21 @@ export interface PKCEOpts {
249
260
  codeVerifier?: string;
250
261
  }
251
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
+
252
278
  export interface AuthorizationRequestOpts {
253
279
  clientId?: string;
254
280
  pkce?: PKCEOpts;
@@ -256,6 +282,7 @@ export interface AuthorizationRequestOpts {
256
282
  authorizationDetails?: AuthorizationDetails | AuthorizationDetails[];
257
283
  redirectUri?: string;
258
284
  scope?: string;
285
+ requestObjectOpts?: RequestObjectOpts;
259
286
  }
260
287
 
261
288
  export interface AuthorizationResponse {