Skip to content

Commit fecf6ad

Browse files
fix: do not encode sessionTranscript as data item for device authentication (#101)
Signed-off-by: Berend Sliedrecht <[email protected]>
1 parent 2963990 commit fecf6ad

20 files changed

+582
-185
lines changed

pnpm-workspace.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
onlyBuiltDependencies:
2+
- esbuild

src/mdoc/models/device-authentication.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import { type CborDecodeOptions, CborStructure, cborDecode } from '../../cbor'
2-
import { DeviceNamespaces } from './device-namespaces'
1+
import { type CborDecodeOptions, CborStructure, cborDecode, DataItem } from '../../cbor'
2+
import { DeviceNamespaces, type DeviceNamespacesStructure } from './device-namespaces'
33
import type { DocType } from './doctype'
4-
import { SessionTranscript } from './session-transcript'
4+
import { SessionTranscript, type SessionTranscriptStructure } from './session-transcript'
55

6-
export type DeviceAuthenticationStructure = [string, Uint8Array, DocType, Uint8Array]
6+
export type DeviceAuthenticationStructure = [
7+
string,
8+
SessionTranscriptStructure,
9+
DocType,
10+
DataItem<DeviceNamespacesStructure>,
11+
]
712

813
export type DeviceAuthenticationOptions = {
914
sessionTranscript: SessionTranscript | Uint8Array
@@ -29,17 +34,17 @@ export class DeviceAuthentication extends CborStructure {
2934
public encodedStructure(): DeviceAuthenticationStructure {
3035
return [
3136
'DeviceAuthentication',
32-
this.sessionTranscript.encode({ asDataItem: true }),
37+
this.sessionTranscript.encodedStructure(),
3338
this.docType,
34-
this.deviceNamespaces.encode({ asDataItem: true }),
39+
DataItem.fromData(this.deviceNamespaces.encodedStructure()),
3540
]
3641
}
3742

3843
public static override fromEncodedStructure(encodedStructure: DeviceAuthenticationStructure): DeviceAuthentication {
3944
return new DeviceAuthentication({
40-
sessionTranscript: SessionTranscript.decode(encodedStructure[1]),
45+
sessionTranscript: SessionTranscript.fromEncodedStructure(encodedStructure[1]),
4146
docType: encodedStructure[2],
42-
deviceNamespaces: DeviceNamespaces.decode(encodedStructure[3]),
47+
deviceNamespaces: DeviceNamespaces.fromEncodedStructure(encodedStructure[3].data),
4348
})
4449
}
4550

src/mdoc/models/handover.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { CborStructure } from '../../cbor'
2+
3+
export abstract class Handover extends CborStructure {
4+
public static isCorrectHandover(_structure: unknown): boolean {
5+
return false
6+
}
7+
}

src/mdoc/models/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './ble-options'
22
export * from './data-element-identifier'
33
export * from './data-element-value'
44
export * from './device-auth'
5+
export * from './device-authentication'
56
export * from './device-engagement'
67
export * from './device-key'
78
export * from './device-key-info'

src/mdoc/models/nfc-handover.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { CborStructure } from '../../cbor'
1+
import { type CborDecodeOptions, cborDecode } from '../../cbor'
2+
import { Handover } from './handover'
23

34
export type NfcHandoverStructure = [Uint8Array, Uint8Array | null]
45

@@ -7,7 +8,7 @@ export type NfcHandoverOptions = {
78
requestMessage?: Uint8Array
89
}
910

10-
export class NfcHandover extends CborStructure {
11+
export class NfcHandover extends Handover {
1112
public selectMessage: Uint8Array
1213
public requestMessage?: Uint8Array
1314

@@ -27,4 +28,17 @@ export class NfcHandover extends CborStructure {
2728
requestMessage: encodedStructure[1] ?? undefined,
2829
})
2930
}
31+
32+
public static override decode(bytes: Uint8Array, options?: CborDecodeOptions): NfcHandover {
33+
const structure = cborDecode<NfcHandoverStructure>(bytes, { ...(options ?? {}), mapsAsObjects: false })
34+
return NfcHandover.fromEncodedStructure(structure)
35+
}
36+
37+
public static isCorrectHandover(structure: unknown): structure is NfcHandoverStructure {
38+
return (
39+
Array.isArray(structure) &&
40+
structure[0] instanceof Uint8Array &&
41+
(structure[1] instanceof Uint8Array || structure[1] === null)
42+
)
43+
}
3044
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { type CborDecodeOptions, CborStructure, cborDecode } from '../../cbor'
2+
3+
export type Oid4vpDcApiDraft24HandoverInfoStructure = [string, string, string]
4+
5+
export type Oid4vpDcApiDraft24HandoverInfoOptions = {
6+
origin: string
7+
clientId: string
8+
nonce: string
9+
}
10+
11+
export class Oid4vpDcApiDraft24HandoverInfo extends CborStructure {
12+
public origin: string
13+
public clientId: string
14+
public nonce: string
15+
16+
public constructor(options: Oid4vpDcApiDraft24HandoverInfoOptions) {
17+
super()
18+
this.origin = options.origin
19+
this.clientId = options.clientId
20+
this.nonce = options.nonce
21+
}
22+
23+
public encodedStructure(): Oid4vpDcApiDraft24HandoverInfoStructure {
24+
return [this.origin, this.clientId, this.nonce]
25+
}
26+
27+
public static override fromEncodedStructure(
28+
encodedStructure: Oid4vpDcApiDraft24HandoverInfoStructure
29+
): Oid4vpDcApiDraft24HandoverInfo {
30+
return new Oid4vpDcApiDraft24HandoverInfo({
31+
origin: encodedStructure[0],
32+
clientId: encodedStructure[1],
33+
nonce: encodedStructure[2],
34+
})
35+
}
36+
37+
public static override decode(bytes: Uint8Array, options?: CborDecodeOptions): Oid4vpDcApiDraft24HandoverInfo {
38+
const structure = cborDecode<Oid4vpDcApiDraft24HandoverInfoStructure>(bytes, {
39+
...(options ?? {}),
40+
mapsAsObjects: false,
41+
})
42+
return Oid4vpDcApiDraft24HandoverInfo.fromEncodedStructure(structure)
43+
}
44+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { type CborDecodeOptions, CborStructure, cborDecode } from '../../cbor'
2+
3+
export type Oid4vpDcApiHandoverInfoStructure = [string, string, Uint8Array | null]
4+
5+
export type Oid4vpDcApiHandoverInfoOptions = {
6+
origin: string
7+
nonce: string
8+
jwkThumbprint?: Uint8Array
9+
}
10+
11+
export class Oid4vpDcApiHandoverInfo extends CborStructure {
12+
public origin: string
13+
public nonce: string
14+
public jwkThumbprint?: Uint8Array
15+
16+
public constructor(options: Oid4vpDcApiHandoverInfoOptions) {
17+
super()
18+
this.origin = options.origin
19+
this.nonce = options.nonce
20+
this.jwkThumbprint = options.jwkThumbprint
21+
}
22+
23+
public encodedStructure(): Oid4vpDcApiHandoverInfoStructure {
24+
return [this.origin, this.nonce, this.jwkThumbprint ?? null]
25+
}
26+
27+
public static override fromEncodedStructure(
28+
encodedStructure: Oid4vpDcApiHandoverInfoStructure
29+
): Oid4vpDcApiHandoverInfo {
30+
return new Oid4vpDcApiHandoverInfo({
31+
origin: encodedStructure[0],
32+
nonce: encodedStructure[1],
33+
jwkThumbprint: encodedStructure[2] ?? undefined,
34+
})
35+
}
36+
37+
public static override decode(bytes: Uint8Array, options?: CborDecodeOptions): Oid4vpDcApiHandoverInfo {
38+
const structure = cborDecode<Oid4vpDcApiHandoverInfoStructure>(bytes, { ...(options ?? {}), mapsAsObjects: false })
39+
return Oid4vpDcApiHandoverInfo.fromEncodedStructure(structure)
40+
}
41+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { type CborDecodeOptions, cborDecode } from '../../cbor'
2+
import type { MdocContext } from '../../context'
3+
import { Handover } from './handover'
4+
import type { Oid4vpDcApiDraft24HandoverInfo } from './oid4vp-dc-api-draft24-handover-info'
5+
import type { Oid4vpDcApiHandoverInfo } from './oid4vp-dc-api-handover-info'
6+
7+
export type Oid4vpDcApiHandoverStructure = [string, Uint8Array]
8+
9+
export type Oid4vpDcApiHandoverOptions = {
10+
oid4vpDcApiHandoverInfo?: Oid4vpDcApiHandoverInfo | Oid4vpDcApiDraft24HandoverInfo
11+
oid4vpDcApiHandoverInfoHash?: Uint8Array
12+
}
13+
14+
export class Oid4vpDcApiHandover extends Handover {
15+
public oid4vpDcApiHandoverInfo?: Oid4vpDcApiHandoverInfo | Oid4vpDcApiDraft24HandoverInfo
16+
public oid4vpDcApiHandoverInfoHash?: Uint8Array
17+
18+
public constructor(options: Oid4vpDcApiHandoverOptions) {
19+
super()
20+
this.oid4vpDcApiHandoverInfo = options.oid4vpDcApiHandoverInfo
21+
this.oid4vpDcApiHandoverInfoHash = options.oid4vpDcApiHandoverInfoHash
22+
}
23+
24+
public async prepare(ctx: Pick<MdocContext, 'crypto'>) {
25+
if (!this.oid4vpDcApiHandoverInfo && !this.oid4vpDcApiHandoverInfoHash) {
26+
throw new Error(`Either the 'oid4vpDcApiHandoverInfo' or 'oid4vpDcApiHandoverInfoHash' must be set`)
27+
}
28+
29+
if (this.oid4vpDcApiHandoverInfo) {
30+
this.oid4vpDcApiHandoverInfoHash = await ctx.crypto.digest({
31+
digestAlgorithm: 'SHA-256',
32+
bytes: this.oid4vpDcApiHandoverInfo.encode(),
33+
})
34+
}
35+
}
36+
37+
public encodedStructure(): Oid4vpDcApiHandoverStructure {
38+
if (!this.oid4vpDcApiHandoverInfoHash) {
39+
throw new Error('Call `prepare` first to create the hash over the handover info')
40+
}
41+
42+
return ['OpenID4VPDCAPIHandover', this.oid4vpDcApiHandoverInfoHash]
43+
}
44+
45+
public static override fromEncodedStructure(encodedStructure: Oid4vpDcApiHandoverStructure): Oid4vpDcApiHandover {
46+
return new Oid4vpDcApiHandover({
47+
oid4vpDcApiHandoverInfoHash: encodedStructure[1],
48+
})
49+
}
50+
51+
public static override decode(bytes: Uint8Array, options?: CborDecodeOptions): Oid4vpDcApiHandover {
52+
const structure = cborDecode<Oid4vpDcApiHandoverStructure>(bytes, { ...(options ?? {}), mapsAsObjects: false })
53+
return Oid4vpDcApiHandover.fromEncodedStructure(structure)
54+
}
55+
56+
public static isCorrectHandover(structure: unknown): structure is Oid4vpDcApiHandoverStructure {
57+
return Array.isArray(structure) && structure[0] === 'OpenID4VPDCAPIHandover'
58+
}
59+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { type CborDecodeOptions, cborDecode, cborEncode } from '../../cbor'
2+
import type { MdocContext } from '../../context'
3+
import { Handover } from './handover'
4+
5+
export type Oid4vpDraft18HandoverStructure = [Uint8Array, Uint8Array, string]
6+
7+
export type Oid4vpDraft18HandoverOptions = {
8+
mdocGeneratedNonce?: string
9+
clientId?: string
10+
responseUri?: string
11+
nonce: string
12+
13+
clientIdHash?: Uint8Array
14+
responseUriHash?: Uint8Array
15+
}
16+
17+
/**
18+
*
19+
* @note this will be removed as it is already a legacy handover structure
20+
*
21+
*/
22+
export class Oid4vpDraft18Handover extends Handover {
23+
public mdocGeneratedNonce?: string
24+
public clientId?: string
25+
public responseUri?: string
26+
public nonce: string
27+
28+
public clientIdHash?: Uint8Array
29+
public responseUriHash?: Uint8Array
30+
31+
public constructor(options: Oid4vpDraft18HandoverOptions) {
32+
super()
33+
this.mdocGeneratedNonce = options.mdocGeneratedNonce
34+
this.clientId = options.clientId
35+
this.responseUri = options.responseUri
36+
this.nonce = options.nonce
37+
38+
this.clientIdHash = options.clientIdHash
39+
this.responseUriHash = options.responseUriHash
40+
}
41+
42+
public async prepare(ctx: Pick<MdocContext, 'crypto'>) {
43+
if (
44+
(!this.mdocGeneratedNonce || !this.clientId || !this.responseUri) &&
45+
(!this.clientIdHash || !this.responseUriHash)
46+
) {
47+
throw new Error(
48+
'Either the responseUriHash and clientIdHash must be set or the clientId, responseUri and mdocGeneratedNonce'
49+
)
50+
}
51+
52+
if (this.clientId && this.mdocGeneratedNonce) {
53+
this.clientIdHash = await ctx.crypto.digest({
54+
digestAlgorithm: 'SHA-256',
55+
bytes: cborEncode([this.clientId, this.mdocGeneratedNonce]),
56+
})
57+
}
58+
59+
if (this.responseUri && this.mdocGeneratedNonce) {
60+
this.responseUriHash = await ctx.crypto.digest({
61+
digestAlgorithm: 'SHA-256',
62+
bytes: cborEncode([this.responseUri, this.mdocGeneratedNonce]),
63+
})
64+
}
65+
66+
if (!this.clientIdHash || !this.responseUriHash) {
67+
throw new Error(
68+
'Could not hash the client id and/or the response uri. Make sure the properties are set on the class, or manually provide the hashed client id and response uri with the mdoc generated nonce'
69+
)
70+
}
71+
}
72+
73+
public encodedStructure(): Oid4vpDraft18HandoverStructure {
74+
if (!this.clientIdHash || !this.responseUriHash) {
75+
throw new Error('Call `prepare` first to create the hash over the client id and response uri')
76+
}
77+
78+
return [this.clientIdHash, this.responseUriHash, this.nonce]
79+
}
80+
81+
public static override fromEncodedStructure(encodedStructure: Oid4vpDraft18HandoverStructure): Oid4vpDraft18Handover {
82+
return new Oid4vpDraft18Handover({
83+
clientIdHash: encodedStructure[0],
84+
responseUriHash: encodedStructure[1],
85+
nonce: encodedStructure[2],
86+
})
87+
}
88+
89+
public static override decode(bytes: Uint8Array, options?: CborDecodeOptions): Oid4vpDraft18Handover {
90+
const structure = cborDecode<Oid4vpDraft18HandoverStructure>(bytes, { ...(options ?? {}), mapsAsObjects: false })
91+
return Oid4vpDraft18Handover.fromEncodedStructure(structure)
92+
}
93+
94+
public static isCorrectHandover(structure: unknown): structure is Oid4vpDraft18HandoverStructure {
95+
return (
96+
Array.isArray(structure) &&
97+
structure[0] instanceof Uint8Array &&
98+
structure[1] instanceof Uint8Array &&
99+
typeof structure[2] === 'string'
100+
)
101+
}
102+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { type CborDecodeOptions, CborStructure, cborDecode } from '../../cbor'
2+
3+
export type Oid4vpHandoverInfoStructure = [string, string, Uint8Array | null, string]
4+
5+
export type Oid4vpHandoverInfoOptions = {
6+
clientId: string
7+
nonce: string
8+
jwkThumbprint?: Uint8Array
9+
responseUri: string
10+
}
11+
12+
export class Oid4vpHandoverInfo extends CborStructure {
13+
public clientId: string
14+
public nonce: string
15+
public jwkThumbprint?: Uint8Array
16+
public responseUri: string
17+
18+
public constructor(options: Oid4vpHandoverInfoOptions) {
19+
super()
20+
this.clientId = options.clientId
21+
this.nonce = options.nonce
22+
this.jwkThumbprint = options.jwkThumbprint
23+
this.responseUri = options.responseUri
24+
}
25+
26+
public encodedStructure(): Oid4vpHandoverInfoStructure {
27+
return [this.clientId, this.nonce, this.jwkThumbprint ?? null, this.responseUri]
28+
}
29+
30+
public static override fromEncodedStructure(encodedStructure: Oid4vpHandoverInfoStructure): Oid4vpHandoverInfo {
31+
return new Oid4vpHandoverInfo({
32+
clientId: encodedStructure[0],
33+
nonce: encodedStructure[1],
34+
jwkThumbprint: encodedStructure[2] ?? undefined,
35+
responseUri: encodedStructure[3],
36+
})
37+
}
38+
39+
public static override decode(bytes: Uint8Array, options?: CborDecodeOptions): Oid4vpHandoverInfo {
40+
const structure = cborDecode<Oid4vpHandoverInfoStructure>(bytes, { ...(options ?? {}), mapsAsObjects: false })
41+
return Oid4vpHandoverInfo.fromEncodedStructure(structure)
42+
}
43+
}

0 commit comments

Comments
 (0)