@sphereon/ssi-sdk-ext.x509-utils 0.26.1-next.5 → 0.26.1-next.86

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.
@@ -1,16 +1,14 @@
1
+ import { AsnParser } from '@peculiar/asn1-schema'
2
+ import { SubjectPublicKeyInfo } from '@peculiar/asn1-x509'
3
+ import { AlgorithmProvider, X509Certificate } from '@peculiar/x509'
4
+ // import {calculateJwkThumbprint} from "@sphereon/ssi-sdk-ext.key-utils";
5
+ import { JWK } from '@sphereon/ssi-types'
1
6
  import x509 from 'js-x509-utils'
2
- import {
3
- AltName,
4
- AttributeTypeAndValue,
5
- Certificate,
6
- CertificateChainValidationEngine,
7
- CryptoEngine,
8
- getCrypto,
9
- id_SubjectAltName,
10
- setEngine,
11
- } from 'pkijs'
7
+ import { AltName, AttributeTypeAndValue, Certificate, CryptoEngine, getCrypto, id_SubjectAltName, setEngine } from 'pkijs'
8
+ import { container } from 'tsyringe'
12
9
  import * as u8a from 'uint8arrays'
13
- import { derToPEM, pemOrDerToX509Certificate } from './x509-utils'
10
+ import { globalCrypto } from './crypto'
11
+ import { areCertificatesEqual, derToPEM, pemOrDerToX509Certificate } from './x509-utils'
14
12
 
15
13
  export type DNInfo = {
16
14
  DN: string
@@ -35,8 +33,10 @@ export type X509ValidationResult = {
35
33
  error: boolean
36
34
  critical: boolean
37
35
  message: string
36
+ detailMessage?: string
38
37
  verificationTime: Date
39
38
  certificateChain?: Array<CertificateInfo>
39
+ trustAnchor?: CertificateInfo
40
40
  client?: {
41
41
  // In case client id and scheme were passed in we return them for easy access. It means they are validated
42
42
  clientId: string
@@ -45,23 +45,9 @@ export type X509ValidationResult = {
45
45
  }
46
46
 
47
47
  const defaultCryptoEngine = () => {
48
- if (typeof self !== 'undefined') {
49
- if ('crypto' in self) {
50
- let engineName = 'webcrypto'
51
- if ('webkitSubtle' in self.crypto) {
52
- engineName = 'safari'
53
- }
54
- setEngine(engineName, new CryptoEngine({ name: engineName, crypto: crypto }))
55
- }
56
- } else if (typeof crypto !== 'undefined' && 'webcrypto' in crypto) {
57
- const name = 'NodeJS ^15'
58
- const nodeCrypto = crypto.webcrypto
59
- // @ts-ignore
60
- setEngine(name, new CryptoEngine({ name, crypto: nodeCrypto }))
61
- } else if (typeof crypto !== 'undefined' && typeof crypto.subtle !== 'undefined') {
62
- const name = 'crypto'
63
- setEngine(name, new CryptoEngine({ name, crypto: crypto }))
64
- }
48
+ const name = 'crypto'
49
+ setEngine(name, new CryptoEngine({ name, crypto: globalCrypto(false) }))
50
+ return getCrypto(true)
65
51
  }
66
52
 
67
53
  export const getCertificateInfo = async (
@@ -70,14 +56,17 @@ export const getCertificateInfo = async (
70
56
  sanTypeFilter: SubjectAlternativeGeneralName | SubjectAlternativeGeneralName[]
71
57
  }
72
58
  ): Promise<CertificateInfo> => {
73
- const publicKeyJWK = await getCertificateSubjectPublicKeyJWK(certificate)
59
+ let publicKeyJWK: JWK | undefined
60
+ try {
61
+ publicKeyJWK = (await getCertificateSubjectPublicKeyJWK(certificate)) as JWK
62
+ } catch (e) {}
74
63
  return {
75
64
  issuer: { dn: getIssuerDN(certificate) },
76
65
  subject: {
77
66
  dn: getSubjectDN(certificate),
78
67
  subjectAlternativeNames: getSubjectAlternativeNames(certificate, { typeFilter: opts?.sanTypeFilter }),
79
68
  },
80
- publicKeyJWK: publicKeyJWK,
69
+ publicKeyJWK,
81
70
  notBefore: certificate.notBefore.value,
82
71
  notAfter: certificate.notAfter.value,
83
72
  // certificate
@@ -85,6 +74,9 @@ export const getCertificateInfo = async (
85
74
  }
86
75
 
87
76
  export type X509CertificateChainValidationOpts = {
77
+ // If no trust anchor is found, but the chain itself checks out, allow. (defaults to false:)
78
+ allowNoTrustAnchorsFound?: boolean
79
+
88
80
  // Trust the supplied root from the chain, when no anchors are being passed in.
89
81
  trustRootWhenNoAnchors?: boolean
90
82
  // Do not perform a chain validation check if the chain only has a single value. This means only the certificate itself will be validated. No chain checks for CA certs will be performed. Only used when the cert has no issuer
@@ -93,6 +85,8 @@ export type X509CertificateChainValidationOpts = {
93
85
  // Similar to regular trust anchors, but no validation is performed whatsoever. Do not use in production settings! Can be handy with self generated certificates as we perform many validations, making it hard to test with self-signed certs. Only applied in case a chain with 1 element is passed in to really make sure people do not abuse this option
94
86
  blindlyTrustedAnchors?: string[]
95
87
 
88
+ disallowReversedChain?: boolean
89
+
96
90
  client?: {
97
91
  // If provided both are required. Validates the leaf certificate against the clientId and scheme
98
92
  clientId: string
@@ -100,21 +94,17 @@ export type X509CertificateChainValidationOpts = {
100
94
  }
101
95
  }
102
96
 
103
- /**
104
- *
105
- * @param pemOrDerChain The order must be that the Certs signing another cert must come one after another. So first the signing cert, then any cert signing that cert and so on
106
- * @param trustedPEMs
107
- * @param verificationTime
108
- * @param opts
109
- */
110
97
  export const validateX509CertificateChain = async ({
111
98
  chain: pemOrDerChain,
112
99
  trustAnchors,
113
100
  verificationTime = new Date(),
114
101
  opts = {
102
+ // If no trust anchor is found, but the chain itself checks out, allow. (defaults to false:)
103
+ allowNoTrustAnchorsFound: false,
115
104
  trustRootWhenNoAnchors: false,
116
105
  allowSingleNoCAChainElement: true,
117
106
  blindlyTrustedAnchors: [],
107
+ disallowReversedChain: false,
118
108
  },
119
109
  }: {
120
110
  chain: (Uint8Array | string)[]
@@ -122,7 +112,37 @@ export const validateX509CertificateChain = async ({
122
112
  verificationTime?: Date
123
113
  opts?: X509CertificateChainValidationOpts
124
114
  }): Promise<X509ValidationResult> => {
125
- const { trustRootWhenNoAnchors = false, allowSingleNoCAChainElement = true, blindlyTrustedAnchors = [], client } = opts
115
+ // We allow 1 reversal. We reverse by default as the implementation expects the root ca first, whilst x5c is the opposite. Reversed becomes true if the impl reverses the chain
116
+ return await validateX509CertificateChainImpl({
117
+ reversed: false,
118
+ chain: [...pemOrDerChain].reverse(),
119
+ trustAnchors,
120
+ verificationTime,
121
+ opts,
122
+ })
123
+ }
124
+ const validateX509CertificateChainImpl = async ({
125
+ reversed,
126
+ chain: pemOrDerChain,
127
+ trustAnchors,
128
+ verificationTime: verifyAt,
129
+ opts,
130
+ }: {
131
+ reversed: boolean
132
+ chain: (Uint8Array | string)[]
133
+ trustAnchors?: string[]
134
+ verificationTime: Date | string // string for REST API
135
+ opts: X509CertificateChainValidationOpts
136
+ }): Promise<X509ValidationResult> => {
137
+ const verificationTime: Date = typeof verifyAt === 'string' ? new Date(verifyAt) : verifyAt
138
+ const {
139
+ allowNoTrustAnchorsFound = false,
140
+ trustRootWhenNoAnchors = false,
141
+ allowSingleNoCAChainElement = true,
142
+ blindlyTrustedAnchors = [],
143
+ disallowReversedChain = false,
144
+ client,
145
+ } = opts
126
146
  const trustedPEMs = trustRootWhenNoAnchors && !trustAnchors ? [pemOrDerChain[pemOrDerChain.length - 1]] : trustAnchors
127
147
 
128
148
  if (pemOrDerChain.length === 0) {
@@ -133,95 +153,323 @@ export const validateX509CertificateChain = async ({
133
153
  verificationTime,
134
154
  }
135
155
  }
136
-
137
- // x5c always starts with the leaf cert at index 0 and then the cas. Our internal pkijs service expects it the other way around
138
- const certs = pemOrDerChain.map(pemOrDerToX509Certificate).reverse()
139
- const trustedCerts = trustedPEMs ? trustedPEMs.map(pemOrDerToX509Certificate) : undefined
140
156
  defaultCryptoEngine()
141
157
 
142
- if (pemOrDerChain.length === 1) {
143
- const singleCert = typeof pemOrDerChain[0] === 'string' ? pemOrDerChain[0] : u8a.toString(pemOrDerChain[0], 'base64pad')
144
- const cert = pemOrDerToX509Certificate(singleCert)
145
- if (client) {
146
- const validation = await validateCertificateChainMatchesClientIdScheme(cert, client.clientId, client.clientIdScheme)
147
- if (validation.error) {
148
- return validation
149
- }
150
- }
151
- if (blindlyTrustedAnchors.includes(singleCert)) {
158
+ // x5c always starts with the leaf cert at index 0 and then the cas. Our internal pkijs service expects it the other way around. Before calling this function the change has been revered
159
+ const chain = await Promise.all(pemOrDerChain.map((raw) => parseCertificate(raw)))
160
+ const x5cOrdereredChain = reversed ? [...chain] : [...chain].reverse()
161
+
162
+ const trustedCerts = trustedPEMs ? await Promise.all(trustedPEMs.map((raw) => parseCertificate(raw))) : undefined
163
+ const blindlyTrusted =
164
+ (
165
+ await Promise.all(
166
+ blindlyTrustedAnchors.map((raw) => {
167
+ try {
168
+ return parseCertificate(raw)
169
+ } catch (e) {
170
+ // @ts-ignore
171
+ console.log(`Failed to parse blindly trusted certificate ${raw}. Error: ${e.message}`)
172
+ return undefined
173
+ }
174
+ })
175
+ )
176
+ ).filter((cert): cert is ParsedCertificate => cert !== undefined) ?? []
177
+ const leafCert = x5cOrdereredChain[0]
178
+
179
+ const chainLength = chain.length
180
+ var foundTrustAnchor: ParsedCertificate | undefined = undefined
181
+ for (let i = 0; i < chainLength; i++) {
182
+ const currentCert = chain[i]
183
+ const previousCert = i > 0 ? chain[i - 1] : undefined
184
+ const blindlyTrustedCert = blindlyTrusted.find((trusted) => areCertificatesEqual(trusted.certificate, currentCert.certificate))
185
+ if (blindlyTrustedCert) {
152
186
  console.log(`Certificate chain validation success as single cert if blindly trusted. WARNING: ONLY USE FOR TESTING PURPOSES.`)
153
187
  return {
154
188
  error: false,
155
- critical: true,
189
+ critical: false,
156
190
  message: `Certificate chain validation success as single cert if blindly trusted. WARNING: ONLY USE FOR TESTING PURPOSES.`,
191
+ detailMessage: `Blindly trusted certificate ${blindlyTrustedCert.certificateInfo.subject.dn.DN} was found in the chain.`,
192
+ trustAnchor: blindlyTrustedCert?.certificateInfo,
157
193
  verificationTime,
158
- certificateChain: [await getCertificateInfo(cert)],
194
+ certificateChain: x5cOrdereredChain.map((cert) => cert.certificateInfo),
159
195
  ...(client && { client }),
160
196
  }
161
197
  }
162
- if (allowSingleNoCAChainElement) {
163
- const subjectDN = getSubjectDN(cert).DN
164
- if (!getIssuerDN(cert).DN || getIssuerDN(cert).DN === subjectDN) {
165
- const passed = await cert.verify()
198
+ if (previousCert) {
199
+ if (currentCert.x509Certificate.issuer !== previousCert.x509Certificate.subject) {
200
+ if (!reversed && !disallowReversedChain) {
201
+ return await validateX509CertificateChainImpl({
202
+ reversed: true,
203
+ chain: [...pemOrDerChain].reverse(),
204
+ opts,
205
+ verificationTime,
206
+ trustAnchors,
207
+ })
208
+ }
166
209
  return {
167
- error: !passed,
210
+ error: true,
168
211
  critical: true,
169
- message: `Certificate chain validation for ${subjectDN}: ${passed ? 'successful' : 'failed'}.`,
212
+ certificateChain: x5cOrdereredChain.map((cert) => cert.certificateInfo),
213
+ message: `Certificate chain validation failed for ${leafCert.certificateInfo.subject.dn.DN}.`,
214
+ detailMessage: `The certificate ${currentCert.certificateInfo.subject.dn.DN} with issuer ${currentCert.x509Certificate.issuer}, is not signed by the previous certificate ${previousCert?.certificateInfo.subject.dn.DN} with subject string ${previousCert?.x509Certificate.subject}.`,
170
215
  verificationTime,
171
- certificateChain: [await getCertificateInfo(cert)],
172
216
  ...(client && { client }),
173
217
  }
174
218
  }
175
219
  }
176
- }
177
-
178
- const validationEngine = new CertificateChainValidationEngine({
179
- certs /*crls: [crl1], ocsps: [ocsp1], */,
180
- checkDate: verificationTime,
181
- trustedCerts,
182
- })
220
+ const result = await currentCert.x509Certificate.verify(
221
+ {
222
+ date: verificationTime,
223
+ publicKey: previousCert?.x509Certificate?.publicKey,
224
+ },
225
+ getCrypto()?.crypto ?? crypto ?? global.crypto
226
+ )
227
+ if (!result) {
228
+ // First cert needs to be self signed
229
+ if (i == 0 && !reversed && !disallowReversedChain) {
230
+ return await validateX509CertificateChainImpl({
231
+ reversed: true,
232
+ chain: [...pemOrDerChain].reverse(),
233
+ opts,
234
+ verificationTime,
235
+ trustAnchors,
236
+ })
237
+ }
183
238
 
184
- try {
185
- const verification = await validationEngine.verify()
186
- if (!verification.result || !verification.certificatePath) {
187
239
  return {
188
240
  error: true,
189
241
  critical: true,
190
- message: verification.resultMessage !== '' ? verification.resultMessage : `Certificate chain validation failed.`,
242
+ message: `Certificate chain validation failed for ${leafCert.certificateInfo.subject.dn.DN}.`,
243
+ certificateChain: x5cOrdereredChain.map((cert) => cert.certificateInfo),
244
+ detailMessage: `Verification of the certificate ${currentCert.certificateInfo.subject.dn.DN} with issuer ${
245
+ currentCert.x509Certificate.issuer
246
+ } failed. Public key: ${JSON.stringify(currentCert.certificateInfo.publicKeyJWK)}.`,
191
247
  verificationTime,
192
248
  ...(client && { client }),
193
249
  }
194
250
  }
195
- const certPath = verification.certificatePath
196
- if (client) {
197
- const clientIdValidation = await validateCertificateChainMatchesClientIdScheme(certs[0], client.clientId, client.clientIdScheme)
198
- if (clientIdValidation.error) {
199
- return clientIdValidation
251
+
252
+ foundTrustAnchor = foundTrustAnchor ?? trustedCerts?.find((trusted) => isSameCertificate(trusted.x509Certificate, currentCert.x509Certificate))
253
+
254
+ if (i === 0 && chainLength === 1 && allowSingleNoCAChainElement) {
255
+ return {
256
+ error: false,
257
+ critical: false,
258
+ message: `Certificate chain succeeded as allow single cert result is allowed: ${leafCert.certificateInfo.subject.dn.DN}.`,
259
+ certificateChain: x5cOrdereredChain.map((cert) => cert.certificateInfo),
260
+ trustAnchor: foundTrustAnchor?.certificateInfo,
261
+ verificationTime,
262
+ ...(client && { client }),
200
263
  }
201
264
  }
202
- const certInfos: Array<CertificateInfo> = await Promise.all(
203
- certPath.map(async (certificate) => {
204
- return getCertificateInfo(certificate)
205
- })
206
- )
265
+ }
266
+
267
+ if (foundTrustAnchor?.certificateInfo || allowNoTrustAnchorsFound) {
207
268
  return {
208
269
  error: false,
209
270
  critical: false,
210
271
  message: `Certificate chain was valid`,
211
- verificationTime,
212
- certificateChain: certInfos,
213
- ...(client && { client }),
214
- }
215
- } catch (error: any) {
216
- return {
217
- error: true,
218
- critical: true,
219
- message: `Certificate chain was invalid, ${error.message ?? '<unknown error>'}`,
272
+ certificateChain: x5cOrdereredChain.map((cert) => cert.certificateInfo),
273
+ detailMessage: foundTrustAnchor
274
+ ? `The leaf certificate ${leafCert.certificateInfo.subject.dn.DN} is part of a chain with trust anchor ${foundTrustAnchor?.certificateInfo.subject.dn.DN}.`
275
+ : `The leaf certificate ${leafCert.certificateInfo.subject.dn.DN} and chain were valid, but no trust anchor has been found. Ignoring as user allowed (allowNoTrustAnchorsFound: ${allowNoTrustAnchorsFound}).)`,
276
+ trustAnchor: foundTrustAnchor?.certificateInfo,
220
277
  verificationTime,
221
278
  ...(client && { client }),
222
279
  }
223
280
  }
281
+
282
+ return {
283
+ error: true,
284
+ critical: true,
285
+ message: `Certificate chain validation failed for ${leafCert.certificateInfo.subject.dn.DN}.`,
286
+ certificateChain: x5cOrdereredChain.map((cert) => cert.certificateInfo),
287
+ detailMessage: `No trust anchor was found in the chain. between (intermediate) CA ${
288
+ x5cOrdereredChain[chain.length - 1].certificateInfo.subject.dn.DN
289
+ } and leaf ${x5cOrdereredChain[0].certificateInfo.subject.dn.DN}.`,
290
+ verificationTime,
291
+ ...(client && { client }),
292
+ }
293
+ }
294
+
295
+ const isSameCertificate = (cert1: X509Certificate, cert2: X509Certificate): boolean => {
296
+ return cert1.rawData.toString() === cert2.rawData.toString()
297
+ }
298
+
299
+ const algorithmProvider: AlgorithmProvider = container.resolve(AlgorithmProvider)
300
+ export const getX509AlgorithmProvider = (): AlgorithmProvider => {
301
+ return algorithmProvider
302
+ }
303
+
304
+ export type ParsedCertificate = {
305
+ publicKeyInfo: SubjectPublicKeyInfo
306
+ publicKeyJwk?: JWK
307
+ publicKeyRaw: Uint8Array
308
+ publicKeyAlgorithm: Algorithm
309
+ certificateInfo: CertificateInfo
310
+ certificate: Certificate
311
+ x509Certificate: X509Certificate
312
+ }
313
+
314
+ export const parseCertificate = async (rawCert: string | Uint8Array): Promise<ParsedCertificate> => {
315
+ const x509Certificate = new X509Certificate(rawCert)
316
+ const publicKeyInfo = AsnParser.parse(x509Certificate.publicKey.rawData, SubjectPublicKeyInfo)
317
+ const publicKeyRaw = new Uint8Array(publicKeyInfo.subjectPublicKey)
318
+ let publicKeyJwk: JWK | undefined = undefined
319
+ try {
320
+ publicKeyJwk = (await getCertificateSubjectPublicKeyJWK(new Uint8Array(x509Certificate.rawData))) as JWK
321
+ } catch (e: any) {
322
+ console.error(e.message)
323
+ }
324
+ const certificate = pemOrDerToX509Certificate(rawCert)
325
+ const certificateInfo = await getCertificateInfo(certificate)
326
+ const publicKeyAlgorithm = getX509AlgorithmProvider().toWebAlgorithm(publicKeyInfo.algorithm)
327
+ return {
328
+ publicKeyAlgorithm,
329
+ publicKeyInfo,
330
+ publicKeyJwk,
331
+ publicKeyRaw,
332
+ certificateInfo,
333
+ certificate,
334
+ x509Certificate,
335
+ }
224
336
  }
337
+ /*
338
+
339
+ /!**
340
+ *
341
+ * @param pemOrDerChain The order must be that the Certs signing another cert must come one after another. So first the signing cert, then any cert signing that cert and so on
342
+ * @param trustedPEMs
343
+ * @param verificationTime
344
+ * @param opts
345
+ *!/
346
+ export const validateX509CertificateChainOrg = async ({
347
+ chain: pemOrDerChain,
348
+ trustAnchors,
349
+ verificationTime = new Date(),
350
+ opts = {
351
+ trustRootWhenNoAnchors: false,
352
+ allowSingleNoCAChainElement: true,
353
+ blindlyTrustedAnchors: [],
354
+ },
355
+ }: {
356
+ chain: (Uint8Array | string)[]
357
+ trustAnchors?: string[]
358
+ verificationTime?: Date
359
+ opts?: X509CertificateChainValidationOpts
360
+ }): Promise<X509ValidationResult> => {
361
+ const {
362
+ trustRootWhenNoAnchors = false,
363
+ allowSingleNoCAChainElement = true,
364
+ blindlyTrustedAnchors = [],
365
+ client
366
+ } = opts
367
+ const trustedPEMs = trustRootWhenNoAnchors && !trustAnchors ? [pemOrDerChain[pemOrDerChain.length - 1]] : trustAnchors
368
+
369
+ if (pemOrDerChain.length === 0) {
370
+ return {
371
+ error: true,
372
+ critical: true,
373
+ message: 'Certificate chain in DER or PEM format must not be empty',
374
+ verificationTime,
375
+ }
376
+ }
377
+
378
+ // x5c always starts with the leaf cert at index 0 and then the cas. Our internal pkijs service expects it the other way around
379
+ const certs = pemOrDerChain.map(pemOrDerToX509Certificate).reverse()
380
+ const trustedCerts = trustedPEMs ? trustedPEMs.map(pemOrDerToX509Certificate) : undefined
381
+ defaultCryptoEngine()
382
+
383
+ if (pemOrDerChain.length === 1) {
384
+ const singleCert = typeof pemOrDerChain[0] === 'string' ? pemOrDerChain[0] : u8a.toString(pemOrDerChain[0], 'base64pad')
385
+ const cert = pemOrDerToX509Certificate(singleCert)
386
+ if (client) {
387
+ const validation = await validateCertificateChainMatchesClientIdScheme(cert, client.clientId, client.clientIdScheme)
388
+ if (validation.error) {
389
+ return validation
390
+ }
391
+ }
392
+ if (blindlyTrustedAnchors.includes(singleCert)) {
393
+ console.log(`Certificate chain validation success as single cert if blindly trusted. WARNING: ONLY USE FOR TESTING PURPOSES.`)
394
+ return {
395
+ error: false,
396
+ critical: true,
397
+ message: `Certificate chain validation success as single cert if blindly trusted. WARNING: ONLY USE FOR TESTING PURPOSES.`,
398
+ verificationTime,
399
+ certificateChain: [await getCertificateInfo(cert)],
400
+ ...(client && {client}),
401
+ }
402
+ }
403
+ if (allowSingleNoCAChainElement) {
404
+ const subjectDN = getSubjectDN(cert).DN
405
+ if (!getIssuerDN(cert).DN || getIssuerDN(cert).DN === subjectDN) {
406
+ const passed = await cert.verify()
407
+ return {
408
+ error: !passed,
409
+ critical: true,
410
+ message: `Certificate chain validation for ${subjectDN}: ${passed ? 'successful' : 'failed'}.`,
411
+ verificationTime,
412
+ certificateChain: [await getCertificateInfo(cert)],
413
+ ...(client && {client}),
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ const validationEngine = new CertificateChainValidationEngine({
420
+ certs /!*crls: [crl1], ocsps: [ocsp1], *!/,
421
+ checkDate: verificationTime,
422
+ trustedCerts,
423
+ })
424
+
425
+ try {
426
+ const verification = await validationEngine.verify()
427
+ if (!verification.result || !verification.certificatePath) {
428
+ return {
429
+ error: true,
430
+ critical: true,
431
+ message: verification.resultMessage !== '' ? verification.resultMessage : `Certificate chain validation failed.`,
432
+ verificationTime,
433
+ ...(client && {client}),
434
+ }
435
+ }
436
+ const certPath = verification.certificatePath
437
+ if (client) {
438
+ const clientIdValidation = await validateCertificateChainMatchesClientIdScheme(certs[0], client.clientId, client.clientIdScheme)
439
+ if (clientIdValidation.error) {
440
+ return clientIdValidation
441
+ }
442
+ }
443
+ let certInfos: Array<CertificateInfo> | undefined
444
+
445
+ for (const certificate of certPath) {
446
+ try {
447
+ certInfos?.push(await getCertificateInfo(certificate))
448
+ } catch (e: any) {
449
+ console.log(`Error getting certificate info ${e.message}`)
450
+ }
451
+ }
452
+
453
+
454
+ return {
455
+ error: false,
456
+ critical: false,
457
+ message: `Certificate chain was valid`,
458
+ verificationTime,
459
+ certificateChain: certInfos,
460
+ ...(client && {client}),
461
+ }
462
+ } catch (error: any) {
463
+ return {
464
+ error: true,
465
+ critical: true,
466
+ message: `Certificate chain was invalid, ${error.message ?? '<unknown error>'}`,
467
+ verificationTime,
468
+ ...(client && {client}),
469
+ }
470
+ }
471
+ }
472
+ */
225
473
 
226
474
  const rdnmap: Record<string, string> = {
227
475
  '2.5.4.6': 'C',
@@ -265,23 +513,34 @@ const getDNString = (typesAndValues: AttributeTypeAndValue[]): string => {
265
513
  .join(',')
266
514
  }
267
515
 
268
- export const getCertificateSubjectPublicKeyJWK = async (pemOrDerCert: string | Uint8Array | Certificate): Promise<JsonWebKey> => {
516
+ export const getCertificateSubjectPublicKeyJWK = async (pemOrDerCert: string | Uint8Array | Certificate): Promise<JWK> => {
269
517
  const pemOrDerStr =
270
518
  typeof pemOrDerCert === 'string'
271
- ? pemOrDerCert
519
+ ? u8a.toString(u8a.fromString(pemOrDerCert, 'base64pad'), 'base64pad')
272
520
  : pemOrDerCert instanceof Uint8Array
273
521
  ? u8a.toString(pemOrDerCert, 'base64pad')
274
- : pemOrDerCert.toString('base64')
522
+ : u8a.toString(u8a.fromString(pemOrDerCert.toString('base64'), 'base64pad'), 'base64pad')
275
523
  const pem = derToPEM(pemOrDerStr)
276
524
  const certificate = pemOrDerToX509Certificate(pem)
525
+ var jwk: JWK | undefined
277
526
  try {
278
527
  const subtle = getCrypto(true).subtle
279
- const pk = await certificate.getPublicKey()
280
- return await subtle.exportKey('jwk', pk)
528
+ const pk = await certificate.getPublicKey(undefined, defaultCryptoEngine())
529
+ jwk = (await subtle.exportKey('jwk', pk)) as JWK | undefined
281
530
  } catch (error: any) {
282
531
  console.log(`Error in primary get JWK from cert:`, error?.message)
283
532
  }
284
- return await x509.toJwk(pem, 'pem')
533
+ if (!jwk) {
534
+ try {
535
+ jwk = (await x509.toJwk(pem, 'pem')) as JWK
536
+ } catch (error: any) {
537
+ console.log(`Error in secondary get JWK from cert as well:`, error?.message)
538
+ }
539
+ }
540
+ if (!jwk) {
541
+ throw Error(`Failed to get JWK from certificate ${pem}`)
542
+ }
543
+ return jwk
285
544
  }
286
545
 
287
546
  /**