From ed7e067ce346e28976065d53861a6b451a1746d8 Mon Sep 17 00:00:00 2001 From: goncalo-frade-iohk Date: Tue, 18 Jun 2024 13:50:45 +0100 Subject: [PATCH] feat(pollux): add jwt credential revocation support Fixes ATL-7034 Signed-off-by: goncalo-frade-iohk --- Core/Sources/Helpers/JSONDecoder+Helper.swift | 13 ++++ .../Credentials/RevocableCredential.swift | 31 ++++++++ .../Domain/Sources/Models/Errors.swift | 18 +++++ .../EdgeAgent/Sources/EdgeAgent+Backup.swift | 2 +- .../JWTCredential+RevocableCredential.swift | 26 +++++++ .../Models/JWT/JWTPayload+Codable.swift | 2 +- .../Sources/Models/JWT/JWTPayload.swift | 4 +- .../Models/JWT/JWTRevocationCheck.swift | 76 +++++++++++++++++++ .../Models/JWT/JWTRevocationStatus.swift | 32 ++++++++ .../PolluxImpl+CredentialVerification.swift | 15 ++++ EdgeAgentSDK/Pollux/Tests/JWTTests.swift | 8 ++ Package.swift | 6 +- .../WalletDemo2/Backup/BackupView.swift | 8 +- .../WalletDemo2/Backup/BackupViewModel.swift | 10 ++- 14 files changed, 241 insertions(+), 10 deletions(-) create mode 100644 EdgeAgentSDK/Domain/Sources/Models/Credentials/RevocableCredential.swift create mode 100644 EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+RevocableCredential.swift create mode 100644 EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTRevocationCheck.swift create mode 100644 EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTRevocationStatus.swift diff --git a/Core/Sources/Helpers/JSONDecoder+Helper.swift b/Core/Sources/Helpers/JSONDecoder+Helper.swift index 2022437d..63179424 100644 --- a/Core/Sources/Helpers/JSONDecoder+Helper.swift +++ b/Core/Sources/Helpers/JSONDecoder+Helper.swift @@ -7,4 +7,17 @@ public extension JSONDecoder { decoder.keyDecodingStrategy = .convertFromSnakeCase return decoder } + + static func backup() -> JSONDecoder { + let decoder = JSONDecoder() + decoder.dataDecodingStrategy = .base64 + decoder.keyDecodingStrategy = .convertFromSnakeCase + decoder.dateDecodingStrategy = .custom({ decoder in + let container = try decoder.singleValueContainer() + let seconds = try container.decode(Int.self) + let date = Date(timeIntervalSince1970: TimeInterval(seconds)) + return date + }) + return decoder + } } diff --git a/EdgeAgentSDK/Domain/Sources/Models/Credentials/RevocableCredential.swift b/EdgeAgentSDK/Domain/Sources/Models/Credentials/RevocableCredential.swift new file mode 100644 index 00000000..2f7bba09 --- /dev/null +++ b/EdgeAgentSDK/Domain/Sources/Models/Credentials/RevocableCredential.swift @@ -0,0 +1,31 @@ +import Foundation + +/// `RevocableCredential` is a protocol that defines the attributes and behaviors +/// of a credential that can be revoked or suspended. +public protocol RevocableCredential { + /// Indicates whether the credential can be revoked. + var canBeRevoked: Bool { get } + + /// Indicates whether the credential can be suspended. + var canBeSuspended: Bool { get } + + /// Checks if the credential is currently revoked. + /// + /// - Returns: A Boolean value indicating whether the credential is revoked. + /// - Throws: An error if the status cannot be determined. + var isRevoked: Bool { get async throws } + + /// Checks if the credential is currently suspended. + /// + /// - Returns: A Boolean value indicating whether the credential is suspended. + /// - Throws: An error if the status cannot be determined. + var isSuspended: Bool { get async throws } +} + +public extension Credential { + /// A Boolean value indicating whether the credential can verify revocability. + var isRevocable: Bool { self is RevocableCredential } + + /// Returns the revocable representation of the credential. + var revocable: RevocableCredential? { self as? RevocableCredential } +} diff --git a/EdgeAgentSDK/Domain/Sources/Models/Errors.swift b/EdgeAgentSDK/Domain/Sources/Models/Errors.swift index 49e1325b..c14e4b69 100644 --- a/EdgeAgentSDK/Domain/Sources/Models/Errors.swift +++ b/EdgeAgentSDK/Domain/Sources/Models/Errors.swift @@ -805,6 +805,14 @@ public enum PolluxError: KnownPrismError { /// An error case indicating that the signature is invalid, with internal errors specified. case invalidSignature(internalErrors: [Error] = []) + /// An error case indicating that the credential is revoked. + /// - Parameter jwtString: The JWT string representing the revoked credential. + case credentialIsRevoked(jwtString: String) + + /// An error case indicating that the credential is suspended. + /// - Parameter jwtString: The JWT string representing the suspended credential. + case credentialIsSuspended(jwtString: String) + /// The error code returned by the server. public var code: Int { switch self { @@ -862,6 +870,10 @@ public enum PolluxError: KnownPrismError { return 76 case .invalidSignature: return 77 + case .credentialIsRevoked: + return 78 + case .credentialIsSuspended: + return 79 } } @@ -942,6 +954,12 @@ Cannot verify input descriptor field \(name.map { "with name: \($0)"} ?? ""), wi """ case .invalidSignature: return "Could not verify one or more JWT signatures" + + case .credentialIsRevoked(let jwtString): + return "Credential (\(jwtString)) is revoked" + + case .credentialIsSuspended(let jwtString): + return "Credential (\(jwtString)) is suspended" } } } diff --git a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Backup.swift b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Backup.swift index e7b227b3..e7b5a4c1 100644 --- a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Backup.swift +++ b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Backup.swift @@ -209,7 +209,7 @@ extension EdgeAgent { let messages = messages.compactMap { messageStr -> (Message, Message.Direction)? in guard let messageData = Data(base64URLEncoded: messageStr), - let message = try? JSONDecoder.didComm().decode(Message.self, from: messageData) + let message = try? JSONDecoder.backup().decode(Message.self, from: messageData) else { return nil } diff --git a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+RevocableCredential.swift b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+RevocableCredential.swift new file mode 100644 index 00000000..5a9118ed --- /dev/null +++ b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+RevocableCredential.swift @@ -0,0 +1,26 @@ +import Domain +import Foundation + +extension JWTCredential: RevocableCredential { + public var canBeRevoked: Bool { + self.jwtVerifiableCredential.verifiableCredential.credentialStatus?.statusPurpose == .revocation + } + + public var canBeSuspended: Bool { + self.jwtVerifiableCredential.verifiableCredential.credentialStatus?.statusPurpose == .suspension + } + + public var isRevoked: Bool { + get async throws { + guard canBeRevoked else { return false } + return try await JWTRevocationCheck(credential: self).checkIsRevoked() + } + } + + public var isSuspended: Bool { + get async throws { + guard canBeSuspended else { return false } + return try await JWTRevocationCheck(credential: self).checkIsRevoked() + } + } +} diff --git a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPayload+Codable.swift b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPayload+Codable.swift index 9774890c..a8ac0990 100644 --- a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPayload+Codable.swift +++ b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPayload+Codable.swift @@ -41,7 +41,7 @@ extension JWTPayload.JWTVerfiableCredential: Codable { } let credentialSubject = try container.decode(AnyCodable.self, forKey: .credentialSubject) let credentialStatus = try? container.decode( - VerifiableCredentialTypeContainer.self, + JWTRevocationStatus.self, forKey: .credentialStatus ) let credentialSchema = try? container.decode( diff --git a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPayload.swift b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPayload.swift index c6cd0679..ff0e29b0 100644 --- a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPayload.swift +++ b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPayload.swift @@ -19,10 +19,10 @@ struct JWTPayload { let type: Set let credentialSchema: VerifiableCredentialTypeContainer? let credentialSubject: AnyCodable - let credentialStatus: VerifiableCredentialTypeContainer? let refreshService: VerifiableCredentialTypeContainer? let evidence: VerifiableCredentialTypeContainer? let termsOfUse: VerifiableCredentialTypeContainer? + let credentialStatus: JWTRevocationStatus? /** Initializes a new instance of `JWTVerifiableCredential`. @@ -42,7 +42,7 @@ struct JWTPayload { type: Set = Set(), credentialSchema: VerifiableCredentialTypeContainer? = nil, credentialSubject: AnyCodable, - credentialStatus: VerifiableCredentialTypeContainer? = nil, + credentialStatus: JWTRevocationStatus? = nil, refreshService: VerifiableCredentialTypeContainer? = nil, evidence: VerifiableCredentialTypeContainer? = nil, termsOfUse: VerifiableCredentialTypeContainer? = nil diff --git a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTRevocationCheck.swift b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTRevocationCheck.swift new file mode 100644 index 00000000..b89c1c6c --- /dev/null +++ b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTRevocationCheck.swift @@ -0,0 +1,76 @@ +import Domain +import Foundation +import Gzip +import JSONWebSignature + +struct JWTRevocationCheck { + let credential: JWTCredential + + init(credential: JWTCredential) { + self.credential = credential + } + + func checkIsRevoked() async throws -> Bool { + guard let status = credential.jwtVerifiableCredential.verifiableCredential.credentialStatus else { + return false + } + + guard status.type == "StatusList2021Entry" else { + throw UnknownError.somethingWentWrongError(customMessage: nil, underlyingErrors: nil) + } + + let listData = try await DownloadDataWithResolver() + .downloadFromEndpoint(urlOrDID: status.statusListCredential) + let statusList = try JSONDecoder.didComm().decode(JWTRevocationStatusListCredential.self, from: listData) + let encodedList = statusList.credentialSubject.encodedList + let index = status.statusListIndex + return try verifyRevocationOnEncodedList(encodedList.tryToData(), index: index) + } + + func verifyRevocationOnEncodedList(_ list: Data, index: Int) throws -> Bool { + let encodedListData = try list.gunzipped() + let bitList = encodedListData.bytes.flatMap { $0.toBits() } + guard index < bitList.count else { + throw UnknownError.somethingWentWrongError(customMessage: "Revocation index out of bounds", underlyingErrors: nil) + } + return bitList[index] + } +} + +extension UInt8 { + func toBits() -> [Bool] { + var bits = [Bool](repeating: false, count: 8) + for i in 0..<8 { + bits[7 - i] = (self & (1 << i)) != 0 + } + return bits + } +} + +fileprivate struct DownloadDataWithResolver: Downloader { + + public func downloadFromEndpoint(urlOrDID: String) async throws -> Data { + let url: URL + + if let validUrl = URL(string: urlOrDID.replacingOccurrences(of: "host.docker.internal", with: "localhost")) { + url = validUrl + } else { + throw CommonError.invalidURLError(url: urlOrDID) + } + + let (data, urlResponse) = try await URLSession.shared.data(from: url) + + guard + let code = (urlResponse as? HTTPURLResponse)?.statusCode, + 200...299 ~= code + else { + throw CommonError.httpError( + code: (urlResponse as? HTTPURLResponse)?.statusCode ?? 500, + message: String(data: data, encoding: .utf8) ?? "" + ) + } + + return data + } +} + diff --git a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTRevocationStatus.swift b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTRevocationStatus.swift new file mode 100644 index 00000000..36499d14 --- /dev/null +++ b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTRevocationStatus.swift @@ -0,0 +1,32 @@ +import Foundation + +struct JWTRevocationStatus: Codable { + enum CredentialStatusListType: String, Codable { + case statusList2021Entry = "StatusList2021Entry" + } + + enum CredentialStatusPurpose: String, Codable { + case revocation + case suspension + } + + let id: String + let type: String + let statusPurpose: CredentialStatusPurpose + let statusListIndex: Int + let statusListCredential: String +} + +struct JWTRevocationStatusListCredential: Codable { + struct StatusListCredentialSubject: Codable { + let type: String + let statusPurpose: String + let encodedList: String + } + let context: [String] + let type: [String] + let id: String + let issuer: String + let issuanceDate: String + let credentialSubject: StatusListCredentialSubject +} diff --git a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift index 85dcdce2..933a9377 100644 --- a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift +++ b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift @@ -116,6 +116,7 @@ extension PolluxImpl { } private func verifyJWT(jwtString: String) async throws -> Bool { + try await verifyJWTCredentialRevocation(jwtString: jwtString) let payload: DefaultJWTClaimsImpl = try JWT.getPayload(jwtString: jwtString) guard let issuer = payload.iss else { throw PolluxError.requiresThatIssuerExistsAndIsAPrismDID @@ -135,6 +136,20 @@ extension PolluxImpl { return !validations.isEmpty } + private func verifyJWTCredentialRevocation(jwtString: String) async throws { + guard let credential = try? JWTCredential(data: jwtString.tryToData()) else { + return + } + let isRevoked = try await credential.isRevoked + let isSuspended = try await credential.isSuspended + guard isRevoked else { + throw PolluxError.credentialIsRevoked(jwtString: jwtString) + } + guard isSuspended else { + throw PolluxError.credentialIsSuspended(jwtString: jwtString) + } + } + private func getDefinition(id: String) async throws -> PresentationExchangeRequest { guard let request = try await pluto.getMessage(id: id).first().await(), diff --git a/EdgeAgentSDK/Pollux/Tests/JWTTests.swift b/EdgeAgentSDK/Pollux/Tests/JWTTests.swift index c7acdee2..962a2fcb 100644 --- a/EdgeAgentSDK/Pollux/Tests/JWTTests.swift +++ b/EdgeAgentSDK/Pollux/Tests/JWTTests.swift @@ -20,4 +20,12 @@ final class JWTTests: XCTestCase { XCTAssertEqual(credential.claims.map(\.key).sorted(), ["id", "test"].sorted()) XCTAssertEqual(credential.id, validJWTString) } + + func testRevoked() throws { + let validJWTString = try "eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6cHJpc206MmU0MGZkNjkyYjgzYzE5ZjlhNTUzNjRjMmNhNWJmNjkyOGI4ODU1NGE1YmYxMTc0YTc4ZjY4NDk4ZDgwZGZjNjpDcmNCQ3JRQkVqa0tCV3RsZVMweEVBSktMZ29KYzJWamNESTFObXN4RWlFQ1pDbDV4aUREb3ZsVFlNNVVSeXdHODZPWjc2RWNTY3NjSEplaHRnbWNKTlFTT2dvR1lYVjBhQzB4RUFSS0xnb0pjMlZqY0RJMU5tc3hFaUVDRUMzTUNPak4xb1lNZjU2ZVVBaTA3NkxGX2hRZDRwbFFib3JKcnBkOHdHY1NPd29IYldGemRHVnlNQkFCU2k0S0NYTmxZM0F5TlRack1SSWhBeTVqVkc4UTRWOHRYV0RoUWNvb2xPTmFIdTZHaW5ockJ6SEtfRXYySW9yNSIsInN1YiI6ImRpZDpwcmlzbTo4ODYwN2Y4YjE3ZWJhZmNhODgwNDdmZDQ0YTMyZTE4NGI1MGYwM2QyNWZhZWQ1ZGRiYWQyZGRjNGYyZjg5YWYzOkNzY0JDc1FCRW1RS0QyRjFkR2hsYm5ScFkyRjBhVzl1TUJBRVFrOEtDWE5sWTNBeU5UWnJNUklncnFDMVhaN2ZsOUpLSjBNT3pTa2hSZFhESHpnSVQzTGJ1MlNLdTJvZWxKVWFJT3gxSzFvY2NDRG14SS05Zm9jRm84emhpTm5BYXBPUGFXQXY0UGg0azZjWkVsd0tCMjFoYzNSbGNqQVFBVUpQQ2dselpXTndNalUyYXpFU0lLNmd0VjJlMzVmU1NpZEREczBwSVVYVnd4ODRDRTl5Mjd0a2lydHFIcFNWR2lEc2RTdGFISEFnNXNTUHZYNkhCYVBNNFlqWndHcVRqMmxnTC1ENGVKT25HUSIsIm5iZiI6MTY4ODA1ODcyNywiZXhwIjoxNjg4MDYyMzI3LCJ2YyI6eyJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6XC9cL2s4cy1kZXYuYXRhbGFwcmlzbS5pb1wvcHJpc20tYWdlbnRcL3NjaGVtYS1yZWdpc3RyeVwvc2NoZW1hc1wvMDIwMTY5M2ItNGQ2ZC0zNmVjLWEzN2QtODFkODhlODcyNTM5IiwidHlwZSI6IkNyZWRlbnRpYWxTY2hlbWEyMDIyIn0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7InRlc3QiOiJUZXN0MSIsImlkIjoiZGlkOnByaXNtOjg4NjA3ZjhiMTdlYmFmY2E4ODA0N2ZkNDRhMzJlMTg0YjUwZjAzZDI1ZmFlZDVkZGJhZDJkZGM0ZjJmODlhZjM6Q3NjQkNzUUJFbVFLRDJGMWRHaGxiblJwWTJGMGFXOXVNQkFFUWs4S0NYTmxZM0F5TlRack1SSWdycUMxWFo3Zmw5SktKME1PelNraFJkWERIemdJVDNMYnUyU0t1Mm9lbEpVYUlPeDFLMW9jY0NEbXhJLTlmb2NGbzh6aGlObkFhcE9QYVdBdjRQaDRrNmNaRWx3S0IyMWhjM1JsY2pBUUFVSlBDZ2x6WldOd01qVTJhekVTSUs2Z3RWMmUzNWZTU2lkRERzMHBJVVhWd3g4NENFOXkyN3RraXJ0cUhwU1ZHaURzZFN0YUhIQWc1c1NQdlg2SEJhUE00WWpad0dxVGoybGdMLUQ0ZUpPbkdRIn0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiQGNvbnRleHQiOlsiaHR0cHM6XC9cL3d3dy53My5vcmdcLzIwMThcL2NyZWRlbnRpYWxzXC92MSJdfX0.JZBqArVFvWgj2W0b7vVPSKR3mSH_X-VOC-YQ_jyLZSOEYUkortkRGi41xwA7SPFSqPdSCHl4iagpBir1tYMBOw".tryToData() + let credential = try JWTCredential(data: validJWTString) + let encodedList = Data(fromBase64URL: "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA")! + XCTAssertFalse(try JWTRevocationCheck(credential: credential) + .verifyRevocationOnEncodedList(encodedList, index: 94567)) + } } diff --git a/Package.swift b/Package.swift index 5a34eb41..422407d8 100644 --- a/Package.swift +++ b/Package.swift @@ -57,13 +57,14 @@ let package = Package( ), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.7.0"), .package(url: "https://github.com/beatt83/didcomm-swift.git", from: "0.1.8"), - .package(url: "https://github.com/beatt83/jose-swift.git", from: "3.1.0"), + .package(url: "https://github.com/beatt83/jose-swift.git", from: "3.2.0"), .package(url: "https://github.com/beatt83/peerdid-swift.git", from: "3.0.1"), .package(url: "https://github.com/input-output-hk/anoncreds-rs.git", exact: "0.4.1"), .package(url: "https://github.com/input-output-hk/atala-prism-apollo.git", exact: "1.3.3"), .package(url: "https://github.com/KittyMac/Sextant.git", exact: "0.4.31"), .package(url: "https://github.com/kylef/JSONSchema.swift.git", exact: "0.6.0"), - .package(url: "https://github.com/goncalo-frade-iohk/eudi-lib-sdjwt-swift.git", from: "0.0.2") + .package(url: "https://github.com/goncalo-frade-iohk/eudi-lib-sdjwt-swift.git", from: "0.0.2"), + .package(url: "https://github.com/1024jp/GzipSwift.git", exact: "6.0.0") ], targets: [ .target( @@ -121,6 +122,7 @@ let package = Package( "jose-swift", "Sextant", "eudi-lib-sdjwt-swift", + .product(name: "Gzip", package: "GzipSwift"), .product(name: "AnoncredsSwift", package: "anoncreds-rs"), .product(name: "JSONSchema", package: "JSONSchema.swift") ], diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupView.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupView.swift index bc77c3a4..93f4c9ec 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupView.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupView.swift @@ -9,10 +9,11 @@ protocol BackupViewModel: ObservableObject { struct BackupView: View { @StateObject var viewModel: ViewModel + @Environment(\.dismiss) var dismiss @State private var jwe: String = "" var body: some View { - VStack(spacing: 10) { - VStack(spacing: 8) { + VStack(spacing: 25) { + VStack(spacing: 10) { AtalaButton( configuration: .primary, action: { @@ -38,6 +39,9 @@ struct BackupView: View { action: { Task { try await self.viewModel.backupWith(jwe) + await MainActor.run { + self.dismiss() + } } }, label: { diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewModel.swift index b35c250d..c33eddb2 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewModel.swift @@ -6,7 +6,7 @@ final class BackupViewModelImpl: BackupViewModel { @Published var newJWE: String? = nil private let agent: EdgeAgent - + init(agent: EdgeAgent) { self.agent = agent } @@ -20,6 +20,12 @@ final class BackupViewModelImpl: BackupViewModel { } func backupWith(_ jwe: String) async throws { - try await agent.recoverWallet(encrypted: jwe) + do { + try await agent.recoverWallet(encrypted: jwe) + } catch { + print(error) + print() + throw error + } } }