@sphereon/ssi-types 0.37.2-next.28 → 0.37.2-next.46
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/index.cjs +84 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +84 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/mapper/credential-mapper.ts +34 -29
- package/src/types/sd-jwt-type-metadata.ts +25 -0
- package/src/utils/mdoc.ts +93 -9
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sphereon/ssi-types",
|
|
3
3
|
"description": "SSI Common Types",
|
|
4
|
-
"version": "0.37.2-next.
|
|
4
|
+
"version": "0.37.2-next.46+f19cac5e",
|
|
5
5
|
"source": "./src/index.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.cjs",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"Verifiable Credentials",
|
|
60
60
|
"DIDs"
|
|
61
61
|
],
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "f19cac5ecfea9c564f576bd4efbc5aa55de6f103"
|
|
63
63
|
}
|
|
@@ -73,7 +73,7 @@ export class CredentialMapper {
|
|
|
73
73
|
*/
|
|
74
74
|
static decodeVerifiablePresentation(
|
|
75
75
|
presentation: OriginalVerifiablePresentation,
|
|
76
|
-
hasher?: HasherSync
|
|
76
|
+
hasher?: HasherSync,
|
|
77
77
|
): JwtDecodedVerifiablePresentation | IVerifiablePresentation | SdJwtDecodedVerifiableCredential | MdocOid4vpMdocVpToken | MdocDeviceResponse {
|
|
78
78
|
if (CredentialMapper.isJwtEncoded(presentation)) {
|
|
79
79
|
const payload = jwtDecode(presentation as string) as JwtDecodedVerifiablePresentation
|
|
@@ -116,8 +116,8 @@ export class CredentialMapper {
|
|
|
116
116
|
*/
|
|
117
117
|
static decodeVerifiableCredential(
|
|
118
118
|
credential: OriginalVerifiableCredential,
|
|
119
|
-
hasher?: HasherSync
|
|
120
|
-
): JwtDecodedVerifiableCredential | IVerifiableCredential | SdJwtDecodedVerifiableCredential {
|
|
119
|
+
hasher?: HasherSync,
|
|
120
|
+
): JwtDecodedVerifiableCredential | IVerifiableCredential | SdJwtDecodedVerifiableCredential | MdocDocument {
|
|
121
121
|
if (CredentialMapper.isJwtEncoded(credential)) {
|
|
122
122
|
const payload = jwtDecode(credential as string) as JwtDecodedVerifiableCredential
|
|
123
123
|
const header = jwtDecode(credential as string, { header: true }) as Record<string, any>
|
|
@@ -138,6 +138,11 @@ export class CredentialMapper {
|
|
|
138
138
|
return decodeSdJwtVc(credential, hasher ?? sha256)
|
|
139
139
|
} else if (CredentialMapper.isSdJwtDecodedCredential(credential)) {
|
|
140
140
|
return credential
|
|
141
|
+
} else if (CredentialMapper.isMsoMdocOid4VPEncoded(credential)) {
|
|
142
|
+
// ISO 18013-5/-7 mdoc IssuerSigned in base64url. Mirrors decodeVerifiablePresentation, which already handles mdoc.
|
|
143
|
+
return decodeMdocIssuerSigned(credential)
|
|
144
|
+
} else if (CredentialMapper.isMsoMdocDecodedCredential(credential)) {
|
|
145
|
+
return credential
|
|
141
146
|
} else {
|
|
142
147
|
return credential as IVerifiableCredential
|
|
143
148
|
}
|
|
@@ -155,7 +160,7 @@ export class CredentialMapper {
|
|
|
155
160
|
*/
|
|
156
161
|
static toWrappedVerifiablePresentation(
|
|
157
162
|
originalPresentation: OriginalVerifiablePresentation,
|
|
158
|
-
opts?: { maxTimeSkewInMS?: number; hasher?: HasherSync }
|
|
163
|
+
opts?: { maxTimeSkewInMS?: number; hasher?: HasherSync },
|
|
159
164
|
): WrappedVerifiablePresentation {
|
|
160
165
|
// MSO_MDOC
|
|
161
166
|
if (CredentialMapper.isMsoMdocDecodedPresentation(originalPresentation) || CredentialMapper.isMsoMdocOid4VPEncoded(originalPresentation)) {
|
|
@@ -170,7 +175,7 @@ export class CredentialMapper {
|
|
|
170
175
|
}
|
|
171
176
|
|
|
172
177
|
const mdocCredentials = deviceResponse.documents?.map(
|
|
173
|
-
(doc) => CredentialMapper.toWrappedVerifiableCredential(doc, opts) as WrappedMdocCredential
|
|
178
|
+
(doc) => CredentialMapper.toWrappedVerifiableCredential(doc, opts) as WrappedMdocCredential,
|
|
174
179
|
)
|
|
175
180
|
if (!mdocCredentials || mdocCredentials.length === 0) {
|
|
176
181
|
throw new Error('could not extract any mdoc credentials from mdoc device response')
|
|
@@ -210,7 +215,7 @@ export class CredentialMapper {
|
|
|
210
215
|
typeof originalPresentation !== 'string' && CredentialMapper.hasJWTProofType(originalPresentation) ? proof?.jwt : originalPresentation
|
|
211
216
|
if (!original) {
|
|
212
217
|
throw Error(
|
|
213
|
-
'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof'
|
|
218
|
+
'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof',
|
|
214
219
|
)
|
|
215
220
|
}
|
|
216
221
|
const decoded = CredentialMapper.decodeVerifiablePresentation(original) as IVerifiablePresentation | JwtDecodedVerifiablePresentation
|
|
@@ -238,7 +243,7 @@ export class CredentialMapper {
|
|
|
238
243
|
? []
|
|
239
244
|
: (CredentialMapper.toWrappedVerifiableCredentials(
|
|
240
245
|
vp.verifiableCredential ?? [] /*.map(value => value.original)*/,
|
|
241
|
-
opts
|
|
246
|
+
opts,
|
|
242
247
|
) as WrappedW3CVerifiableCredential[])
|
|
243
248
|
|
|
244
249
|
const presentation = {
|
|
@@ -266,7 +271,7 @@ export class CredentialMapper {
|
|
|
266
271
|
*/
|
|
267
272
|
static toWrappedVerifiableCredentials(
|
|
268
273
|
verifiableCredentials: OriginalVerifiableCredential[],
|
|
269
|
-
opts?: { maxTimeSkewInMS?: number; hasher?: HasherSync }
|
|
274
|
+
opts?: { maxTimeSkewInMS?: number; hasher?: HasherSync },
|
|
270
275
|
): WrappedVerifiableCredential[] {
|
|
271
276
|
return verifiableCredentials.map((vc) => CredentialMapper.toWrappedVerifiableCredential(vc, opts))
|
|
272
277
|
}
|
|
@@ -282,7 +287,7 @@ export class CredentialMapper {
|
|
|
282
287
|
*/
|
|
283
288
|
static toWrappedVerifiableCredential(
|
|
284
289
|
verifiableCredential: OriginalVerifiableCredential,
|
|
285
|
-
opts?: { maxTimeSkewInMS?: number; hasher?: HasherSync }
|
|
290
|
+
opts?: { maxTimeSkewInMS?: number; hasher?: HasherSync },
|
|
286
291
|
): WrappedVerifiableCredential {
|
|
287
292
|
// MSO_MDOC
|
|
288
293
|
if (CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential) || CredentialMapper.isMsoMdocOid4VPEncoded(verifiableCredential)) {
|
|
@@ -322,10 +327,10 @@ export class CredentialMapper {
|
|
|
322
327
|
|
|
323
328
|
// If the VC is not an encoded/decoded SD-JWT, we assume it will be a W3C VC
|
|
324
329
|
const proof = CredentialMapper.getFirstProof(verifiableCredential)
|
|
325
|
-
const original = CredentialMapper.hasJWTProofType(verifiableCredential) && proof ? proof.jwt ?? verifiableCredential : verifiableCredential
|
|
330
|
+
const original = CredentialMapper.hasJWTProofType(verifiableCredential) && proof ? (proof.jwt ?? verifiableCredential) : verifiableCredential
|
|
326
331
|
if (!original) {
|
|
327
332
|
throw Error(
|
|
328
|
-
'Could not determine original credential, probably it was a converted JWT credential, that is now missing the JWT value in the proof'
|
|
333
|
+
'Could not determine original credential, probably it was a converted JWT credential, that is now missing the JWT value in the proof',
|
|
329
334
|
)
|
|
330
335
|
}
|
|
331
336
|
const decoded = CredentialMapper.decodeVerifiableCredential(original) as JwtDecodedVerifiableCredential | IVerifiableCredential
|
|
@@ -441,7 +446,7 @@ export class CredentialMapper {
|
|
|
441
446
|
}
|
|
442
447
|
|
|
443
448
|
public static isW3cPresentation(
|
|
444
|
-
presentation: UniformVerifiablePresentation | IPresentation | SdJwtDecodedVerifiableCredential | DeviceResponseCbor
|
|
449
|
+
presentation: UniformVerifiablePresentation | IPresentation | SdJwtDecodedVerifiableCredential | DeviceResponseCbor,
|
|
445
450
|
): presentation is IPresentation {
|
|
446
451
|
return (
|
|
447
452
|
typeof presentation === 'object' &&
|
|
@@ -451,7 +456,7 @@ export class CredentialMapper {
|
|
|
451
456
|
}
|
|
452
457
|
|
|
453
458
|
public static isSdJwtDecodedCredentialPayload(
|
|
454
|
-
credential: ICredential | SdJwtDecodedVerifiableCredentialPayload
|
|
459
|
+
credential: ICredential | SdJwtDecodedVerifiableCredentialPayload,
|
|
455
460
|
): credential is SdJwtDecodedVerifiableCredentialPayload {
|
|
456
461
|
return typeof credential === 'object' && 'vct' in credential
|
|
457
462
|
}
|
|
@@ -483,7 +488,7 @@ export class CredentialMapper {
|
|
|
483
488
|
}
|
|
484
489
|
|
|
485
490
|
public static isSdJwtDecodedCredential(
|
|
486
|
-
original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
|
|
491
|
+
original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation,
|
|
487
492
|
): original is SdJwtDecodedVerifiableCredential {
|
|
488
493
|
return (
|
|
489
494
|
typeof original === 'object' &&
|
|
@@ -492,7 +497,7 @@ export class CredentialMapper {
|
|
|
492
497
|
}
|
|
493
498
|
|
|
494
499
|
public static isSdJwtVcdm2DecodedCredential(
|
|
495
|
-
original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
|
|
500
|
+
original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation,
|
|
496
501
|
): original is SdJwtDecodedVerifiableCredential {
|
|
497
502
|
if (typeof original !== 'object') {
|
|
498
503
|
return false
|
|
@@ -502,7 +507,7 @@ export class CredentialMapper {
|
|
|
502
507
|
}
|
|
503
508
|
|
|
504
509
|
public static isMsoMdocDecodedCredential(
|
|
505
|
-
original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
|
|
510
|
+
original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation,
|
|
506
511
|
): original is MdocDocument {
|
|
507
512
|
return typeof original === 'object' && 'issuerSigned' in original && (<MdocDocument>original).issuerSigned !== undefined
|
|
508
513
|
}
|
|
@@ -537,7 +542,7 @@ export class CredentialMapper {
|
|
|
537
542
|
static jwtEncodedPresentationToUniformPresentation(
|
|
538
543
|
jwt: string,
|
|
539
544
|
makeCredentialsUniform: boolean = true,
|
|
540
|
-
opts?: { maxTimeSkewInMS?: number }
|
|
545
|
+
opts?: { maxTimeSkewInMS?: number },
|
|
541
546
|
): IPresentation {
|
|
542
547
|
return CredentialMapper.jwtDecodedPresentationToUniformPresentation(jwtDecode(jwt), makeCredentialsUniform, opts)
|
|
543
548
|
}
|
|
@@ -545,7 +550,7 @@ export class CredentialMapper {
|
|
|
545
550
|
static jwtDecodedPresentationToUniformPresentation(
|
|
546
551
|
decoded: JwtDecodedVerifiablePresentation,
|
|
547
552
|
makeCredentialsUniform: boolean = true,
|
|
548
|
-
opts?: { maxTimeSkewInMS?: number }
|
|
553
|
+
opts?: { maxTimeSkewInMS?: number },
|
|
549
554
|
): IVerifiablePresentation {
|
|
550
555
|
const { iss, aud, jti, vp, ...rest } = decoded
|
|
551
556
|
|
|
@@ -592,7 +597,7 @@ export class CredentialMapper {
|
|
|
592
597
|
opts?: {
|
|
593
598
|
maxTimeSkewInMS?: number
|
|
594
599
|
hasher?: HasherSync
|
|
595
|
-
}
|
|
600
|
+
},
|
|
596
601
|
): IVerifiableCredential {
|
|
597
602
|
if (CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential)) {
|
|
598
603
|
return mdocDecodedCredentialToUniformCredential(verifiableCredential)
|
|
@@ -606,7 +611,7 @@ export class CredentialMapper {
|
|
|
606
611
|
: verifiableCredential
|
|
607
612
|
if (!original) {
|
|
608
613
|
throw Error(
|
|
609
|
-
'Could not determine original credential from passed in credential. Probably because a JWT proof type was present, but now is not available anymore'
|
|
614
|
+
'Could not determine original credential from passed in credential. Probably because a JWT proof type was present, but now is not available anymore',
|
|
610
615
|
)
|
|
611
616
|
}
|
|
612
617
|
const decoded = CredentialMapper.decodeVerifiableCredential(original, opts?.hasher ?? sha256)
|
|
@@ -629,7 +634,7 @@ export class CredentialMapper {
|
|
|
629
634
|
|
|
630
635
|
static toUniformPresentation(
|
|
631
636
|
presentation: OriginalVerifiablePresentation,
|
|
632
|
-
opts?: { maxTimeSkewInMS?: number; addContextIfMissing?: boolean; hasher?: HasherSync }
|
|
637
|
+
opts?: { maxTimeSkewInMS?: number; addContextIfMissing?: boolean; hasher?: HasherSync },
|
|
633
638
|
): IVerifiablePresentation {
|
|
634
639
|
if (CredentialMapper.isSdJwtDecodedCredential(presentation)) {
|
|
635
640
|
throw new Error('Converting SD-JWT VC to uniform VP is not supported.')
|
|
@@ -641,7 +646,7 @@ export class CredentialMapper {
|
|
|
641
646
|
const original = typeof presentation !== 'string' && CredentialMapper.hasJWTProofType(presentation) ? proof?.jwt : presentation
|
|
642
647
|
if (!original) {
|
|
643
648
|
throw Error(
|
|
644
|
-
'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof'
|
|
649
|
+
'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof',
|
|
645
650
|
)
|
|
646
651
|
}
|
|
647
652
|
const decoded = CredentialMapper.decodeVerifiablePresentation(original, opts?.hasher ?? sha256)
|
|
@@ -658,7 +663,7 @@ export class CredentialMapper {
|
|
|
658
663
|
}
|
|
659
664
|
|
|
660
665
|
uniformPresentation.verifiableCredential = uniformPresentation.verifiableCredential?.map((vc) =>
|
|
661
|
-
CredentialMapper.toUniformCredential(vc, opts)
|
|
666
|
+
CredentialMapper.toUniformCredential(vc, opts),
|
|
662
667
|
) as IVerifiableCredential[] // We cast it because we IPresentation needs a VC. The internal Credential doesn't have the required Proof anymore (that is intended)
|
|
663
668
|
return uniformPresentation
|
|
664
669
|
}
|
|
@@ -667,14 +672,14 @@ export class CredentialMapper {
|
|
|
667
672
|
jwt: string,
|
|
668
673
|
opts?: {
|
|
669
674
|
maxTimeSkewInMS?: number
|
|
670
|
-
}
|
|
675
|
+
},
|
|
671
676
|
): IVerifiableCredential {
|
|
672
677
|
return CredentialMapper.jwtDecodedCredentialToUniformCredential(jwtDecode(jwt), opts)
|
|
673
678
|
}
|
|
674
679
|
|
|
675
680
|
static jwtDecodedCredentialToUniformCredential(
|
|
676
681
|
decoded: JwtDecodedVerifiableCredential,
|
|
677
|
-
opts?: { maxTimeSkewInMS?: number }
|
|
682
|
+
opts?: { maxTimeSkewInMS?: number },
|
|
678
683
|
): IVerifiableCredential {
|
|
679
684
|
const { exp, nbf, iss, vc, sub, jti, ...rest } = decoded
|
|
680
685
|
const credential: IVerifiableCredential = {
|
|
@@ -842,7 +847,7 @@ export class CredentialMapper {
|
|
|
842
847
|
}
|
|
843
848
|
|
|
844
849
|
static toCompactJWT(
|
|
845
|
-
jwtDocument: W3CVerifiableCredential | JwtDecodedVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiablePresentation | string
|
|
850
|
+
jwtDocument: W3CVerifiableCredential | JwtDecodedVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiablePresentation | string,
|
|
846
851
|
): string {
|
|
847
852
|
if (!jwtDocument || CredentialMapper.detectDocumentType(jwtDocument) !== DocumentFormat.JWT) {
|
|
848
853
|
throw Error('Cannot convert non JWT credential to JWT')
|
|
@@ -872,7 +877,7 @@ export class CredentialMapper {
|
|
|
872
877
|
| JwtDecodedVerifiablePresentation
|
|
873
878
|
| SdJwtDecodedVerifiableCredential
|
|
874
879
|
| MdocDeviceResponse
|
|
875
|
-
| MdocDocument
|
|
880
|
+
| MdocDocument,
|
|
876
881
|
): DocumentFormat {
|
|
877
882
|
if (this.isMsoMdocOid4VPEncoded(document as any) || this.isMsoMdocDecodedCredential(document as any)) {
|
|
878
883
|
return DocumentFormat.MSO_MDOC
|
|
@@ -900,7 +905,7 @@ export class CredentialMapper {
|
|
|
900
905
|
}
|
|
901
906
|
|
|
902
907
|
private static hasJWTProofType(
|
|
903
|
-
document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation
|
|
908
|
+
document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation,
|
|
904
909
|
): boolean {
|
|
905
910
|
if (typeof document === 'string') {
|
|
906
911
|
return false
|
|
@@ -909,7 +914,7 @@ export class CredentialMapper {
|
|
|
909
914
|
}
|
|
910
915
|
|
|
911
916
|
private static getFirstProof(
|
|
912
|
-
document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation
|
|
917
|
+
document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation,
|
|
913
918
|
): IProof | undefined {
|
|
914
919
|
if (!document || typeof document === 'string') {
|
|
915
920
|
return undefined
|
|
@@ -152,6 +152,11 @@ export interface SdJwtSimpleRenderingMetadata {
|
|
|
152
152
|
*/
|
|
153
153
|
logo?: SdJwtLogoMetadata
|
|
154
154
|
|
|
155
|
+
/**
|
|
156
|
+
* OPTIONAL. Metadata for the background image.
|
|
157
|
+
*/
|
|
158
|
+
background_image?: SdJwtImageMetadata
|
|
159
|
+
|
|
155
160
|
/**
|
|
156
161
|
* OPTIONAL. Background color for the credential.
|
|
157
162
|
*/
|
|
@@ -163,6 +168,26 @@ export interface SdJwtSimpleRenderingMetadata {
|
|
|
163
168
|
text_color?: string
|
|
164
169
|
}
|
|
165
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Represents metadata for an image (logo, background image, etc.).
|
|
173
|
+
*/
|
|
174
|
+
export interface SdJwtImageMetadata {
|
|
175
|
+
/**
|
|
176
|
+
* REQUIRED. URI pointing to the image.
|
|
177
|
+
*/
|
|
178
|
+
uri: string
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* OPTIONAL. Integrity metadata string for the 'uri' field.
|
|
182
|
+
*/
|
|
183
|
+
['uri#integrity']?: string
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* OPTIONAL. Alternative text for the image.
|
|
187
|
+
*/
|
|
188
|
+
alt_text?: string
|
|
189
|
+
}
|
|
190
|
+
|
|
166
191
|
/**
|
|
167
192
|
* Represents metadata for a logo.
|
|
168
193
|
*/
|
package/src/utils/mdoc.ts
CHANGED
|
@@ -79,36 +79,76 @@ export function decodeMdocDeviceResponse(vpToken: MdocOid4vpMdocVpToken): MdocDe
|
|
|
79
79
|
return deviceResponse
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
function bytesToImageDataUri(value: unknown, mimeType = 'image/jpeg'): string {
|
|
83
|
+
if (typeof value === 'string') {
|
|
84
|
+
if (value.startsWith('data:image/')) return value
|
|
85
|
+
// Convert base64url to standard base64 if needed
|
|
86
|
+
let b64 = value.replace(/-/g, '+').replace(/_/g, '/')
|
|
87
|
+
const pad = b64.length % 4
|
|
88
|
+
if (pad) b64 += '='.repeat(4 - pad)
|
|
89
|
+
return `data:${mimeType};base64,${b64}`
|
|
90
|
+
}
|
|
91
|
+
// Int8Array or Uint8Array from CBOR byte string decoder
|
|
92
|
+
if (value && typeof value === 'object' && ('length' in value || Symbol.iterator in Object(value))) {
|
|
93
|
+
try {
|
|
94
|
+
const int8 = value instanceof Int8Array ? value : new Int8Array(value as ArrayLike<number>)
|
|
95
|
+
const base64 = com.sphereon.kmp.encodeTo(int8, com.sphereon.kmp.Encoding.BASE64)
|
|
96
|
+
return `data:${mimeType};base64,${base64}`
|
|
97
|
+
} catch {
|
|
98
|
+
// Fallback: try manual base64 encoding for Uint8Array
|
|
99
|
+
const bytes = value instanceof Uint8Array ? value : new Uint8Array(value as ArrayLike<number>)
|
|
100
|
+
let binary = ''
|
|
101
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i])
|
|
102
|
+
const base64 = typeof btoa === 'function' ? btoa(binary) : com.sphereon.kmp.encodeTo(new Int8Array(bytes), com.sphereon.kmp.Encoding.BASE64)
|
|
103
|
+
return `data:${mimeType};base64,${base64}`
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return String(value)
|
|
107
|
+
}
|
|
108
|
+
|
|
82
109
|
// TODO naive implementation of mapping a mdoc onto a IVerifiableCredential. Needs some fixes and further implementation and needs to be moved out of ssi-types
|
|
83
110
|
export const mdocDecodedCredentialToUniformCredential = (
|
|
84
111
|
decoded: MdocDocument,
|
|
85
112
|
// @ts-ignore
|
|
86
|
-
opts?: { maxTimeSkewInMS?: number },
|
|
113
|
+
opts?: { maxTimeSkewInMS?: number; issuer?: string },
|
|
87
114
|
): IVerifiableCredential => {
|
|
88
115
|
const document = decoded.toJson()
|
|
89
116
|
const json = document.toJsonDTO<DocumentJson>()
|
|
90
|
-
const type = 'Personal Identification Data'
|
|
91
117
|
const MSO = document.MSO
|
|
92
118
|
if (!MSO || !json.issuerSigned?.nameSpaces) {
|
|
93
119
|
throw Error(`Cannot access Mobile Security Object or Issuer Signed items from the Mdoc`)
|
|
94
120
|
}
|
|
95
121
|
const nameSpaces = json.issuerSigned.nameSpaces as unknown as Record<string, IssuerSignedItemJson[]>
|
|
96
|
-
|
|
97
|
-
|
|
122
|
+
const nameSpaceKeys = Object.keys(nameSpaces)
|
|
123
|
+
if (nameSpaceKeys.length === 0) {
|
|
124
|
+
throw Error(`No namespaces found in issuer signed items`)
|
|
125
|
+
}
|
|
126
|
+
// Collect items from all namespaces
|
|
127
|
+
const items: IssuerSignedItemJson[] = []
|
|
128
|
+
for (const ns of nameSpaceKeys) {
|
|
129
|
+
items.push(...(nameSpaces[ns] || []))
|
|
98
130
|
}
|
|
99
|
-
|
|
100
|
-
if (!items || items.length === 0) {
|
|
131
|
+
if (items.length === 0) {
|
|
101
132
|
throw Error(`No issuer signed items were found`)
|
|
102
133
|
}
|
|
103
134
|
type DisclosuresAccumulator = {
|
|
104
135
|
[key: string]: any
|
|
105
136
|
}
|
|
106
137
|
|
|
138
|
+
// Known image claims in ISO 18013-5
|
|
139
|
+
const IMAGE_CLAIMS = new Set(['portrait', 'signature_usual_mark'])
|
|
140
|
+
|
|
107
141
|
const credentialSubject = items.reduce((acc: DisclosuresAccumulator, item: IssuerSignedItemJson) => {
|
|
108
142
|
if (Array.isArray(item.value)) {
|
|
109
143
|
acc[item.key] = item.value.map((val) => val.value).join(', ')
|
|
110
144
|
} else {
|
|
111
|
-
|
|
145
|
+
const value = item.value.value
|
|
146
|
+
if (IMAGE_CLAIMS.has(item.key) && value != null) {
|
|
147
|
+
// Convert byte string to data URI for image rendering
|
|
148
|
+
acc[item.key] = bytesToImageDataUri(value)
|
|
149
|
+
} else {
|
|
150
|
+
acc[item.key] = value
|
|
151
|
+
}
|
|
112
152
|
}
|
|
113
153
|
return acc
|
|
114
154
|
}, {})
|
|
@@ -123,11 +163,55 @@ export const mdocDecodedCredentialToUniformCredential = (
|
|
|
123
163
|
throw Error(`JWT issuance date is required but was not present`)
|
|
124
164
|
}
|
|
125
165
|
|
|
126
|
-
|
|
166
|
+
// Determine issuer: x5c CN > issuing_authority claim > opts.issuer fallback > docType
|
|
167
|
+
let issuer: string = opts?.issuer ?? docType
|
|
168
|
+
// Try issuing_authority from credential claims (present in mDL and some other doctypes)
|
|
169
|
+
if (credentialSubject.issuing_authority) {
|
|
170
|
+
issuer = credentialSubject.issuing_authority
|
|
171
|
+
}
|
|
172
|
+
// Try to extract CN from x5chain certificate in issuerAuth (most authoritative for mdoc)
|
|
173
|
+
try {
|
|
174
|
+
const x5chain = json.issuerSigned?.issuerAuth?.unprotectedHeader?.x5chain ?? json.issuerSigned?.issuerAuth?.protectedHeader?.x5chain
|
|
175
|
+
if (x5chain && x5chain.length > 0) {
|
|
176
|
+
// x5chain[0] is base64-encoded DER certificate. Extract CN by searching for OID 2.5.4.3 (55 04 03)
|
|
177
|
+
const b64 = x5chain[0]
|
|
178
|
+
let bytes: Uint8Array
|
|
179
|
+
if (typeof atob === 'function') {
|
|
180
|
+
const binaryStr = atob(b64)
|
|
181
|
+
bytes = new Uint8Array(binaryStr.length)
|
|
182
|
+
for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i)
|
|
183
|
+
} else {
|
|
184
|
+
const int8 = com.sphereon.kmp.decodeFrom(b64, com.sphereon.kmp.Encoding.BASE64)
|
|
185
|
+
bytes = new Uint8Array(int8)
|
|
186
|
+
}
|
|
187
|
+
// Find last CN OID (55 04 03) — last occurrence is the subject CN
|
|
188
|
+
let lastCn: string | undefined
|
|
189
|
+
for (let i = 0; i < bytes.length - 5; i++) {
|
|
190
|
+
if (bytes[i] === 0x55 && bytes[i + 1] === 0x04 && bytes[i + 2] === 0x03) {
|
|
191
|
+
const tag = bytes[i + 3] // 0x0c=UTF8String, 0x13=PrintableString
|
|
192
|
+
if (tag === 0x0c || tag === 0x13) {
|
|
193
|
+
const len = bytes[i + 4]
|
|
194
|
+
if (i + 5 + len <= bytes.length) {
|
|
195
|
+
const cn = String.fromCharCode(...bytes.slice(i + 5, i + 5 + len))
|
|
196
|
+
lastCn = cn
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (lastCn) {
|
|
202
|
+
issuer = lastCn
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch {
|
|
206
|
+
// Certificate parsing failed, keep previous issuer value
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const credential: Omit<IVerifiableCredential, 'issuanceDate'> = {
|
|
127
210
|
type: [docType], // Mdoc not a W3C VC, so no VerifiableCredential
|
|
128
211
|
'@context': [], // Mdoc has no JSON-LD by default. Certainly not the VC DM1 default context for JSON-LD
|
|
212
|
+
issuer,
|
|
129
213
|
credentialSubject: {
|
|
130
|
-
type,
|
|
214
|
+
type: docType,
|
|
131
215
|
...credentialSubject,
|
|
132
216
|
},
|
|
133
217
|
issuanceDate,
|