Skip to content

Commit edd7464

Browse files
authored
feat: update openid4vp to 1.0 final (#81)
Signed-off-by: Timo Glastra <timo@animo.id>
1 parent 16e5b1c commit edd7464

10 files changed

Lines changed: 75 additions & 34 deletions

File tree

.changeset/angry-parks-ring.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@openid4vc/openid4vp": minor
3+
---
4+
5+
update the parsed dcql vp_token presentation result to always return an array of presentations

.changeset/shiny-zebras-wash.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@openid4vc/openid4vp": minor
3+
---
4+
5+
feat: update openid4vp to 1.0 final.
6+
7+
The `version` returned in the `resolveOpenid4vpAuthorizationRequest` now returns `100` instead of `29` for the 1.0 final version of OpenID4VP.

packages/openid4vp/src/authorization-request/resolve-authorization-request.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from '../jar/z-jar-authorization-request'
1515
import type { PexPresentationDefinition } from '../models/z-pex'
1616
import { type ParsedTransactionDataEntry, parseTransactionData } from '../transaction-data/parse-transaction-data'
17-
import { type Openid4vpDraftVersionNumber, parseAuthorizationRequestVersion } from '../version'
17+
import { type Openid4vpVersionNumber, parseAuthorizationRequestVersion } from '../version'
1818
import {
1919
type WalletVerificationOptions,
2020
validateOpenid4vpAuthorizationRequestPayload,
@@ -50,10 +50,13 @@ export type ResolvedOpenid4vpAuthorizationRequest = {
5050
dcql?: { query: unknown } | undefined
5151

5252
/**
53-
* The highest possible draft version number based on draft-specific version checks done on the request.
53+
* The highest possible version number based on (draft)-version checks done on the request.
54+
*
55+
* 100 means 1.0 final, all other numbers are draft versions.
5456
*/
55-
version: Openid4vpDraftVersionNumber
57+
version: Openid4vpVersionNumber
5658
}
59+
5760
export async function resolveOpenid4vpAuthorizationRequest(
5861
options: ResolveOpenid4vpAuthorizationRequestOptions
5962
): Promise<ResolvedOpenid4vpAuthorizationRequest> {

packages/openid4vp/src/authorization-response/validate-authorization-response-result.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type { PexPresentationSubmission } from '../models/z-pex'
2-
import type { VpTokenDcql, VpTokenPexEntry } from '../vp-token/z-vp-token'
2+
import type { VpTokenPresentationEntry } from '../vp-token/z-vp-token'
33

44
export interface ValidateOpenid4VpPexAuthorizationResponseResult {
55
type: 'pex'
66

77
pex: {
88
presentationSubmission: PexPresentationSubmission
9-
presentations: [VpTokenPexEntry, ...VpTokenPexEntry[]]
9+
presentations: [VpTokenPresentationEntry, ...VpTokenPresentationEntry[]]
1010
} & (
1111
| { scope: string; presentationDefinition?: never }
1212
| { scope?: never; presentationDefinition: Record<string, unknown> | string }
@@ -16,7 +16,7 @@ export interface ValidateOpenid4VpPexAuthorizationResponseResult {
1616
export interface ValidateOpenid4VpDcqlAuthorizationResponseResult {
1717
type: 'dcql'
1818
dcql: {
19-
presentations: VpTokenDcql
19+
presentations: Record<string, [VpTokenPresentationEntry, ...VpTokenPresentationEntry[]]>
2020
} & ({ scope: string; query?: never } | { scope?: never; query: unknown })
2121
}
2222

packages/openid4vp/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export {
5656
parsePexVpToken,
5757
parseDcqlVpToken,
5858
} from './vp-token/parse-vp-token'
59-
export type { VpToken, VpTokenDcql, VpTokenPex, VpTokenPexEntry } from './vp-token/z-vp-token'
59+
export type { VpToken, VpTokenDcql, VpTokenPex, VpTokenPresentationEntry } from './vp-token/z-vp-token'
6060

6161
export {
6262
parseOpenid4vpAuthorizationResponse,

packages/openid4vp/src/version.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,23 @@ import {
66
} from './authorization-request/z-authorization-request-dc-api'
77
import { zClientIdPrefix } from './client-identifier-prefix/z-client-id-prefix'
88

9-
export type Openid4vpDraftVersionNumber = 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29
9+
/**
10+
* The Openid4vpVersionNumber
11+
*
12+
* 100 means 1.0 final, all others are draft versions
13+
*/
14+
export type Openid4vpVersionNumber = 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 100
1015

1116
export function parseAuthorizationRequestVersion(
1217
request: Openid4vpAuthorizationRequest | Openid4vpAuthorizationRequestDcApi
13-
): Openid4vpDraftVersionNumber {
14-
const requirements: ['<' | '>=', Openid4vpDraftVersionNumber][] = []
18+
): Openid4vpVersionNumber {
19+
const requirements: ['<' | '>=', Openid4vpVersionNumber][] = []
1520
// 29
1621
if (request.verifier_info) {
17-
requirements.push(['>=', 29])
22+
requirements.push(['>=', 100])
1823
}
1924
if (request.verifier_attestations) {
20-
requirements.push(['<', 29])
25+
requirements.push(['<', 100])
2126
}
2227

2328
// 28
@@ -171,20 +176,20 @@ export function parseAuthorizationRequestVersion(
171176
// Find the minimum version that satisfies all "less than" constraints
172177
const highestPossibleVersion =
173178
lessThanVersions.length > 0
174-
? (Math.max(Math.min(...lessThanVersions) - 1, 18) as Openid4vpDraftVersionNumber)
175-
: (29 as const) // Default to highest version
179+
? (Math.max(Math.min(...lessThanVersions) - 1, 18) as Openid4vpVersionNumber)
180+
: (100 as const) // Default to highest version
176181

177182
// Find the maximum version that satisfies all "greater than or equal to" constraints
178183
const lowestRequiredVersion =
179-
greaterThanVersions.length > 0 ? (Math.max(...greaterThanVersions) as Openid4vpDraftVersionNumber) : (18 as const) // Default to lowest version
184+
greaterThanVersions.length > 0 ? (Math.max(...greaterThanVersions) as Openid4vpVersionNumber) : (18 as const) // Default to lowest version
180185

181186
// The acceptable range is [lowestRequiredVersion, highestPossibleVersion]
182187
// We return the lowest possible version that satisfies all constraints
183188
if (lowestRequiredVersion > highestPossibleVersion) {
184189
// No valid version exists that satisfies all constraints
185190
throw new Oauth2ServerErrorResponseError({
186191
error: Oauth2ErrorCodes.InvalidRequest,
187-
error_description: 'Could not infer openid4vp version from the openid4vp request payload.',
192+
error_description: `Could not infer openid4vp version from the openid4vp request payload. Based on specification requirements, lowest required version is ${lowestRequiredVersion} and highest possible version is ${highestPossibleVersion}`,
188193
})
189194
}
190195

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
11
import { parseIfJson, parseWithErrorHandling } from '@openid4vc/utils'
2-
import { type VpTokenDcql, type VpTokenPexEntry, zVpTokenDcql, zVpTokenPex } from './z-vp-token'
2+
import { type VpTokenPresentationEntry, zVpTokenDcql, zVpTokenPex } from './z-vp-token'
33

4-
export function parsePexVpToken(vpToken: unknown): [VpTokenPexEntry, ...VpTokenPexEntry[]] {
4+
export function parsePexVpToken(vpToken: unknown): [VpTokenPresentationEntry, ...VpTokenPresentationEntry[]] {
55
const parsedVpToken = parseWithErrorHandling(
66
zVpTokenPex,
77
parseIfJson(vpToken),
88
'Could not parse presentation exchange vp_token. Expected a string or an array of strings'
99
)
1010

11-
return Array.isArray(parsedVpToken) ? (parsedVpToken as [VpTokenPexEntry, ...VpTokenPexEntry[]]) : [parsedVpToken]
11+
return Array.isArray(parsedVpToken)
12+
? (parsedVpToken as [VpTokenPresentationEntry, ...VpTokenPresentationEntry[]])
13+
: [parsedVpToken]
1214
}
1315

14-
export function parseDcqlVpToken(vpToken: unknown): VpTokenDcql {
15-
return parseWithErrorHandling(
16+
export function parseDcqlVpToken(
17+
vpToken: unknown
18+
): Record<string, [VpTokenPresentationEntry, ...VpTokenPresentationEntry[]]> {
19+
const parsedVpToken = parseWithErrorHandling(
1620
zVpTokenDcql,
1721
parseIfJson(vpToken),
1822
'Could not parse dcql vp_token. Expected an object where the values are encoded presentations'
1923
)
24+
25+
return Object.fromEntries(
26+
Object.entries(parsedVpToken).map(([queryId, presentations]) => [
27+
queryId,
28+
Array.isArray(presentations)
29+
? (presentations as [VpTokenPresentationEntry, ...VpTokenPresentationEntry[]])
30+
: [presentations],
31+
])
32+
)
2033
}

packages/openid4vp/src/vp-token/z-vp-token.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
import { z } from 'zod'
22

3-
const zVpTokenPexEntry = z.union([z.string(), z.record(z.any())], {
4-
message: 'pex vp_token entry must be a string or object',
3+
const zVpTokenPresentationEntry = z.union([z.string(), z.record(z.any())], {
4+
message: 'vp_token presentation entry must be string or object',
55
})
6+
export type VpTokenPresentationEntry = z.infer<typeof zVpTokenPresentationEntry>
67

78
export const zVpTokenPex = z.union(
8-
[zVpTokenPexEntry, z.array(zVpTokenPexEntry).nonempty('Must have at least entry in vp_token array')],
9+
[
10+
zVpTokenPresentationEntry,
11+
z.array(zVpTokenPresentationEntry).nonempty('Must have at least entry in vp_token array'),
12+
],
913
{
10-
message: 'pex vp_token must be a string, object or array of strings and objects',
14+
message: 'pex vp_token must be a string, object or non-empty array of strings and objects',
1115
}
1216
)
1317
export type VpTokenPex = z.infer<typeof zVpTokenPex>
14-
export type VpTokenPexEntry = z.infer<typeof zVpTokenPexEntry>
1518

16-
export const zVpTokenDcql = z.record(z.union([z.string(), z.record(z.any())]), {
17-
message:
18-
'dcql vp_token must be an object with keys referencing the dcql credential query id, and values the encoded (string or object) presentation',
19-
})
19+
export const zVpTokenDcql = z.record(
20+
z.union([z.array(zVpTokenPresentationEntry).nonempty(), zVpTokenPresentationEntry]),
21+
{
22+
message:
23+
'dcql vp_token must be an object with keys referencing the dcql credential query id, and values a non-empty array of strings and objects, or string, or object',
24+
}
25+
)
2026
export type VpTokenDcql = z.infer<typeof zVpTokenDcql>
2127

2228
export const zVpToken = zVpTokenDcql.or(zVpTokenPex)

packages/openid4vp/tests/full-flow.test.mts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,12 @@ describe('Full E2E openid4vp test', () => {
104104
dcql: {
105105
query: exampleDcqlQuery,
106106
presentations: {
107-
orgeuuniversity:
107+
orgeuuniversity: [
108108
'uQADZ3ZlcnNpb25jMS4waWRvY3VtZW50c4GjZ2RvY1R5cGVxb3JnLmV1LnVuaXZlcnNpdHlsaXNzdWVyU2lnbmVkuQACam5hbWVTcGFjZXOhd2V1LmV1cm9wYS5lYy5ldWRpLnBpZC4xg9gYWGGkaGRpZ2VzdElEA3FlbGVtZW50SWRlbnRpZmllcmRuYW1lbGVsZW1lbnRWYWx1ZWhKb2huIERvZWZyYW5kb21YIEWyJGCZTVxZPQlUipZgJrFHAG953ShscUxOhqcVj5zZ2BhYY6RoZGlnZXN0SUQBcWVsZW1lbnRJZGVudGlmaWVyZmRlZ3JlZWxlbGVtZW50VmFsdWVoYmFjaGVsb3JmcmFuZG9tWCAK5tJW8iDu2_pFMZbncXHsVSoMPB-j6NHzTidrmAwglNgYWGakaGRpZ2VzdElEAnFlbGVtZW50SWRlbnRpZmllcmRkYXRlbGVsZW1lbnRWYWx1ZdkD7GoyMDI1LTAyLTI3ZnJhbmRvbVggHkxQ5P0ym4b-S2YhPULns-6950O_pu1f01YH4KZf7RFqaXNzdWVyQXV0aIRDoQEmogRYMXpEbmFlYVpVV3dVUmpyNVE1TTJnMnVjZjE2bjNwVUx4bzRDaHFuZktYTnJNMk53MUsYIYFZAR4wggEaMIHAoAMCAQICEBe3X5XsrOs2ZhTfjDA0whkwCgYIKoZIzj0EAwIwDTELMAkGA1UEAxMCREUwHhcNMjUwMjI3MDkxOTMzWhcNMjYwMjI3MDkxOTMzWjANMQswCQYDVQQDEwJERTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJaBQfh-jpNhVxqwlTlv39Gm3nkewRvcA4p9TRao8YlC271XGo2ojTBcNh-RX65ql--tNygiJh6BHNhz98VPcVCjAjAAMAoGCCqGSM49BAMCA0kAMEYCIQCx9YNNPzp7YPPdy4k3IVR_XLl6e7bnKS91cGEwArbMzgIhAIuglfUtfZM-ZYoEX1xYB47wMm666Trcykjag1sYMfgZWQIA2BhZAfu5AAZndmVyc2lvbmMxLjBvZGlnZXN0QWxnb3JpdGhtZ1NIQS0yNTZsdmFsdWVEaWdlc3RzoXdldS5ldXJvcGEuZWMuZXVkaS5waWQuMaUAWCAVPsuROLqNqsVsaaCrTJzdHdZ_IznS1nCh1qVKWZ2ezQFYIPeazaLvXv5M-s2h9713AG_QJcCZW-eu6UGzoGI6O9ZnAlggqnuFzR9wHj_51ftmjpqo5s-XIvjoLPw-5sfQ-IGtp1kDWCByQ8dnllyBLPNL1DgHqA0B8yAuvY-EoCVGmEAWMAbeowRYICrRJfwVhE9JJzLpa6tfc1LJ9rvv0sr2OzQ9ot-68CnNbWRldmljZUtleUluZm-5AAFpZGV2aWNlS2V5pAECIAEhWCAakKubHmMGh9_OHyUGEZ8102VOMM6j7C-MlEyHDyYJ1yJYIJbQibZhVZZ2ghRAClhsQO_fXpWcqRQKhriSZ4azbE2oZ2RvY1R5cGVxb3JnLmV1LnVuaXZlcnNpdHlsdmFsaWRpdHlJbmZvuQAEZnNpZ25lZMB0MjAyNS0wMi0yN1QwOToxOTozM1ppdmFsaWRGcm9twHQyMDI1LTAyLTI3VDA5OjE5OjMzWmp2YWxpZFVudGlswHQyMDI2LTAyLTI3VDA5OjE5OjMzWm5leHBlY3RlZFVwZGF0ZfdYQCFznxzCxRUqSe65YB3p1pjTEK7Sma4-JTUhnbwsdmtAoLBv5NMlu54mHj7oGCRBmN3G_un8GeX2opmG78yVdJNsZGV2aWNlU2lnbmVkuQACam5hbWVTcGFjZXPYGEGgamRldmljZUF1dGi5AAJvZGV2aWNlU2lnbmF0dXJlhEOhASag9lhA0kcpmpDK-lGZnNZ_cCjb_CbEz6UZ_MwymXE9r1j9YFrSpoahLj6dkprCZuaS1K79dvSZQTHw23KHzP_JAGFcj2lkZXZpY2VNYWP3ZnN0YXR1cwA',
109-
OpenBadgeCredentialDescriptor:
109+
],
110+
OpenBadgeCredentialDescriptor: [
110111
'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSIsImtpZCI6IiN6Nk1rcnpRUEJyNHB5cUM3NzZLS3RyejEzU2NoTTVlUFBic3N1UHVRWmI1dDR1S1EifQ.eyJ2Y3QiOiJPcGVuQmFkZ2VDcmVkZW50aWFsIiwiZGVncmVlIjoiYmFjaGVsb3IiLCJjbmYiOnsia2lkIjoiZGlkOmtleTp6Nk1rcEdSNGdzNFJjM1pwaDR2ajh3Um5qbkF4Z0FQU3hjUjhNQVZLdXRXc3BRemMjejZNa3BHUjRnczRSYzNacGg0dmo4d1Juam5BeGdBUFN4Y1I4TUFWS3V0V3NwUXpjIn0sImlzcyI6ImRpZDprZXk6ejZNa3J6UVBCcjRweXFDNzc2S0t0cnoxM1NjaE01ZVBQYnNzdVB1UVpiNXQ0dUtRIiwiaWF0IjoxNzQwNjQ3OTczLCJfc2QiOlsiSEtyNW1mYkE2OGRZY0JYZTVMRDREdFJHdjVoNWp0NEVDT2JSOWF5VkJCOCIsImlBYS1YVXhGaG1nU0g2SWhTOHZ2cm1TWF95VHJ2ZTQtZjFjTWRPLU41VUUiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.pP2G3YxexmDGpF-vfb04zMhVLLJGjkiVUiA-I-aLVdhNqzCjexOAu9xQOt0uTGT-4_ly_j66FXR2v4p0z9iyBw~WyI2NTgyNDY2MzM4MjYyODgyMjY2Nzc2MTkiLCJ1bml2ZXJzaXR5IiwiaW5uc2JydWNrIl0~eyJ0eXAiOiJrYitqd3QiLCJhbGciOiJFZERTQSJ9.eyJpYXQiOjE3NDA2NDc5NzYsIm5vbmNlIjoiNDczODIzNzM5Nzg4MzU1NzEyMTc3MzUwIiwiYXVkIjoieDUwOV9zYW5fZG5zOmxvY2FsaG9zdDoxMjM0IiwidHJhbnNhY3Rpb25fZGF0YV9oYXNoZXMiOlsiWHd5VmQ3d0ZSRWRWV0xwbmk1UU5IZ2dOV1hvMko0TG41OHQyX2VjSjczcyJdLCJ0cmFuc2FjdGlvbl9kYXRhX2hhc2hlc19hbGciOiJzaGEtMjU2Iiwic2RfaGFzaCI6IkJFQ09xRm9OMjM0UldtRzEtTUlSWXl5SnpXTDg1XzRLdTdHNC1KcUl4V0UifQ.ZnDZBS8o_WPsTlvuUO0SnARUIvx1M8t9Fd6TiaQbMtT_0OA7QCvdpisSh9-NfLAb40frAED875W8RI3zi06-DQ',
112+
],
111113
},
112114
},
113115
})
@@ -142,7 +144,7 @@ describe('Full E2E openid4vp test', () => {
142144

143145
expect(resolved).toEqual({
144146
transactionData: undefined,
145-
version: 29,
147+
version: 100,
146148
authorizationRequestPayload: {
147149
response_type: 'vp_token',
148150
client_id: 'client_id',

packages/openid4vp/tests/version-inference.test.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('Version inference test', () => {
1919
response_type: 'vp_token',
2020
})
2121

22-
expect(version).toBe(29)
22+
expect(version).toBe(100)
2323
})
2424

2525
test('client_metadata_uri requires version below 21', () => {

0 commit comments

Comments
 (0)