From 24f3d99a65edfc3abe19a28c3880d4a7c64ae750 Mon Sep 17 00:00:00 2001 From: dtsiflit Date: Tue, 8 Oct 2024 16:23:53 +0300 Subject: [PATCH] [wip] swift 6 migration --- Package.swift | 2 +- Sources/Digest/DigestCreator.swift | 15 +- .../HashingAlgorithm/HashingAlgorithm.swift | 2 +- Sources/Digest/HashingAlgorithm/SHA256.swift | 4 +- Sources/Digest/HashingAlgorithm/SHA384.swift | 4 +- Sources/Digest/HashingAlgorithm/SHA512.swift | 4 +- Sources/Digest/SaltProvider.swift | 4 +- Sources/Factory/SDJWTFactory.swift | 4 +- .../SdJwtVcIssuerMetaDataFetcher.swift | 5 +- Sources/Issuer/JWT.swift | 4 +- Sources/Issuer/SDJWT.swift | 5 +- Sources/Model/SdJwtVcIssuerMetaData.swift | 4 +- Sources/Networking/Networking.swift | 2 +- Sources/Types.swift | 2 +- .../Utilities/Extensions/JSON+Extension.swift | 2 +- Sources/Utilities/TimeRange.swift | 2 +- Sources/Verifier/ClaimsVerifier.swift | 25 ++- Sources/Verifier/DisclosuresVerifier.swift | 20 ++- Sources/Verifier/KeyBindingVerifier.swift | 15 +- Sources/Verifier/SDJWTVCVerifier.swift | 17 +- Sources/Verifier/SDJWTVerifier.swift | 8 +- Sources/Verifier/SignatureVerifier.swift | 14 +- .../X509CertificateChainVerifier.swift | 6 +- Tests/Helpers/Constants.swift | 6 +- Tests/Helpers/Utilities.swift | 62 +++---- Tests/Issuance/IssuerTests.swift | 11 +- Tests/Issuance/KeyBindingTest.swift | 2 +- Tests/Issuance/SignedJwtTest.swift | 1 + Tests/Mocks/NetworkingBundleMock.swift | 4 +- Tests/Verification/SerializerTest.swift | 2 + Tests/Verification/VcVerifierTest.swift | 26 +-- Tests/Verification/VerifierTest.swift | 170 +++++++++--------- 32 files changed, 244 insertions(+), 210 deletions(-) diff --git a/Package.swift b/Package.swift index a24c0d7..0ddb4fc 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.8.1 +// swift-tools-version: 6.0.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/Sources/Digest/DigestCreator.swift b/Sources/Digest/DigestCreator.swift index 882c150..2247e06 100644 --- a/Sources/Digest/DigestCreator.swift +++ b/Sources/Digest/DigestCreator.swift @@ -15,18 +15,21 @@ */ import Foundation -class DigestCreator { +final class DigestCreator: Sendable { // MARK: - Properties - var hashingAlgorithm: HashingAlgorithm - - let saltProvider = DefaultSaltProvider() + let hashingAlgorithm: HashingAlgorithm + let saltProvider: SaltProvider // MARK: - LifeCycle - init(hashingAlgorithm: HashingAlgorithm = SHA256Hashing()) { + init( + hashingAlgorithm: HashingAlgorithm = SHA256Hashing(), + saltProvider: SaltProvider = DefaultSaltProvider() + ) { self.hashingAlgorithm = hashingAlgorithm + self.saltProvider = saltProvider } // MARK: - Methods @@ -47,7 +50,7 @@ class DigestCreator { } -public enum DigestType: RawRepresentable, Hashable { +public enum DigestType: RawRepresentable, Hashable, Sendable { public typealias RawValue = DisclosureDigest diff --git a/Sources/Digest/HashingAlgorithm/HashingAlgorithm.swift b/Sources/Digest/HashingAlgorithm/HashingAlgorithm.swift index 4ad5799..75515d5 100644 --- a/Sources/Digest/HashingAlgorithm/HashingAlgorithm.swift +++ b/Sources/Digest/HashingAlgorithm/HashingAlgorithm.swift @@ -18,7 +18,7 @@ import Foundation public typealias Disclosure = String public typealias DisclosureDigest = String -protocol HashingAlgorithm { +protocol HashingAlgorithm: Sendable { var identifier: String { get } func hash(disclosure: Disclosure) -> Data? diff --git a/Sources/Digest/HashingAlgorithm/SHA256.swift b/Sources/Digest/HashingAlgorithm/SHA256.swift index 6993759..b668334 100644 --- a/Sources/Digest/HashingAlgorithm/SHA256.swift +++ b/Sources/Digest/HashingAlgorithm/SHA256.swift @@ -16,11 +16,11 @@ import Foundation import CryptoKit -class SHA256Hashing: HashingAlgorithm { +final class SHA256Hashing: HashingAlgorithm { // MARK: - Properties - var identifier: String = "sha-256" + let identifier: String = "sha-256" // MARK: - Methods diff --git a/Sources/Digest/HashingAlgorithm/SHA384.swift b/Sources/Digest/HashingAlgorithm/SHA384.swift index b74e872..7dd9118 100644 --- a/Sources/Digest/HashingAlgorithm/SHA384.swift +++ b/Sources/Digest/HashingAlgorithm/SHA384.swift @@ -16,11 +16,11 @@ import Foundation import CryptoKit -class SHA384Hashing: HashingAlgorithm { +final class SHA384Hashing: HashingAlgorithm { // MARK: - Properties - var identifier: String = "sha-384" + let identifier: String = "sha-384" // MARK: - Methods diff --git a/Sources/Digest/HashingAlgorithm/SHA512.swift b/Sources/Digest/HashingAlgorithm/SHA512.swift index 5c83188..8ff633b 100644 --- a/Sources/Digest/HashingAlgorithm/SHA512.swift +++ b/Sources/Digest/HashingAlgorithm/SHA512.swift @@ -16,11 +16,11 @@ import Foundation import CryptoKit -class SHA512Hashing: HashingAlgorithm { +final class SHA512Hashing: HashingAlgorithm { // MARK: - Properties - var identifier: String = "sha-512" + let identifier: String = "sha-512" // MARK: - Methods diff --git a/Sources/Digest/SaltProvider.swift b/Sources/Digest/SaltProvider.swift index 0c45fd2..adfc9df 100644 --- a/Sources/Digest/SaltProvider.swift +++ b/Sources/Digest/SaltProvider.swift @@ -17,12 +17,12 @@ import Foundation typealias Salt = String -protocol SaltProvider { +protocol SaltProvider: Sendable { var salt: Data { get } var saltString: Salt { get } } -class DefaultSaltProvider: SaltProvider { +final class DefaultSaltProvider: SaltProvider { // MARK: - Properties diff --git a/Sources/Factory/SDJWTFactory.swift b/Sources/Factory/SDJWTFactory.swift index b2d5a14..102fcf1 100644 --- a/Sources/Factory/SDJWTFactory.swift +++ b/Sources/Factory/SDJWTFactory.swift @@ -85,7 +85,9 @@ class SDJWTFactory { private func encodeObject(sdJwtObject: [String: SdElement]?) throws -> ClaimSet { // Check if the input object is of correct format guard let sdJwtObject else { - throw SDJWTError.nonObjectFormat(ofElement: sdJwtObject) + throw SDJWTError.nonObjectFormat( + ofElement: (try? sdJwtObject?.toJSONString()) ?? "" + ) } // Initialize arrays to store disclosures and JSON output diff --git a/Sources/Fetchers/SdJwtVcIssuerMetaDataFetcher.swift b/Sources/Fetchers/SdJwtVcIssuerMetaDataFetcher.swift index e625996..6d2ecc9 100644 --- a/Sources/Fetchers/SdJwtVcIssuerMetaDataFetcher.swift +++ b/Sources/Fetchers/SdJwtVcIssuerMetaDataFetcher.swift @@ -15,11 +15,11 @@ */ import Foundation import SwiftyJSON -import JSONWebKey +@preconcurrency import JSONWebKey public protocol SdJwtVcIssuerMetaDataFetching { var session: Networking { get } - func fetchIssuerMetaData(issuer: URL) async throws -> SdJwtVcIssuerMetaData? + @MainActor func fetchIssuerMetaData(issuer: URL) async throws -> SdJwtVcIssuerMetaData? } public class SdJwtVcIssuerMetaDataFetcher: SdJwtVcIssuerMetaDataFetching { @@ -70,6 +70,7 @@ private extension SdJwtVcIssuerMetaDataFetcher { return components.url! } + @MainActor func fetch(from url: URL, with session: Networking) async throws -> T { let (data, response) = try await session.data(from: url) diff --git a/Sources/Issuer/JWT.swift b/Sources/Issuer/JWT.swift index 10202f7..67df8bf 100644 --- a/Sources/Issuer/JWT.swift +++ b/Sources/Issuer/JWT.swift @@ -15,10 +15,10 @@ */ import Foundation import JSONWebAlgorithms -import JSONWebSignature +@preconcurrency import JSONWebSignature import SwiftyJSON -public struct JWT: JWTRepresentable { +public struct JWT: JWTRepresentable, Sendable { // MARK: - Properties diff --git a/Sources/Issuer/SDJWT.swift b/Sources/Issuer/SDJWT.swift index 5b454c2..6bfe860 100644 --- a/Sources/Issuer/SDJWT.swift +++ b/Sources/Issuer/SDJWT.swift @@ -20,8 +20,9 @@ import JSONWebToken import SwiftyJSON public typealias KBJWT = JWT +extension JWS: @unchecked @retroactive Sendable {} -struct SDJWT { +struct SDJWT: Sendable { // MARK: - Properties @@ -67,7 +68,7 @@ struct SDJWT { } } -public struct SignedSDJWT { +public struct SignedSDJWT: Sendable { // MARK: - Properties diff --git a/Sources/Model/SdJwtVcIssuerMetaData.swift b/Sources/Model/SdJwtVcIssuerMetaData.swift index 64646b1..0e81ff5 100644 --- a/Sources/Model/SdJwtVcIssuerMetaData.swift +++ b/Sources/Model/SdJwtVcIssuerMetaData.swift @@ -14,10 +14,10 @@ * limitations under the License. */ import Foundation -import JSONWebKey +@preconcurrency import JSONWebKey import SwiftyJSON -struct SdJwtVcIssuerMetadataTO: Decodable { +struct SdJwtVcIssuerMetadataTO: Decodable, Sendable { let issuer: String let jwksUri: String? let jwks: JWKSet? diff --git a/Sources/Networking/Networking.swift b/Sources/Networking/Networking.swift index aff93f6..b58b096 100644 --- a/Sources/Networking/Networking.swift +++ b/Sources/Networking/Networking.swift @@ -17,7 +17,7 @@ import Foundation extension URLSession: Networking {} -public protocol Networking { +public protocol Networking: Sendable { func data( from url: URL ) async throws -> (Data, URLResponse) diff --git a/Sources/Types.swift b/Sources/Types.swift index 7364a87..c1fdfba 100644 --- a/Sources/Types.swift +++ b/Sources/Types.swift @@ -25,7 +25,7 @@ public enum SDJWTError: Error { case encodingError case discloseError case serializationError - case nonObjectFormat(ofElement: Any) + case nonObjectFormat(ofElement: String) case keyCreation case algorithmMissMatch case noneAsAlgorithm diff --git a/Sources/Utilities/Extensions/JSON+Extension.swift b/Sources/Utilities/Extensions/JSON+Extension.swift index 92dc35f..d28e670 100644 --- a/Sources/Utilities/Extensions/JSON+Extension.swift +++ b/Sources/Utilities/Extensions/JSON+Extension.swift @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import SwiftyJSON +@preconcurrency import SwiftyJSON extension JSON { subscript(key: Keys) -> JSON { diff --git a/Sources/Utilities/TimeRange.swift b/Sources/Utilities/TimeRange.swift index 99bf047..9025aec 100644 --- a/Sources/Utilities/TimeRange.swift +++ b/Sources/Utilities/TimeRange.swift @@ -15,7 +15,7 @@ */ import Foundation -public struct TimeRange { +public struct TimeRange: Sendable { let startTime: Date let endTime: Date diff --git a/Sources/Verifier/ClaimsVerifier.swift b/Sources/Verifier/ClaimsVerifier.swift index e14ea08..6441155 100644 --- a/Sources/Verifier/ClaimsVerifier.swift +++ b/Sources/Verifier/ClaimsVerifier.swift @@ -16,17 +16,17 @@ import Foundation import SwiftyJSON -public class ClaimsVerifier: VerifierProtocol { +public final class ClaimsVerifier: VerifierProtocol { // MARK: - Properties - var iat: Date? - var iatValidWindow: TimeRange? + let iat: Date? + let iatValidWindow: TimeRange? - var nbf: Date? - var exp: Date? + let nbf: Date? + let exp: Date? - var audClaim: JSON? - var expectedAud: String? + let audClaim: JSON? + let expectedAud: String? let currentDate: Date @@ -43,17 +43,26 @@ public class ClaimsVerifier: VerifierProtocol { if let iat { self.iat = Date(timeIntervalSince1970: TimeInterval(iat)) + } else { + self.iat = nil } + if let nbf { self.nbf = Date(timeIntervalSince1970: TimeInterval(nbf)) + } else { + self.nbf = nil } + if let exp { self.exp = Date(timeIntervalSince1970: TimeInterval(exp)) + } else { + self.exp = nil } self.audClaim = JSON(parseJSON: audClaim ?? "") self.expectedAud = expectedAud self.currentDate = currentDate + self.iatValidWindow = iatValidWindow } // MARK: - Methods @@ -61,7 +70,7 @@ public class ClaimsVerifier: VerifierProtocol { public func verify() throws -> Bool { if let iat, let iatValidWindow, - iatValidWindow.contains(date: iat) { + !iatValidWindow.contains(date: iat) { throw SDJWTVerifierError.invalidJwt } diff --git a/Sources/Verifier/DisclosuresVerifier.swift b/Sources/Verifier/DisclosuresVerifier.swift index e636d10..3b75b1e 100644 --- a/Sources/Verifier/DisclosuresVerifier.swift +++ b/Sources/Verifier/DisclosuresVerifier.swift @@ -22,17 +22,17 @@ public struct DisclosuresVerifierOutput { var recreatedClaims: JSON } -public class DisclosuresVerifier: VerifierProtocol { +public final class DisclosuresVerifier: VerifierProtocol { // MARK: - Properties let disclosuresReceivedInSDJWT: [Disclosure] - var digestsFoundOnPayload: [DigestType] = [] + let digestsFoundOnPayload: [DigestType] let digestCreator: DigestCreator - var digestsOfDisclosuresDict: [DisclosureDigest: Disclosure] + let digestsOfDisclosuresDict: [DisclosureDigest: Disclosure] private let sdJwt: SDJWT - private var recreatedClaims: JSON = .empty + private let recreatedClaims: JSON // MARK: - Lifecycle @@ -44,19 +44,21 @@ public class DisclosuresVerifier: VerifierProtocol { self.disclosuresReceivedInSDJWT = sdJwt.disclosures - digestsOfDisclosuresDict = [:] + var dict: [DisclosureDigest: Disclosure] = [:] for disclosure in disclosuresReceivedInSDJWT { let hashed = digestCreator.hashAndBase64Encode(input: disclosure) if let hashed { - self.digestsOfDisclosuresDict[hashed] = disclosure + dict[hashed] = disclosure } else { throw SDJWTVerifierError.failedToCreateVerifier } } - + digestsOfDisclosuresDict = dict + let claimExtractor = - try ClaimExtractor(digestsOfDisclosuresDict: digestsOfDisclosuresDict) - .findDigests(payload: sdJwt.jwt.payload, disclosures: sdJwt.disclosures) + try ClaimExtractor( + digestsOfDisclosuresDict: digestsOfDisclosuresDict + ).findDigests(payload: sdJwt.jwt.payload, disclosures: sdJwt.disclosures) digestsFoundOnPayload = claimExtractor.digestsFoundOnPayload recreatedClaims = claimExtractor.recreatedClaims diff --git a/Sources/Verifier/KeyBindingVerifier.swift b/Sources/Verifier/KeyBindingVerifier.swift index 5508494..1464e6d 100644 --- a/Sources/Verifier/KeyBindingVerifier.swift +++ b/Sources/Verifier/KeyBindingVerifier.swift @@ -18,12 +18,10 @@ import JSONWebKey import JSONWebSignature import SwiftyJSON -public class KeyBindingVerifier: VerifierProtocol { +public final class KeyBindingVerifier: VerifierProtocol { static let kbJwt = "kb+jwt" - private var signatureVerifier: SignatureVerifier? - public init() { } @@ -49,7 +47,8 @@ public class KeyBindingVerifier: VerifierProtocol { throw SDJWTVerifierError.keyBindingFailed(description: "No Nonce Provided") } - self.signatureVerifier = try SignatureVerifier(signedJWT: challenge, publicKey: extractedKey) + let signatureVerifier = try SignatureVerifier(signedJWT: challenge, publicKey: extractedKey) + _ = try signatureVerifier.verify() try verifyIat(iatOffset: iatOffset, iat: Date(timeIntervalSince1970: TimeInterval(timeInterval))) try verifyAud(aud: aud, expectedAudience: expectedAudience) @@ -69,15 +68,13 @@ public class KeyBindingVerifier: VerifierProtocol { throw SDJWTVerifierError.keyBindingFailed(description: "No Nonce Provided") } - self.signatureVerifier = try SignatureVerifier(signedJWT: challenge, publicKey: extractedKey) + let signatureVerifier = try SignatureVerifier(signedJWT: challenge, publicKey: extractedKey) + _ = try signatureVerifier.verify() } @discardableResult public func verify() throws -> JWS { - guard let verifier = signatureVerifier else { - throw SDJWTVerifierError.keyBindingFailed(description: "Invalid signature verifier") - } - return try verifier.verify() + throw SDJWTVerifierError.keyBindingFailed(description: "Invalid signature verifier") } } diff --git a/Sources/Verifier/SDJWTVCVerifier.swift b/Sources/Verifier/SDJWTVCVerifier.swift index 95cbff0..15e653d 100644 --- a/Sources/Verifier/SDJWTVCVerifier.swift +++ b/Sources/Verifier/SDJWTVCVerifier.swift @@ -15,8 +15,8 @@ */ import Foundation import X509 -import JSONWebKey -import SwiftyJSON +@preconcurrency import JSONWebKey +@preconcurrency import SwiftyJSON import JSONWebSignature import JSONWebToken @@ -24,6 +24,8 @@ private let HTTPS_URI_SCHEME = "https" private let DID_URI_SCHEME = "did" private let SD_JWT_VC_TYPE = "vc+sd-jwt" +extension JSON: @unchecked @retroactive Sendable { } + /** * A protocol to look up public keys from DIDs/DID URLs. */ @@ -36,7 +38,7 @@ public protocol LookupPublicKeysFromDIDDocument { * - didUrl: The DID URL (optional). * - Returns: An array of JWKs (public keys) or `nil` if the lookup fails. */ - func lookup(did: String, didUrl: String?) async throws -> [JWK]? + @MainActor func lookup(did: String, didUrl: String?) async throws -> [JWK]? } /** @@ -110,7 +112,7 @@ public class SDJWTVCVerifier: SdJwtVcVerifierType { fetcher: SdJwtVcIssuerMetaDataFetching = SdJwtVcIssuerMetaDataFetcher( session: URLSession.shared ), - trust: X509CertificateTrust = X509CertificateTrustFactory.none, + trust: X509CertificateTrust,// = X509CertificateTrustFactory.none, lookup: LookupPublicKeysFromDIDDocument? = nil ) { self.parser = parser @@ -125,6 +127,7 @@ public class SDJWTVCVerifier: SdJwtVcVerifierType { * - Parameter unverifiedSdJwt: The unverified SD-JWT in string format. * - Returns: A `Result` containing either the verified `SignedSDJWT` or an error. */ + @MainActor func verifyIssuance( unverifiedSdJwt: String ) async throws -> Result { @@ -157,6 +160,7 @@ public class SDJWTVCVerifier: SdJwtVcVerifierType { * - Parameter unverifiedSdJwt: The unverified SD-JWT in `JSON` format. * - Returns: A `Result` containing either the verified `SignedSDJWT` or an error. */ + @MainActor func verifyIssuance( unverifiedSdJwt: JSON ) async throws -> Result { @@ -170,6 +174,8 @@ public class SDJWTVCVerifier: SdJwtVcVerifierType { } let jws = sdJwt.jwt + let lookup = self.lookup + let jwk = try await issuerJwsKeySelector( jws: jws, trust: trust, @@ -191,6 +197,7 @@ public class SDJWTVCVerifier: SdJwtVcVerifierType { } } + @MainActor func verifyPresentation( unverifiedSdJwt: String, claimsVerifier: ClaimsVerifier, @@ -227,6 +234,7 @@ public class SDJWTVCVerifier: SdJwtVcVerifierType { } } + @MainActor func verifyPresentation( unverifiedSdJwt: JSON, claimsVerifier: ClaimsVerifier, @@ -282,6 +290,7 @@ private extension SDJWTVCVerifier { * - lookup: Optional service for looking up public keys from DID documents. * - Returns: A `Result` containing either the selected `JWK` or an error. */ + @MainActor func issuerJwsKeySelector( jws: JWS, trust: X509CertificateTrust, diff --git a/Sources/Verifier/SDJWTVerifier.swift b/Sources/Verifier/SDJWTVerifier.swift index 6d005f3..8a2de8f 100644 --- a/Sources/Verifier/SDJWTVerifier.swift +++ b/Sources/Verifier/SDJWTVerifier.swift @@ -17,7 +17,7 @@ import Foundation import JSONWebKey import JSONWebSignature -public protocol VerifierProtocol { +public protocol VerifierProtocol: Sendable { associatedtype ReturnType @discardableResult @@ -67,6 +67,7 @@ public class SDJWTVerifier { /// - claimVerifier: An optional closure to verify claims. /// - Returns: A `Result` containing the verified `SignedSDJWT` or an error. /// + @MainActor public func verifyIssuance( issuersSignatureVerifier: (JWS) throws -> SignatureVerifier, claimVerifier: ((_ nbf: Int?, _ exp: Int?) throws -> ClaimsVerifier)? = nil @@ -107,7 +108,10 @@ public class SDJWTVerifier { throw SDJWTVerifierError.keyBindingFailed(description: "No key binding verifier provided") } - try result.verify() + try result.verify( + challenge: kbJwt, + extractedKey: extractedKey + ) if let sdHash = try? kbJwt.payloadJSON()["sd_hash"].stringValue { if sdHash != sdjwt.delineatedCompactSerialisation { diff --git a/Sources/Verifier/SignatureVerifier.swift b/Sources/Verifier/SignatureVerifier.swift index 3909eff..c7c5e8b 100644 --- a/Sources/Verifier/SignatureVerifier.swift +++ b/Sources/Verifier/SignatureVerifier.swift @@ -14,18 +14,18 @@ * limitations under the License. */ import Foundation -import JSONWebKey -import JSONWebSignature +@preconcurrency import JSONWebKey +@preconcurrency import JSONWebSignature // To Constraint What can be passed as a key // JOSE Supports SecKey for RSA and EC and Data for HMAC -public protocol KeyExpressible {} +public protocol KeyExpressible: Sendable {} -extension SecKey: KeyExpressible {} -extension Data: KeyExpressible {} -extension JWK: KeyExpressible {} +extension SecKey: KeyExpressible, @unchecked @retroactive Sendable {} +extension Data: KeyExpressible, @unchecked @retroactive Sendable {} +extension JWK: KeyExpressible, @unchecked @retroactive Sendable {} -public class SignatureVerifier: VerifierProtocol { +public final class SignatureVerifier: VerifierProtocol { // MARK: - Properties let jws: JWS diff --git a/Sources/Verifier/X509CertificateChainVerifier.swift b/Sources/Verifier/X509CertificateChainVerifier.swift index 7722e56..fd971d3 100644 --- a/Sources/Verifier/X509CertificateChainVerifier.swift +++ b/Sources/Verifier/X509CertificateChainVerifier.swift @@ -18,8 +18,8 @@ import X509 import SwiftASN1 import Security -public protocol X509CertificateTrust { - func isTrusted(chain: [Certificate]) async -> Bool +public protocol X509CertificateTrust: Sendable { + @MainActor func isTrusted(chain: [Certificate]) async -> Bool } struct X509CertificateTrustNone: X509CertificateTrust { @@ -28,7 +28,7 @@ struct X509CertificateTrustNone: X509CertificateTrust { } } -public struct X509CertificateTrustFactory { +public struct X509CertificateTrustFactory: Sendable { public static let none: X509CertificateTrust = X509CertificateTrustNone() } diff --git a/Tests/Helpers/Constants.swift b/Tests/Helpers/Constants.swift index fc12edb..b47b1cc 100644 --- a/Tests/Helpers/Constants.swift +++ b/Tests/Helpers/Constants.swift @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Foundation +@preconcurrency import Foundation let key = """ @@ -26,9 +26,9 @@ let key = """ .clean() // Key Pairs Used in the examples -let holdersKeyPair = generateES256KeyPair() +@MainActor let holdersKeyPair = generateES256KeyPair() -let issuersKeyPair = generateES256KeyPair() +@MainActor let issuersKeyPair = generateES256KeyPair() struct SDJWTConstants { diff --git a/Tests/Helpers/Utilities.swift b/Tests/Helpers/Utilities.swift index 32aa23d..e9e038c 100644 --- a/Tests/Helpers/Utilities.swift +++ b/Tests/Helpers/Utilities.swift @@ -47,7 +47,7 @@ extension SdElement { }) output += 1 } - + return output } } @@ -84,72 +84,66 @@ func generateES256KeyPair() -> KeyPair { kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits as String: 256 ] - + var error: Unmanaged? guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { throw error!.takeRetainedValue() as Error } return privateKey } - + func generateECDHPublicKey(from privateKey: SecKey) throws -> SecKey { guard let publicKey = SecKeyCopyPublicKey(privateKey) else { throw SDJWTError.keyCreation } return publicKey } - + let privateKey = try! generateECDHPrivateKey() let publicKey = try! generateECDHPublicKey(from: privateKey) - + return KeyPair(publicKey, privateKey) } -class MockSaltProvider: SaltProvider { - +final class MockSaltProvider: SaltProvider { + // MARK: - Properties - + var saltString: Salt { return salt.base64EncodedString().base64ToUTF8() ?? "" } - - var salt: Data - + + let salt: Data + // MARK: - LifeCycle - + init(saltString: String) { self.salt = Data(saltString.utf8) } - - // MARK: - Methods - - func updateSalt(string: Salt) { - self.salt = Data(saltString.utf8) - } } class TestLogger { - static func log(_ message: String) { - #if DEBUG -// if isRunningTests() { - print(message) -// } - #endif - } - - private static func isRunningTests() -> Bool { - let environment = ProcessInfo.processInfo.environment - if let injectBundle = environment["XCInjectBundleInto"] { - return NSString(string: injectBundle).pathExtension == "xctest" - } - return false + static func log(_ message: String) { +#if DEBUG + // if isRunningTests() { + print(message) + // } +#endif + } + + private static func isRunningTests() -> Bool { + let environment = ProcessInfo.processInfo.environment + if let injectBundle = environment["XCInjectBundleInto"] { + return NSString(string: injectBundle).pathExtension == "xctest" } + return false + } } extension String { func clean() -> String { self - .replacingOccurrences(of: "\n", with: "") - .replacingOccurrences(of: " ", with: "") + .replacingOccurrences(of: "\n", with: "") + .replacingOccurrences(of: " ", with: "") } } diff --git a/Tests/Issuance/IssuerTests.swift b/Tests/Issuance/IssuerTests.swift index cd83691..b99673d 100644 --- a/Tests/Issuance/IssuerTests.swift +++ b/Tests/Issuance/IssuerTests.swift @@ -22,6 +22,7 @@ import SwiftyJSON @testable import eudi_lib_sdjwt_swift +@MainActor final class IssuerTest: XCTestCase { func testIssuer_ForIssuance_WhenProvidedWithAsetOfClaimsAndIssuersPrivateKey() throws -> SignedSDJWT { @@ -50,17 +51,11 @@ final class IssuerTest: XCTestCase { func testEnvelopedFormatSerializeation_WhenProvidedWithABuiltSDJWT() throws { let sdjwt = try self.testIssuer_ForIssuance_WhenProvidedWithAsetOfClaimsAndIssuersPrivateKey() -// let envelopeJWT = try JWT(header: .init(parameters: [Keys.sdAlg.rawValue: SignatureAlgorithm.ES256.rawValue]), -// payload: JSON([ -// "aud": "https://verifier.example.com", -// "iat": 1580000000, -// "nonce": "iRnRdKuu1AtLM4ltc16by2XF0accSeutUescRw6BWC14" -// ])) - let payload = try JSON([ "aud": "https://verifier.example.com", "iat": 1580000000, - "nonce": "iRnRdKuu1AtLM4ltc16by2XF0accSeutUescRw6BWC14"]).rawData() + "nonce": "iRnRdKuu1AtLM4ltc16by2XF0accSeutUescRw6BWC14" + ]).rawData() let envelopedFormat = try EnvelopedSerialiser(SDJWT: sdjwt, jwTpayload: payload) diff --git a/Tests/Issuance/KeyBindingTest.swift b/Tests/Issuance/KeyBindingTest.swift index ba6585c..5207c32 100644 --- a/Tests/Issuance/KeyBindingTest.swift +++ b/Tests/Issuance/KeyBindingTest.swift @@ -21,6 +21,7 @@ import XCTest @testable import eudi_lib_sdjwt_swift +@MainActor final class KeyBindingTest: XCTestCase { let kbJwt = """ @@ -60,7 +61,6 @@ final class KeyBindingTest: XCTestCase { func testcCreateKeyBindingJWT_whenPassedECPublicKey() throws { - let json = JSON(parseJSON: jwk) let ecPk = try JSONDecoder.jwt.decode(JWK.self, from: jwk.tryToData()) let kbJws = try JWS(jwsString: kbJwt) diff --git a/Tests/Issuance/SignedJwtTest.swift b/Tests/Issuance/SignedJwtTest.swift index 8cd0a30..de279f4 100644 --- a/Tests/Issuance/SignedJwtTest.swift +++ b/Tests/Issuance/SignedJwtTest.swift @@ -22,6 +22,7 @@ import XCTest final class SignedJwtTest: XCTestCase { + @MainActor func testGivenASampleUnsignedJWT_WhenSupplyingWithES256PublicKeyPair_ThenCreateTheSDJW_WithNoKeyBidning() throws { let keyPair = generateES256KeyPair() diff --git a/Tests/Mocks/NetworkingBundleMock.swift b/Tests/Mocks/NetworkingBundleMock.swift index 7517dad..7c51397 100644 --- a/Tests/Mocks/NetworkingBundleMock.swift +++ b/Tests/Mocks/NetworkingBundleMock.swift @@ -20,7 +20,7 @@ import XCTest @testable import eudi_lib_sdjwt_swift -class NetworkingBundleMock: Networking { +final class NetworkingBundleMock: Networking { let path: String let `extension`: String @@ -59,7 +59,7 @@ class NetworkingBundleMock: Networking { } } -class NetworkingJSONMock: Networking { +final class NetworkingJSONMock: Networking { let json: JSON let statusCode: Int diff --git a/Tests/Verification/SerializerTest.swift b/Tests/Verification/SerializerTest.swift index e8e75bc..26f454a 100644 --- a/Tests/Verification/SerializerTest.swift +++ b/Tests/Verification/SerializerTest.swift @@ -18,7 +18,9 @@ import XCTest @testable import eudi_lib_sdjwt_swift +@MainActor final class SerialiserTest: XCTestCase { + func testSerializerWhenSerializedFormatIsSelected_ThenExpectSerialisedFormattedSignedSDJWT() throws -> String { let keyBindingTest = KeyBindingTest() let (issuersSDJWT, holdersSDJWT) = try keyBindingTest.testKeyBindingCreation_WhenKeybindingIsPresent_ThenExpectCorrectVerification() diff --git a/Tests/Verification/VcVerifierTest.swift b/Tests/Verification/VcVerifierTest.swift index b838649..ceba893 100644 --- a/Tests/Verification/VcVerifierTest.swift +++ b/Tests/Verification/VcVerifierTest.swift @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Foundation +@preconcurrency import Foundation import JSONWebKey import JSONWebSignature import JSONWebToken @@ -59,7 +59,8 @@ final class VcVerifierTest: XCTestCase { path: "issuer_meta_data", extension: "json" ) - ) + ), + trust: X509CertificateTrustFactory.none ).verifyIssuance( unverifiedSdJwt: sdJwtString ) @@ -75,6 +76,7 @@ final class VcVerifierTest: XCTestCase { // When let result = try await SDJWTVCVerifier( + trust: X509CertificateTrustFactory.none, lookup: LookupPublicKeysFromDIDDocumentMock() ).verifyIssuance( unverifiedSdJwt: sdJwtString @@ -152,7 +154,8 @@ final class VcVerifierTest: XCTestCase { path: "issuer_meta_data", extension: "json" ) - ) + ), + trust: X509CertificateTrustFactory.none ).verifyIssuance( unverifiedSdJwt: json ) @@ -173,7 +176,8 @@ final class VcVerifierTest: XCTestCase { path: "issuer_meta_data", extension: "json" ) - ) + ), + trust: X509CertificateTrustFactory.none ).verifyPresentation( unverifiedSdJwt: sdJwtString, claimsVerifier: ClaimsVerifier(), @@ -204,7 +208,8 @@ final class VcVerifierTest: XCTestCase { path: "issuer_meta_data", extension: "json" ) - ) + ), + trust: X509CertificateTrustFactory.none ).verifyPresentation( unverifiedSdJwt: json, claimsVerifier: ClaimsVerifier(), @@ -217,10 +222,10 @@ final class VcVerifierTest: XCTestCase { func testVerifyPresentation_WithDSLBuiltValidSDJWT_WithIssuerMetaData_Presentation_ShouldSucceed() async throws { - let issuersKey = issuersKeyPair.public + let issuersKey = await issuersKeyPair.public let issuerJwk = try issuersKey.jwk - let holdersKey = holdersKeyPair.public + let holdersKey = await holdersKeyPair.public let holdersJwk = try holdersKey.jwk let jsonObject: JSON = [ @@ -238,7 +243,7 @@ final class VcVerifierTest: XCTestCase { ] ] - let issuerSignedSDJWT = try SDJWTIssuer.issue( + let issuerSignedSDJWT = try await SDJWTIssuer.issue( issuersPrivateKey: issuersKeyPair.private, header: DefaultJWSHeaderImpl( algorithm: .ES256, @@ -278,7 +283,7 @@ final class VcVerifierTest: XCTestCase { ).serialised )! - let holder = try SDJWTIssuer + let holder = try await SDJWTIssuer .presentation( holdersPrivateKey: holdersKeyPair.private, signedSDJWT: issuerSignedSDJWT, @@ -301,7 +306,8 @@ final class VcVerifierTest: XCTestCase { session: NetworkingJSONMock( json: jsonObject ) - ) + ), + trust: X509CertificateTrustFactory.none ).verifyPresentation( unverifiedSdJwt: serialized, claimsVerifier: ClaimsVerifier(), diff --git a/Tests/Verification/VerifierTest.swift b/Tests/Verification/VerifierTest.swift index 3cfb507..2516bf4 100644 --- a/Tests/Verification/VerifierTest.swift +++ b/Tests/Verification/VerifierTest.swift @@ -22,10 +22,11 @@ import XCTest @testable import eudi_lib_sdjwt_swift +@MainActor final class VerifierTest: XCTestCase { - + func testVerifierBehaviour_WhenPassedValidSignatures_ThenExpectToPassAllCriterias() throws { - + let pk = try JSONDecoder.jwt.decode(JWK.self, from: key.tryToData()) // Copied from Spec https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-05.html#name-example-3-complex-structure let complexStructureSDJWTString = @@ -60,7 +61,7 @@ final class VerifierTest: XCTestCase { 9jb2RlIjogIjEyMzQ0IiwgImNvdW50cnkiOiAiREUiLCAic3RyZWV0X2FkZHJlc3MiOi AiV2VpZGVuc3RyYVx1MDBkZmUgMjIifV0~ """.clean() - + let result = try SDJWTVerifier( parser: CompactParser(), serialisedString: complexStructureSDJWTString @@ -70,18 +71,18 @@ final class VerifierTest: XCTestCase { } claimVerifier: { _, _ in ClaimsVerifier() } - + XCTAssertNoThrow(try result.get()) - + let recreatedClaimsResult = try CompactParser() .getSignedSdJwt(serialisedString: complexStructureSDJWTString) .recreateClaims() - + XCTAssertTrue(recreatedClaimsResult.recreatedClaims.exists()) XCTAssertTrue(recreatedClaimsResult.digestsFoundOnPayload.count == 6) - + } - + func testVerifierBehaviour_WhenPassedNoSignature_ThenExpectToPassAllCriterias() throws { let ComplexStructureSDJWTString = """ @@ -127,7 +128,7 @@ final class VerifierTest: XCTestCase { 0dyIKICAgICAgXSwKICAgICAgInR5cGUiOiAiVmFjY2luZVJlY2 lwaWVudCIKICAgIH0sCiAgICAidHlwZSI6ICJWYWNjaW5hdGlvb kV2ZW50IgogIH0sCiAgIl9zZF9hbGciOiAic2hhLTI1NiIKfQ - + .tKnLymr8fQfupOgvMgBK3GCEIDEzhgta4MgnxYm9fWGMkqrz2R5PSkv0I-AXKXtIF6bdZRbjL-t43vC87jVoZQ~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImF0Y0NvZGUiLCAiSjA3QlgwMyJd ~WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgIm1lZGljaW5hbFByb2R1Y3ROYW1lIiwgIkNPVklELTE5IFZhY2NpbmUgTW9kZXJuYSJd ~WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgIm1hcmtldGluZ0F1dGhvcml6YXRpb25Ib2xkZXIiLCAiTW9kZXJuYSBCaW90ZWNoIl0 @@ -140,23 +141,23 @@ final class VerifierTest: XCTestCase { ~WyJ5MXNWVTV3ZGZKYWhWZGd3UGdTN1JRIiwgImJhdGNoTnVtYmVyIiwgIjE2MjYzODI3MzYiXQ~WyJIYlE0WDhzclZXM1FEeG5JSmRxeU9BIiwgImhlYWx0aFByb2Zlc3Npb25hbCIsICI4ODMxMTAwMDAwMTUzNzYiXQ~ """ .clean() - + let result = try SDJWTVerifier( parser: CompactParser(), serialisedString: ComplexStructureSDJWTString ).unsingedVerify { signedSDJWT in try DisclosuresVerifier(signedSDJWT: signedSDJWT) } - + XCTAssertNoThrow(try result.get()) } - + func testVerifier_WhenPassingSameKeys_ThenExpectToFail() throws { - + let jsonElementArray: JSON = [ "...": "tYJ0TDucyZZCRMbROG4qRO5vkPSFRxFhUELc18CSl3k" ] - + let json: JSON = [ "evidence": [ jsonElementArray @@ -167,7 +168,7 @@ final class VerifierTest: XCTestCase { "WpxQ4HSoEtcTmCCKOeDslB_emucYLz2oO8oHNr1bEVQ" ] ] - + let element = """ WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgeyJfc2QiOiBbIjl3cGpWUFd1 RDdQSzBuc1FETDhCMDZsbWRnVjNMVnliaEh5ZFFwVE55TEkiLCAiRzVFbmhP @@ -176,24 +177,24 @@ final class VerifierTest: XCTestCase { eFE0SFNvRXRjVG1DQ0tPZURzbEJfZW11Y1lMejJvTzhvSE5yMWJFVlEiXX1d """ .clean() - + let enclosedInDisclosure = """ WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgInRpbWUiLCAiMjAxMi0wNC0y MlQxMTozMFoiXQ - + """ .clean() let duplicateSD = """ WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgIm1ldGhvZCIsICJwaXBwIl0 """ .clean() - + let disclosureForDigestDict = [ "tYJ0TDucyZZCRMbROG4qRO5vkPSFRxFhUELc18CSl3k": element, "9wpjVPWuD7PK0nsQDL8B06lmdgV3LVybhHydQpTNyLI": enclosedInDisclosure, "WpxQ4HSoEtcTmCCKOeDslB_emucYLz2oO8oHNr1bEVQ": duplicateSD ] - + let claimExtractor = ClaimExtractor(digestsOfDisclosuresDict: disclosureForDigestDict) XCTAssertThrowsError(try claimExtractor.findDigests(payload: json, disclosures: [element, enclosedInDisclosure])) { error in let error = error as? SDJWTVerifierError @@ -205,28 +206,28 @@ final class VerifierTest: XCTestCase { } } } - + func testVerifierWhenClaimsContainIatExpNbfClaims_ThenExpectTobeInCorrectTimeRanges() throws { let iatJwt = try SDJWTIssuer.issue(issuersPrivateKey: issuersKeyPair.private, header: DefaultJWSHeaderImpl(algorithm: .ES256), buildSDJWT: { ConstantClaims.iat(time: Date()) FlatDisclosedClaim("time", "is created at \(Date())") }) - + let expSdJwt = try SDJWTIssuer.issue( issuersPrivateKey: issuersKeyPair.private, header: DefaultJWSHeaderImpl(algorithm: .ES256)) { ConstantClaims.exp(time: Date(timeIntervalSinceNow: 36000)) FlatDisclosedClaim("time", "time runs out") - } - + } + let nbfSdJwt = try SDJWTIssuer.issue( issuersPrivateKey: issuersKeyPair.private, header: DefaultJWSHeaderImpl(algorithm: .ES256)) { ConstantClaims.nbf(time: Date(timeIntervalSinceNow: -36000)) FlatDisclosedClaim("time", "we are ahead of time") - } - + } + let nbfAndExpSdJwt = try SDJWTIssuer.issue( issuersPrivateKey: issuersKeyPair.private, header: DefaultJWSHeaderImpl(algorithm: .ES256) @@ -235,33 +236,38 @@ final class VerifierTest: XCTestCase { ConstantClaims.nbf(time: Date(timeIntervalSinceNow: -36000)) FlatDisclosedClaim("time", "time runs out or maybe not") } - + for sdjwt in [iatJwt, expSdJwt, nbfSdJwt, nbfAndExpSdJwt] { - let result = try SDJWTVerifier(sdJwt: sdjwt).verifyIssuance { jws in - try SignatureVerifier(signedJWT: jws, publicKey: issuersKeyPair.public) + let result = try SDJWTVerifier( + sdJwt: sdjwt + ).verifyIssuance { jws in + try SignatureVerifier( + signedJWT: jws, + publicKey: issuersKeyPair.public + ) } claimVerifier: { nbf, exp in ClaimsVerifier( - iat: Int(Date().timeIntervalSince1970.rounded()), + iat: Int(Date(timeIntervalSinceNow: .zero).timeIntervalSinceNow.rounded()), iatValidWindow: TimeRange( - startTime: Date(), - endTime: Date(timeIntervalSinceNow: 10) + startTime: Date(timeIntervalSince1970: -10), + endTime: Date(timeIntervalSince1970: 10) ), nbf: nbf, exp: exp ) } - + XCTAssertNoThrow(try result.get()) } } - + func testVerifierWhenProvidingAKeyBindingJWT_WHenProvidedWithAudNonceAndIatClaims_ThenExpectToPassClaimVerificationAndKBVerification () throws { let holdersJWK = holdersKeyPair.public let jwk = try holdersJWK.jwk - + let issuerSignedSDJWT = try SDJWTIssuer.issue( - issuersPrivateKey: issuersKeyPair.private, - header: DefaultJWSHeaderImpl(algorithm: .ES256) + issuersPrivateKey: issuersKeyPair.private, + header: DefaultJWSHeaderImpl(algorithm: .ES256) ) { ConstantClaims.iat(time: Date()) ConstantClaims.exp(time: Date() + 3600) @@ -278,7 +284,7 @@ final class VerifierTest: XCTestCase { FlatDisclosedClaim("country", "JP") } FlatDisclosedClaim("birthdate", "1940-01-01") - + ObjectClaim("cnf") { ObjectClaim("jwk") { PlainClaim("kty", "EC") @@ -288,14 +294,14 @@ final class VerifierTest: XCTestCase { } } } - + let sdHash = DigestCreator() .hashAndBase64Encode( input: CompactSerialiser( signedSDJWT: issuerSignedSDJWT ).serialised ) ?? "" - + let holder = try SDJWTIssuer .presentation( holdersPrivateKey: holdersKeyPair.private, @@ -311,7 +317,7 @@ final class VerifierTest: XCTestCase { ]) ) ) - + let verifier = SDJWTVerifier( sdJwt: holder ).verifyPresentation { jws in @@ -319,10 +325,10 @@ final class VerifierTest: XCTestCase { signedJWT: jws, publicKey: issuersKeyPair.public ) - + } claimVerifier: { _, _ in ClaimsVerifier() - + } keyBindingVerifier: { jws, holdersPublicKey in let verifier = KeyBindingVerifier() try verifier.verify( @@ -336,61 +342,63 @@ final class VerifierTest: XCTestCase { ) return verifier } - + XCTAssertEqual(sdHash, holder.delineatedCompactSerialisation) XCTAssertNoThrow(try verifier.get()) } - + func testSerialiseWhenChosingEnvelopeFormat_AppylingEnvelopeBinding_ThenExpectACorrectJWT() throws { let serializerTest = SerialiserTest() - + let compactParser = CompactParser() - + let envelopeSerializer = try EnvelopedSerialiser( - SDJWT: compactParser.getSignedSdJwt( - serialisedString: serializerTest.testSerializerWhenSerializedFormatIsSelected_ThenExpectSerialisedFormattedSignedSDJWT() - ), - jwTpayload: JWTBody(nonce: "", aud: "sub", iat: 1234 - ).toJSONData()) - + SDJWT: compactParser.getSignedSdJwt( + serialisedString: serializerTest.testSerializerWhenSerializedFormatIsSelected_ThenExpectSerialisedFormattedSignedSDJWT() + ), + jwTpayload: JWTBody( + nonce: "", + aud: "sub", + iat: 1234 + ).toJSONData() + ) + _ = try SignatureVerifier( - signedJWT: JWS( - payload: envelopeSerializer.data, - protectedHeader: DefaultJWSHeaderImpl(algorithm: .ES256), - key: holdersKeyPair.private - ), - publicKey: holdersKeyPair.public) - + signedJWT: JWS( + payload: envelopeSerializer.data, + protectedHeader: DefaultJWSHeaderImpl(algorithm: .ES256), + key: holdersKeyPair.private + ), + publicKey: holdersKeyPair.public) + let jwt = try JWS( payload: envelopeSerializer.data, protectedHeader: DefaultJWSHeaderImpl(algorithm: .ES256), key: holdersKeyPair.private ) - + let envelopedJws = try JWS(jwsString: jwt.compactSerialization) - - let verifyEnvelope = - try SDJWTVerifier( + + let verifyEnvelope = try SDJWTVerifier( parser: EnvelopedParser(), serialisedString: envelopeSerializer.serialised - ) - .verifyEnvelope(envelope: envelopedJws) { jws in - - try SignatureVerifier(signedJWT: jws, publicKey: issuersKeyPair.public) - } holdersSignatureVerifier: { - - try SignatureVerifier(signedJWT: envelopedJws, publicKey: holdersKeyPair.public) - } claimVerifier: { audClaim, iat in - ClaimsVerifier( - iat: iat, - iatValidWindow: .init( - startTime: Date(timeIntervalSince1970: 1234-10), - endTime: Date(timeIntervalSince1970: 1234+10) - ), - audClaim: audClaim, - expectedAud: "sub" - ) - } + ).verifyEnvelope(envelope: envelopedJws) { jws in + try SignatureVerifier(signedJWT: jws, publicKey: issuersKeyPair.public) + + } holdersSignatureVerifier: { + try SignatureVerifier(signedJWT: envelopedJws, publicKey: holdersKeyPair.public) + + } claimVerifier: { audClaim, iat in + ClaimsVerifier( + iat: iat, + iatValidWindow: .init( + startTime: Date(timeIntervalSince1970: 1234 - 10), + endTime: Date(timeIntervalSince1970: 1234 + 10) + ), + audClaim: audClaim, + expectedAud: "sub" + ) + } XCTAssertNoThrow(try verifyEnvelope.get()) } }