Skip to content

Commit 8d087c7

Browse files
committed
feat!: verify jwt is now async
XCode 26 is more strict with concurrency, so the "hack" to not have X5C validtor as async is not possible anymore. This forces us to make JWT token verify async, which is good but its a breaking change.
1 parent 52858a5 commit 8d087c7

6 files changed

Lines changed: 118 additions & 113 deletions

File tree

Sources/JSONWebToken/JWT+Verification.swift

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ extension JWT {
113113
.nbf(required: false),
114114
.iat(required: false)
115115
]
116-
) throws -> JWT {
116+
) async throws -> JWT {
117117
let components = jwtString.components(separatedBy: ".")
118118
switch components.count {
119119
case 3:
@@ -123,7 +123,7 @@ extension JWT {
123123
keys: try nestedKeys.map { try $0.jwk },
124124
header: jws.protectedHeader
125125
) else {
126-
return try verifyNestedWithMultipleKeys(
126+
return try await verifyNestedWithMultipleKeys(
127127
jwtString: jws.payload.tryToString(),
128128
senderKey: senderKey,
129129
recipientKey: recipientKey,
@@ -132,7 +132,7 @@ extension JWT {
132132
)
133133
}
134134

135-
return try verify(
135+
return try await verify(
136136
jwtString: jws.payload.tryToString(),
137137
senderKey: key,
138138
recipientKey: nil,
@@ -144,7 +144,7 @@ extension JWT {
144144
guard try jws.verify(key: senderKey) else {
145145
throw JWTError.invalidSignature
146146
}
147-
try validateClaimsCluster(jwtString, validators: validators.map(\.validator))
147+
try await validateClaimsCluster(jwtString, validators: validators.map(\.validator))
148148
return .init(payload: jws.payload, format: .jws(jws))
149149
case 5:
150150
let jwe = try JWE(compactString: jwtString)
@@ -159,7 +159,7 @@ extension JWT {
159159
keys: try nestedKeys.map { try $0.jwk },
160160
header: jwe.protectedHeader
161161
) else {
162-
return try verifyNestedWithMultipleKeys(
162+
return try await verifyNestedWithMultipleKeys(
163163
jwtString: decryptedPayload.tryToString(),
164164
senderKey: senderKey,
165165
recipientKey: recipientKey,
@@ -168,15 +168,15 @@ extension JWT {
168168
)
169169
}
170170

171-
return try verify(
171+
return try await verify(
172172
jwtString: decryptedPayload.tryToString(),
173173
senderKey: senderKey,
174174
recipientKey: key,
175175
nestedKeys: nestedKeys,
176176
validators: validators
177177
)
178178
}
179-
try validateClaimsCluster(jwtString, validators: validators.map(\.validator))
179+
try await validateClaimsCluster(jwtString, validators: validators.map(\.validator))
180180

181181
return .init(payload: decryptedPayload, format: .jwe(jwe))
182182
default:
@@ -211,7 +211,7 @@ extension JWT {
211211
.nbf(required: false),
212212
.iat(required: false)
213213
]
214-
) throws -> JWT {
214+
) async throws -> JWT {
215215
let components = jwtString.components(separatedBy: ".")
216216
switch components.count {
217217
case 3:
@@ -221,7 +221,7 @@ extension JWT {
221221
keys: try nestedKeys.map { try $0.jwk },
222222
header: jws.protectedHeader
223223
) else {
224-
return try verifyNestedWithMultipleKeys(
224+
return try await verifyNestedWithMultipleKeys(
225225
jwtString: jws.payload.tryToString(),
226226
senderKey: senderKey,
227227
recipientKey: recipientKey,
@@ -230,7 +230,7 @@ extension JWT {
230230
)
231231
}
232232

233-
return try verify(
233+
return try await verify(
234234
jwtString: jws.payload.tryToString(),
235235
senderKey: key,
236236
recipientKey: nil,
@@ -242,7 +242,7 @@ extension JWT {
242242
guard try jws.verify(key: signerKey) else {
243243
throw JWTError.invalidSignature
244244
}
245-
try validateClaimsCluster(jwtString, validators: validators.map(\.validator))
245+
try await validateClaimsCluster(jwtString, validators: validators.map(\.validator))
246246
return .init(payload: jws.payload, format: .jws(jws))
247247
case 5:
248248
let jwe = try JWE(compactString: jwtString)
@@ -257,7 +257,7 @@ extension JWT {
257257
keys: try nestedKeys.map { try $0.jwk },
258258
header: jwe.protectedHeader
259259
) else {
260-
return try verifyNestedWithMultipleKeys(
260+
return try await verifyNestedWithMultipleKeys(
261261
jwtString: decryptedPayload.tryToString(),
262262
senderKey: senderKey,
263263
recipientKey: recipientKey,
@@ -266,15 +266,15 @@ extension JWT {
266266
)
267267
}
268268

269-
return try verify(
269+
return try await verify(
270270
jwtString: decryptedPayload.tryToString(),
271271
senderKey: senderKey,
272272
recipientKey: key,
273273
nestedKeys: nestedKeys,
274274
validators: validators
275275
)
276276
}
277-
try validateClaimsCluster(jwtString, validators: validators.map(\.validator))
277+
try await validateClaimsCluster(jwtString, validators: validators.map(\.validator))
278278

279279
return .init(payload: decryptedPayload, format: .jwe(jwe))
280280
default:
@@ -290,8 +290,8 @@ extension JWT {
290290
///
291291
/// - Parameter validators: An array of `Validator` used to validate specific claims within the JWT.
292292
/// - Throws: A `JWT.JWTError` if any of the validations fail, such as when a required claim is missing or invalid.
293-
public func validateClaims(validators: [Validator]) throws {
294-
try Self.validateClaims(self.jwtString, validators: validators)
293+
public func validateClaims(validators: [Validator]) async throws {
294+
try await Self.validateClaims(self.jwtString, validators: validators)
295295
}
296296

297297
/// Validates the claims of a JWT string using the provided validators.
@@ -304,8 +304,8 @@ extension JWT {
304304
/// - jwtString: The JWT string whose claims are to be validated.
305305
/// - validators: An array of `Validator` used to validate specific claims within the JWT.
306306
/// - Throws: A `JWT.JWTError` if any of the claim validations fail.
307-
public static func validateClaims(_ jwtString: String, validators: [Validator]) throws {
308-
try validateClaimsCluster(jwtString, validators: validators.map(\.validator))
307+
public static func validateClaims(_ jwtString: String, validators: [Validator]) async throws {
308+
try await validateClaimsCluster(jwtString, validators: validators.map(\.validator))
309309
}
310310

311311
private static func verifyNestedWithMultipleKeys(
@@ -314,20 +314,20 @@ extension JWT {
314314
recipientKey: KeyRepresentable?,
315315
nestedKeys: [KeyRepresentable],
316316
validators: [Validator]
317-
) throws -> JWT {
317+
) async throws -> JWT {
318318
for key in nestedKeys {
319319
do {
320320
switch try jwtFormat(jwtString: jwtString) {
321321
case .jws:
322-
return try verify(
322+
return try await verify(
323323
jwtString: jwtString,
324324
senderKey: key,
325325
recipientKey: recipientKey,
326326
nestedKeys: nestedKeys,
327327
validators: validators
328328
)
329329
case .jwe:
330-
return try verify(
330+
return try await verify(
331331
jwtString: jwtString,
332332
senderKey: senderKey,
333333
recipientKey: key,
@@ -344,11 +344,11 @@ extension JWT {
344344
}
345345
}
346346

347-
private func validateClaimsCluster(_ jwtString: String, validators: [ClaimValidator]) throws {
347+
private func validateClaimsCluster(_ jwtString: String, validators: [ClaimValidator]) async throws {
348348
var collectedErrors = [Error]()
349-
validators.forEach {
349+
for validator in validators {
350350
do {
351-
try $0.isValid(jwtString)
351+
try await validator.isValid(jwtString)
352352
} catch {
353353
collectedErrors.append(error)
354354
}

Sources/JSONWebToken/Validators/ClaimValidator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ public protocol ClaimValidator {
2424
///
2525
/// - Parameter jwtString: The JWT string containing the claim to be validated.
2626
/// - Throws: An error if the claim is missing (when required) or does not meet the validation criteria.
27-
func isValid(_ jwtString: String) throws
27+
func isValid(_ jwtString: String) async throws
2828
}

Sources/JSONWebToken/Validators/X5CValidator.swift

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public struct X5CValidator<Policy: VerifierPolicy>: ClaimValidator, Sendable {
101101
/// - Throws: An error if the x5c header is required but missing,
102102
/// if any certificate in the chain is invalid,
103103
/// or if the certificate chain fails verification.
104-
public func isValid(_ jwtString: String) throws {
104+
public func isValid(_ jwtString: String) async throws {
105105
let jwt = try JWT(jwtString: jwtString)
106106
let x5c: [String]
107107

@@ -137,7 +137,7 @@ public struct X5CValidator<Policy: VerifierPolicy>: ClaimValidator, Sendable {
137137
date = Date()
138138
}
139139

140-
let result = try verify(
140+
let result = try await verify(
141141
trustedStore: trustedStore,
142142
certificates: certificates,
143143
policy: {
@@ -152,51 +152,27 @@ public struct X5CValidator<Policy: VerifierPolicy>: ClaimValidator, Sendable {
152152

153153
let pemKey = try certificates[0].publicKey.serializeAsPEM().pemString
154154
let key = try JWK(pem: pemKey)
155-
_ = try JWT.verify(jwtString: jwt.jwtString, signerKey: key)
155+
_ = try await JWT.verify(jwtString: jwt.jwtString, signerKey: key)
156156
}
157157

158-
/// Verifies the certificate chain by invoking an asynchronous chain verification function in a synchronous manner.
158+
/// Asynchronously verifies the certificate chain using the provided trusted store and verification policy.
159159
///
160160
/// - Parameters:
161-
/// - trustedStore: The certificate store containing trusted root certificates.
162-
/// - certificates: An array of certificates extracted from the x5c header.
163-
/// - policy: A closure that produces a `VerifierPolicy` used to verify the chain.
164-
/// - Returns: A `VerificationResult` if available, or `nil` if verification was not completed.
165-
/// - Throws: Any error encountered during verification.
161+
/// - trustedStore: The trusted certificate store containing the root certificates.
162+
/// - certificates: An array of certificates to be validated.
163+
/// - policy: A closure that produces a `VerifierPolicy` used for chain verification.
164+
/// - Returns: A `VerificationResult` indicating whether the certificate chain could be validated.
165+
/// - Throws: Any error encountered during the asynchronous verification process.
166166
private func verify(
167167
trustedStore: CertificateStore,
168168
certificates: [Certificate],
169169
@PolicyBuilder policy: @escaping @Sendable () throws -> some VerifierPolicy
170-
) throws -> VerificationResult? {
171-
nonisolated(unsafe) var result: VerificationResult?
172-
let semaphore = DispatchSemaphore(value: 0)
173-
Task {
174-
result = try await verifyChain(trustedStore: trustedStore, certificates: certificates, policy: policy)
175-
semaphore.signal()
176-
}
177-
semaphore.wait()
178-
return result
170+
) async throws -> VerificationResult? {
171+
let untrustedChain = CertificateStore(certificates)
172+
var verifier = try Verifier(rootCertificates: trustedStore, policy: policy)
173+
return await verifier.validate(
174+
leafCertificate: certificates[0],
175+
intermediates: untrustedChain
176+
)
179177
}
180178
}
181-
182-
/// Asynchronously verifies the certificate chain using the provided trusted store and verification policy.
183-
///
184-
/// - Parameters:
185-
/// - trustedStore: The trusted certificate store containing the root certificates.
186-
/// - certificates: An array of certificates to be validated.
187-
/// - policy: A closure that produces a `VerifierPolicy` used for chain verification.
188-
/// - Returns: A `VerificationResult` indicating whether the certificate chain could be validated.
189-
/// - Throws: Any error encountered during the asynchronous verification process.
190-
private func verifyChain(
191-
trustedStore: CertificateStore,
192-
certificates: [Certificate],
193-
policy: @escaping @Sendable () throws -> some VerifierPolicy
194-
) async throws -> VerificationResult {
195-
let untrustedChain = CertificateStore(certificates)
196-
var verifier = try Verifier(rootCertificates: trustedStore, policy: policy)
197-
let result = await verifier.validate(
198-
leafCertificate: certificates[0],
199-
intermediates: untrustedChain
200-
)
201-
return result
202-
}

0 commit comments

Comments
 (0)