Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions Sources/JSONWebToken/JWT+Verification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ extension JWT {
.nbf(required: false),
.iat(required: false)
]
) throws -> JWT {
) async throws -> JWT {
let components = jwtString.components(separatedBy: ".")
switch components.count {
case 3:
Expand All @@ -123,7 +123,7 @@ extension JWT {
keys: try nestedKeys.map { try $0.jwk },
header: jws.protectedHeader
) else {
return try verifyNestedWithMultipleKeys(
return try await verifyNestedWithMultipleKeys(
jwtString: jws.payload.tryToString(),
senderKey: senderKey,
recipientKey: recipientKey,
Expand All @@ -132,7 +132,7 @@ extension JWT {
)
}

return try verify(
return try await verify(
jwtString: jws.payload.tryToString(),
senderKey: key,
recipientKey: nil,
Expand All @@ -144,7 +144,7 @@ extension JWT {
guard try jws.verify(key: senderKey) else {
throw JWTError.invalidSignature
}
try validateClaimsCluster(jwtString, validators: validators.map(\.validator))
try await validateClaimsCluster(jwtString, validators: validators.map(\.validator))
return .init(payload: jws.payload, format: .jws(jws))
case 5:
let jwe = try JWE(compactString: jwtString)
Expand All @@ -159,7 +159,7 @@ extension JWT {
keys: try nestedKeys.map { try $0.jwk },
header: jwe.protectedHeader
) else {
return try verifyNestedWithMultipleKeys(
return try await verifyNestedWithMultipleKeys(
jwtString: decryptedPayload.tryToString(),
senderKey: senderKey,
recipientKey: recipientKey,
Expand All @@ -168,15 +168,15 @@ extension JWT {
)
}

return try verify(
return try await verify(
jwtString: decryptedPayload.tryToString(),
senderKey: senderKey,
recipientKey: key,
nestedKeys: nestedKeys,
validators: validators
)
}
try validateClaimsCluster(jwtString, validators: validators.map(\.validator))
try await validateClaimsCluster(jwtString, validators: validators.map(\.validator))

return .init(payload: decryptedPayload, format: .jwe(jwe))
default:
Expand Down Expand Up @@ -211,7 +211,7 @@ extension JWT {
.nbf(required: false),
.iat(required: false)
]
) throws -> JWT {
) async throws -> JWT {
let components = jwtString.components(separatedBy: ".")
switch components.count {
case 3:
Expand All @@ -221,7 +221,7 @@ extension JWT {
keys: try nestedKeys.map { try $0.jwk },
header: jws.protectedHeader
) else {
return try verifyNestedWithMultipleKeys(
return try await verifyNestedWithMultipleKeys(
jwtString: jws.payload.tryToString(),
senderKey: senderKey,
recipientKey: recipientKey,
Expand All @@ -230,7 +230,7 @@ extension JWT {
)
}

return try verify(
return try await verify(
jwtString: jws.payload.tryToString(),
senderKey: key,
recipientKey: nil,
Expand All @@ -242,7 +242,7 @@ extension JWT {
guard try jws.verify(key: signerKey) else {
throw JWTError.invalidSignature
}
try validateClaimsCluster(jwtString, validators: validators.map(\.validator))
try await validateClaimsCluster(jwtString, validators: validators.map(\.validator))
return .init(payload: jws.payload, format: .jws(jws))
case 5:
let jwe = try JWE(compactString: jwtString)
Expand All @@ -257,7 +257,7 @@ extension JWT {
keys: try nestedKeys.map { try $0.jwk },
header: jwe.protectedHeader
) else {
return try verifyNestedWithMultipleKeys(
return try await verifyNestedWithMultipleKeys(
jwtString: decryptedPayload.tryToString(),
senderKey: senderKey,
recipientKey: recipientKey,
Expand All @@ -266,15 +266,15 @@ extension JWT {
)
}

return try verify(
return try await verify(
jwtString: decryptedPayload.tryToString(),
senderKey: senderKey,
recipientKey: key,
nestedKeys: nestedKeys,
validators: validators
)
}
try validateClaimsCluster(jwtString, validators: validators.map(\.validator))
try await validateClaimsCluster(jwtString, validators: validators.map(\.validator))

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

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

private static func verifyNestedWithMultipleKeys(
Expand All @@ -314,20 +314,20 @@ extension JWT {
recipientKey: KeyRepresentable?,
nestedKeys: [KeyRepresentable],
validators: [Validator]
) throws -> JWT {
) async throws -> JWT {
for key in nestedKeys {
do {
switch try jwtFormat(jwtString: jwtString) {
case .jws:
return try verify(
return try await verify(
jwtString: jwtString,
senderKey: key,
recipientKey: recipientKey,
nestedKeys: nestedKeys,
validators: validators
)
case .jwe:
return try verify(
return try await verify(
jwtString: jwtString,
senderKey: senderKey,
recipientKey: key,
Expand All @@ -344,11 +344,11 @@ extension JWT {
}
}

private func validateClaimsCluster(_ jwtString: String, validators: [ClaimValidator]) throws {
private func validateClaimsCluster(_ jwtString: String, validators: [ClaimValidator]) async throws {
var collectedErrors = [Error]()
validators.forEach {
for validator in validators {
do {
try $0.isValid(jwtString)
try await validator.isValid(jwtString)
} catch {
collectedErrors.append(error)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/JSONWebToken/Validators/ClaimValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ public protocol ClaimValidator {
///
/// - Parameter jwtString: The JWT string containing the claim to be validated.
/// - Throws: An error if the claim is missing (when required) or does not meet the validation criteria.
func isValid(_ jwtString: String) throws
func isValid(_ jwtString: String) async throws
}
56 changes: 16 additions & 40 deletions Sources/JSONWebToken/Validators/X5CValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public struct X5CValidator<Policy: VerifierPolicy>: ClaimValidator, Sendable {
/// - Throws: An error if the x5c header is required but missing,
/// if any certificate in the chain is invalid,
/// or if the certificate chain fails verification.
public func isValid(_ jwtString: String) throws {
public func isValid(_ jwtString: String) async throws {
let jwt = try JWT(jwtString: jwtString)
let x5c: [String]

Expand Down Expand Up @@ -137,7 +137,7 @@ public struct X5CValidator<Policy: VerifierPolicy>: ClaimValidator, Sendable {
date = Date()
}

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

let pemKey = try certificates[0].publicKey.serializeAsPEM().pemString
let key = try JWK(pem: pemKey)
_ = try JWT.verify(jwtString: jwt.jwtString, signerKey: key)
_ = try await JWT.verify(jwtString: jwt.jwtString, signerKey: key)
}

/// Verifies the certificate chain by invoking an asynchronous chain verification function in a synchronous manner.
/// Asynchronously verifies the certificate chain using the provided trusted store and verification policy.
///
/// - Parameters:
/// - trustedStore: The certificate store containing trusted root certificates.
/// - certificates: An array of certificates extracted from the x5c header.
/// - policy: A closure that produces a `VerifierPolicy` used to verify the chain.
/// - Returns: A `VerificationResult` if available, or `nil` if verification was not completed.
/// - Throws: Any error encountered during verification.
/// - trustedStore: The trusted certificate store containing the root certificates.
/// - certificates: An array of certificates to be validated.
/// - policy: A closure that produces a `VerifierPolicy` used for chain verification.
/// - Returns: A `VerificationResult` indicating whether the certificate chain could be validated.
/// - Throws: Any error encountered during the asynchronous verification process.
private func verify(
trustedStore: CertificateStore,
certificates: [Certificate],
@PolicyBuilder policy: @escaping @Sendable () throws -> some VerifierPolicy
) throws -> VerificationResult? {
nonisolated(unsafe) var result: VerificationResult?
let semaphore = DispatchSemaphore(value: 0)
Task {
result = try await verifyChain(trustedStore: trustedStore, certificates: certificates, policy: policy)
semaphore.signal()
}
semaphore.wait()
return result
) async throws -> VerificationResult? {
let untrustedChain = CertificateStore(certificates)
var verifier = try Verifier(rootCertificates: trustedStore, policy: policy)
return await verifier.validate(
leafCertificate: certificates[0],
intermediates: untrustedChain
)
}
}

/// Asynchronously verifies the certificate chain using the provided trusted store and verification policy.
///
/// - Parameters:
/// - trustedStore: The trusted certificate store containing the root certificates.
/// - certificates: An array of certificates to be validated.
/// - policy: A closure that produces a `VerifierPolicy` used for chain verification.
/// - Returns: A `VerificationResult` indicating whether the certificate chain could be validated.
/// - Throws: Any error encountered during the asynchronous verification process.
private func verifyChain(
trustedStore: CertificateStore,
certificates: [Certificate],
policy: @escaping @Sendable () throws -> some VerifierPolicy
) async throws -> VerificationResult {
let untrustedChain = CertificateStore(certificates)
var verifier = try Verifier(rootCertificates: trustedStore, policy: policy)
let result = await verifier.validate(
leafCertificate: certificates[0],
intermediates: untrustedChain
)
return result
}
Loading
Loading