@sphereon/ssi-sdk-ext.x509-utils 0.26.1-next.9 → 0.27.1-next.4
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/x509/crypto.d.ts +2 -0
- package/dist/x509/crypto.d.ts.map +1 -0
- package/dist/x509/crypto.js +28 -0
- package/dist/x509/crypto.js.map +1 -0
- package/dist/x509/rsa-key.d.ts.map +1 -1
- package/dist/x509/rsa-key.js +4 -3
- package/dist/x509/rsa-key.js.map +1 -1
- package/dist/x509/rsa-signer.d.ts.map +1 -1
- package/dist/x509/rsa-signer.js +3 -2
- package/dist/x509/rsa-signer.js.map +1 -1
- package/dist/x509/x509-validator.d.ts +4 -15
- package/dist/x509/x509-validator.d.ts.map +1 -1
- package/dist/x509/x509-validator.js +175 -91
- package/dist/x509/x509-validator.js.map +1 -1
- package/package.json +2 -2
- package/src/x509/crypto.ts +19 -0
- package/src/x509/rsa-key.ts +4 -3
- package/src/x509/rsa-signer.ts +3 -2
- package/src/x509/x509-validator.ts +211 -159
|
@@ -4,18 +4,10 @@ import { AlgorithmProvider, X509Certificate } from '@peculiar/x509'
|
|
|
4
4
|
// import {calculateJwkThumbprint} from "@sphereon/ssi-sdk-ext.key-utils";
|
|
5
5
|
import { JWK } from '@sphereon/ssi-types'
|
|
6
6
|
import x509 from 'js-x509-utils'
|
|
7
|
-
import {
|
|
8
|
-
AltName,
|
|
9
|
-
AttributeTypeAndValue,
|
|
10
|
-
Certificate,
|
|
11
|
-
CertificateChainValidationEngine,
|
|
12
|
-
CryptoEngine,
|
|
13
|
-
getCrypto,
|
|
14
|
-
id_SubjectAltName,
|
|
15
|
-
setEngine,
|
|
16
|
-
} from 'pkijs'
|
|
7
|
+
import { AltName, AttributeTypeAndValue, Certificate, CryptoEngine, getCrypto, id_SubjectAltName, setEngine } from 'pkijs'
|
|
17
8
|
import { container } from 'tsyringe'
|
|
18
9
|
import * as u8a from 'uint8arrays'
|
|
10
|
+
import { globalCrypto } from './crypto'
|
|
19
11
|
import { areCertificatesEqual, derToPEM, pemOrDerToX509Certificate } from './x509-utils'
|
|
20
12
|
|
|
21
13
|
export type DNInfo = {
|
|
@@ -41,6 +33,7 @@ export type X509ValidationResult = {
|
|
|
41
33
|
error: boolean
|
|
42
34
|
critical: boolean
|
|
43
35
|
message: string
|
|
36
|
+
detailMessage?: string
|
|
44
37
|
verificationTime: Date
|
|
45
38
|
certificateChain?: Array<CertificateInfo>
|
|
46
39
|
trustAnchor?: CertificateInfo
|
|
@@ -52,23 +45,9 @@ export type X509ValidationResult = {
|
|
|
52
45
|
}
|
|
53
46
|
|
|
54
47
|
const defaultCryptoEngine = () => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if ('webkitSubtle' in self.crypto) {
|
|
59
|
-
engineName = 'safari'
|
|
60
|
-
}
|
|
61
|
-
setEngine(engineName, new CryptoEngine({ name: engineName, crypto: crypto }))
|
|
62
|
-
}
|
|
63
|
-
} else if (typeof crypto !== 'undefined' && 'webcrypto' in crypto) {
|
|
64
|
-
const name = 'NodeJS ^15'
|
|
65
|
-
const nodeCrypto = crypto.webcrypto
|
|
66
|
-
// @ts-ignore
|
|
67
|
-
setEngine(name, new CryptoEngine({ name, crypto: nodeCrypto }))
|
|
68
|
-
} else if (typeof crypto !== 'undefined' && typeof crypto.subtle !== 'undefined') {
|
|
69
|
-
const name = 'crypto'
|
|
70
|
-
setEngine(name, new CryptoEngine({ name, crypto: crypto }))
|
|
71
|
-
}
|
|
48
|
+
const name = 'crypto'
|
|
49
|
+
setEngine(name, new CryptoEngine({ name, crypto: globalCrypto(false) }))
|
|
50
|
+
return getCrypto(true)
|
|
72
51
|
}
|
|
73
52
|
|
|
74
53
|
export const getCertificateInfo = async (
|
|
@@ -77,14 +56,17 @@ export const getCertificateInfo = async (
|
|
|
77
56
|
sanTypeFilter: SubjectAlternativeGeneralName | SubjectAlternativeGeneralName[]
|
|
78
57
|
}
|
|
79
58
|
): Promise<CertificateInfo> => {
|
|
80
|
-
|
|
59
|
+
let publicKeyJWK: JWK | undefined
|
|
60
|
+
try {
|
|
61
|
+
publicKeyJWK = (await getCertificateSubjectPublicKeyJWK(certificate)) as JWK
|
|
62
|
+
} catch (e) {}
|
|
81
63
|
return {
|
|
82
64
|
issuer: { dn: getIssuerDN(certificate) },
|
|
83
65
|
subject: {
|
|
84
66
|
dn: getSubjectDN(certificate),
|
|
85
67
|
subjectAlternativeNames: getSubjectAlternativeNames(certificate, { typeFilter: opts?.sanTypeFilter }),
|
|
86
68
|
},
|
|
87
|
-
publicKeyJWK
|
|
69
|
+
publicKeyJWK,
|
|
88
70
|
notBefore: certificate.notBefore.value,
|
|
89
71
|
notAfter: certificate.notAfter.value,
|
|
90
72
|
// certificate
|
|
@@ -92,6 +74,9 @@ export const getCertificateInfo = async (
|
|
|
92
74
|
}
|
|
93
75
|
|
|
94
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
|
+
|
|
95
80
|
// Trust the supplied root from the chain, when no anchors are being passed in.
|
|
96
81
|
trustRootWhenNoAnchors?: boolean
|
|
97
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
|
|
@@ -114,6 +99,8 @@ export const validateX509CertificateChain = async ({
|
|
|
114
99
|
trustAnchors,
|
|
115
100
|
verificationTime = new Date(),
|
|
116
101
|
opts = {
|
|
102
|
+
// If no trust anchor is found, but the chain itself checks out, allow. (defaults to false:)
|
|
103
|
+
allowNoTrustAnchorsFound: false,
|
|
117
104
|
trustRootWhenNoAnchors: false,
|
|
118
105
|
allowSingleNoCAChainElement: true,
|
|
119
106
|
blindlyTrustedAnchors: [],
|
|
@@ -128,7 +115,7 @@ export const validateX509CertificateChain = async ({
|
|
|
128
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
|
|
129
116
|
return await validateX509CertificateChainImpl({
|
|
130
117
|
reversed: false,
|
|
131
|
-
chain: pemOrDerChain.reverse(),
|
|
118
|
+
chain: [...pemOrDerChain].reverse(),
|
|
132
119
|
trustAnchors,
|
|
133
120
|
verificationTime,
|
|
134
121
|
opts,
|
|
@@ -149,6 +136,7 @@ const validateX509CertificateChainImpl = async ({
|
|
|
149
136
|
}): Promise<X509ValidationResult> => {
|
|
150
137
|
const verificationTime: Date = typeof verifyAt === 'string' ? new Date(verifyAt) : verifyAt
|
|
151
138
|
const {
|
|
139
|
+
allowNoTrustAnchorsFound = false,
|
|
152
140
|
trustRootWhenNoAnchors = false,
|
|
153
141
|
allowSingleNoCAChainElement = true,
|
|
154
142
|
blindlyTrustedAnchors = [],
|
|
@@ -167,34 +155,52 @@ const validateX509CertificateChainImpl = async ({
|
|
|
167
155
|
}
|
|
168
156
|
defaultCryptoEngine()
|
|
169
157
|
|
|
170
|
-
// x5c always starts with the leaf cert at index 0 and then the cas. Our internal pkijs service expects it the other way around
|
|
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
|
|
171
159
|
const chain = await Promise.all(pemOrDerChain.map((raw) => parseCertificate(raw)))
|
|
160
|
+
const x5cOrdereredChain = reversed ? [...chain] : [...chain].reverse()
|
|
161
|
+
|
|
172
162
|
const trustedCerts = trustedPEMs ? await Promise.all(trustedPEMs.map((raw) => parseCertificate(raw))) : undefined
|
|
173
|
-
const blindlyTrusted =
|
|
174
|
-
|
|
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]
|
|
175
178
|
|
|
176
179
|
const chainLength = chain.length
|
|
177
180
|
var foundTrustAnchor: ParsedCertificate | undefined = undefined
|
|
178
181
|
for (let i = 0; i < chainLength; i++) {
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
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) {
|
|
182
186
|
console.log(`Certificate chain validation success as single cert if blindly trusted. WARNING: ONLY USE FOR TESTING PURPOSES.`)
|
|
183
187
|
return {
|
|
184
188
|
error: false,
|
|
185
189
|
critical: false,
|
|
186
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,
|
|
187
193
|
verificationTime,
|
|
188
|
-
certificateChain:
|
|
194
|
+
certificateChain: x5cOrdereredChain.map((cert) => cert.certificateInfo),
|
|
189
195
|
...(client && { client }),
|
|
190
196
|
}
|
|
191
197
|
}
|
|
192
|
-
if (
|
|
193
|
-
if (
|
|
198
|
+
if (previousCert) {
|
|
199
|
+
if (currentCert.x509Certificate.issuer !== previousCert.x509Certificate.subject) {
|
|
194
200
|
if (!reversed && !disallowReversedChain) {
|
|
195
201
|
return await validateX509CertificateChainImpl({
|
|
196
202
|
reversed: true,
|
|
197
|
-
chain: pemOrDerChain.reverse(),
|
|
203
|
+
chain: [...pemOrDerChain].reverse(),
|
|
198
204
|
opts,
|
|
199
205
|
verificationTime,
|
|
200
206
|
trustAnchors,
|
|
@@ -203,45 +209,54 @@ const validateX509CertificateChainImpl = async ({
|
|
|
203
209
|
return {
|
|
204
210
|
error: true,
|
|
205
211
|
critical: true,
|
|
212
|
+
certificateChain: x5cOrdereredChain.map((cert) => cert.certificateInfo),
|
|
206
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}.`,
|
|
207
215
|
verificationTime,
|
|
208
216
|
...(client && { client }),
|
|
209
217
|
}
|
|
210
218
|
}
|
|
211
219
|
}
|
|
212
|
-
const result = await
|
|
220
|
+
const result = await currentCert.x509Certificate.verify(
|
|
213
221
|
{
|
|
214
222
|
date: verificationTime,
|
|
215
|
-
publicKey:
|
|
223
|
+
publicKey: previousCert?.x509Certificate?.publicKey,
|
|
216
224
|
},
|
|
217
225
|
getCrypto()?.crypto ?? crypto ?? global.crypto
|
|
218
226
|
)
|
|
219
227
|
if (!result) {
|
|
228
|
+
// First cert needs to be self signed
|
|
220
229
|
if (i == 0 && !reversed && !disallowReversedChain) {
|
|
221
230
|
return await validateX509CertificateChainImpl({
|
|
222
231
|
reversed: true,
|
|
223
|
-
chain: pemOrDerChain.reverse(),
|
|
232
|
+
chain: [...pemOrDerChain].reverse(),
|
|
224
233
|
opts,
|
|
225
234
|
verificationTime,
|
|
226
235
|
trustAnchors,
|
|
227
236
|
})
|
|
228
237
|
}
|
|
238
|
+
|
|
229
239
|
return {
|
|
230
240
|
error: true,
|
|
231
241
|
critical: true,
|
|
232
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)}.`,
|
|
233
247
|
verificationTime,
|
|
234
248
|
...(client && { client }),
|
|
235
249
|
}
|
|
236
250
|
}
|
|
237
251
|
|
|
238
|
-
foundTrustAnchor = foundTrustAnchor ?? trustedCerts?.find((trusted) => isSameCertificate(trusted.x509Certificate,
|
|
252
|
+
foundTrustAnchor = foundTrustAnchor ?? trustedCerts?.find((trusted) => isSameCertificate(trusted.x509Certificate, currentCert.x509Certificate))
|
|
239
253
|
|
|
240
254
|
if (i === 0 && chainLength === 1 && allowSingleNoCAChainElement) {
|
|
241
255
|
return {
|
|
242
256
|
error: false,
|
|
243
257
|
critical: false,
|
|
244
258
|
message: `Certificate chain succeeded as allow single cert result is allowed: ${leafCert.certificateInfo.subject.dn.DN}.`,
|
|
259
|
+
certificateChain: x5cOrdereredChain.map((cert) => cert.certificateInfo),
|
|
245
260
|
trustAnchor: foundTrustAnchor?.certificateInfo,
|
|
246
261
|
verificationTime,
|
|
247
262
|
...(client && { client }),
|
|
@@ -249,11 +264,15 @@ const validateX509CertificateChainImpl = async ({
|
|
|
249
264
|
}
|
|
250
265
|
}
|
|
251
266
|
|
|
252
|
-
if (foundTrustAnchor) {
|
|
267
|
+
if (foundTrustAnchor?.certificateInfo || allowNoTrustAnchorsFound) {
|
|
253
268
|
return {
|
|
254
269
|
error: false,
|
|
255
270
|
critical: false,
|
|
256
271
|
message: `Certificate chain was valid`,
|
|
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}).)`,
|
|
257
276
|
trustAnchor: foundTrustAnchor?.certificateInfo,
|
|
258
277
|
verificationTime,
|
|
259
278
|
...(client && { client }),
|
|
@@ -264,6 +283,10 @@ const validateX509CertificateChainImpl = async ({
|
|
|
264
283
|
error: true,
|
|
265
284
|
critical: true,
|
|
266
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}.`,
|
|
267
290
|
verificationTime,
|
|
268
291
|
...(client && { client }),
|
|
269
292
|
}
|
|
@@ -280,7 +303,7 @@ export const getX509AlgorithmProvider = (): AlgorithmProvider => {
|
|
|
280
303
|
|
|
281
304
|
export type ParsedCertificate = {
|
|
282
305
|
publicKeyInfo: SubjectPublicKeyInfo
|
|
283
|
-
publicKeyJwk
|
|
306
|
+
publicKeyJwk?: JWK
|
|
284
307
|
publicKeyRaw: Uint8Array
|
|
285
308
|
publicKeyAlgorithm: Algorithm
|
|
286
309
|
certificateInfo: CertificateInfo
|
|
@@ -292,7 +315,12 @@ export const parseCertificate = async (rawCert: string | Uint8Array): Promise<Pa
|
|
|
292
315
|
const x509Certificate = new X509Certificate(rawCert)
|
|
293
316
|
const publicKeyInfo = AsnParser.parse(x509Certificate.publicKey.rawData, SubjectPublicKeyInfo)
|
|
294
317
|
const publicKeyRaw = new Uint8Array(publicKeyInfo.subjectPublicKey)
|
|
295
|
-
|
|
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
|
+
}
|
|
296
324
|
const certificate = pemOrDerToX509Certificate(rawCert)
|
|
297
325
|
const certificateInfo = await getCertificateInfo(certificate)
|
|
298
326
|
const publicKeyAlgorithm = getX509AlgorithmProvider().toWebAlgorithm(publicKeyInfo.algorithm)
|
|
@@ -306,129 +334,142 @@ export const parseCertificate = async (rawCert: string | Uint8Array): Promise<Pa
|
|
|
306
334
|
x509Certificate,
|
|
307
335
|
}
|
|
308
336
|
}
|
|
337
|
+
/*
|
|
309
338
|
|
|
310
|
-
|
|
339
|
+
/!**
|
|
311
340
|
*
|
|
312
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
|
|
313
342
|
* @param trustedPEMs
|
|
314
343
|
* @param verificationTime
|
|
315
344
|
* @param opts
|
|
316
|
-
|
|
345
|
+
*!/
|
|
317
346
|
export const validateX509CertificateChainOrg = async ({
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}: {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
331
360
|
}): Promise<X509ValidationResult> => {
|
|
332
|
-
|
|
333
|
-
|
|
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
|
+
}
|
|
334
377
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
+
}
|
|
341
417
|
}
|
|
342
|
-
}
|
|
343
418
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
419
|
+
const validationEngine = new CertificateChainValidationEngine({
|
|
420
|
+
certs /!*crls: [crl1], ocsps: [ocsp1], *!/,
|
|
421
|
+
checkDate: verificationTime,
|
|
422
|
+
trustedCerts,
|
|
423
|
+
})
|
|
348
424
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
critical: true,
|
|
376
|
-
message: `Certificate chain validation for ${subjectDN}: ${passed ? 'successful' : 'failed'}.`,
|
|
377
|
-
verificationTime,
|
|
378
|
-
certificateChain: [await getCertificateInfo(cert)],
|
|
379
|
-
...(client && { client }),
|
|
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
|
+
}
|
|
380
451
|
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
452
|
|
|
385
|
-
const validationEngine = new CertificateChainValidationEngine({
|
|
386
|
-
certs /*crls: [crl1], ocsps: [ocsp1], */,
|
|
387
|
-
checkDate: verificationTime,
|
|
388
|
-
trustedCerts,
|
|
389
|
-
})
|
|
390
453
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
const certInfos: Array<CertificateInfo> = await Promise.all(
|
|
410
|
-
certPath.map(async (certificate) => {
|
|
411
|
-
return getCertificateInfo(certificate)
|
|
412
|
-
})
|
|
413
|
-
)
|
|
414
|
-
return {
|
|
415
|
-
error: false,
|
|
416
|
-
critical: false,
|
|
417
|
-
message: `Certificate chain was valid`,
|
|
418
|
-
verificationTime,
|
|
419
|
-
certificateChain: certInfos,
|
|
420
|
-
...(client && { client }),
|
|
421
|
-
}
|
|
422
|
-
} catch (error: any) {
|
|
423
|
-
return {
|
|
424
|
-
error: true,
|
|
425
|
-
critical: true,
|
|
426
|
-
message: `Certificate chain was invalid, ${error.message ?? '<unknown error>'}`,
|
|
427
|
-
verificationTime,
|
|
428
|
-
...(client && { client }),
|
|
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
|
+
}
|
|
429
470
|
}
|
|
430
|
-
}
|
|
431
471
|
}
|
|
472
|
+
*/
|
|
432
473
|
|
|
433
474
|
const rdnmap: Record<string, string> = {
|
|
434
475
|
'2.5.4.6': 'C',
|
|
@@ -472,23 +513,34 @@ const getDNString = (typesAndValues: AttributeTypeAndValue[]): string => {
|
|
|
472
513
|
.join(',')
|
|
473
514
|
}
|
|
474
515
|
|
|
475
|
-
export const getCertificateSubjectPublicKeyJWK = async (pemOrDerCert: string | Uint8Array | Certificate): Promise<
|
|
516
|
+
export const getCertificateSubjectPublicKeyJWK = async (pemOrDerCert: string | Uint8Array | Certificate): Promise<JWK> => {
|
|
476
517
|
const pemOrDerStr =
|
|
477
518
|
typeof pemOrDerCert === 'string'
|
|
478
|
-
? pemOrDerCert
|
|
519
|
+
? u8a.toString(u8a.fromString(pemOrDerCert, 'base64pad'), 'base64pad')
|
|
479
520
|
: pemOrDerCert instanceof Uint8Array
|
|
480
521
|
? u8a.toString(pemOrDerCert, 'base64pad')
|
|
481
|
-
: pemOrDerCert.toString('base64')
|
|
522
|
+
: u8a.toString(u8a.fromString(pemOrDerCert.toString('base64'), 'base64pad'), 'base64pad')
|
|
482
523
|
const pem = derToPEM(pemOrDerStr)
|
|
483
524
|
const certificate = pemOrDerToX509Certificate(pem)
|
|
525
|
+
var jwk: JWK | undefined
|
|
484
526
|
try {
|
|
485
527
|
const subtle = getCrypto(true).subtle
|
|
486
|
-
const pk = await certificate.getPublicKey()
|
|
487
|
-
|
|
528
|
+
const pk = await certificate.getPublicKey(undefined, defaultCryptoEngine())
|
|
529
|
+
jwk = (await subtle.exportKey('jwk', pk)) as JWK | undefined
|
|
488
530
|
} catch (error: any) {
|
|
489
531
|
console.log(`Error in primary get JWK from cert:`, error?.message)
|
|
490
532
|
}
|
|
491
|
-
|
|
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
|
|
492
544
|
}
|
|
493
545
|
|
|
494
546
|
/**
|