From 78da753e260211759b6101c82327ed5d2c71a86a Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Thu, 21 Sep 2023 19:12:48 +0200 Subject: [PATCH] CryptorModule * Reusing existing CryptoStream + CryptoInputStream by removing Crypto dependency from them * Moved old encryption/decryption to LegacyCryptor * Introduced new AESCBCCryptor * Introduced new CryptorModule instead of Crypto * Fixing tests --- .../ConfigDetailTableViewController.swift | 2 +- Podfile.lock | 2 +- PubNub.xcodeproj/project.pbxproj | 202 ++++++++- Sources/PubNub/APIs/File+PubNub.swift | 8 +- Sources/PubNub/Errors/ErrorDescription.swift | 6 + Sources/PubNub/Errors/PubNubError.swift | 5 + .../PubNub/Extensions/URLRequest+PubNub.swift | 42 +- Sources/PubNub/Helpers/Crypto/Crypto.swift | 400 ------------------ .../PubNub/Helpers/Crypto/CryptoError.swift | 98 +++++ .../PubNub/Helpers/Crypto/CryptorModule.swift | 194 +++++++++ .../Crypto/Cryptors/AESCBCCryptor.swift | 160 +++++++ .../Helpers/Crypto/Cryptors/Cryptor.swift | 51 +++ .../Crypto/Cryptors/LegacyCryptor.swift | 180 ++++++++ .../Helpers/Crypto/Header/CryptorHeader.swift | 159 +++++++ .../CryptorHeaderWithinStreamFinder.swift | 80 ++++ .../Miscellaneous}/CryptoInputStream.swift | 129 ++++-- .../{ => Miscellaneous}/CryptoStream.swift | 11 +- .../Crypto/Miscellaneous/CryptorUtils.swift | 48 +++ .../Crypto/Miscellaneous/CryptorVector.swift | 77 ++++ .../Miscellaneous/Data+CommonCrypto.swift | 79 ++++ .../Streams/MultipartInputStream.swift | 2 +- Sources/PubNub/Networking/HTTPFileTask.swift | 36 +- Sources/PubNub/Networking/HTTPRouter.swift | 4 +- .../Networking/Routers/HistoryRouter.swift | 73 ++-- .../Networking/Routers/PublishRouter.swift | 10 +- .../Networking/Routers/SubscribeRouter.swift | 32 +- Sources/PubNub/PubNub.swift | 32 -- Sources/PubNub/PubNubConfiguration.swift | 10 +- .../SubscribeSessionFactory.swift | 2 +- .../PubNubSubscribeContractTestSteps.swift | 10 +- Tests/PubNubTests/Helpers/CryptoTests.swift | 262 ++++++------ .../Routers/HistoryRouterTests.swift | 6 +- .../PubNubConfigurationTests.swift | 2 +- 33 files changed, 1679 insertions(+), 735 deletions(-) delete mode 100644 Sources/PubNub/Helpers/Crypto/Crypto.swift create mode 100644 Sources/PubNub/Helpers/Crypto/CryptoError.swift create mode 100644 Sources/PubNub/Helpers/Crypto/CryptorModule.swift create mode 100644 Sources/PubNub/Helpers/Crypto/Cryptors/AESCBCCryptor.swift create mode 100644 Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift create mode 100644 Sources/PubNub/Helpers/Crypto/Cryptors/LegacyCryptor.swift create mode 100644 Sources/PubNub/Helpers/Crypto/Header/CryptorHeader.swift create mode 100644 Sources/PubNub/Helpers/Crypto/Header/CryptorHeaderWithinStreamFinder.swift rename Sources/PubNub/Helpers/{Streams => Crypto/Miscellaneous}/CryptoInputStream.swift (78%) rename Sources/PubNub/Helpers/Crypto/{ => Miscellaneous}/CryptoStream.swift (95%) create mode 100644 Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptorUtils.swift create mode 100644 Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptorVector.swift create mode 100644 Sources/PubNub/Helpers/Crypto/Miscellaneous/Data+CommonCrypto.swift diff --git a/Examples/Sources/ConfigDetailTableViewController.swift b/Examples/Sources/ConfigDetailTableViewController.swift index 7c93fa1b..8bff34e2 100644 --- a/Examples/Sources/ConfigDetailTableViewController.swift +++ b/Examples/Sources/ConfigDetailTableViewController.swift @@ -90,7 +90,7 @@ class ConfigDetailTableViewController: UITableViewController { case .subscribeKey: return config.subscribeKey case .cipherKey: - return config.cipherKey?.key.description ?? "Key Not Found" + return config.cryptorModule?.description ?? "CryptorModule Not Found" case .authKey: return config.authKey case .uuid: diff --git a/Podfile.lock b/Podfile.lock index 8db976fb..00bd0c60 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -19,4 +19,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 61a40240486621bb01f596fdd5bc632504940fab -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/PubNub.xcodeproj/project.pbxproj b/PubNub.xcodeproj/project.pbxproj index 92d99c5b..eded1b21 100644 --- a/PubNub.xcodeproj/project.pbxproj +++ b/PubNub.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 0162B986DE5A8773D6F8C8A0 /* Pods_PubNubContractTestsBeta.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E812941F9706B958CD448B54 /* Pods_PubNubContractTestsBeta.framework */; }; 35012EB22850039D00CF7E0A /* PubNubUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358B8975284D323300DB0F3D /* PubNubUser.swift */; }; 35012EB3285003A100CF7E0A /* Patcher+PubNubUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358B8974284D323300DB0F3D /* Patcher+PubNubUser.swift */; }; 35012EB5285003EC00CF7E0A /* PubNubUserEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35012EB4285003EC00CF7E0A /* PubNubUserEvent.swift */; }; @@ -60,11 +61,9 @@ 350EFBDC22C951F700FA33AA /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 350EFBDB22C951F700FA33AA /* Request.swift */; }; 350EFBE022C9573F00FA33AA /* NSLocking+PubNub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 350EFBDF22C9573F00FA33AA /* NSLocking+PubNub.swift */; }; 350EFBE422C95FED00FA33AA /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 350EFBE322C95FED00FA33AA /* Atomic.swift */; }; - 35133196253115C500242CC2 /* CryptoStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35133195253115C500242CC2 /* CryptoStream.swift */; }; 3513AB2723A967D9002D4B57 /* PAMTokenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3513AB2523A967C7002D4B57 /* PAMTokenTests.swift */; }; 3520962F2358D64C00A641DF /* TestSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35458BA6230D91BB0085B502 /* TestSetup.swift */; }; 352096302358D67000A641DF /* TestLogWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35458BA4230D8E500085B502 /* TestLogWriter.swift */; }; - 352224EE253397AA00A5B330 /* CryptoInputStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 352224ED253397AA00A5B330 /* CryptoInputStream.swift */; }; 352224FD2533D15A00A5B330 /* file_upload_sample_encrypted.txt in Resources */ = {isa = PBXBuildFile; fileRef = 352224FB2533D15A00A5B330 /* file_upload_sample_encrypted.txt */; }; 352224FE2533D15A00A5B330 /* file_upload_sample.txt in Resources */ = {isa = PBXBuildFile; fileRef = 352224FC2533D15A00A5B330 /* file_upload_sample.txt */; }; 35270C0323AC124800501388 /* CBORDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35270C0223AC124800501388 /* CBORDecoder.swift */; }; @@ -208,7 +207,7 @@ 3595120922FF7B4800C9D3AE /* messageHistory_Delete_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 3595120822FF7B4800C9D3AE /* messageHistory_Delete_success.json */; }; 3595120B22FF8AC100C9D3AE /* messageHistory_Fetch_success_multipleChannels.json in Resources */ = {isa = PBXBuildFile; fileRef = 3595120A22FF8AC100C9D3AE /* messageHistory_Fetch_success_multipleChannels.json */; }; 3595120D22FFA67300C9D3AE /* history_delete_not_enabled_for_key_Message.json in Resources */ = {isa = PBXBuildFile; fileRef = 3595120C22FFA67300C9D3AE /* history_delete_not_enabled_for_key_Message.json */; }; - 359512102301DCAB00C9D3AE /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3595120F2301DCAB00C9D3AE /* Crypto.swift */; }; + 359512102301DCAB00C9D3AE /* CryptoError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3595120F2301DCAB00C9D3AE /* CryptoError.swift */; }; 359C2C1422EBB56A009C3B4B /* Int+PubNubTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 359C2C1322EBB56A009C3B4B /* Int+PubNubTests.swift */; }; 359C2C1622EE29ED009C3B4B /* time_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 359C2C1522EE29E2009C3B4B /* time_success.json */; }; 35A66A7E22F861BA00AC67A9 /* SubscriptionSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35A66A7422F861BA00AC67A9 /* SubscriptionSession.swift */; }; @@ -376,7 +375,19 @@ 35FE941822EFCB7F0051C455 /* SessionStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FE941722EFCB7F0051C455 /* SessionStreamTests.swift */; }; 35FE941B22EFE5400051C455 /* EventStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FE941A22EFE5400051C455 /* EventStreamTests.swift */; }; 35FE941F22F0929A0051C455 /* RequestRetrierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FE941E22F0929A0051C455 /* RequestRetrierTests.swift */; }; + 3D6265D72ABCA79100FDD5E6 /* CryptorUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D6265D62ABCA79100FDD5E6 /* CryptorUtils.swift */; }; + 3D758DBF2AAA1C49005D2B36 /* CryptorModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DBE2AAA1C49005D2B36 /* CryptorModule.swift */; }; + 3D758DC82AB06A12005D2B36 /* CryptoInputStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DC62AB06A12005D2B36 /* CryptoInputStream.swift */; }; + 3D758DC92AB06A12005D2B36 /* CryptoStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DC72AB06A12005D2B36 /* CryptoStream.swift */; }; + 3D758DCB2AB06A2D005D2B36 /* Cryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DCA2AB06A2D005D2B36 /* Cryptor.swift */; }; + 3D758DCE2AB0A835005D2B36 /* LegacyCryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DCD2AB0A835005D2B36 /* LegacyCryptor.swift */; }; + 3D758DD02AB0A8C6005D2B36 /* CryptorVector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DCF2AB0A8C5005D2B36 /* CryptorVector.swift */; }; + 3D758DD22AB0A91C005D2B36 /* AESCBCCryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DD12AB0A91C005D2B36 /* AESCBCCryptor.swift */; }; + 3D758DD52AB48A6A005D2B36 /* CryptorHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DD32AB48A6A005D2B36 /* CryptorHeader.swift */; }; + 3D758DD62AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DD42AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift */; }; 3D9134972A1216F7000A5124 /* PubNubPushTargetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9134962A1216F7000A5124 /* PubNubPushTargetTests.swift */; }; + 3DACC7F72AB88F8E00210B14 /* Data+CommonCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DACC7F62AB88F8E00210B14 /* Data+CommonCrypto.swift */; }; + 4C2A8D84BCD39B07A66FD9B4 /* Pods_PubNubContractTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E7F3D449F2D66FC29674EF6 /* Pods_PubNubContractTests.framework */; }; 79407BD2271D4CFA0032076C /* PubNubContractTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79407BBF271D4CFA0032076C /* PubNubContractTestCase.swift */; }; 79407BD3271D4CFA0032076C /* PubNubContractTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79407BBF271D4CFA0032076C /* PubNubContractTestCase.swift */; }; 79407BD4271D4CFA0032076C /* PubNubContractCucumberTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 79407BC0271D4CFA0032076C /* PubNubContractCucumberTest.m */; }; @@ -530,6 +541,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 01F80D9221FA218C6E7D5572 /* Pods-PubNubContractTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubNubContractTests.release.xcconfig"; path = "Target Support Files/Pods-PubNubContractTests/Pods-PubNubContractTests.release.xcconfig"; sourceTree = ""; }; + 1E7F3D449F2D66FC29674EF6 /* Pods_PubNubContractTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PubNubContractTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35012EB4285003EC00CF7E0A /* PubNubUserEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubUserEvent.swift; sourceTree = ""; }; 35012EB9285004E300CF7E0A /* PubNubMembershipEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubMembershipEvent.swift; sourceTree = ""; }; 35012EBB2850052500CF7E0A /* PubNubSpaceEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubSpaceEvent.swift; sourceTree = ""; }; @@ -569,9 +582,7 @@ 350EFBDD22C9520400FA33AA /* EndpointResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointResponse.swift; sourceTree = ""; }; 350EFBDF22C9573F00FA33AA /* NSLocking+PubNub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLocking+PubNub.swift"; sourceTree = ""; }; 350EFBE322C95FED00FA33AA /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = ""; }; - 35133195253115C500242CC2 /* CryptoStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoStream.swift; sourceTree = ""; }; 3513AB2523A967C7002D4B57 /* PAMTokenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAMTokenTests.swift; sourceTree = ""; }; - 352224ED253397AA00A5B330 /* CryptoInputStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoInputStream.swift; sourceTree = ""; }; 352224FB2533D15A00A5B330 /* file_upload_sample_encrypted.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = file_upload_sample_encrypted.txt; sourceTree = ""; }; 352224FC2533D15A00A5B330 /* file_upload_sample.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = file_upload_sample.txt; sourceTree = ""; }; 35270C0223AC124800501388 /* CBORDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBORDecoder.swift; sourceTree = ""; }; @@ -729,7 +740,7 @@ 3595120822FF7B4800C9D3AE /* messageHistory_Delete_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = messageHistory_Delete_success.json; sourceTree = ""; }; 3595120A22FF8AC100C9D3AE /* messageHistory_Fetch_success_multipleChannels.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = messageHistory_Fetch_success_multipleChannels.json; sourceTree = ""; }; 3595120C22FFA67300C9D3AE /* history_delete_not_enabled_for_key_Message.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = history_delete_not_enabled_for_key_Message.json; sourceTree = ""; }; - 3595120F2301DCAB00C9D3AE /* Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = ""; }; + 3595120F2301DCAB00C9D3AE /* CryptoError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoError.swift; sourceTree = ""; }; 359C2C1322EBB56A009C3B4B /* Int+PubNubTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+PubNubTests.swift"; sourceTree = ""; }; 359C2C1522EE29E2009C3B4B /* time_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = time_success.json; sourceTree = ""; }; 359F778A22B7FDDB00B6B46F /* Gemfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; fileEncoding = 4; path = Gemfile; sourceTree = ""; }; @@ -897,7 +908,19 @@ 35FE941722EFCB7F0051C455 /* SessionStreamTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionStreamTests.swift; sourceTree = ""; }; 35FE941A22EFE5400051C455 /* EventStreamTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventStreamTests.swift; sourceTree = ""; }; 35FE941E22F0929A0051C455 /* RequestRetrierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestRetrierTests.swift; sourceTree = ""; }; + 3D6265D62ABCA79100FDD5E6 /* CryptorUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptorUtils.swift; sourceTree = ""; }; + 3D758DBE2AAA1C49005D2B36 /* CryptorModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptorModule.swift; sourceTree = ""; }; + 3D758DC62AB06A12005D2B36 /* CryptoInputStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoInputStream.swift; sourceTree = ""; }; + 3D758DC72AB06A12005D2B36 /* CryptoStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoStream.swift; sourceTree = ""; }; + 3D758DCA2AB06A2D005D2B36 /* Cryptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cryptor.swift; sourceTree = ""; }; + 3D758DCD2AB0A835005D2B36 /* LegacyCryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyCryptor.swift; sourceTree = ""; }; + 3D758DCF2AB0A8C5005D2B36 /* CryptorVector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptorVector.swift; sourceTree = ""; }; + 3D758DD12AB0A91C005D2B36 /* AESCBCCryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AESCBCCryptor.swift; sourceTree = ""; }; + 3D758DD32AB48A6A005D2B36 /* CryptorHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptorHeader.swift; sourceTree = ""; }; + 3D758DD42AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptorHeaderWithinStreamFinder.swift; sourceTree = ""; }; 3D9134962A1216F7000A5124 /* PubNubPushTargetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubPushTargetTests.swift; sourceTree = ""; }; + 3DACC7F62AB88F8E00210B14 /* Data+CommonCrypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+CommonCrypto.swift"; sourceTree = ""; }; + 3DE632651BA8B2E27ACFC4AD /* Pods-PubNubContractTestsBeta.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubNubContractTestsBeta.release.xcconfig"; path = "Target Support Files/Pods-PubNubContractTestsBeta/Pods-PubNubContractTestsBeta.release.xcconfig"; sourceTree = ""; }; 793079152667C63700F23B72 /* CODEOWNERS */ = {isa = PBXFileReference; lastKnownFileType = text; path = CODEOWNERS; sourceTree = ""; }; 793079172667C63700F23B72 /* validate-yml.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "validate-yml.js"; sourceTree = ""; }; 793079182667C63700F23B72 /* validate-pubnub-yml.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = "validate-pubnub-yml.yml"; sourceTree = ""; }; @@ -917,6 +940,7 @@ 7941EF47270E46B40054D9EF /* PubNubContractTests_Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = PubNubContractTests_Info.plist; path = PubNub.xcodeproj/PubNubContractTests_Info.plist; sourceTree = SOURCE_ROOT; }; 7951954D26C955CE001E308C /* PAMToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAMToken.swift; sourceTree = ""; }; 79657AAB271A13F700BACEC5 /* PubNubContractTestsBeta.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PubNubContractTestsBeta.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 979DD162B86D78BE9B72DEEF /* Pods-PubNubContractTestsBeta.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubNubContractTestsBeta.debug.xcconfig"; path = "Target Support Files/Pods-PubNubContractTestsBeta/Pods-PubNubContractTestsBeta.debug.xcconfig"; sourceTree = ""; }; A5115F2429195AF400F6ADA1 /* PubNubObjectsMembersContractTestSteps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubObjectsMembersContractTestSteps.swift; sourceTree = ""; }; A5115F27291D54F500F6ADA1 /* PubNubObjectsMembershipsContractTestSteps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubObjectsMembershipsContractTestSteps.swift; sourceTree = ""; }; A5115F2A291D5C2700F6ADA1 /* PubNubObjectsContractTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubObjectsContractTests.swift; sourceTree = ""; }; @@ -925,6 +949,8 @@ A5A574D329C309750065D333 /* leave_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = leave_success.json; sourceTree = ""; }; A5F19EE229126D8200F185A9 /* PubNubObjectsUUIDMetadataContractTestSteps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PubNubObjectsUUIDMetadataContractTestSteps.swift; sourceTree = ""; }; D2635DFA22FCCF080097CF64 /* message_counts_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = message_counts_success.json; sourceTree = ""; }; + E812941F9706B958CD448B54 /* Pods_PubNubContractTestsBeta.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PubNubContractTestsBeta.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FB0696E0E433339FBC48D1D3 /* Pods-PubNubContractTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubNubContractTests.debug.xcconfig"; path = "Target Support Files/Pods-PubNubContractTests/Pods-PubNubContractTests.debug.xcconfig"; sourceTree = ""; }; OBJ_11 /* PubNub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNub.swift; sourceTree = ""; }; OBJ_15 /* PubNubTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubTests.swift; sourceTree = ""; }; OBJ_21 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; @@ -1019,6 +1045,7 @@ buildActionMask = 0; files = ( 7941EEA9270E433F0054D9EF /* PubNub.framework in Frameworks */, + 4C2A8D84BCD39B07A66FD9B4 /* Pods_PubNubContractTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1027,6 +1054,7 @@ buildActionMask = 0; files = ( 79657AA3271A13F700BACEC5 /* PubNub.framework in Frameworks */, + 0162B986DE5A8773D6F8C8A0 /* Pods_PubNubContractTestsBeta.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1052,7 +1080,6 @@ isa = PBXGroup; children = ( 357CA287251D3C9100BC40D3 /* MultipartInputStream.swift */, - 352224ED253397AA00A5B330 /* CryptoInputStream.swift */, ); path = Streams; sourceTree = ""; @@ -1060,8 +1087,11 @@ 3522250B25340B8900A5B330 /* Crypto */ = { isa = PBXGroup; children = ( - 3595120F2301DCAB00C9D3AE /* Crypto.swift */, - 35133195253115C500242CC2 /* CryptoStream.swift */, + 3595120F2301DCAB00C9D3AE /* CryptoError.swift */, + 3D758DBE2AAA1C49005D2B36 /* CryptorModule.swift */, + 3D758DCC2AB06B98005D2B36 /* Header */, + 3D758DC52AB06983005D2B36 /* Cryptors */, + 3D758DC42AB06977005D2B36 /* Miscellaneous */, ); path = Crypto; sourceTree = ""; @@ -1876,6 +1906,44 @@ path = EndpointError; sourceTree = ""; }; + 3D6265D22ABC8E6900FDD5E6 /* Crypto */ = { + isa = PBXGroup; + children = ( + ); + path = Crypto; + sourceTree = ""; + }; + 3D758DC42AB06977005D2B36 /* Miscellaneous */ = { + isa = PBXGroup; + children = ( + 3D758DC72AB06A12005D2B36 /* CryptoStream.swift */, + 3D758DC62AB06A12005D2B36 /* CryptoInputStream.swift */, + 3D758DCF2AB0A8C5005D2B36 /* CryptorVector.swift */, + 3D6265D62ABCA79100FDD5E6 /* CryptorUtils.swift */, + 3DACC7F62AB88F8E00210B14 /* Data+CommonCrypto.swift */, + ); + path = Miscellaneous; + sourceTree = ""; + }; + 3D758DC52AB06983005D2B36 /* Cryptors */ = { + isa = PBXGroup; + children = ( + 3D758DCA2AB06A2D005D2B36 /* Cryptor.swift */, + 3D758DCD2AB0A835005D2B36 /* LegacyCryptor.swift */, + 3D758DD12AB0A91C005D2B36 /* AESCBCCryptor.swift */, + ); + path = Cryptors; + sourceTree = ""; + }; + 3D758DCC2AB06B98005D2B36 /* Header */ = { + isa = PBXGroup; + children = ( + 3D758DD32AB48A6A005D2B36 /* CryptorHeader.swift */, + 3D758DD42AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift */, + ); + path = Header; + sourceTree = ""; + }; 3D9134952A12161A000A5124 /* Push */ = { isa = PBXGroup; children = ( @@ -1887,6 +1955,10 @@ 3DBD7CDD58292DFFDF108B95 /* Pods */ = { isa = PBXGroup; children = ( + FB0696E0E433339FBC48D1D3 /* Pods-PubNubContractTests.debug.xcconfig */, + 01F80D9221FA218C6E7D5572 /* Pods-PubNubContractTests.release.xcconfig */, + 979DD162B86D78BE9B72DEEF /* Pods-PubNubContractTestsBeta.debug.xcconfig */, + 3DE632651BA8B2E27ACFC4AD /* Pods-PubNubContractTestsBeta.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -1924,6 +1996,7 @@ 79407BC1271D4CFA0032076C /* Steps */ = { isa = PBXGroup; children = ( + 3D6265D22ABC8E6900FDD5E6 /* Crypto */, A5F88ECF2906A9DE00F49D5C /* Objects */, 79407BC2271D4CFA0032076C /* Access */, 79407BC4271D4CFA0032076C /* Message Actions */, @@ -2002,6 +2075,15 @@ path = Files; sourceTree = ""; }; + A311BADC0F7EA586FC713D4A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1E7F3D449F2D66FC29674EF6 /* Pods_PubNubContractTests.framework */, + E812941F9706B958CD448B54 /* Pods_PubNubContractTestsBeta.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; A5F88ECF2906A9DE00F49D5C /* Objects */ = { isa = PBXGroup; children = ( @@ -2078,6 +2160,7 @@ OBJ_12 /* Tests */, OBJ_17 /* Products */, 3DBD7CDD58292DFFDF108B95 /* Pods */, + A311BADC0F7EA586FC713D4A /* Frameworks */, ); sourceTree = ""; }; @@ -2318,9 +2401,11 @@ isa = PBXNativeTarget; buildConfigurationList = 7941EF3D270E433F0054D9EF /* Build configuration list for PBXNativeTarget "PubNubContractTests" */; buildPhases = ( + EE01701F9284EFC4A7EFAA3A /* [CP] Check Pods Manifest.lock */, 7941EE6E270E433F0054D9EF /* Sources */, 7941EEA8270E433F0054D9EF /* Frameworks */, 7941EEAA270E433F0054D9EF /* Resources */, + 185ED5B4E3661C5C0C84A642 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -2336,9 +2421,11 @@ isa = PBXNativeTarget; buildConfigurationList = 79657AA8271A13F700BACEC5 /* Build configuration list for PBXNativeTarget "PubNubContractTestsBeta" */; buildPhases = ( + 1B5C0AA410DAD91677EDB428 /* [CP] Check Pods Manifest.lock */, 79657A97271A13F700BACEC5 /* Sources */, 79657AA2271A13F700BACEC5 /* Frameworks */, 79657AA5271A13F700BACEC5 /* Resources */, + 2DA248AB77311AD12178403F /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -2697,6 +2784,62 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 185ED5B4E3661C5C0C84A642 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubNubContractTests/Pods-PubNubContractTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubNubContractTests/Pods-PubNubContractTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PubNubContractTests/Pods-PubNubContractTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 1B5C0AA410DAD91677EDB428 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PubNubContractTestsBeta-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2DA248AB77311AD12178403F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubNubContractTestsBeta/Pods-PubNubContractTestsBeta-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PubNubContractTestsBeta/Pods-PubNubContractTestsBeta-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PubNubContractTestsBeta/Pods-PubNubContractTestsBeta-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 359152A822BA9F5B0048842D /* Swift Format */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -2733,6 +2876,28 @@ shellPath = /bin/sh; shellScript = "swiftlint\n"; }; + EE01701F9284EFC4A7EFAA3A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PubNubContractTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -2905,7 +3070,11 @@ 35481BF6252275B5004E07B5 /* PubNubFile.swift in Sources */, 35CDA4CC2510031E00218137 /* XMLDecoder.swift in Sources */, 35D0615F2304830600FDB2F9 /* GenericServicePayloadResponse.swift in Sources */, + 3D758DD62AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift in Sources */, 35A66A8E22F911DB00AC67A9 /* SubscribeSessionFactory.swift in Sources */, + 3D758DBF2AAA1C49005D2B36 /* CryptorModule.swift in Sources */, + 3D758DD02AB0A8C6005D2B36 /* CryptorVector.swift in Sources */, + 3D6265D72ABCA79100FDD5E6 /* CryptorUtils.swift in Sources */, 35D8D4C522EB4600001B07D9 /* AnyJSON.swift in Sources */, 35AC16332487179400A66030 /* PubNubPage.swift in Sources */, 35AC162F2486C9A400A66030 /* PubNubMessageAction.swift in Sources */, @@ -2915,11 +3084,13 @@ 354ADA8C22D923F20093EFFB /* Replaceables+PubNub.swift in Sources */, 35CF549D248D73500099FE81 /* SubscribeObjectPayload.swift in Sources */, 35277A7022D6B3F90083B9B6 /* URL+PubNub.swift in Sources */, + 3D758DC92AB06A12005D2B36 /* CryptoStream.swift in Sources */, 3534D4E222C56533008E89FA /* TimeRouter.swift in Sources */, 35CDFEAF22E7664D00F3B9F2 /* URLQueryItem+PubNub.swift in Sources */, 35304F8A22FE5425006A02CA /* Validated.swift in Sources */, 35EE358822E247B200E3F081 /* URLSessionConfiguration+PubNub.swift in Sources */, 35A6C7A822FBCC8B00E97CC5 /* PushRouter.swift in Sources */, + 3D758DCE2AB0A835005D2B36 /* LegacyCryptor.swift in Sources */, 35A66A7E22F861BA00AC67A9 /* SubscriptionSession.swift in Sources */, 356D48B32360BD6B00C65C40 /* EventStream.swift in Sources */, 35C6B6E322F515760054F242 /* SubscribeRouter.swift in Sources */, @@ -2935,6 +3106,7 @@ 356D48B42360BD7000C65C40 /* SubscriptionStream.swift in Sources */, 35599799230C5878000BCFD1 /* LogWriter.swift in Sources */, 354ADA9422DCBC360093EFFB /* ResponseOperator.swift in Sources */, + 3DACC7F72AB88F8E00210B14 /* Data+CommonCrypto.swift in Sources */, 357CA28E251D3D0C00BC40D3 /* HTTPFileTask.swift in Sources */, 3534D4E622C67CCA008E89FA /* HTTPRouter.swift in Sources */, 35CF5490248971DD0099FE81 /* ObjectsMembershipsRouter.swift in Sources */, @@ -2942,21 +3114,20 @@ 35B6FBAF22F226F4005EE490 /* NSNumber+PubNub.swift in Sources */, 357024BF283C07C900567EE8 /* Objects+PubNub.swift in Sources */, 35B0ACE3252BE36D00537A18 /* File+PubNub.swift in Sources */, + 3D758DD52AB48A6A005D2B36 /* CryptorHeader.swift in Sources */, 35CF549C248ABE8B0099FE81 /* PubNubObjectMetadataPatcher.swift in Sources */, 35C829DC23147AC000F59D3C /* SubscriptionState.swift in Sources */, 35E71C3C2490678E0032A991 /* PubNubPresence.swift in Sources */, - 35133196253115C500242CC2 /* CryptoStream.swift in Sources */, 35D8D4CD22EB90F1001B07D9 /* Int+PubNub.swift in Sources */, 35B3824A233AAB8C0028803F /* JSONCodable.swift in Sources */, 35599792230A3F11000BCFD1 /* Thread+PubNub.swift in Sources */, 3567434822E1E4F700BF2639 /* Collection+PubNub.swift in Sources */, 354FC4C122D04D3600318932 /* DispatchQueue+PubNub.swift in Sources */, - 359512102301DCAB00C9D3AE /* Crypto.swift in Sources */, + 359512102301DCAB00C9D3AE /* CryptoError.swift in Sources */, 354ADA8E22DA7F280093EFFB /* SessionStream.swift in Sources */, 3534D4E822C67D0E008E89FA /* OperationQueue+PubNub.swift in Sources */, 3585A02423C63EE900FDA860 /* CBORSerialization.swift in Sources */, 359152A122BA9AA30048842D /* PubNubConfiguration.swift in Sources */, - 352224EE253397AA00A5B330 /* CryptoInputStream.swift in Sources */, 358C641F238C5FCA009CE354 /* FCMWebpushPayload.swift in Sources */, 352DBFEA237CCB9D00A0106E /* EndpointResponse.swift in Sources */, 350EFBE422C95FED00FA33AA /* Atomic.swift in Sources */, @@ -2981,12 +3152,14 @@ 354ADA8822D909A30093EFFB /* Convertibles+PubNub.swift in Sources */, 3556E3762485936B004FDC25 /* SubscribePresencePayload.swift in Sources */, 350EFBDC22C951F700FA33AA /* Request.swift in Sources */, + 3D758DD22AB0A91C005D2B36 /* AESCBCCryptor.swift in Sources */, 35A66A7F22F861BA00AC67A9 /* WeakBox.swift in Sources */, 357CA288251D3C9100BC40D3 /* MultipartInputStream.swift in Sources */, 35CF54942489918E0099FE81 /* PubNubChannelMetadata.swift in Sources */, OBJ_31 /* PubNub.swift in Sources */, 358C641A2388BF76009CE354 /* PubNubFCMPayload.swift in Sources */, 35A6C77D22FB159F00E97CC5 /* PresenceRouter.swift in Sources */, + 3D758DCB2AB06A2D005D2B36 /* Cryptor.swift in Sources */, 35A6C7B522FBDD9300E97CC5 /* Data+PubNub.swift in Sources */, 35A6C78F22FB4F0500E97CC5 /* ChannelGroupsRouter.swift in Sources */, 354ADA9022DA81650093EFFB /* DateFormatter+PubNub.swift in Sources */, @@ -2998,6 +3171,7 @@ 35AE6A3224FD6CEE00BBFA37 /* FileManagementRouter.swift in Sources */, 35089A0B22E56F1F002BCC94 /* Constants.swift in Sources */, 358C6421238C6787009CE354 /* PubNubPushMessage.swift in Sources */, + 3D758DC82AB06A12005D2B36 /* CryptoInputStream.swift in Sources */, 35E4604F234B8B9D005D04AE /* ErrorDescription.swift in Sources */, 35089A0922E3C08D002BCC94 /* Error+PubNub.swift in Sources */, 3534D4E422C57659008E89FA /* PublishRouter.swift in Sources */, @@ -3887,6 +4061,7 @@ }; 7941EF3E270E433F0054D9EF /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = FB0696E0E433339FBC48D1D3 /* Pods-PubNubContractTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; @@ -3928,6 +4103,7 @@ }; 7941EF3F270E433F0054D9EF /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 01F80D9221FA218C6E7D5572 /* Pods-PubNubContractTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; @@ -3968,6 +4144,7 @@ }; 79657AA9271A13F700BACEC5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 979DD162B86D78BE9B72DEEF /* Pods-PubNubContractTestsBeta.debug.xcconfig */; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; @@ -4010,6 +4187,7 @@ }; 79657AAA271A13F700BACEC5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 3DE632651BA8B2E27ACFC4AD /* Pods-PubNubContractTestsBeta.release.xcconfig */; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; diff --git a/Sources/PubNub/APIs/File+PubNub.swift b/Sources/PubNub/APIs/File+PubNub.swift index 883e9aee..54425b85 100644 --- a/Sources/PubNub/APIs/File+PubNub.swift +++ b/Sources/PubNub/APIs/File+PubNub.swift @@ -160,9 +160,9 @@ public extension PubNub { switch result { case let .success(response): do { - let autoCrypto = requestConfig.customConfiguration?.cipherKey ?? configuration.cipherKey + let cryptorModule = requestConfig.customConfiguration?.cryptorModule ?? configuration.cryptorModule completion?(.success(( - try URLRequest(from: response.payload, uploading: content, crypto: autoCrypto), + try URLRequest(from: response.payload, uploading: content, cryptorModule: cryptorModule), response.payload.fileId, response.payload.filename ))) @@ -450,7 +450,7 @@ public extension PubNub { /// - downloadTo: The async `Result` of the method call /// - Returns: The new file download task. The `urlSessionTask` property can be used to access the underlying `URLSessionDownloadTask` func createFileURLSessionDownloadTask( - _ taskType: FileDownloadTaskType, session: URLSessionReplaceable, downloadTo url: URL, decrypt: Crypto? = nil + _ taskType: FileDownloadTaskType, session: URLSessionReplaceable, downloadTo url: URL, decrypt: CryptorModule? = nil ) -> HTTPFileDownloadTask { let downloadTask: URLSessionDownloadTask switch taskType { @@ -464,7 +464,7 @@ public extension PubNub { task: downloadTask, session: session.configuration.identifier, downloadTo: url, - crypto: decrypt ?? configuration.cipherKey + cryptorModule: decrypt ?? configuration.cryptorModule ) // Create task map inside Delegate diff --git a/Sources/PubNub/Errors/ErrorDescription.swift b/Sources/PubNub/Errors/ErrorDescription.swift index 5307e838..34d51f4f 100644 --- a/Sources/PubNub/Errors/ErrorDescription.swift +++ b/Sources/PubNub/Errors/ErrorDescription.swift @@ -294,6 +294,12 @@ extension PubNubError.Reason: CustomStringConvertible, LocalizedError { return "The Content-Length was incorrect for the content being uploaded" case .serviceNotEnabled: return "The PubNub Service that you're attempting to use has not be enabled for your keyset." + case .encryptionError: + return "Failure performing an encryption operation" + case .decryptionError: + return "Failure performing a decryption operation" + case .unknownCryptorError: + return "Unknown Cryptor error" } } diff --git a/Sources/PubNub/Errors/PubNubError.swift b/Sources/PubNub/Errors/PubNubError.swift index 456112b1..12c7dfe0 100644 --- a/Sources/PubNub/Errors/PubNubError.swift +++ b/Sources/PubNub/Errors/PubNubError.swift @@ -105,6 +105,9 @@ public struct PubNubError: Error { // Crypto case missingCryptoKey + case encryptionError + case decryptionError + case unknownCryptorError // Request Processing case requestMutatorFailure @@ -232,6 +235,8 @@ public struct PubNubError: Error { return .streamFailure case .fileTooLarge, .fileMissingAtPath, .fileAccessDenied, .fileContentLength: return .fileManagement + case .encryptionError, .decryptionError, .unknownCryptorError: + return .crypto } } } diff --git a/Sources/PubNub/Extensions/URLRequest+PubNub.swift b/Sources/PubNub/Extensions/URLRequest+PubNub.swift index 01c1ee0b..8d64cd86 100644 --- a/Sources/PubNub/Extensions/URLRequest+PubNub.swift +++ b/Sources/PubNub/Extensions/URLRequest+PubNub.swift @@ -46,7 +46,7 @@ public extension URLRequest { internal init( from response: GenerateUploadURLResponse, uploading content: PubNub.FileUploadContent, - crypto: Crypto? = nil + cryptorModule: CryptorModule? = nil ) throws { self.init(url: response.uploadRequestURL) method = response.uploadMethod @@ -65,30 +65,32 @@ public extension URLRequest { postfixData.append("\r\n--\(response.fileId)--") // Get Content InputStream - guard var contentStream = content.inputStream else { + guard let contentStream = content.inputStream else { throw PubNubError(.streamCouldNotBeInitialized, additional: [content.debugDescription]) } - - // If we were given a Crypto payload we should convert the stream to a secure stream - if let crypto = crypto { - let cryptoStream = CryptoInputStream( - .encrypt, input: contentStream, contentLength: content.contentLength, with: crypto - ) - setValue( - "\(prefixData.count + cryptoStream.estimatedCryptoCount + postfixData.count)", - forHTTPHeaderField: "Content-Length" - ) - contentStream = cryptoStream + + let finalStream: InputStream + let contentLength: Int + + // If we were given a Crypto module we should convert the stream to a secure stream + if let cryptorModule = cryptorModule { + switch cryptorModule.encrypt(stream: contentStream, contentLength: content.contentLength) { + case .success(let encryptingResult): + finalStream = encryptingResult.stream + contentLength = prefixData.count + encryptingResult.contentLength + postfixData.count + case .failure(let encryptionError): + throw encryptionError + } } else { - setValue("\(prefixData.count + content.contentLength + postfixData.count)", forHTTPHeaderField: "Content-Length") + finalStream = contentStream + contentLength = prefixData.count + content.contentLength + postfixData.count } - let inputStream = MultipartInputStream( - inputStreams: [InputStream(data: prefixData), contentStream, InputStream(data: postfixData)] - ) - - httpBodyStream = inputStream - + setValue("\(contentLength)", forHTTPHeaderField: "Content-Length") setValue("multipart/form-data; boundary=\(response.fileId)", forHTTPHeaderField: "Content-Type") + + httpBodyStream = MultipartInputStream( + inputStreams: [InputStream(data: prefixData), finalStream, InputStream(data: postfixData)] + ) } } diff --git a/Sources/PubNub/Helpers/Crypto/Crypto.swift b/Sources/PubNub/Helpers/Crypto/Crypto.swift deleted file mode 100644 index 43731167..00000000 --- a/Sources/PubNub/Helpers/Crypto/Crypto.swift +++ /dev/null @@ -1,400 +0,0 @@ -// -// Crypto.swift -// -// PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks -// Copyright © 2019 PubNub Inc. -// https://www.pubnub.com/ -// https://www.pubnub.com/terms -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import CommonCrypto -import Foundation - -/// Object capable of encryption/decryption -public struct Crypto: Hashable { - /// The key used when encrypting/decrypting - public let key: Data - /// The algorithm that will be used when encrypting/decrypting - public let cipher: Cipher - /// The String Encoding strategy to be used by default - public let defaultStringEncoding: String.Encoding - /// Whether random initialization vector should be used - public internal(set) var randomizeIV: Bool - - public static let paddingLength = CCOptions(kCCOptionPKCS7Padding) - - public enum Operation: CCOperation { - case encrypt - case decrypt - - var ccValue: CCOperation { - switch self { - case .encrypt: - return CCOperation(kCCEncrypt) - case .decrypt: - return CCOperation(kCCDecrypt) - } - } - } - - public init(key data: Data, cipher: Cipher = .aes, withRandomIV: Bool = true, encoding: String.Encoding = .utf8) { - key = data - self.cipher = cipher - defaultStringEncoding = encoding - randomizeIV = withRandomIV - } - -// public init( -// key: String, -// cipher: Cipher = .aes, -// encoding: String.Encoding = .utf8 -// ) throws { -// guard let data = key.data(using: encoding), let keyData = SHA256.hash(data: data) else { -// throw CryptoError.invalidKey -// } -// -// try cipher.validate(keySize: keyData.count) -// -// self.init(key: keyData, cipher: cipher, withRandomIV: true, encoding: encoding) -// } - - public init?( - key: String, - cipher: Cipher = .aes, - withRandomIV: Bool = true, - encoding: String.Encoding = .utf8 - ) { - guard let data = key.data(using: encoding), let keyData = SHA256.hash(data: data) else { - PubNub.log.error("Crypto failed to `init` while converting `String` key to `Data`") - return nil - } - - do { - try cipher.validate(keySize: keyData.count) - } catch { - PubNub.log.error("Crypto failed to `init` due to \(error)") - } - - self.init(key: keyData, cipher: cipher, withRandomIV: withRandomIV, encoding: encoding) - } - - /// An algorithm that can be used to encrypt/decrypt data - public enum Cipher: RawRepresentable, Hashable { - case aes - - public init?(rawValue: CCAlgorithm) { - switch Int(rawValue) { - case kCCAlgorithmAES128: - self = .aes - default: - return nil - } - } - - public var rawValue: CCAlgorithm { - switch self { - case .aes: - return UInt32(kCCAlgorithmAES128) - } - } - - /// Block size for the algorithm - public var blockSize: Int { - switch self { - case .aes: - return kCCBlockSizeAES128 - } - } - - public var keySize: Int { - switch self { - case .aes: - return kCCKeySizeAES128 - } - } - - /// Key size for the algorithm - public var keySizeRange: ClosedRange { - switch self { - case .aes: - return kCCKeySizeAES128 ... kCCKeySizeAES256 - } - } - - public func outputSize(from inputBytes: Int) -> Int { - switch self { - case .aes: - return inputBytes + (blockSize - (inputBytes % blockSize)) - } - } - - /// Determines if a provided key size is valid for this algorithm - public func validate(keySize: Int) throws { - if !keySizeRange.contains(keySize) { - PubNub.log.error("Key size not valid for algorithm: \(keySize) not in \(keySizeRange)") - throw CryptoError.keySizeError - } - } - } - - /// An implementation of the SHA-256 hash algorithm - public enum SHA256 { - /// Perform a hash operation on provided `Data` - public static func hash(data: Data) -> Data? { - var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) - data.withUnsafeBytes { - _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) - } - return hexFrom(Data(hash)).lowercased(with: .current).data(using: .utf8) - } - - static func hexFrom(_ data: Data) -> String { - let midpoint = data.count / 2 - return data[.. Data { - guard let initializationVector = "0123456789012345".data(using: .utf8) else { - throw CryptoError.rngFailure - } - return initializationVector - } - - static func randomInitializationVector(byteCount: Int) throws -> [UInt8] { - guard byteCount > 0 else { throw CryptoError.rngFailure } - - var bytes: [UInt8] = Array(repeating: UInt8(0), count: byteCount) - let status = CCRandomGenerateBytes(&bytes, byteCount) - - guard status == kCCSuccess else { throw CryptoError(from: status) } - - return bytes - } - - // MARK: - Encrypt - - public func encrypt(plaintext stringIn: String, encoding override: String.Encoding? = nil) -> Result { - guard let messageData = stringIn.data(using: override ?? defaultStringEncoding) else { - return .failure(CryptoError.illegalParameter) - } - - return encrypt(encoded: messageData).map { $0.base64EncodedString() } - } - - public func encrypt(encoded dataIn: Data) -> Result { - do { - return .success(try dataIn.encrypt(using: self)) - } catch { - return .failure(error) - } - } - - // MARK: - Decrypt - - public func decrypt( - base64Encoded stringIn: String, - encoding override: String.Encoding? = nil - ) -> Result { - guard let messageData = Data(base64Encoded: stringIn) else { - return .failure(CryptoError.illegalParameter) - } - - return decrypt(encrypted: messageData).flatMap { data in - guard let decodedString = String(bytes: data, encoding: override ?? defaultStringEncoding) else { - return .failure(CryptoError.decodeError) - } - return .success(decodedString) - } - } - - public func decrypt(encrypted dataIn: Data, dataMovedOut _: Int = 0) -> Result { - do { - return .success(try dataIn.decrypt(using: self)) - } catch { - return .failure(error) - } - } -} - -/// An Error returned from a `Crypto` function -public enum CryptoError: CCCryptorStatus, Error, LocalizedError { - /// Insufficent buffer provided for specified operation. - case bufferTooSmall - /// Input size was not aligned properly. - case alignmentError - /// Input data did not decode or decrypt properly. - case decodeError - /// Illegal parameter value. - case illegalParameter - /// Memory allocation failure. - case memoryFailure - /// Buffer overflow occurred - case overflow - /// Failed to generate RNG - case rngFailure - /// Unspecified status - case unspecifiedError - /// Called `CCCrytor` sequence out of order - case callSequenceError - /// /Key is not a valid size for the specified cipher - case keySizeError - /// Key is not valid. - case invalidKey - /// Function not implemented for the current algorithm. - case unimplemented - /// Unknown error - case unknown - - public init?(rawValue: CCCryptorStatus) { - switch Int(rawValue) { - case kCCSuccess: - return nil - default: - self.init(from: rawValue) - } - } - - // swiftlint:disable:next cyclomatic_complexity - public init(from nonSuccess: CCCryptorStatus) { - switch Int(nonSuccess) { - case kCCParamError: - self = .illegalParameter - case kCCBufferTooSmall: - self = .bufferTooSmall - case kCCMemoryFailure: - self = .memoryFailure - case kCCAlignmentError: - self = .alignmentError - case kCCDecodeError: - self = .decodeError - case kCCOverflow: - self = .overflow - case kCCRNGFailure: - self = .rngFailure - case kCCCallSequenceError: - self = .callSequenceError - case kCCKeySizeError: - self = .keySizeError - case kCCUnimplemented: - self = .unimplemented - case kCCUnspecifiedError: - self = .unspecifiedError - default: - self = .unknown - } - } -} - -extension Data { - init(randomBytes count: Int) throws { - self.init(bytes: try Crypto.randomInitializationVector(byteCount: count), count: count) - } - - func encrypt(using crypto: Crypto) throws -> Data { - do { - let ivData: Data - if crypto.randomizeIV { - ivData = try Data(randomBytes: crypto.cipher.blockSize) - } else { - ivData = try Crypto.staticInitializationVector() - } - - let encrypted = try crypt( - operation: CCOperation(kCCEncrypt), - algorithm: crypto.cipher.rawValue, - options: CCOptions(kCCOptionPKCS7Padding), - blockSize: crypto.cipher.blockSize, - key: crypto.key, - initializationVector: ivData, - messageData: self - ) - - // Join IV and Encrypted when using a random IV - return crypto.randomizeIV ? ivData + encrypted : encrypted - } catch { - throw error - } - } - - func decrypt(using crypto: Crypto) throws -> Data { - let iv: Data - let ciphertext: Data - - if crypto.randomizeIV { - iv = prefix(kCCBlockSizeAES128) - ciphertext = suffix(from: kCCBlockSizeAES128) - } else { - iv = try Crypto.staticInitializationVector() - ciphertext = self - } - - return try crypt( - operation: CCOperation(kCCDecrypt), - algorithm: crypto.cipher.rawValue, - options: CCOptions(kCCOptionPKCS7Padding), - blockSize: crypto.cipher.blockSize, - key: crypto.key, - initializationVector: iv, - messageData: ciphertext - ) - } - - func crypt( - operation: CCOperation, algorithm: CCAlgorithm, options: CCOptions, blockSize: Int, - key: Data, initializationVector: Data, messageData dataIn: Data, dataMovedOut _: Int = 0 - ) throws -> Data { - return try key.withUnsafeBytes { keyUnsafeRawBufferPointer in - try dataIn.withUnsafeBytes { dataInUnsafeRawBufferPointer in - try initializationVector.withUnsafeBytes { ivUnsafeRawBufferPointer in - - let paddingSize = operation == kCCEncrypt ? blockSize : 0 - - let dataOutSize: Int = dataIn.count + paddingSize - let dataOut = UnsafeMutableRawPointer.allocate(byteCount: dataOutSize, alignment: 1) - defer { dataOut.deallocate() } - var dataOutMoved: Int = 0 - let status = CCCrypt(operation, algorithm, options, - keyUnsafeRawBufferPointer.baseAddress, key.count, - ivUnsafeRawBufferPointer.baseAddress, - dataInUnsafeRawBufferPointer.baseAddress, dataIn.count, - dataOut, dataOutSize, &dataOutMoved) - - if let error = CryptoError(rawValue: status) { - if error == .bufferTooSmall { - return try crypt( - operation: operation, algorithm: algorithm, options: options, - blockSize: blockSize, key: key, - initializationVector: initializationVector, messageData: dataIn, - dataMovedOut: dataOutMoved - ) - } - throw error - } - - return Data(bytes: dataOut, count: dataOutMoved) - } - } - } - } -} diff --git a/Sources/PubNub/Helpers/Crypto/CryptoError.swift b/Sources/PubNub/Helpers/Crypto/CryptoError.swift new file mode 100644 index 00000000..134b50c5 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/CryptoError.swift @@ -0,0 +1,98 @@ +// +// Crypto.swift +// +// PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks +// Copyright © 2019 PubNub Inc. +// https://www.pubnub.com/ +// https://www.pubnub.com/terms +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CommonCrypto +import Foundation + +/// An Error returned from a `Crypto` function +public enum CryptoError: CCCryptorStatus, Error, LocalizedError { + /// Insufficent buffer provided for specified operation. + case bufferTooSmall + /// Input size was not aligned properly. + case alignmentError + /// Input data did not decode or decrypt properly. + case decodeError + /// Illegal parameter value. + case illegalParameter + /// Memory allocation failure. + case memoryFailure + /// Buffer overflow occurred + case overflow + /// Failed to generate RNG + case rngFailure + /// Unspecified status + case unspecifiedError + /// Called `CCCrytor` sequence out of order + case callSequenceError + /// /Key is not a valid size for the specified cipher + case keySizeError + /// Key is not valid. + case invalidKey + /// Function not implemented for the current algorithm. + case unimplemented + /// Unknown error + case unknown + + public init?(rawValue: CCCryptorStatus) { + switch Int(rawValue) { + case kCCSuccess: + return nil + default: + self.init(from: rawValue) + } + } + + // swiftlint:disable:next cyclomatic_complexity + public init(from nonSuccess: CCCryptorStatus) { + switch Int(nonSuccess) { + case kCCParamError: + self = .illegalParameter + case kCCBufferTooSmall: + self = .bufferTooSmall + case kCCMemoryFailure: + self = .memoryFailure + case kCCAlignmentError: + self = .alignmentError + case kCCDecodeError: + self = .decodeError + case kCCOverflow: + self = .overflow + case kCCRNGFailure: + self = .rngFailure + case kCCCallSequenceError: + self = .callSequenceError + case kCCKeySizeError: + self = .keySizeError + case kCCUnimplemented: + self = .unimplemented + case kCCUnspecifiedError: + self = .unspecifiedError + default: + self = .unknown + } + } +} diff --git a/Sources/PubNub/Helpers/Crypto/CryptorModule.swift b/Sources/PubNub/Helpers/Crypto/CryptorModule.swift new file mode 100644 index 00000000..45090f41 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/CryptorModule.swift @@ -0,0 +1,194 @@ +// +// CryptorModule.swift +// +// PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks +// Copyright © 2023 PubNub Inc. +// https://www.pubnub.com/ +// https://www.pubnub.com/terms +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public struct EncryptedStreamResult { + let stream: InputStream + let contentLength: Int +} + +public struct CryptorModule { + private let defaultCryptor: Cryptor + private let cryptors: [Cryptor] + private let legacyCryptorId: CryptorId = [] + private let defaultStringEncoding: String.Encoding + + typealias Base64EncodedString = String + + internal init(default cryptor: Cryptor, cryptors: [Cryptor], encoding: String.Encoding = .utf8) { + self.defaultCryptor = cryptor + self.cryptors = cryptors + self.defaultStringEncoding = encoding + } + + public func encrypt(data: Data) -> Result { + defaultCryptor.encrypt(data: data).map { + CryptorHeader.v1( + cryptorId: defaultCryptor.id, + data: $0.metadata + ).asData() + $0.data + }.mapError { + PubNubError(.encryptionError, underlying: $0) + } + } + + public func decrypt(data: Data) -> Result { + guard let header = try? CryptorHeader.from(data: data) else { + return .failure(PubNubError( + .unknownCryptorError, + additional: ["Unable to decrypt Data due to malformed Cryptor's header"] + )) + } + guard let cryptor = cryptor(matching: header) else { + return .failure(PubNubError( + .unknownCryptorError, + additional: ["Cannot find matching Cryptor for \(header.cryptorId())"] + )) + } + return cryptor.decrypt( + data: EncryptedData( + metadata: header.metadataIfAny(), + data: data.subdata(in: header.length().. Result { + return defaultCryptor.encrypt( + stream: stream, + contentLength: contentLength + ).map { + let header = CryptorHeader.v1( + cryptorId: defaultCryptor.id, + data: $0.metadata + ) + let multipartInputStream = MultipartInputStream( + inputStreams: [InputStream(data: header.asData()), $0.stream] + ) + return EncryptedStreamResult( + stream: multipartInputStream, + contentLength: $0.contentLength + header.length() + ) + }.mapError { + PubNubError(.encryptionError, underlying: $0) + } + } + + @discardableResult + public func decrypt( + stream: InputStream, + contentLength: Int, + to outputPath: URL + ) -> Result { + guard let readHeaderResponse = try? CryptorHeaderWithinStreamFinder(stream: stream).findHeader() else { + return .failure(PubNubError( + .decryptionError, + additional: ["Unable to decrypt InputStream due to malformed Cryptor's header"] + )) + } + guard let cryptor = cryptor(matching: readHeaderResponse.header) else { + return .failure(PubNubError( + .unknownCryptorError, + additional: ["Cannot find matching Cryptor for \(readHeaderResponse.header.cryptorId())"] + )) + } + return cryptor.decrypt( + data: EncryptedStreamData( + stream: readHeaderResponse.continuationStream, + contentLength: contentLength - readHeaderResponse.header.length(), + metadata: readHeaderResponse.header.metadataIfAny() + ), + outputPath: outputPath + ).mapError { + PubNubError(.decryptionError, underlying: $0) + } + } + + private func cryptor(matching header: CryptorHeader) -> Cryptor? { + header.cryptorId() == defaultCryptor.id ? defaultCryptor : cryptors.first(where: { + $0.id == header.cryptorId() + }) + } +} + +public extension CryptorModule { + static func aesCbcCryptoModule(with key: String, withRandomIV: Bool = true) -> CryptorModule { + CryptorModule(default: AESCBCCryptor(key: key), cryptors: [LegacyCryptor(key: key, withRandomIV: withRandomIV)]) + } + static func legacyCryptoModule(with key: String, withRandomIV: Bool = true) -> CryptorModule { + CryptorModule(default: LegacyCryptor(key: key, withRandomIV: withRandomIV), cryptors: []) + } +} + +extension CryptorModule: Equatable { + public static func ==(lhs: CryptorModule, rhs: CryptorModule) -> Bool { + lhs.cryptors.map { $0.id } == rhs.cryptors.map { $0.id } + } +} + +extension CryptorModule: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(cryptors.map { $0.id }) + } +} + +extension CryptorModule: CustomStringConvertible { + public var description: String { + "Default cryptor: \(defaultCryptor.id), other: \(cryptors.map { $0.id })" + } +} + +internal extension CryptorModule { + func encrypt(string: String) -> Result { + guard let data = string.data(using: defaultStringEncoding) else { + return .failure(PubNubError( + .encryptionError, + additional: ["Cannot create Data from provided \(string)"] + )) + } + return encrypt(data: data).map { + $0.base64EncodedString() + } + } + + func decryptedString(from data: Data) -> Result { + decrypt(data: data).flatMap { + if let stringValue = String(data: $0, encoding: defaultStringEncoding) { + return .success(stringValue) + } else { + return .failure(PubNubError( + .decryptionError, + additional: ["Cannot create String from provided Data \(data)"]) + ) + } + } + } +} diff --git a/Sources/PubNub/Helpers/Crypto/Cryptors/AESCBCCryptor.swift b/Sources/PubNub/Helpers/Crypto/Cryptors/AESCBCCryptor.swift new file mode 100644 index 00000000..549dd1b8 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Cryptors/AESCBCCryptor.swift @@ -0,0 +1,160 @@ +// +// ImprovedCryptoAlgorithm.swift +// +// PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks +// Copyright © 2023 PubNub Inc. +// https://www.pubnub.com/ +// https://www.pubnub.com/terms +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import CommonCrypto + +public struct AESCBCCryptor: Cryptor { + private let key: Data + + public init(key: String) { + self.key = CryptorUtils.SHA256.hash(from: key.data(using: .utf8) ?? Data()) + } + + public var id: CryptorId { + [0x41, 0x43, 0x52, 0x48] + } + + public func encrypt(data: Data) -> Result { + do { + let ivGenerator = CryptorVector.random(bytesCount: kCCBlockSizeAES128) + let ivData = try ivGenerator.data() + + let encrypted = try data.crypt( + operation: CCOperation(kCCEncrypt), + algorithm: CCAlgorithm(kCCAlgorithmAES128), + options: CCOptions(kCCOptionPKCS7Padding), + blockSize: kCCBlockSizeAES128, + key: key, + initializationVector: ivData, + messageData: data + ) + + return .success(EncryptedData( + metadata: ivData, + data: encrypted + )) + } catch { + return .failure(PubNubError( + .decryptionError, + underlying: error + )) + } + } + + public func decrypt(data: EncryptedData) -> Result { + do { + return .success( + try data.data.crypt( + operation: CCOperation(kCCDecrypt), + algorithm: CCAlgorithm(kCCAlgorithmAES128), + options: CCOptions(kCCOptionPKCS7Padding), + blockSize: kCCBlockSizeAES128, + key: key, + initializationVector: data.metadata, + messageData: data.data + ) + ) + } catch { + return .failure(PubNubError( + .decryptionError, + underlying: error + )) + } + } + + public func encrypt(stream: InputStream, contentLength: Int) -> Result { + do { + let ivGenerator = CryptorVector.random(bytesCount: kCCBlockSizeAES128) + let ivData = try ivGenerator.data() + + let cryptoInputStreamCipher = CryptoInputStream.Cipher( + algorithm: CCAlgorithm(kCCAlgorithmAES128), + blockSize: kCCBlockSizeAES128 + ) + let dataForCryptoInputStream = CryptoInputStream.DataSource( + key: key, + iv: ivData, + options: CCOptions(kCCOptionPKCS7Padding), + cipher: cryptoInputStreamCipher + ) + let cryptoInputStream = CryptoInputStream( + operation: .encrypt, + input: stream, + contentLength: contentLength, + with: dataForCryptoInputStream + ) + return .success(EncryptedStreamData( + stream: cryptoInputStream, + contentLength: cryptoInputStream.estimatedCryptoCount, + metadata: ivData + )) + } catch { + return .failure(PubNubError( + .encryptionError, + underlying: error + )) + } + } + + public func decrypt(data: EncryptedStreamData, outputPath: URL) -> Result { + do { + let cryptoInputStreamCipher = CryptoInputStream.Cipher( + algorithm: CCAlgorithm(kCCAlgorithmAES128), + blockSize: kCCBlockSizeAES128 + ) + let dataForCryptoInputStream = CryptoInputStream.DataSource( + key: key, + iv: data.metadata, + options: CCOptions(kCCOptionPKCS7Padding), + cipher: cryptoInputStreamCipher + ) + let cryptoInputStream = CryptoInputStream( + operation: .decrypt, + input: data.stream, + contentLength: data.contentLength, + with: dataForCryptoInputStream + ) + try cryptoInputStream.writeEncodedData( + to: outputPath + ) + if let stream = InputStream(url: outputPath) { + return .success(stream) + } else { + return .failure(PubNubError( + .decryptionError, + additional: ["Cannot create final decoded stream"]) + ) + } + } catch { + return .failure(PubNubError( + .decryptionError, + underlying: error + )) + } + } +} diff --git a/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift b/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift new file mode 100644 index 00000000..858e34d5 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift @@ -0,0 +1,51 @@ +// +// CryptoAlgorithm.swift +// +// PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks +// Copyright © 2023 PubNub Inc. +// https://www.pubnub.com/ +// https://www.pubnub.com/terms +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import CommonCrypto + +public struct EncryptedData { + let metadata: Data + let data: Data +} + +public struct EncryptedStreamData { + let stream: InputStream + let contentLength: Int + let metadata: Data +} + +public typealias CryptorId = [UInt8] + +public protocol Cryptor { + var id: CryptorId { get } + + func encrypt(data: Data) -> Result + func decrypt(data: EncryptedData) -> Result + func encrypt(stream: InputStream, contentLength: Int) -> Result + func decrypt(data: EncryptedStreamData, outputPath: URL) -> Result +} diff --git a/Sources/PubNub/Helpers/Crypto/Cryptors/LegacyCryptor.swift b/Sources/PubNub/Helpers/Crypto/Cryptors/LegacyCryptor.swift new file mode 100644 index 00000000..a40bc26e --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Cryptors/LegacyCryptor.swift @@ -0,0 +1,180 @@ +// +// LegacyCryptoAlgorithm.swift +// +// PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks +// Copyright © 2023 PubNub Inc. +// https://www.pubnub.com/ +// https://www.pubnub.com/terms +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import CommonCrypto + +public struct LegacyCryptor: Cryptor { + private let key: Data + private let withRandomIV: Bool + + static let legacyCryptorId: CryptorId = [0x00, 0x00, 0x00, 0x00] + + public init(key: String, withRandomIV: Bool = true) { + let hash = CryptorUtils.SHA256.hash(from: key.data(using: .utf8) ?? Data()) + let hexStrData = CryptorUtils.hexFrom(hash).lowercased(with: .current).data(using: .utf8) ?? Data() + self.key = hexStrData + self.withRandomIV = withRandomIV + } + + public var id: CryptorId { + Self.legacyCryptorId + } + + public func encrypt(data: Data) -> Result { + do { + let vectorGen = withRandomIV ? CryptorVector.random(bytesCount: kCCBlockSizeAES128) : CryptorVector.fixed + let ivData = try vectorGen.data() + + let encrypted = try data.crypt( + operation: CCOperation(kCCEncrypt), + algorithm: CCAlgorithm(kCCAlgorithmAES128), + options: CCOptions(kCCOptionPKCS7Padding), + blockSize: kCCBlockSizeAES128, + key: key, + initializationVector: ivData, + messageData: data + ) + + // Join IV and encrypted content when using a random IV + return .success(EncryptedData( + metadata: ivData, + data: vectorGen.isRandom() ? ivData + encrypted : encrypted + )) + } catch { + return .failure(PubNubError( + .decryptionError, + underlying: error + )) + } + } + + public func decrypt(data: EncryptedData) -> Result { + let iv: Data + let cipherText: Data + + do { + if withRandomIV { + iv = data.data.prefix(kCCBlockSizeAES128) + cipherText = data.data.suffix(from: kCCBlockSizeAES128) + } else { + iv = try CryptorVector.fixed.data() + cipherText = data.data + } + + return .success( + try data.data.crypt( + operation: CCOperation(kCCDecrypt), + algorithm: CCAlgorithm(kCCAlgorithmAES128), + options: CCOptions(kCCOptionPKCS7Padding), + blockSize: kCCBlockSizeAES128, + key: key, + initializationVector: iv, + messageData: cipherText + ) + ) + } catch { + return .failure(PubNubError( + .decryptionError, + underlying: error + )) + } + } + + public func encrypt(stream: InputStream, contentLength: Int) -> Result { + do { + // Always uses random IV for InputStream processing + let ivGenerator = CryptorVector.random(bytesCount: kCCBlockSizeAES128) + let iv = try ivGenerator.data() + + let cryptoInputStreamCipher = CryptoInputStream.Cipher( + algorithm: CCAlgorithm(kCCAlgorithmAES128), + blockSize: kCCBlockSizeAES128 + ) + let dataForCryptoInputStream = CryptoInputStream.DataSource( + key: key, + iv: iv, + options: CCOptions(kCCOptionPKCS7Padding), + cipher: cryptoInputStreamCipher + ) + let cryptoInputStream = CryptoInputStream( + operation: .encrypt, + input: stream, + contentLength: contentLength, + with: dataForCryptoInputStream, + includeInitializationVectorInContent: true + ) + return .success(EncryptedStreamData( + stream: cryptoInputStream, + contentLength: cryptoInputStream.estimatedCryptoCount, + metadata: iv + )) + } catch { + return .failure(PubNubError( + .encryptionError, + underlying: error + )) + } + } + + public func decrypt(data: EncryptedStreamData, outputPath: URL) -> Result { + do { + let cryptoInputStreamCipher = CryptoInputStream.Cipher( + algorithm: CCAlgorithm(kCCAlgorithmAES128), + blockSize: kCCBlockSizeAES128 + ) + let dataForCryptoInputStream = CryptoInputStream.DataSource( + key: key, + iv: data.metadata, + options: CCOptions(kCCOptionPKCS7Padding), + cipher: cryptoInputStreamCipher + ) + let cryptoInputStream = CryptoInputStream( + operation: .decrypt, + input: data.stream, + contentLength: data.contentLength, + with: dataForCryptoInputStream, + includeInitializationVectorInContent: true + ) + try cryptoInputStream.writeEncodedData( + to: outputPath + ) + if let inputStream = InputStream(url: outputPath) { + return .success(inputStream) + } + return .failure(PubNubError( + .decryptionError, + additional: ["Cannot create resulting InputStream at \(outputPath)"] + )) + } catch { + return .failure(PubNubError( + .decryptionError, + underlying: error + )) + } + } +} diff --git a/Sources/PubNub/Helpers/Crypto/Header/CryptorHeader.swift b/Sources/PubNub/Helpers/Crypto/Header/CryptorHeader.swift new file mode 100644 index 00000000..d158c834 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Header/CryptorHeader.swift @@ -0,0 +1,159 @@ +// +// CryptorHeader.swift +// +// PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks +// Copyright © 2023 PubNub Inc. +// https://www.pubnub.com/ +// https://www.pubnub.com/terms +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import CommonCrypto + +private let sentinel = "PNED" + +enum CryptorHeader: Equatable { + case none + case v1(cryptorId: CryptorId, data: Data) + + func length() -> Int { + asData().count + } + + func metadataIfAny() -> Data { + switch self { + case .none: + return Data() + case .v1(_, let data): + return data + } + } + + func cryptorId() -> CryptorId { + switch self { + case .none: + return LegacyCryptor.legacyCryptorId + case .v1(let cryptorId, _): + return cryptorId + } + } + + func asData() -> Data { + guard case .v1(let cryptorId, let data) = self else { + return Data() + } + var finalData = sentinel.data(using: .ascii) ?? Data() + finalData += Data(bytes: [1], count: 1) + finalData += Data(bytes: cryptorId, count: cryptorId.count) + + if data.count < 255 { + finalData += Data(bytes: [data.count], count: 1) + finalData += data + } else { + finalData += Data(bytes: [0xFF, data.count & 0xFF, data.count >> 8], count: 3) + finalData += data + } + return finalData + } + + static func from(data: Data) throws -> Self { + try CryptorHeaderParser(data: data).parse() + } +} + +fileprivate class CryptorHeaderDataScanner { + private var nextIndex: Int = 0 + private let data: Data + + init(data: Data) { + self.data = data + } + + func nextBytes(_ count: Int) -> Data? { + let previousValue = nextIndex + let newValue = nextIndex + count + + guard newValue <= data.count else { return nil } + nextIndex = newValue + + return data.subdata(in: previousValue.. UInt8? { + nextBytes(1)?.first + } + + func bytesRead() -> Data { + data.suffix(nextIndex) + } +} + +struct CryptorHeaderParser { + private let scanner: CryptorHeaderDataScanner + + init(data: Data) { + self.scanner = CryptorHeaderDataScanner(data: data) + } + + func parseAndReturnProcessedBytes() throws -> (header: CryptorHeader, bytesProcessed: Data) { + return (header: try parse(), bytesProcessed: scanner.bytesRead()) + } + + func parse() throws -> CryptorHeader { + guard let possibleSentinelBytes = scanner.nextBytes(4) else { + return .none + } + guard let sentinelString = String(data: possibleSentinelBytes, encoding: .ascii) else { + return .none + } + guard sentinelString == sentinel else { + return .none + } + guard let headerVersion = scanner.nextByte() else { + throw PubNubError(.unknownCryptorError, additional: ["Cannot find Crypto header version"]) + } + guard (1...1).contains(headerVersion) else { + throw PubNubError(.unknownCryptorError, additional: ["Invalid Crypto header version \(headerVersion)"]) + } + guard let cryptorId = scanner.nextBytes(4) else { + throw PubNubError(.unknownCryptorError, additional: ["Cannot find Cryptor identifier"]) + } + guard let cryptorDataSize = scanner.nextByte() else { + throw PubNubError(.unknownCryptorError, additional: ["Cannot read Cryptor data size"]) + } + guard let cryptorDefinedData = scanner.nextBytes(Int(try computeCryptorDataSize(with: cryptorDataSize))) else { + throw PubNubError(.unknownCryptorError, additional: ["Cannot retrieve Cryptor defined data"]) + } + return .v1(cryptorId: cryptorId.map { $0 }, data: cryptorDefinedData) + } + + private func computeCryptorDataSize(with sizeIndicator: UInt8) throws -> UInt16 { + if sizeIndicator < UInt8.max { + return UInt16(sizeIndicator) + } + guard let nextBytes = scanner.nextBytes(2) else { + throw PubNubError(.unknownCryptorError, additional: ["Cannot read next Cryptor data size bytes"]) + } + return nextBytes.withUnsafeBytes { + $0.load(as: UInt16.self) + } + } +} diff --git a/Sources/PubNub/Helpers/Crypto/Header/CryptorHeaderWithinStreamFinder.swift b/Sources/PubNub/Helpers/Crypto/Header/CryptorHeaderWithinStreamFinder.swift new file mode 100644 index 00000000..a7aad078 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Header/CryptorHeaderWithinStreamFinder.swift @@ -0,0 +1,80 @@ +// +// CryptorHeaderFinder.swift +// +// PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks +// Copyright © 2023 PubNub Inc. +// https://www.pubnub.com/ +// https://www.pubnub.com/terms +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +struct CryptorHeaderWithinStreamFinder { + let stream: InputStream + + func findHeader() throws -> (header: CryptorHeader, continuationStream: InputStream) { + let possibleHeaderBytes = read(maxLength: 100) + let parsingRes = try CryptorHeaderParser(data: possibleHeaderBytes).parseAndReturnProcessedBytes() + let noOfBytesProcessedByParser = parsingRes.bytesProcessed.count + let continuationStream: InputStream + + switch parsingRes.header { + case .none: + continuationStream = MultipartInputStream( + inputStreams: [InputStream(data: possibleHeaderBytes), stream] + ) + default: + continuationStream = MultipartInputStream( + inputStreams: [InputStream(data: possibleHeaderBytes.suffix(from: noOfBytesProcessedByParser)), stream] + ) + } + return ( + header: parsingRes.header, + continuationStream: continuationStream + ) + } + + private func read(maxLength: Int) -> Data { + var buffer = [UInt8](repeating: 0, count: maxLength) + var numberOfBytesRead = 0 + var content: [UInt8] = [] + + if stream.streamStatus == .notOpen { + stream.open() + } + repeat { + numberOfBytesRead = stream.read( + &buffer, + maxLength: maxLength + ) + if numberOfBytesRead > 0 && buffer.count != numberOfBytesRead { + buffer = Array(buffer[0 ..< numberOfBytesRead]) + } + content += buffer + + } while numberOfBytesRead < maxLength && stream.hasBytesAvailable; + + return Data( + bytes: content, + count: content.count + ) + } +} diff --git a/Sources/PubNub/Helpers/Streams/CryptoInputStream.swift b/Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptoInputStream.swift similarity index 78% rename from Sources/PubNub/Helpers/Streams/CryptoInputStream.swift rename to Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptoInputStream.swift index 2104466a..976545c9 100644 --- a/Sources/PubNub/Helpers/Streams/CryptoInputStream.swift +++ b/Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptoInputStream.swift @@ -26,11 +26,30 @@ // import Foundation +import CommonCrypto /// A stream that provides read-only stream functionality while performing crypto operations public class CryptoInputStream: InputStream { // swiftlint:disable:previous type_body_length - + public struct DataSource { + let key: Data + let iv: Data + let options: CCOptions + let cipher: Cipher + } + public struct Cipher { + let algorithm: CCAlgorithm + let blockSize: Int + + func outputSize(from inputBytes: Int) -> Int { + inputBytes + (blockSize - (inputBytes % blockSize)) + } + } + public enum Operation { + case decrypt + case encrypt + } + /// Estimated size of the final crypted output public var estimatedCryptoCount: Int = 0 @@ -42,8 +61,8 @@ public class CryptoInputStream: InputStream { private var rawDataRead: Int = 0 private var cryptoStream: CryptoStream? - private var operation: Crypto.Operation - private var crypto: Crypto + private var operation: Operation + private var crypto: DataSource // Buffer for data which has been encrypted / decrypted from cipher stream. private var cryptedBuffer: [UInt8]? @@ -57,38 +76,63 @@ public class CryptoInputStream: InputStream { private weak var _delegate: StreamDelegate? private var _streamStatus: Stream.Status = .notOpen private var _streamError: Error? - - public init(_ operation: Crypto.Operation, input: InputStream, contentLength: Int, with crypto: Crypto) { + + // A flag describing whether an IV vector is included at the beginning of encoded/decoded content + private let includeInitializationVectorInContent: Bool + + public init( + operation: CryptoInputStream.Operation, + input: InputStream, + contentLength: Int, + with crypto: CryptoInputStream.DataSource, + includeInitializationVectorInContent: Bool = false + ) { self.operation = operation self.crypto = crypto - // We should always be using a random IV - self.crypto.randomizeIV = true - - rawDataLength = contentLength - (operation == .decrypt ? crypto.cipher.blockSize : 0) - // The estimated content length is the IV length plus the crypted length - estimatedCryptoCount = crypto.cipher.blockSize + crypto.cipher.outputSize(from: rawDataLength) + self.includeInitializationVectorInContent = includeInitializationVectorInContent + + if includeInitializationVectorInContent { + rawDataLength = contentLength - (operation == .decrypt ? crypto.cipher.blockSize : 0) + // The estimated content length is the IV length plus the crypted length + estimatedCryptoCount = crypto.cipher.blockSize + crypto.cipher.outputSize(from: rawDataLength) + } else { + rawDataLength = contentLength + estimatedCryptoCount = crypto.cipher.outputSize(from: rawDataLength) + } cipherStream = input // required because `init()` is not marked as a designated initializer super.init(data: Data()) } - public convenience init?(_ operation: Crypto.Operation, url: URL, with crypto: Crypto) { + public convenience init?( + operation: CryptoInputStream.Operation, + url: URL, + with crypto: CryptoInputStream.DataSource + ) { // Create a stream from the content source guard let plaintextStream = InputStream(url: url) else { PubNub.log.error("Could not create `SecureInputStream` due to underlying InputStream(url:) failing for \(url)") return nil } - self.init(operation, input: plaintextStream, contentLength: url.sizeOf, with: crypto) + self.init(operation: operation, input: plaintextStream, contentLength: url.sizeOf, with: crypto) } - public convenience init(_ operation: Crypto.Operation, data: Data, with crypto: Crypto) { - self.init(operation, input: InputStream(data: data), contentLength: data.count, with: crypto) + public convenience init( + operation: CryptoInputStream.Operation, + data: Data, + with crypto: CryptoInputStream.DataSource + ) { + self.init(operation: operation, input: InputStream(data: data), contentLength: data.count, with: crypto) } - public convenience init?(_ operation: Crypto.Operation, fileAtPath path: String, with crypto: Crypto) { - self.init(operation, url: URL(fileURLWithPath: path), with: crypto) + public convenience init?( + operation: CryptoInputStream.Operation, + fileAtPath path: String, + with crypto: DataSource + ) { + self.init(operation: operation, url: URL(fileURLWithPath: path), with: crypto) } deinit { @@ -304,21 +348,25 @@ public class CryptoInputStream: InputStream { } else { // Init the Crypto Stream do { - // Create a randomized buffer of data the length of the cipher block size - let ivBuffer = try Crypto.randomInitializationVector(byteCount: crypto.cipher.blockSize) - cryptedBuffer = ivBuffer - + let ivBuffer = crypto.iv.map { $0 } + + if includeInitializationVectorInContent { + cryptedBuffer = ivBuffer + } cryptedBufferRead = 0 encryptStream = try CryptoStream( - operation: operation, algorithm: crypto.cipher, options: Crypto.paddingLength, - keyBuffer: crypto.key.map { $0 }, keyLength: crypto.key.count, + operation: CCOperation(operation == .decrypt ? kCCDecrypt : kCCEncrypt), + algorithm: crypto.cipher.algorithm, + options: crypto.options, + keyBuffer: crypto.key.map { $0 }, + keyLength: crypto.key.count, ivBuffer: ivBuffer ) cryptedDataLength = encryptStream.getOutputLength( inputLength: rawDataLength, isFinal: true - ) + crypto.cipher.blockSize + ) + (includeInitializationVectorInContent ? crypto.cipher.blockSize : 0) } catch { _streamError = error _streamStatus = .error @@ -348,23 +396,30 @@ public class CryptoInputStream: InputStream { } else { // Create a buffer to store the IV in that matches the cipher block size var initializationVectorBuffer = [UInt8](repeating: 0, count: crypto.cipher.blockSize) - - switch cipherStream.read(&initializationVectorBuffer, maxLength: crypto.cipher.blockSize) { - case let bytesRead where bytesRead < 0: - // -1 means that the operation failed; more information about the error can be obtained with `streamError`. - _streamStatus = .error - _streamError = cipherStream.streamError - return bytesRead - default: - // 0 represents end of the current buffer - break + + if includeInitializationVectorInContent { + switch cipherStream.read(&initializationVectorBuffer, maxLength: crypto.cipher.blockSize) { + case let bytesRead where bytesRead < 0: + // -1 means that the operation failed; more information about the error can be obtained with `streamError`. + _streamStatus = .error + _streamError = cipherStream.streamError + return bytesRead + default: + // 0 represents end of the current buffer + break + } + } else { + initializationVectorBuffer = crypto.iv.map { $0 } } - + // Init the Crypto Stream do { decryptStream = try CryptoStream( - operation: operation, algorithm: crypto.cipher, options: Crypto.paddingLength, - keyBuffer: crypto.key.map { $0 }, keyLength: crypto.key.count, + operation: CCOperation(operation == .decrypt ? kCCDecrypt : kCCEncrypt), + algorithm: crypto.cipher.algorithm, + options: crypto.options, + keyBuffer: crypto.key.map { $0 }, + keyLength: crypto.key.count, ivBuffer: initializationVectorBuffer ) } catch { diff --git a/Sources/PubNub/Helpers/Crypto/CryptoStream.swift b/Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptoStream.swift similarity index 95% rename from Sources/PubNub/Helpers/Crypto/CryptoStream.swift rename to Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptoStream.swift index 93d30264..0a23a8af 100644 --- a/Sources/PubNub/Helpers/Crypto/CryptoStream.swift +++ b/Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptoStream.swift @@ -30,9 +30,6 @@ import Foundation /// Encrypts of Decrypts a stream of data public class CryptoStream { - /// The operation that will be performed - public let operation: Crypto.Operation - /// A pointer to the returned CCCryptorRef. private var context = UnsafeMutablePointer.allocate(capacity: 1) @@ -46,12 +43,12 @@ public class CryptoStream { /// - keyLength: Length of key material. /// - ivBuffer: Initialization vector material public init( - operation: Crypto.Operation, algorithm: Crypto.Cipher, options: CCOptions, + operation: CCOperation, algorithm: CCAlgorithm, options: CCOptions, keyBuffer: UnsafeRawPointer, keyLength: Int, ivBuffer: UnsafeRawPointer ) throws { let status = CCCryptorCreate( - operation.ccValue, - algorithm.rawValue, + operation, + algorithm, options, keyBuffer, keyLength, @@ -62,8 +59,6 @@ public class CryptoStream { if status != kCCSuccess { throw CryptoError(from: status) } - - self.operation = operation } //// Process (encrypt, decrypt) some data. The result, if any, is written to a caller-provided buffer. diff --git a/Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptorUtils.swift b/Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptorUtils.swift new file mode 100644 index 00000000..9fa3abf4 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptorUtils.swift @@ -0,0 +1,48 @@ +// +// CryptoUtils.swift +// +// PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks +// Copyright © 2023 PubNub Inc. +// https://www.pubnub.com/ +// https://www.pubnub.com/terms +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import CommonCrypto + +enum CryptorUtils { + enum SHA256 { + static func hash(from data: Data) -> Data { + var hash = [UInt8]( + repeating: 0, + count: Int(CC_SHA256_DIGEST_LENGTH) + ) + data.withUnsafeBytes { + _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) + } + return Data(hash) + } + } + static func hexFrom(_ data: Data) -> String { + let midpoint = data.count / 2 + return data[.. Data { + switch self { + case .fixed: + return try staticInitializationVector() + case .random(let byteCount): + return try randomInitializationVector(with: byteCount) + } + } + + func isFixed() -> Bool { + if case .fixed = self { + return true + } else { + return false + } + } + + func isRandom() -> Bool { + if case .random(_) = self { + return true + } else { + return false + } + } + + private func staticInitializationVector() throws -> Data { + guard let initializationVector = "0123456789012345".data(using: .utf8) else { + throw CryptoError.rngFailure + } + return initializationVector + } + + private func randomInitializationVector(with byteCount: Int) throws -> Data { + var bytes: [UInt8] = Array(repeating: UInt8(0), count: byteCount) + let status = CCRandomGenerateBytes(&bytes, byteCount) + + if status == kCCSuccess { + return Data(bytes: bytes, count: byteCount) + } else { + throw CryptoError(from: status) + } + } +} diff --git a/Sources/PubNub/Helpers/Crypto/Miscellaneous/Data+CommonCrypto.swift b/Sources/PubNub/Helpers/Crypto/Miscellaneous/Data+CommonCrypto.swift new file mode 100644 index 00000000..b1c092e0 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Miscellaneous/Data+CommonCrypto.swift @@ -0,0 +1,79 @@ +// +// Data+CommonCrypto.swift +// +// PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks +// Copyright © 2023 PubNub Inc. +// https://www.pubnub.com/ +// https://www.pubnub.com/terms +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import CommonCrypto + +extension Data { + func crypt( + operation: CCOperation, + algorithm: CCAlgorithm, + options: CCOptions, + blockSize: Int, + key: Data, + initializationVector: Data, + messageData dataIn: Data, + dataMovedOut _: Int = 0 + ) throws -> Data { + return try key.withUnsafeBytes { keyUnsafeRawBufferPointer in + try dataIn.withUnsafeBytes { dataInUnsafeRawBufferPointer in + try initializationVector.withUnsafeBytes { ivUnsafeRawBufferPointer in + let paddingSize = operation == kCCEncrypt ? blockSize : 0 + let dataOutSize: Int = dataIn.count + paddingSize + let dataOut = UnsafeMutableRawPointer.allocate(byteCount: dataOutSize, alignment: 1) + defer { dataOut.deallocate() } + var dataOutMoved: Int = 0 + let status = CCCrypt( + operation, + algorithm, + options, + keyUnsafeRawBufferPointer.baseAddress, + key.count, + ivUnsafeRawBufferPointer.baseAddress, + dataInUnsafeRawBufferPointer.baseAddress, + dataIn.count, + dataOut, + dataOutSize, + &dataOutMoved + ) + if let error = CryptoError(rawValue: status) { + if error == .bufferTooSmall { + return try crypt( + operation: operation, algorithm: algorithm, options: options, + blockSize: blockSize, key: key, + initializationVector: initializationVector, messageData: dataIn, + dataMovedOut: dataOutMoved + ) + } + throw error + } + return Data(bytes: dataOut, count: dataOutMoved) + } + } + } + } +} diff --git a/Sources/PubNub/Helpers/Streams/MultipartInputStream.swift b/Sources/PubNub/Helpers/Streams/MultipartInputStream.swift index 964cad3a..2a868dd0 100644 --- a/Sources/PubNub/Helpers/Streams/MultipartInputStream.swift +++ b/Sources/PubNub/Helpers/Streams/MultipartInputStream.swift @@ -124,7 +124,7 @@ class MultipartInputStream: InputStream { } override func open() { - guard _streamStatus == .open else { + guard _streamStatus != .open else { return } _streamStatus = .open diff --git a/Sources/PubNub/Networking/HTTPFileTask.swift b/Sources/PubNub/Networking/HTTPFileTask.swift index 0dd8f8c3..2f2caece 100644 --- a/Sources/PubNub/Networking/HTTPFileTask.swift +++ b/Sources/PubNub/Networking/HTTPFileTask.swift @@ -236,7 +236,7 @@ public class HTTPFileDownloadTask: HTTPFileTask { /// The block that is called when the task completes public var completionBlock: ((Result) -> Void)? /// The crypto object that will attempt to decrypt the file - public var crypto: Crypto? + public var cryptorModule: CryptorModule? /// The location where the temporary downloaded file should be copied public private(set) var destinationURL: URL @@ -250,19 +250,25 @@ public class HTTPFileDownloadTask: HTTPFileTask { (urlSessionTask as? URLSessionDownloadTask)?.cancel(byProducingResumeData: byProducingResumeData) } - init(task: URLSessionDownloadTask, session identifier: String?, downloadTo url: URL, crypto: Crypto?) { - destinationURL = url - self.crypto = crypto + init(task: URLSessionDownloadTask, session identifier: String?, downloadTo url: URL, cryptorModule: CryptorModule?) { + self.destinationURL = url + self.cryptorModule = cryptorModule super.init(task: task, session: identifier) } - func decrypt(_ encryptedURL: URL, to outpuURL: URL, using crypto: Crypto) throws { - // If we were provided a crypto object we should try and decrypt the file - guard let secureStream = CryptoInputStream(.decrypt, url: encryptedURL, with: crypto) else { + func decrypt(_ encryptedURL: URL, to outpuURL: URL, using cryptorModule: CryptorModule) throws { + // If we were provided a Crypto object we should try and decrypt the file + + guard let inputStream = InputStream(url: encryptedURL) else { throw PubNubError(.streamCouldNotBeInitialized, additional: [encryptedURL.absoluteString]) } - try secureStream.writeEncodedData(to: outpuURL) + + _ = cryptorModule.decrypt( + stream: inputStream, + contentLength: encryptedURL.sizeOf, + to: outpuURL + ) } open func temporaryURL() -> URL { @@ -318,15 +324,19 @@ public class HTTPFileDownloadTask: HTTPFileTask { // Update destination to be a unique file destinationURL = fileManager.makeUniqueFilename(destinationURL) - if let crypto = crypto { + if let cryptorModule = cryptorModule { // Set the encrypted in case something goes wrong encryptedURL = url - - // If we were provided a crypto object we should try and decrypt the file - guard let secureStream = CryptoInputStream(.decrypt, url: url, with: crypto) else { + + guard let stream = InputStream(url: url) else { throw PubNubError(.streamCouldNotBeInitialized, additional: [url.absoluteString]) } - try secureStream.writeEncodedData(to: destinationURL) + + _ = cryptorModule.decrypt( + stream: stream, + contentLength: url.sizeOf, + to: destinationURL + ) } else { try fileManager.moveItem(at: url, to: destinationURL) } diff --git a/Sources/PubNub/Networking/HTTPRouter.swift b/Sources/PubNub/Networking/HTTPRouter.swift index 57464ab3..e4951a08 100644 --- a/Sources/PubNub/Networking/HTTPRouter.swift +++ b/Sources/PubNub/Networking/HTTPRouter.swift @@ -43,8 +43,8 @@ public protocol RouterConfiguration { var authKey: String? { get } /// If Access Manager (PAM) is enabled, client will use `authToken` instead of `authKey` on all requests var authToken: String? { get } - /// If set, all communication will be encrypted with this key - var cipherKey: Crypto? { get } + /// If set, all communication will be encrypted with this module + var cryptorModule: CryptorModule? { get } /// Whether a request identifier should be included on outgoing requests var useRequestId: Bool { get } /// Ordered list of key-value pairs which identify various consumers. diff --git a/Sources/PubNub/Networking/Routers/HistoryRouter.swift b/Sources/PubNub/Networking/Routers/HistoryRouter.swift index a676ddfb..5bb6a1fb 100644 --- a/Sources/PubNub/Networking/Routers/HistoryRouter.swift +++ b/Sources/PubNub/Networking/Routers/HistoryRouter.swift @@ -188,7 +188,7 @@ struct MessageHistoryResponseDecoder: ResponseDecoder { response: EndpointResponse ) -> Result, Error> { // End early if we don't have a cipher key - guard let crypto = response.router.configuration.cipherKey else { + guard let cryptorModule = response.router.configuration.cryptorModule else { return .success(response) } @@ -200,28 +200,23 @@ struct MessageHistoryResponseDecoder: ResponseDecoder { // Convert base64 string into Data if let messageData = message.message.dataOptional { // If a message fails we just return the original and move on - do { - let decryptedPayload = try crypto.decrypt(encrypted: messageData).get() - if let decodedString = String(bytes: decryptedPayload, encoding: crypto.defaultStringEncoding) { - messages[index] = MessageHistoryMessagePayload( - message: AnyJSON(reverse: decodedString), - timetoken: message.timetoken, - meta: message.meta, - uuid: message.uuid, - messageType: message.messageType - ) - } else { - // swiftlint:disable:next line_length - PubNub.log.error("Decrypted History payload data failed to stringify for base64 encoded payload \(decryptedPayload.base64EncodedString())") - } - } catch { + switch cryptorModule.decryptedString(from: messageData) { + case .success(let decodedString): + messages[index] = MessageHistoryMessagePayload( + message: AnyJSON(reverse: decodedString), + timetoken: message.timetoken, + meta: message.meta, + uuid: message.uuid, + messageType: message.messageType + ) + case .failure(let error): PubNub.log.error("History message failed to decrypt due to \(error)") } } } return messages } - + // Replace previous payload with decrypted one let decryptedPayload = MessageHistoryResponse(status: response.payload.status, error: response.payload.error, @@ -243,9 +238,9 @@ struct MessageHistoryResponse: Codable { let error: Bool let errorMessage: String let channels: [String: [MessageHistoryMessagePayload]] - + let start: Timetoken? - + enum CodingKeys: String, CodingKey { case errorMessage = "error_message" case error @@ -253,11 +248,11 @@ struct MessageHistoryResponse: Codable { case channels case more } - + enum MoreCodingKeys: String, CodingKey { case start } - + init( status: Int = 200, error: Bool = false, @@ -271,25 +266,25 @@ struct MessageHistoryResponse: Codable { self.channels = channels self.start = start } - + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) status = try container.decode(Int.self, forKey: .status) error = try container.decode(Bool.self, forKey: .error) errorMessage = try container.decode(String.self, forKey: .errorMessage) channels = try container.decodeIfPresent([String: [MessageHistoryMessagePayload]].self, forKey: .channels) ?? [:] - + let moreContainer = try? container.nestedContainer(keyedBy: MoreCodingKeys.self, forKey: .more) start = Timetoken(try moreContainer?.decodeIfPresent(String.self, forKey: .start) ?? "") } - + func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(status, forKey: .status) try container.encode(error, forKey: .error) try container.encode(errorMessage, forKey: .errorMessage) try container.encode(channels, forKey: .channels) - + var moreContainer = container.nestedContainer(keyedBy: MoreCodingKeys.self, forKey: .more) try moreContainer.encodeIfPresent(start?.description, forKey: .start) } @@ -299,14 +294,14 @@ struct MessageHistoryMessagePayload: Codable { typealias ActionType = String typealias ActionValue = String typealias RawMessageAction = [ActionType: [ActionValue: [MessageHistoryMessageAction]]] - + let message: AnyJSON let timetoken: Timetoken let meta: AnyJSON? let uuid: String? let messageType: PubNubMessageType? let actions: RawMessageAction - + init( message: JSONCodable, timetoken: Timetoken = 0, @@ -322,7 +317,7 @@ struct MessageHistoryMessagePayload: Codable { self.meta = meta?.codableValue self.actions = actions } - + enum CodingKeys: String, CodingKey { case message case timetoken @@ -331,10 +326,10 @@ struct MessageHistoryMessagePayload: Codable { case messageType = "message_type" case actions } - + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - + message = try container.decode(AnyJSON.self, forKey: .message) meta = try container.decodeIfPresent(AnyJSON.self, forKey: .meta) uuid = try container.decodeIfPresent(String.self, forKey: .uuid) @@ -342,10 +337,10 @@ struct MessageHistoryMessagePayload: Codable { timetoken = Timetoken(try container.decode(String.self, forKey: .timetoken)) ?? 0 actions = try container.decodeIfPresent(RawMessageAction.self, forKey: .actions) ?? [:] } - + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - + try container.encode(message, forKey: .message) try container.encode(timetoken.description, forKey: .timetoken) try container.encodeIfPresent(meta, forKey: .meta) @@ -358,26 +353,26 @@ struct MessageHistoryMessagePayload: Codable { struct MessageHistoryMessageAction: Codable, Hashable { let uuid: String let actionTimetoken: Timetoken - + init(uuid: String, actionTimetoken: Timetoken) { self.uuid = uuid self.actionTimetoken = actionTimetoken } - + enum CodingKeys: String, CodingKey { case uuid case actionTimetoken } - + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) uuid = try container.decode(String.self, forKey: .uuid) actionTimetoken = Timetoken(try container.decode(String.self, forKey: .actionTimetoken)) ?? 0 } - + func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - + try container.encode(uuid, forKey: .uuid) try container.encode(actionTimetoken.description, forKey: .actionTimetoken) } @@ -394,13 +389,13 @@ struct MessageCountsResponsePayload: Codable, Hashable { let error: Bool let errorMessage: String let channels: [String: Int] - + enum CodingKeys: String, CodingKey { case status case error case errorMessage = "error_message" case channels } - + // swiftlint:disable:next file_length } diff --git a/Sources/PubNub/Networking/Routers/PublishRouter.swift b/Sources/PubNub/Networking/Routers/PublishRouter.swift index ab148443..7cf7377f 100644 --- a/Sources/PubNub/Networking/Routers/PublishRouter.swift +++ b/Sources/PubNub/Networking/Routers/PublishRouter.swift @@ -92,9 +92,9 @@ struct PublishRouter: HTTPRouter { } func append(message: JSONCodable, to partialPath: String) -> Result { - if let crypto = configuration.cipherKey { + if let cryptorModule = configuration.cryptorModule { return message.jsonDataResult.flatMap { jsonData in - crypto.encrypt(encoded: jsonData) + cryptorModule.encrypt(data: jsonData).mapError { $0 as Error } .flatMap { .success("\(partialPath)\($0.base64EncodedString().urlEncodeSlash.jsonDescription)") } } } @@ -145,9 +145,11 @@ struct PublishRouter: HTTPRouter { var body: Result { switch endpoint { case let .compressedPublish(message, _, _, _, _): - if let crypto = configuration.cipherKey { + if let cryptorModule = configuration.cryptorModule { return message.jsonStringifyResult.flatMap { - crypto.encrypt(plaintext: $0).map { $0.jsonDescription.data(using: .utf8) } + cryptorModule.encrypt(string: $0) + .map { $0.jsonDescription.data(using: .utf8) } + .mapError { $0 as Error } } } return message.jsonDataResult.map { .some($0) } diff --git a/Sources/PubNub/Networking/Routers/SubscribeRouter.swift b/Sources/PubNub/Networking/Routers/SubscribeRouter.swift index 51784c20..8f8be3c4 100644 --- a/Sources/PubNub/Networking/Routers/SubscribeRouter.swift +++ b/Sources/PubNub/Networking/Routers/SubscribeRouter.swift @@ -136,25 +136,19 @@ struct SubscribeDecoder: ResponseDecoder { } } - func decrypt(_ crypto: Crypto, message: SubscribeMessagePayload) -> SubscribeMessagePayload { + func decrypt(_ cryptorModule: CryptorModule, message: SubscribeMessagePayload) -> SubscribeMessagePayload { // Convert base64 string into Data if let messageData = message.payload.dataOptional { // If a message fails we just return the original and move on - do { - let decryptedPayload = try crypto.decrypt(encrypted: messageData).get() - if let decodedString = String(bytes: decryptedPayload, encoding: crypto.defaultStringEncoding) { - // Create mutable copy of payload - var message = message - message.payload = AnyJSON(reverse: decodedString) - - return message - } else { - PubNub.log.error("\(ErrorDescription.cryptoStringEncodeFailed) \(decryptedPayload.base64EncodedString())") - - return message - } - } catch { + switch cryptorModule.decryptedString(from: messageData) { + case .success(let decodedString): + // Create mutable copy of payload + var message = message + message.payload = AnyJSON(reverse: decodedString) + return message + case .failure(let error): PubNub.log.error("Subscribe message failed to decrypt due to \(error)") + return message } } @@ -163,7 +157,7 @@ struct SubscribeDecoder: ResponseDecoder { func decrypt(response: SubscribeEndpointResponse) -> Result { // End early if we don't have a cipher key - guard let crypto = response.router.configuration.cipherKey else { + guard let cryptorModule = response.router.configuration.cryptorModule else { return .success(response) } @@ -171,11 +165,11 @@ struct SubscribeDecoder: ResponseDecoder { for (index, message) in messages.enumerated() { switch message.messageType { case .message: - messages[index] = decrypt(crypto, message: message) + messages[index] = decrypt(cryptorModule, message: message) case .signal: - messages[index] = decrypt(crypto, message: message) + messages[index] = decrypt(cryptorModule, message: message) case .file: - messages[index] = decrypt(crypto, message: message) + messages[index] = decrypt(cryptorModule, message: message) default: messages[index] = message } diff --git a/Sources/PubNub/PubNub.swift b/Sources/PubNub/PubNub.swift index 4c8af428..9d724187 100644 --- a/Sources/PubNub/PubNub.swift +++ b/Sources/PubNub/PubNub.swift @@ -1244,38 +1244,6 @@ public extension PubNub { } } -// MARK: - Crypto - -extension PubNub { - /// Encrypt some `Data` using the configuration Cipher Key value - /// - Parameter message: The plaintext message to be encrypted - /// - Returns: A `Result` containing either the encryped Data or the Crypto Error - func encrypt(message: String) -> Result { - guard let crypto = configuration.cipherKey else { - PubNub.log.error(ErrorDescription.missingCryptoKey) - return .failure(CryptoError.invalidKey) - } - - guard let dataMessage = message.data(using: .utf8) else { - return .failure(CryptoError.decodeError) - } - - return crypto.encrypt(encoded: dataMessage) - } - - /// Decrypt some `Data` using the configuration Cipher Key value - /// - Parameter message: The encrypted `Data` to decrypt - /// - Returns: A `Result` containing either the decrypted plaintext message as `Data` or the Crypto Error - func decrypt(data: Data) -> Result { - guard let crypto = configuration.cipherKey else { - PubNub.log.error(ErrorDescription.missingCryptoKey) - return .failure(CryptoError.invalidKey) - } - - return crypto.decrypt(encrypted: data) - } -} - // MARK: - PAM public extension PubNub { diff --git a/Sources/PubNub/PubNubConfiguration.swift b/Sources/PubNub/PubNubConfiguration.swift index 11d27f09..32842386 100644 --- a/Sources/PubNub/PubNubConfiguration.swift +++ b/Sources/PubNub/PubNubConfiguration.swift @@ -91,7 +91,7 @@ public struct PubNubConfiguration: Hashable { publishKey: String?, subscribeKey: String, userId: String, - cipherKey: Crypto? = nil, + cryptorModule: CryptorModule? = nil, authKey: String? = nil, authToken: String? = nil, useSecureConnections: Bool = true, @@ -112,7 +112,7 @@ public struct PubNubConfiguration: Hashable { self.publishKey = publishKey self.subscribeKey = subscribeKey - self.cipherKey = cipherKey + self.cryptorModule = cryptorModule self.authKey = authKey self.authToken = authToken self.userId = userId @@ -144,7 +144,7 @@ public struct PubNubConfiguration: Hashable { publishKey: String?, subscribeKey: String, uuid: String, - cipherKey: Crypto? = nil, + cryptorModule: CryptorModule? = nil, authKey: String? = nil, authToken: String? = nil, useSecureConnections: Bool = true, @@ -167,7 +167,7 @@ public struct PubNubConfiguration: Hashable { publishKey: publishKey, subscribeKey: subscribeKey, userId: uuid, - cipherKey: cipherKey, + cryptorModule: cryptorModule, authKey: authKey, authToken: authToken, useSecureConnections: useSecureConnections, @@ -189,7 +189,7 @@ public struct PubNubConfiguration: Hashable { /// Specifies the PubNub Subscribe Key to be used when subscribing to a channel public var subscribeKey: String /// If set, all communication will be encrypted with this key - public var cipherKey: Crypto? + public var cryptorModule: CryptorModule? /// If Access Manager (PAM) is enabled, client will use `authKey` on all requests public var authKey: String? /// If Access Manager (PAM) is enabled, client will use `authToken` instead of `authKey` on all requests diff --git a/Sources/PubNub/Subscription/SubscribeSessionFactory.swift b/Sources/PubNub/Subscription/SubscribeSessionFactory.swift index 93db3aab..f11011e1 100644 --- a/Sources/PubNub/Subscription/SubscribeSessionFactory.swift +++ b/Sources/PubNub/Subscription/SubscribeSessionFactory.swift @@ -134,7 +134,7 @@ extension SubscriptionConfiguration { hasher.combine(useSecureConnections.hashValue) hasher.combine(origin.hashValue) hasher.combine(authKey.hashValue) - hasher.combine(cipherKey.hashValue) + hasher.combine(cryptorModule.hashValue) return hasher.finalize() } } diff --git a/Tests/PubNubContractTest/Steps/Subscribe/PubNubSubscribeContractTestSteps.swift b/Tests/PubNubContractTest/Steps/Subscribe/PubNubSubscribeContractTestSteps.swift index fad20b33..aec8e922 100644 --- a/Tests/PubNubContractTest/Steps/Subscribe/PubNubSubscribeContractTestSteps.swift +++ b/Tests/PubNubContractTest/Steps/Subscribe/PubNubSubscribeContractTestSteps.swift @@ -30,11 +30,11 @@ import Foundation import PubNub public class PubNubSubscribeContractTestSteps: PubNubContractTestCase { - fileprivate var cipherKey: Crypto? + fileprivate var cryptorModule: CryptorModule? override public var configuration: PubNubConfiguration { var config = super.configuration - config.cipherKey = cipherKey + config.cryptorModule = cryptorModule if let scenario = self.currentScenario, scenario.name.contains("auto-retry") { config.automaticRetry = AutomaticRetry(retryLimit: 10, policy: .linear(delay: 0.1)) @@ -52,7 +52,7 @@ public class PubNubSubscribeContractTestSteps: PubNubContractTestCase { } override public func handleBeforeHook() { - cipherKey = nil + cryptorModule = nil super.handleBeforeHook() } @@ -64,11 +64,11 @@ public class PubNubSubscribeContractTestSteps: PubNubContractTestCase { startCucumberHookEventsListening() Given("the crypto keyset") { _, _ in - self.cipherKey = Crypto(key: "enigma") + self.cryptorModule = CryptorModule.legacyCryptoModule(with: "enigma") } Given("the invalid-crypto keyset") { _, _ in - self.cipherKey = Crypto(key: "secret") + self.cryptorModule = CryptorModule.legacyCryptoModule(with: "secret") } When("I subscribe") { _, _ in diff --git a/Tests/PubNubTests/Helpers/CryptoTests.swift b/Tests/PubNubTests/Helpers/CryptoTests.swift index 0bd64576..83af5c3f 100644 --- a/Tests/PubNubTests/Helpers/CryptoTests.swift +++ b/Tests/PubNubTests/Helpers/CryptoTests.swift @@ -31,143 +31,135 @@ import XCTest class CryptoTests: XCTestCase { func testEncryptDecrypt_Data() { - let crypto = Crypto(key: "SomeTestString") + let cryptorModule = CryptorModule.legacyCryptoModule(with: "SomeTestString") let testMessage = "Test Message To Be Encrypted" + guard let testData = testMessage.data(using: .utf16) else { return XCTFail("Could not create Data from test string") } - guard let encryptedData = try? crypto?.encrypt(encoded: testData).get() else { + guard let encryptedData = try? cryptorModule.encrypt(data: testData).get() else { return XCTFail("Encrypted Data should not be nil") } - guard let decryptedData = try? crypto?.decrypt(encrypted: encryptedData).get() else { + guard let decryptedData = try? cryptorModule.decrypt(data: encryptedData).get() else { return XCTFail("Decrypted Data should not be nil") } - let decryptedString = String(bytes: decryptedData, encoding: .utf16) + let decryptedString = String( + bytes: decryptedData, + encoding: .utf16 + ) XCTAssertEqual(testMessage, decryptedString) } func testEncryptDecrypt_String() { - let crypto = Crypto(key: "SomeTestString") + let cryptorModule = CryptorModule.legacyCryptoModule(with: "SomeTestString") let testMessage = true.description - guard let encryptedString = try? crypto?.encrypt(plaintext: testMessage).get() else { + + guard let encryptedString = try? cryptorModule.encrypt(string: testMessage).get() else { return XCTFail("Encrypted Data should not be nil") } - - guard let decryptedString = try? crypto?.decrypt(base64Encoded: encryptedString).get() else { + guard + let encryptedStringAsData = Data(base64Encoded: encryptedString), + let decryptedString = try? cryptorModule.decryptedString(from: encryptedStringAsData).get() + else { return XCTFail("Decrypted Data should not be nil") } XCTAssertEqual(testMessage, decryptedString) } func testEncryptDecrypt_JSONString() { - // - let crypto = Crypto(key: "SomeTestString") + let cryptorModule = CryptorModule.legacyCryptoModule(with: "SomeTestString") let testMessage = "Test Message To Be Encrypted" let jsonMessage = testMessage.jsonDescription + guard let testData = jsonMessage.data(using: .utf8) else { return XCTFail("Could not create Data from test string") } - guard let encryptedData = try? crypto?.encrypt(encoded: testData).get() else { + guard let encryptedData = try? cryptorModule.encrypt(data: testData).get() else { return XCTFail("Encrypted Data should not be nil") } - guard let decryptedData = try? crypto?.decrypt(encrypted: encryptedData).get() else { + guard let decryptedData = try? cryptorModule.decrypt(data: encryptedData).get() else { return XCTFail("Decrypted Data should not be nil") } - let decryptedString = String(bytes: decryptedData, encoding: .utf8)?.reverseJSONDescription + let decryptedString = String( + bytes: decryptedData, + encoding: .utf8 + )?.reverseJSONDescription + XCTAssertEqual(testMessage, decryptedString) } func testDefaultRandomizedIVEncryptDecrypt() { let testMessage = "Test Message To Be Encrypted" - guard let crypto = Crypto(key: "MyCoolCipherKey") else { - return XCTFail("Could not create crypto instance") - } - guard let encryptedString1 = try? crypto.encrypt(plaintext: testMessage).get() else { + let cryptorModule = CryptorModule.legacyCryptoModule(with: "MyCoolCipherKey") + + guard let encryptedString1 = try? cryptorModule.encrypt(string: testMessage).get() else { return XCTFail("Encrypted Data should not be nil") } - guard let encryptedString2 = try? crypto.encrypt(plaintext: testMessage).get() else { + guard let encryptedString2 = try? cryptorModule.encrypt(string: testMessage).get() else { return XCTFail("Encrypted Data should not be nil") } - guard let decryptedString1 = try? crypto.decrypt(base64Encoded: encryptedString1).get() else { + guard let encryptedString1Data = Data(base64Encoded: encryptedString1) else { + return XCTFail("Cannot create Data from Base-64 encoded \(encryptedString1)") + } + guard let decryptedString1 = try? cryptorModule.decryptedString(from: encryptedString1Data).get() else { return XCTFail("Decrypted Data should not be nil") } - guard let decryptedString2 = try? crypto.decrypt(base64Encoded: encryptedString2).get() else { + guard let encryptedString2Data = Data(base64Encoded: encryptedString2) else { + return XCTFail("Cannot create Data from Base-64 encoded \(encryptedString2)") + } + guard let decryptedString2 = try? cryptorModule.decryptedString(from: encryptedString2Data).get() else { return XCTFail("Decrypted Data should not be nil") } + XCTAssertNotEqual(encryptedString1, encryptedString2) XCTAssertEqual(decryptedString1, decryptedString2) XCTAssertEqual(testMessage, decryptedString1) } func testOtherSDKContractTest() { - guard let crypto = Crypto(key: "MyCoolCipherKey", withRandomIV: false) else { - return XCTFail("Could not create crypto instance") - } - - // Validate common key value - XCTAssertEqual("NTQ5YzNlNGZjOGEzNDRmZThhNzMxOTQ3ODg4ZTRhMDE=", - crypto.key.base64EncodedString()) - + let cryptorModule = CryptorModule.legacyCryptoModule(with: "MyCoolCipherKey", withRandomIV: false) let message = "\"Hello there!\"" + guard let messageData = message.data(using: .utf8) else { return XCTFail("Could not create message data") } do { - // Validate Common IV - let ivData = try Crypto.staticInitializationVector() - XCTAssertEqual(ivData.base64EncodedString(), "MDEyMzQ1Njc4OTAxMjM0NQ==") - - let encryptedMessage = try crypto.encrypt(encoded: messageData).get() - - XCTAssertEqual(encryptedMessage.base64EncodedString(), - "Ej+YVJcPtbDrY2fM4OhaLQ==") - - let decrypted = try crypto.decrypt(encrypted: encryptedMessage).get() - - XCTAssertEqual(message, - String(bytes: decrypted, encoding: .utf8)) + let encryptedMessage = try cryptorModule.encrypt(data: messageData).get() + let decrypted = try cryptorModule.decrypt(data: encryptedMessage).get() + XCTAssertEqual(message, String(bytes: decrypted, encoding: .utf8)) } catch { XCTFail("Crypto failed due to \(error)") } } func testOtherSDK_RandomIV() { - guard let crypto = Crypto(key: "enigma", withRandomIV: true) else { - return XCTFail("Could not create crypto instance") - } - - let plaintext = "yay!" + let cryptorModule = CryptorModule.legacyCryptoModule(with: "enigma", withRandomIV: true) + let plainText = "yay!" let otherSDKBase64 = "MTIzNDU2Nzg5MDEyMzQ1NjdnONoCgo0wbuMGGMmfMX0=" do { - let swiftEncryptedString = try crypto.encrypt(plaintext: plaintext).get() + let swiftEncryptedString = try cryptorModule.encrypt(string: plainText).get() + let swiftEncryptedStringAsData = Data(base64Encoded: swiftEncryptedString)! + let swiftDecryptedString = try cryptorModule.decryptedString(from: swiftEncryptedStringAsData).get() - let swiftDecryptedString = try crypto.decrypt( - base64Encoded: swiftEncryptedString - ).get() - - XCTAssertEqual(plaintext, swiftDecryptedString) + XCTAssertEqual(plainText, swiftDecryptedString) guard let otherData = Data(base64Encoded: otherSDKBase64) else { return XCTFail("Could not create data from Base64") } - - let otherDecrypted = try crypto.decrypt( - encrypted: otherData - ).get() - - XCTAssertEqual(plaintext, String(data: otherDecrypted, encoding: .utf8)) + let otherDecrypted = try cryptorModule.decrypt(data: otherData).get() + XCTAssertEqual(plainText, String(data: otherDecrypted, encoding: .utf8)) } catch { XCTFail("Crypto failed due to \(error)") } } func testStreamOtherSDK() { - guard let crypto = Crypto(key: "enigma", withRandomIV: true) else { - return XCTFail("Could not create crypto instance") - } - + let cryptorModule = CryptorModule.legacyCryptoModule( + with: "enigma", + withRandomIV: true + ) do { let ecrypted = try ImportTestResource.importResource("file_upload_sample_encrypted", withExtension: "txt") let final = try ImportTestResource.importResource("file_upload_sample", withExtension: "txt") @@ -175,74 +167,66 @@ class CryptoTests: XCTestCase { XCTAssertEqual(finalString?.isEmpty, false) - let decryptedStream = CryptoInputStream(.decrypt, data: ecrypted, with: crypto) - let decryptedURL = try FileManager.default.temporaryFile( - using: "decryptedStream", - writing: decryptedStream, - purgeExisting: true - ) - let decrypted = try Data(contentsOf: decryptedURL) + let outputPath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("testFile") + // Purges existing item (if any) + try? FileManager.default.removeItem(at: outputPath) + cryptorModule.decrypt( + stream: InputStream(data: ecrypted), + contentLength: ecrypted.count, + to: outputPath + ) + + let decrypted = try Data(contentsOf: outputPath) XCTAssertEqual(finalString, String(data: decrypted, encoding: .utf8)) - + } catch { XCTFail("Could not write to temp file \(error)") } } func testStreamEncryptDecrypt() { - guard let crypto = Crypto(key: "enigma", withRandomIV: true) else { - return XCTFail("Could not create crypto instance") - } - + let cryptorModule = CryptorModule.legacyCryptoModule( + with: "enigma", + withRandomIV: true + ) do { guard let plainTextURL = ImportTestResource.testsBundle.url( forResource: "file_upload_sample", withExtension: "txt" ) else { return XCTFail("Could not get the URL for resource") } - guard let plaintextString = String(data: try Data(contentsOf: plainTextURL), encoding: .utf8) else { + guard let plainTextString = String(data: try Data(contentsOf: plainTextURL), encoding: .utf8) else { return XCTFail("Could not create string from data") } - XCTAssertEqual(plaintextString.isEmpty, false) + XCTAssertEqual(plainTextString.isEmpty, false) - let encryptedStream = CryptoInputStream(.encrypt, url: plainTextURL, with: crypto) - let encryptedURL = try FileManager.default.temporaryFile( - using: "encryptedStream", - writing: encryptedStream, - purgeExisting: true - ) + let data = try Data(contentsOf: plainTextURL) + let inputStream = InputStream(data: data) + + let encryptedStreamResult = try cryptorModule.encrypt( + stream: inputStream, + contentLength: data.count + ).get() - let decryptedStream = CryptoInputStream(.decrypt, url: encryptedURL, with: crypto) - let decryptedURL = try FileManager.default.temporaryFile( - using: "decryptedStream", - writing: decryptedStream, - purgeExisting: true + let decryptedURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("decryptedStream") + try? FileManager.default.removeItem(at: decryptedURL) + + cryptorModule.decrypt( + stream: encryptedStreamResult.stream, + contentLength: encryptedStreamResult.contentLength, + to: decryptedURL ) + let decryptedString = String(data: try Data(contentsOf: decryptedURL), encoding: .utf8) - - XCTAssertEqual(plaintextString, decryptedString) + XCTAssertEqual(plainTextString, decryptedString) } catch { XCTFail("Could not write to temp file \(error)") } } - // MARK: - Cipher - - func testValidateKeySize() { - let aesCipher = Crypto.Cipher.aes - - XCTAssertNoThrow(try aesCipher.validate(keySize: kCCKeySizeAES128)) - } - - func testValidateKeySize_Failure() { - let aesCipher = Crypto.Cipher.aes - - XCTAssertThrowsError(try aesCipher.validate(keySize: 0)) - } - // MARK: - CryptoError func testRawValue_Nil() { @@ -250,62 +234,86 @@ class CryptoTests: XCTestCase { } func testRawValue_IllegalParameter() { - XCTAssertEqual(CryptoError.illegalParameter, - CryptoError(rawValue: CCCryptorStatus(kCCParamError))) + XCTAssertEqual( + CryptoError.illegalParameter, + CryptoError(rawValue: CCCryptorStatus(kCCParamError)) + ) } func testRawValue_BufferTooSmall() { - XCTAssertEqual(CryptoError.bufferTooSmall, - CryptoError(rawValue: CCCryptorStatus(kCCBufferTooSmall))) + XCTAssertEqual( + CryptoError.bufferTooSmall, + CryptoError(rawValue: CCCryptorStatus(kCCBufferTooSmall)) + ) } func testRawValue_MemoryFailure() { - XCTAssertEqual(CryptoError.memoryFailure, - CryptoError(rawValue: CCCryptorStatus(kCCMemoryFailure))) + XCTAssertEqual( + CryptoError.memoryFailure, + CryptoError(rawValue: CCCryptorStatus(kCCMemoryFailure)) + ) } func testRawValue_AlignmentError() { - XCTAssertEqual(CryptoError.alignmentError, - CryptoError(rawValue: CCCryptorStatus(kCCAlignmentError))) + XCTAssertEqual( + CryptoError.alignmentError, + CryptoError(rawValue: CCCryptorStatus(kCCAlignmentError)) + ) } func testRawValue_DecodeError() { - XCTAssertEqual(CryptoError.decodeError, - CryptoError(rawValue: CCCryptorStatus(kCCDecodeError))) + XCTAssertEqual( + CryptoError.decodeError, + CryptoError(rawValue: CCCryptorStatus(kCCDecodeError)) + ) } func testRawValue_Overflow() { - XCTAssertEqual(CryptoError.overflow, - CryptoError(rawValue: CCCryptorStatus(kCCOverflow))) + XCTAssertEqual( + CryptoError.overflow, + CryptoError(rawValue: CCCryptorStatus(kCCOverflow)) + ) } func testRawValue_RNGFailure() { - XCTAssertEqual(CryptoError.rngFailure, - CryptoError(rawValue: CCCryptorStatus(kCCRNGFailure))) + XCTAssertEqual( + CryptoError.rngFailure, + CryptoError(rawValue: CCCryptorStatus(kCCRNGFailure)) + ) } func testRawValue_CallSequenceError() { - XCTAssertEqual(CryptoError.callSequenceError, - CryptoError(rawValue: CCCryptorStatus(kCCCallSequenceError))) + XCTAssertEqual( + CryptoError.callSequenceError, + CryptoError(rawValue: CCCryptorStatus(kCCCallSequenceError)) + ) } func testRawValue_KeySizeError() { - XCTAssertEqual(CryptoError.keySizeError, - CryptoError(rawValue: CCCryptorStatus(kCCKeySizeError))) + XCTAssertEqual( + CryptoError.keySizeError, + CryptoError(rawValue: CCCryptorStatus(kCCKeySizeError)) + ) } func testRawValue_Unimplemented() { - XCTAssertEqual(CryptoError.unimplemented, - CryptoError(rawValue: CCCryptorStatus(kCCUnimplemented))) + XCTAssertEqual( + CryptoError.unimplemented, + CryptoError(rawValue: CCCryptorStatus(kCCUnimplemented)) + ) } func testRawValue_UnspecifiedError() { - XCTAssertEqual(CryptoError.unspecifiedError, - CryptoError(rawValue: CCCryptorStatus(kCCUnspecifiedError))) + XCTAssertEqual( + CryptoError.unspecifiedError, + CryptoError(rawValue: CCCryptorStatus(kCCUnspecifiedError)) + ) } func testRawValue_Unknown() { - XCTAssertEqual(CryptoError.unknown, - CryptoError(rawValue: CCCryptorStatus(1_240_124))) + XCTAssertEqual( + CryptoError.unknown, + CryptoError(rawValue: CCCryptorStatus(1_240_124)) + ) } } diff --git a/Tests/PubNubTests/Networking/Routers/HistoryRouterTests.swift b/Tests/PubNubTests/Networking/Routers/HistoryRouterTests.swift index fc6ebd6d..89fb11e1 100644 --- a/Tests/PubNubTests/Networking/Routers/HistoryRouterTests.swift +++ b/Tests/PubNubTests/Networking/Routers/HistoryRouterTests.swift @@ -176,7 +176,7 @@ extension HistoryRouterTests { } var configWithCipher = config - configWithCipher.cipherKey = Crypto(key: "SomeTestString", withRandomIV: false) + configWithCipher.cryptorModule = CryptorModule.legacyCryptoModule(with: "SomeTestString", withRandomIV: false) let pubnub = PubNub(configuration: configWithCipher, session: sessions.session) pubnub.fetchMessageHistory(for: testMultiChannels) { result in @@ -203,7 +203,7 @@ extension HistoryRouterTests { } var configWithCipher = config - configWithCipher.cipherKey = Crypto(key: "NotTheRightKey", withRandomIV: false) + configWithCipher.cryptorModule = CryptorModule.legacyCryptoModule(with: "NotTheRightKey", withRandomIV: false) let pubnub = PubNub(configuration: config, session: sessions.session) pubnub.fetchMessageHistory(for: testMultiChannels) { result in @@ -231,7 +231,7 @@ extension HistoryRouterTests { } var configWithCipher = config - configWithCipher.cipherKey = Crypto(key: "SomeTestString", withRandomIV: false) + configWithCipher.cryptorModule = CryptorModule.legacyCryptoModule(with: "SomeTestString", withRandomIV: false) let pubnub = PubNub(configuration: configWithCipher, session: sessions.session) pubnub.fetchMessageHistory(for: testMultiChannels) { result in diff --git a/Tests/PubNubTests/PubNubConfigurationTests.swift b/Tests/PubNubTests/PubNubConfigurationTests.swift index e0733967..731affc1 100644 --- a/Tests/PubNubTests/PubNubConfigurationTests.swift +++ b/Tests/PubNubTests/PubNubConfigurationTests.swift @@ -46,7 +46,7 @@ class PubNubConfigurationTests: XCTestCase { XCTAssertNil(config.publishKey) XCTAssertEqual(config.subscribeKey, plistSubscribeKeyValue) - XCTAssertNil(config.cipherKey) + XCTAssertNil(config.cryptorModule) XCTAssertNil(config.authKey) XCTAssertNotNil(config.uuid) XCTAssertEqual(config.useSecureConnections, true)