@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.
- 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 -7
- package/dist/functions/CredentialOfferUtil.d.ts.map +1 -1
- package/dist/functions/CredentialOfferUtil.js +63 -64
- package/dist/functions/CredentialOfferUtil.js.map +1 -1
- package/dist/functions/CredentialRequestUtil.d.ts.map +1 -1
- package/dist/functions/CredentialRequestUtil.js +7 -10
- 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/IssuerMetadataUtils.d.ts +10 -3
- package/dist/functions/IssuerMetadataUtils.d.ts.map +1 -1
- package/dist/functions/IssuerMetadataUtils.js +147 -30
- 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 +15 -1
- 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 +7 -3
- 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 +21 -3
- package/dist/types/Generic.types.d.ts.map +1 -1
- package/dist/types/Generic.types.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/v1_0_11.types.d.ts +2 -1
- 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_13.types.d.ts +6 -9
- package/dist/types/v1_0_13.types.d.ts.map +1 -1
- package/lib/__tests__/CredentialOfferUtil.spec.ts +19 -3
- package/lib/events/index.ts +20 -0
- package/lib/experimental/holder-vci.ts +19 -0
- package/lib/functions/CredentialOfferUtil.ts +57 -60
- package/lib/functions/CredentialRequestUtil.ts +8 -11
- package/lib/functions/CredentialResponseUtil.ts +4 -4
- package/lib/functions/IssuerMetadataUtils.ts +141 -38
- 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 +17 -1
- package/lib/types/CredentialIssuance.types.ts +8 -3
- package/lib/types/Generic.types.ts +38 -12
- package/lib/types/StateManager.types.ts +1 -0
- package/lib/types/v1_0_11.types.ts +3 -0
- package/lib/types/v1_0_13.types.ts +19 -14
- 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
|
|
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
|
-
:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
};
|
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,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
|
|
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
|
}
|