@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.
- package/dist/events/index.d.ts +17 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +21 -0
- package/dist/events/index.js.map +1 -0
- package/dist/experimental/holder-vci.d.ts +13 -0
- package/dist/experimental/holder-vci.d.ts.map +1 -0
- package/dist/experimental/holder-vci.js +9 -0
- package/dist/experimental/holder-vci.js.map +1 -0
- package/dist/functions/CredentialOfferUtil.d.ts +3 -3
- package/dist/functions/CredentialOfferUtil.d.ts.map +1 -1
- package/dist/functions/CredentialOfferUtil.js +71 -28
- package/dist/functions/CredentialOfferUtil.js.map +1 -1
- package/dist/functions/CredentialRequestUtil.d.ts +3 -3
- package/dist/functions/CredentialRequestUtil.d.ts.map +1 -1
- package/dist/functions/CredentialRequestUtil.js +13 -4
- package/dist/functions/CredentialRequestUtil.js.map +1 -1
- package/dist/functions/CredentialResponseUtil.d.ts +3 -1
- package/dist/functions/CredentialResponseUtil.d.ts.map +1 -1
- package/dist/functions/CredentialResponseUtil.js +1 -1
- package/dist/functions/CredentialResponseUtil.js.map +1 -1
- package/dist/functions/Encoding.d.ts +7 -6
- package/dist/functions/Encoding.d.ts.map +1 -1
- package/dist/functions/Encoding.js +9 -8
- package/dist/functions/Encoding.js.map +1 -1
- package/dist/functions/IssuerMetadataUtils.d.ts +19 -12
- package/dist/functions/IssuerMetadataUtils.d.ts.map +1 -1
- package/dist/functions/IssuerMetadataUtils.js +155 -88
- package/dist/functions/IssuerMetadataUtils.js.map +1 -1
- package/dist/functions/ProofUtil.d.ts +38 -0
- package/dist/functions/ProofUtil.d.ts.map +1 -0
- package/dist/functions/ProofUtil.js +141 -0
- package/dist/functions/ProofUtil.js.map +1 -0
- package/dist/functions/index.d.ts +1 -0
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/index.js +1 -0
- package/dist/functions/index.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/types/Authorization.types.d.ts +26 -4
- package/dist/types/Authorization.types.d.ts.map +1 -1
- package/dist/types/Authorization.types.js +7 -1
- package/dist/types/Authorization.types.js.map +1 -1
- package/dist/types/CredentialIssuance.types.d.ts +17 -10
- package/dist/types/CredentialIssuance.types.d.ts.map +1 -1
- package/dist/types/CredentialIssuance.types.js.map +1 -1
- package/dist/types/Generic.types.d.ts +104 -17
- package/dist/types/Generic.types.d.ts.map +1 -1
- package/dist/types/Generic.types.js.map +1 -1
- package/dist/types/OpenID4VCIErrors.d.ts +1 -0
- package/dist/types/OpenID4VCIErrors.d.ts.map +1 -1
- package/dist/types/OpenID4VCIErrors.js +2 -1
- package/dist/types/OpenID4VCIErrors.js.map +1 -1
- package/dist/types/OpenID4VCIVersions.types.d.ts +1 -0
- package/dist/types/OpenID4VCIVersions.types.d.ts.map +1 -1
- package/dist/types/OpenID4VCIVersions.types.js +1 -0
- package/dist/types/OpenID4VCIVersions.types.js.map +1 -1
- package/dist/types/ServerMetadata.d.ts +0 -7
- package/dist/types/ServerMetadata.d.ts.map +1 -1
- package/dist/types/ServerMetadata.js.map +1 -1
- package/dist/types/StateManager.types.d.ts +1 -0
- package/dist/types/StateManager.types.d.ts.map +1 -1
- package/dist/types/StateManager.types.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/v1_0_11.types.d.ts +35 -5
- package/dist/types/v1_0_11.types.d.ts.map +1 -1
- package/dist/types/v1_0_11.types.js.map +1 -1
- package/dist/types/v1_0_12.types.d.ts +30 -1
- package/dist/types/v1_0_12.types.d.ts.map +1 -1
- package/dist/types/v1_0_13.types.d.ts +120 -0
- package/dist/types/v1_0_13.types.d.ts.map +1 -0
- package/dist/types/v1_0_13.types.js +3 -0
- package/dist/types/v1_0_13.types.js.map +1 -0
- package/lib/__tests__/CredentialOfferUtil.spec.ts +25 -9
- package/lib/__tests__/IssuerMetadataUtils.spec.ts +38 -0
- package/lib/events/index.ts +20 -0
- package/lib/experimental/holder-vci.ts +19 -0
- package/lib/functions/CredentialOfferUtil.ts +78 -26
- package/lib/functions/CredentialRequestUtil.ts +23 -8
- package/lib/functions/CredentialResponseUtil.ts +4 -4
- package/lib/functions/Encoding.ts +10 -8
- package/lib/functions/IssuerMetadataUtils.ts +164 -93
- package/lib/functions/ProofUtil.ts +185 -0
- package/lib/functions/index.ts +1 -0
- package/lib/index.ts +7 -0
- package/lib/types/Authorization.types.ts +31 -4
- package/lib/types/CredentialIssuance.types.ts +26 -10
- package/lib/types/Generic.types.ts +150 -27
- package/lib/types/OpenID4VCIErrors.ts +1 -0
- package/lib/types/OpenID4VCIVersions.types.ts +1 -0
- package/lib/types/ServerMetadata.ts +0 -10
- package/lib/types/StateManager.types.ts +1 -0
- package/lib/types/index.ts +1 -0
- package/lib/types/v1_0_11.types.ts +41 -4
- package/lib/types/v1_0_12.types.ts +39 -1
- package/lib/types/v1_0_13.types.ts +158 -0
- 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
|
-
|
|
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 |
|
|
16
|
+
issuerMetadata?: CredentialIssuerMetadata | IssuerMetadata;
|
|
16
17
|
version: OpenId4VCIVersion;
|
|
17
18
|
types?: string[][];
|
|
18
|
-
format?:
|
|
19
|
-
}):
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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 (
|
|
69
|
-
|
|
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
|
|
74
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
149
|
+
throw Error(`Either < v11 configurations or V13 configurations should have been filtered at this point`);
|
|
101
150
|
}
|
|
102
151
|
|
|
103
|
-
export function getTypesFromCredentialSupported(
|
|
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
|
|
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
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
184
|
+
Object.assign(credentialConfigsSupported, credentialSupportedV8ToV13(type, supportedV8));
|
|
141
185
|
});
|
|
186
|
+
return credentialConfigsSupported;
|
|
142
187
|
}
|
|
143
188
|
|
|
144
|
-
export function
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
+
credentialConfigsSupported[key] = credentialConfigSupported as CredentialConfigurationSupported;
|
|
159
204
|
});
|
|
205
|
+
return credentialConfigsSupported;
|
|
160
206
|
}
|
|
161
207
|
|
|
162
|
-
export function getIssuerDisplays(metadata: CredentialIssuerMetadata |
|
|
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 |
|
|
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
|
+
};
|
package/lib/functions/index.ts
CHANGED
|
@@ -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
|
|
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 {
|