From 56c86877d8a0dd50ae981905837940341ead89af Mon Sep 17 00:00:00 2001 From: jguz-pubnub <102806147+jguz-pubnub@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:57:04 +0200 Subject: [PATCH] CryptorModule (#144) fix(crypto): Improved security of crypto implementation by adding AES-CBC cryptor feat(crypto): Add CryptorModule that allows configuring SDK to encrypt and decrypt messages PubNub SDK 6.2.0 release --- .pubnub.yml | 11 +- .../ConfigDetailTableViewController.swift | 2 +- LICENSE | 44 +-- Podfile.lock | 2 +- PubNub.xcodeproj/project.pbxproj | 224 ++++++++++-- PubNubSwift.podspec | 2 +- 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/Constants.swift | 2 +- Sources/PubNub/Helpers/Crypto/Crypto.swift | 305 +---------------- .../PubNub/Helpers/Crypto/CryptorModule.swift | 324 ++++++++++++++++++ .../Crypto/Cryptors/AESCBCCryptor.swift | 166 +++++++++ .../Helpers/Crypto/Cryptors/Cryptor.swift | 88 +++++ .../Crypto/Cryptors/LegacyCryptor.swift | 190 ++++++++++ .../Helpers/Crypto/Header/CryptorHeader.swift | 149 ++++++++ .../CryptorHeaderWithinStreamFinder.swift | 96 ++++++ .../Miscellaneous}/CryptoInputStream.swift | 221 +++++++----- .../{ => Miscellaneous}/CryptoStream.swift | 11 +- .../Crypto/Miscellaneous/CryptorUtils.swift | 48 +++ .../Crypto/Miscellaneous/CryptorVector.swift | 77 +++++ .../Miscellaneous/Data+CommonCrypto.swift | 79 +++++ .../Streams/MultipartInputStream.swift | 8 +- 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 | 42 ++- Sources/PubNub/PubNubConfiguration.swift | 17 +- .../SubscribeSessionFactory.swift | 2 +- .../PubNubContractTestCase.swift | 8 +- .../PubNubCryptoModuleContractTestSteps.swift | 224 ++++++++++++ .../PubNubSubscribeContractTestSteps.swift | 10 +- Tests/PubNubTests/Helpers/CryptoTests.swift | 266 +++++++------- .../Routers/HistoryRouterTests.swift | 6 +- .../PubNubConfigurationTests.swift | 2 +- 38 files changed, 2139 insertions(+), 703 deletions(-) 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 (73%) 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 create mode 100644 Tests/PubNubContractTest/Steps/CryptorModule/PubNubCryptoModuleContractTestSteps.swift diff --git a/.pubnub.yml b/.pubnub.yml index 7ae40a8a..718ced5f 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,9 +1,16 @@ --- name: swift scm: github.com/pubnub/swift -version: "6.1.0" +version: "6.2.0" schema: 1 changelog: + - date: 2023-10-16 + version: v6.2.0 + changes: + - type: feature + text: "Add CryptorModule that allows configuring SDK to encrypt and decrypt messages." + - type: bug + text: "Improved security of crypto implementation by adding AES-CBC cryptor." - date: 2023-08-30 version: 6.1.0 changes: @@ -490,7 +497,7 @@ sdks: - distribution-type: source distribution-repository: GitHub release package-name: PubNub - location: https://github.com/pubnub/swift/archive/refs/tags/6.1.0.zip + location: https://github.com/pubnub/swift/archive/refs/tags/6.2.0.zip supported-platforms: supported-operating-systems: macOS: 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/LICENSE b/LICENSE index 45988481..5e1ef188 100644 --- a/LICENSE +++ b/LICENSE @@ -1,27 +1,29 @@ -PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks -Copyright (c) 2020 PubNub Inc. -https://www.pubnub.com/ -https://www.pubnub.com/terms +PubNub Software Development Kit License Agreement +Copyright © 2023 PubNub Inc. All rights reserved. + +Subject to the terms and conditions of the license, you are hereby granted +a non-exclusive, worldwide, royalty-free license to (a) copy and modify +the software in source code or binary form for use with the software services +and interfaces provided by PubNub, and (b) redistribute unmodified copies +of the software to third parties. The software may not be incorporated in +or used to provide any product or service competitive with the products +and services of PubNub. -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 license shall be included +in or with all copies or substantial portions of the software. -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +This license does not grant you permission to use the trade names, trademarks, +service marks, or product names of PubNub, except as required for reasonable +and customary use in describing the origin of the software and reproducing +the content of this license. -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. +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 PUBNUB OR THE AUTHORS OR COPYRIGHT HOLDERS OF THE SOFTWARE 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. -PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks -Copyright (c) 2013 PubNub Inc. https://www.pubnub.com/ https://www.pubnub.com/terms 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..c8274e99 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 */; }; @@ -376,7 +375,21 @@ 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 */; }; + 3DBB2C212ABD8053008A100E /* PubNubCryptoModuleContractTestSteps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBB2C202ABD8053008A100E /* PubNubCryptoModuleContractTestSteps.swift */; }; + 3DBB2C222ABD8053008A100E /* PubNubCryptoModuleContractTestSteps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBB2C202ABD8053008A100E /* PubNubCryptoModuleContractTestSteps.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 +543,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 +584,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 = ""; }; @@ -897,7 +910,20 @@ 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 = ""; }; + 3DBB2C202ABD8053008A100E /* PubNubCryptoModuleContractTestSteps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PubNubCryptoModuleContractTestSteps.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 +943,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 +952,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 +1048,7 @@ buildActionMask = 0; files = ( 7941EEA9270E433F0054D9EF /* PubNub.framework in Frameworks */, + 4C2A8D84BCD39B07A66FD9B4 /* Pods_PubNubContractTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1027,6 +1057,7 @@ buildActionMask = 0; files = ( 79657AA3271A13F700BACEC5 /* PubNub.framework in Frameworks */, + 0162B986DE5A8773D6F8C8A0 /* Pods_PubNubContractTestsBeta.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1052,7 +1083,6 @@ isa = PBXGroup; children = ( 357CA287251D3C9100BC40D3 /* MultipartInputStream.swift */, - 352224ED253397AA00A5B330 /* CryptoInputStream.swift */, ); path = Streams; sourceTree = ""; @@ -1061,7 +1091,10 @@ isa = PBXGroup; children = ( 3595120F2301DCAB00C9D3AE /* Crypto.swift */, - 35133195253115C500242CC2 /* CryptoStream.swift */, + 3D758DBE2AAA1C49005D2B36 /* CryptorModule.swift */, + 3D758DCC2AB06B98005D2B36 /* Header */, + 3D758DC52AB06983005D2B36 /* Cryptors */, + 3D758DC42AB06977005D2B36 /* Miscellaneous */, ); path = Crypto; sourceTree = ""; @@ -1876,6 +1909,45 @@ path = EndpointError; sourceTree = ""; }; + 3D6265D22ABC8E6900FDD5E6 /* CryptorModule */ = { + isa = PBXGroup; + children = ( + 3DBB2C202ABD8053008A100E /* PubNubCryptoModuleContractTestSteps.swift */, + ); + path = CryptorModule; + 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 +1959,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 +2000,7 @@ 79407BC1271D4CFA0032076C /* Steps */ = { isa = PBXGroup; children = ( + 3D6265D22ABC8E6900FDD5E6 /* CryptorModule */, A5F88ECF2906A9DE00F49D5C /* Objects */, 79407BC2271D4CFA0032076C /* Access */, 79407BC4271D4CFA0032076C /* Message Actions */, @@ -2002,6 +2079,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 +2164,7 @@ OBJ_12 /* Tests */, OBJ_17 /* Products */, 3DBD7CDD58292DFFDF108B95 /* Pods */, + A311BADC0F7EA586FC713D4A /* Frameworks */, ); sourceTree = ""; }; @@ -2318,9 +2405,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 +2425,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 +2788,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 +2880,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 */ @@ -2859,6 +3028,7 @@ 79407BD2271D4CFA0032076C /* PubNubContractTestCase.swift in Sources */, 79407BDE271D4CFA0032076C /* PubNubPushContractTestSteps.swift in Sources */, A5115F2B291D5C2700F6ADA1 /* PubNubObjectsContractTests.swift in Sources */, + 3DBB2C212ABD8053008A100E /* PubNubCryptoModuleContractTestSteps.swift in Sources */, 79407BE4271D4CFA0032076C /* PubNubFilesContractTestSteps.swift in Sources */, 79407BD8271D4CFA0032076C /* PubNubMessageActionsContractTestSteps.swift in Sources */, A5115F28291D54F500F6ADA1 /* PubNubObjectsMembershipsContractTestSteps.swift in Sources */, @@ -2882,6 +3052,7 @@ 79407BD3271D4CFA0032076C /* PubNubContractTestCase.swift in Sources */, 79407BDF271D4CFA0032076C /* PubNubPushContractTestSteps.swift in Sources */, A5115F2C291D5C2700F6ADA1 /* PubNubObjectsContractTests.swift in Sources */, + 3DBB2C222ABD8053008A100E /* PubNubCryptoModuleContractTestSteps.swift in Sources */, 79407BE5271D4CFA0032076C /* PubNubFilesContractTestSteps.swift in Sources */, 79407BD9271D4CFA0032076C /* PubNubMessageActionsContractTestSteps.swift in Sources */, A5115F29291D54F500F6ADA1 /* PubNubObjectsMembershipsContractTestSteps.swift in Sources */, @@ -2905,7 +3076,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 +3090,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 +3112,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,10 +3120,10 @@ 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 */, @@ -2956,7 +3134,6 @@ 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 +3158,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 +3177,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 */, @@ -3256,7 +3436,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 6.1.0; + MARKETING_VERSION = 6.2.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.pubnub.swift.PubNubUser; @@ -3303,7 +3483,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 6.1.0; + MARKETING_VERSION = 6.2.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.pubnub.swift.PubNubUser; @@ -3403,7 +3583,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 6.1.0; + MARKETING_VERSION = 6.2.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.pubnub.swift.PubNubSpace; @@ -3452,7 +3632,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 6.1.0; + MARKETING_VERSION = 6.2.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.pubnub.swift.PubNubSpace; @@ -3565,7 +3745,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 6.1.0; + MARKETING_VERSION = 6.2.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.pubnub.swift.PubNubMembership; @@ -3613,7 +3793,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 6.1.0; + MARKETING_VERSION = 6.2.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.pubnub.swift.PubNubMembership; @@ -3887,6 +4067,7 @@ }; 7941EF3E270E433F0054D9EF /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = FB0696E0E433339FBC48D1D3 /* Pods-PubNubContractTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; @@ -3900,7 +4081,7 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = PubNub.xcodeproj/PubNubContractTests_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@loader_path/../Frameworks", @@ -3928,6 +4109,7 @@ }; 7941EF3F270E433F0054D9EF /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 01F80D9221FA218C6E7D5572 /* Pods-PubNubContractTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; @@ -3941,7 +4123,7 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = PubNub.xcodeproj/PubNubContractTests_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@loader_path/../Frameworks", @@ -3968,6 +4150,7 @@ }; 79657AA9271A13F700BACEC5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 979DD162B86D78BE9B72DEEF /* Pods-PubNubContractTestsBeta.debug.xcconfig */; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; @@ -3982,7 +4165,7 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = PubNub.xcodeproj/PubNubContractTests_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@loader_path/../Frameworks", @@ -4010,6 +4193,7 @@ }; 79657AAA271A13F700BACEC5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 3DE632651BA8B2E27ACFC4AD /* Pods-PubNubContractTestsBeta.release.xcconfig */; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; @@ -4024,7 +4208,7 @@ ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = PubNub.xcodeproj/PubNubContractTests_Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@loader_path/../Frameworks", @@ -4069,7 +4253,7 @@ "$(inherited)", "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", ); - MARKETING_VERSION = 6.1.0; + MARKETING_VERSION = 6.2.0; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; @@ -4108,7 +4292,7 @@ "$(inherited)", "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", ); - MARKETING_VERSION = 6.1.0; + MARKETING_VERSION = 6.2.0; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; diff --git a/PubNubSwift.podspec b/PubNubSwift.podspec index ba07248c..ebc94a0a 100644 --- a/PubNubSwift.podspec +++ b/PubNubSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'PubNubSwift' - s.version = '6.1.0' + s.version = '6.2.0' s.homepage = 'https://github.com/pubnub/swift' s.documentation_url = 'https://www.pubnub.com/docs/swift-native/pubnub-swift-sdk' s.authors = { 'PubNub, Inc.' => 'support@pubnub.com' } 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..acb8cb80 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 .encryptionFailure: + return "Failure to perform encryption" + case .decryptionFailure: + return "Failure to perform decryption" + case .unknownCryptorFailure: + return "Unknown Cryptor error" } } diff --git a/Sources/PubNub/Errors/PubNubError.swift b/Sources/PubNub/Errors/PubNubError.swift index 456112b1..26afabdb 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 encryptionFailure + case decryptionFailure + case unknownCryptorFailure // Request Processing case requestMutatorFailure @@ -232,6 +235,8 @@ public struct PubNubError: Error { return .streamFailure case .fileTooLarge, .fileMissingAtPath, .fileAccessDenied, .fileContentLength: return .fileManagement + case .encryptionFailure, .decryptionFailure, .unknownCryptorFailure: + return .crypto } } } diff --git a/Sources/PubNub/Extensions/URLRequest+PubNub.swift b/Sources/PubNub/Extensions/URLRequest+PubNub.swift index 01c1ee0b..e4ab9add 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 + contentLength = prefixData.count + ((encryptingResult as? MultipartInputStream)?.length ?? 0) + 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/Constants.swift b/Sources/PubNub/Helpers/Constants.swift index d184389c..c7c94bb3 100644 --- a/Sources/PubNub/Helpers/Constants.swift +++ b/Sources/PubNub/Helpers/Constants.swift @@ -57,7 +57,7 @@ public enum Constant { static let pubnubSwiftSDKName: String = "PubNubSwift" - static let pubnubSwiftSDKVersion: String = "6.1.0" + static let pubnubSwiftSDKVersion: String = "6.2.0" static let appBundleId: String = { if let info = Bundle.main.infoDictionary, diff --git a/Sources/PubNub/Helpers/Crypto/Crypto.swift b/Sources/PubNub/Helpers/Crypto/Crypto.swift index 43731167..00c25058 100644 --- a/Sources/PubNub/Helpers/Crypto/Crypto.swift +++ b/Sources/PubNub/Helpers/Crypto/Crypto.swift @@ -29,211 +29,17 @@ import CommonCrypto import Foundation /// Object capable of encryption/decryption +/// +/// - Warning: This struct is deprecated. Use ``CryptorModule`` instead. 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 + /// Key initially provided by the user + let key: String /// 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) - } + let randomizeIV: Bool + + public init(key: String, withRandomIV: Bool = true) { + self.key = key + self.randomizeIV = withRandomIV } } @@ -305,96 +111,3 @@ public enum CryptoError: CCCryptorStatus, Error, LocalizedError { } } } - -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/CryptorModule.swift b/Sources/PubNub/Helpers/Crypto/CryptorModule.swift new file mode 100644 index 00000000..77eced43 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/CryptorModule.swift @@ -0,0 +1,324 @@ +// +// 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 + +/// Object capable of encryption/decryption +public struct CryptorModule { + private let defaultCryptor: Cryptor + private let cryptors: [Cryptor] + private let legacyCryptorId: CryptorId = [] + + typealias Base64EncodedString = String + + /// Initializes `CryptorModule` with custom ``Cryptor`` objects capable of encryption and decryption + /// + /// Use this constructor if you would like to provide **custom** objects for decryption and encryption and don't want to use PubNub's built-in `Cryptors`. + /// Otherwise, refer to convenience static factory methods such as ``aesCbcCryptoModule(with:withRandomIV:)`` + /// and ``legacyCryptoModule(with:withRandomIV:)`` that return `CryptorModule` configured for you. + /// + /// - Parameters: + /// - default: Primary ``Cryptor`` instance used for encryption and decryption + /// - cryptors: An optional list of ``Cryptor`` instances which older messages/files were encoded + public init(default cryptor: Cryptor, cryptors: [Cryptor] = []) { + self.defaultCryptor = cryptor + self.cryptors = cryptors + } + + /// Encrypts the given `Data` object + /// + /// - Parameters: + /// - data: Data to encrypt + /// - Returns: A success, storing encrypted `Data` if operation succeeds. Otherwise, a failure storing `PubNubError` is returned + public func encrypt(data: Data) -> Result { + guard !data.isEmpty else { + return .failure(PubNubError( + .encryptionFailure, + additional: ["Cannot encrypt empty Data"]) + ) + } + return defaultCryptor.encrypt(data: data).map { + if defaultCryptor.id == LegacyCryptor.ID { + return $0.data + } + return CryptorHeader.v1( + cryptorId: defaultCryptor.id, + dataLength: $0.metadata.count + ).toData() + $0.metadata + $0.data + }.mapError { + PubNubError(.encryptionFailure, underlying: $0) + } + } + + /// Decrypts the given `Data` object + /// + /// - Parameters: + /// - data: Data to decrypt + /// - Returns: A success, storing decrypted `Data` if operation succeeds. Otherwise, a failure storing `PubNubError` is returned + public func decrypt(data: Data) -> Result { + guard !data.isEmpty else { + return .failure(PubNubError( + .decryptionFailure, + additional: ["Cannot decrypt empty Data in \(String(describing: self))"]) + ) + } + do { + let header = try CryptorHeader.from(data: data) + + guard let cryptor = cryptor(matching: header) else { + return .failure(PubNubError( + .unknownCryptorFailure, + additional: [ + "Could not find matching Cryptor for \(header.cryptorId()) while decrypting Data. " + + "Ensure the corresponding instance is registered in \(String(describing: Self.self))" + ] + )) + } + + let metadata: Data + let contentData: Data + + switch header { + case .none: + metadata = Data() + contentData = data + case .v1(_, let dataLength): + let offset = header.toData().count + contentData = data.suffix(from: offset + dataLength) + metadata = data.subdata(in: offset.. Result { + guard contentLength > 0 else { + return .failure(PubNubError( + .encryptionFailure, + additional: ["Cannot encrypt empty InputStream"] + )) + } + return defaultCryptor.encrypt( + stream: stream, + contentLength: contentLength + ).map { + let header = defaultCryptor.id != LegacyCryptor.ID ? CryptorHeader.v1( + cryptorId: defaultCryptor.id, + dataLength: $0.metadata.count + ) : .none + + switch header { + case .none: + return MultipartInputStream( + inputStreams: [InputStream(data: header.toData()), $0.stream], + length: $0.contentLength + ) + case .v1(_, let dataLength): + return MultipartInputStream( + inputStreams: [InputStream(data: header.toData() + $0.metadata), $0.stream], + length: $0.contentLength + header.toData().count + dataLength + ) + } + }.mapError { + PubNubError(.encryptionFailure, underlying: $0) + } + } + + /// Decrypts the given `InputStream` object + /// + /// - Parameters: + /// - stream: Stream to decrypt + /// - contentLength: Content length of encrypted stream + /// - to: URL where the stream should be decrypted to + /// - Returns: A success, storing a decrypted `InputStream` value if operation succeeds. Otherwise, a failure storing `PubNubError` is returned + @discardableResult + public func decrypt( + stream: InputStream, + contentLength: Int, + to outputPath: URL + ) -> Result { + do { + guard contentLength > 0 else { + return .failure(PubNubError( + .decryptionFailure, + additional: ["Cannot decrypt empty InputStream"] + )) + } + + let finder = CryptorHeaderWithinStreamFinder(stream: stream) + let readHeaderResp = try finder.findHeader() + let cryptorDefinedData = readHeaderResp.cryptorDefinedData + + guard let cryptor = cryptor(matching: readHeaderResp.header) else { + return .failure(PubNubError( + .unknownCryptorFailure, + additional: [ + "Could not find matching Cryptor for \(readHeaderResp.header.cryptorId()) while decrypting InputStream. " + + "Ensure the corresponding instance is registered in \(String(describing: Self.self))" + ] + )) + } + return cryptor.decrypt( + data: EncryptedStreamData( + stream: readHeaderResp.continuationStream, + contentLength: contentLength - readHeaderResp.header.length() - cryptorDefinedData.count, + metadata: cryptorDefinedData + ), + outputPath: outputPath + ).flatMap { + if outputPath.sizeOf == 0 { + return .failure(PubNubError( + .decryptionFailure, + additional: ["Decrypting resulted with an empty File"]) + ) + } + return .success($0) + } + .mapError { + PubNubError(.decryptionFailure, underlying: $0) + } + } catch let error as PubNubError { + return .failure(error) + } catch { + return .failure(PubNubError( + .decryptionFailure, + underlying: error, + additional: ["Could not decrypt InputStream"] + )) + } + } + + private func cryptor(matching header: CryptorHeader) -> Cryptor? { + header.cryptorId() == defaultCryptor.id ? defaultCryptor : cryptors.first(where: { + $0.id == header.cryptorId() + }) + } +} + +/// Convenience methods for creating `CryptorModule` +public extension CryptorModule { + + /// Returns **recommended** `CryptorModule` for encryption/decryption + /// + /// - Parameters: + /// - key: Key used for encryption/decryption + /// - withRandomIV: A flag describing whether random initialization vector should be used + /// + /// This method sets ``AESCBCCryptor`` as the primary object for decryption and encryption. It also + /// instantiates ``LegacyCryptor``under the hood with `withRandomIV`. This way, you can interact with historical + /// messages or messages sent from older clients + static func aesCbcCryptoModule(with key: String, withRandomIV: Bool = true) -> CryptorModule { + CryptorModule(default: AESCBCCryptor(key: key), cryptors: [LegacyCryptor(key: key, withRandomIV: withRandomIV)]) + } + + /// Returns legacy `CryptorModule` for encryption/decryption + /// + /// - Parameters: + /// - key: Key used for encryption/decryption + /// - withRandomIV: A flag describing whether random initialization vector should be used + /// - Warning: It's highly recommended to always use ``aesCbcCryptoModule(with:withRandomIV:)`` + static func legacyCryptoModule(with key: String, withRandomIV: Bool = true) -> CryptorModule { + CryptorModule(default: LegacyCryptor(key: key, withRandomIV: withRandomIV), cryptors: [AESCBCCryptor(key: key)]) + } +} + +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), others: \(cryptors.map { $0.id })" + } +} + +internal extension CryptorModule { + func encrypt(string: String) -> Result { + guard let data = string.data(using: .utf8) else { + return .failure(PubNubError( + .encryptionFailure, + 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: .utf8) { + return .success(stringValue) + } else { + return .failure(PubNubError( + .decryptionFailure, + additional: ["Cannot create String from provided 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..9e7a67fa --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Cryptors/AESCBCCryptor.swift @@ -0,0 +1,166 @@ +// +// 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 + +/// Provides PubNub's **recommended** ``Cryptor`` for encryption/decryption +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( + .decryptionFailure, + underlying: error + )) + } + } + + public func decrypt(data: EncryptedData) -> Result { + do { + if data.data.isEmpty { + return .failure(PubNubError( + .decryptionFailure, + additional: ["Cannot decrypt empty Data in \(String(describing: self))"]) + ) + } + 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( + .decryptionFailure, + 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( + .encryptionFailure, + 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) + } + return .failure(PubNubError( + .decryptionFailure, + additional: ["Cannot create resulting InputStream at \(outputPath)"] + )) + } catch { + return .failure(PubNubError( + .decryptionFailure, + 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..eabe942a --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift @@ -0,0 +1,88 @@ +// +// 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 + +/// Represents the result of encrypted `Data` +public struct EncryptedData { + /// Metadata (if any) used while encrypting + let metadata: Data + /// Resulting encrypted `Data` + let data: Data +} + +/// Represents the result of encrypted `InputStream` +public struct EncryptedStreamData { + /// Encrypted stream you can read from + let stream: InputStream + /// Content length of encrypted stream + let contentLength: Int + /// Metadata (if any) used while encrypting + let metadata: Data +} + +/// Typealias for uniquely identifying applied encryption +public typealias CryptorId = [UInt8] + +/// Protocol for all types that encapsulate concrete encryption/decryption operations +public protocol Cryptor { + /// Unique 4-byte identifier across all `Cryptor` + /// + /// - Important: `[0x41, 0x43, 0x52, 0x48]` and `[0x00, 0x00, 0x00, 0x00]` values are reserved + var id: CryptorId { get } + + /// Encrypts the given `Data` object + /// + /// - Parameters: + /// - data: Data to encrypt + /// - Returns: A success, storing an ``EncryptedData`` value if operation succeeds. Otherwise, a failure storing `PubNubError` is returned + func encrypt(data: Data) -> Result + + /// Decrypts the given `Data` object + /// + /// - Parameters: + /// - data: Data to encrypt + /// - Returns: A success, storing decrypted `Data` if operation succeeds. Otherwise, a failure storing `PubNubError` is returned + func decrypt(data: EncryptedData) -> Result + + /// Encrypts the given `InputStream` object + /// + /// - Parameters: + /// - stream: Stream to encrypt + /// - contentLength: Content length of encoded stream + /// - Returns: A success, storing an ``EncryptedStreamData`` value if operation succeeds. Otherwise, a failure storing `PubNubError` is returned + func encrypt(stream: InputStream, contentLength: Int) -> Result + + /// Decrypts the given `InputStream` object + /// + /// - Parameters: + /// - data: A value describing encrypted stream + /// - outputPath: URL where the stream should be decrypted to + /// - Returns: A success, storing a decrypted `InputStream` value at the given path if operation succeeds. Otherwise, a failure storing `PubNubError` is returned + 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..c28d3c84 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Cryptors/LegacyCryptor.swift @@ -0,0 +1,190 @@ +// +// 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 + +/// Provides a backward-compatible way of encryption/decryption +/// +/// - Important: Using this `Cryptor` for encoding is strongly discouraged. Use ``AESCBCCryptor`` instead. +public struct LegacyCryptor: Cryptor { + private let key: Data + private let withRandomIV: Bool + + static let ID: 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.ID + } + + 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: Data(), + data: vectorGen.isRandom() ? ivData + encrypted : encrypted + )) + } catch { + return .failure(PubNubError( + .decryptionFailure, + 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 + } + + if cipherText.isEmpty { + return .failure(PubNubError( + .decryptionFailure, + additional: ["Cannot decrypt empty Data in \(String(describing: self))"]) + ) + } + + 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( + .decryptionFailure, + 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( + .encryptionFailure, + 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( + .decryptionFailure, + additional: ["Cannot create resulting InputStream at \(outputPath)"] + )) + } catch { + return .failure(PubNubError( + .decryptionFailure, + 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..d4111000 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Header/CryptorHeader.swift @@ -0,0 +1,149 @@ +// +// 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, dataLength: Int) + + func length() -> Int { + toData().count + } + + func cryptorId() -> CryptorId { + switch self { + case .none: + return LegacyCryptor.ID + case .v1(let cryptorId, _): + return cryptorId + } + } + + func toData() -> Data { + guard case .v1(let cryptorId, let dataLength) = 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 dataLength < 255 { + finalData += Data(bytes: [dataLength], count: 1) + } else { + finalData += Data(bytes: [0xFF, dataLength >> 8, dataLength & 0xFF], count: 3) + } + return finalData + } + + static func from(data: Data) throws -> Self { + try CryptorHeaderParser(data: data).parse() + } +} + +fileprivate class CryptorHeaderDataScanner { + private(set) 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 + private let supportedVersionsRange: ClosedRange = (1...1) + + init(data: Data) { + self.scanner = CryptorHeaderDataScanner(data: data) + } + + 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(.decryptionFailure, additional: ["Could not find CryptorHeader version"]) + } + guard supportedVersionsRange.contains(headerVersion) else { + throw PubNubError(.unknownCryptorFailure, additional: ["Unsupported or invalid CryptorHeader version \(headerVersion)"]) + } + guard let cryptorId = scanner.nextBytes(4) else { + throw PubNubError(.decryptionFailure, additional: ["Could not find Cryptor identifier"]) + } + guard let cryptorDataSizeByte = scanner.nextByte() else { + throw PubNubError(.decryptionFailure, additional: ["Could not find Cryptor data size byte"]) + } + guard let finalCryptorDataSize = try? computeCryptorDataSize(with: Int(cryptorDataSizeByte)) else { + throw PubNubError(.decryptionFailure, additional: ["Could not retrieve Cryptor defined data size"]) + } + return .v1( + cryptorId: cryptorId.map { $0 }, + dataLength: Int(finalCryptorDataSize) + ) + } + + private func computeCryptorDataSize(with sizeIndicator: Int) throws -> UInt16 { + guard sizeIndicator > 255 else { + return UInt16(sizeIndicator) + } + guard let nextBytes = scanner.nextBytes(2) else { + throw PubNubError(.unknownCryptorFailure, additional: ["Could not find 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..5d3b9720 --- /dev/null +++ b/Sources/PubNub/Helpers/Crypto/Header/CryptorHeaderWithinStreamFinder.swift @@ -0,0 +1,96 @@ +// +// 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 + + // Attempts to find CryptorHeader in the given InputStream. + // Returns InputStream that immediately follows CryptorHeader + func findHeader() throws -> (header: CryptorHeader, cryptorDefinedData: Data, continuationStream: InputStream) { + let buffer = read(maxLength: 1024) + let header = try CryptorHeaderParser(data: buffer).parse() + let headerLength = header.length() + + let continuationStream: InputStream + let cryptorDefinedData: Data + + switch header { + case .none: + // There is no CryptorHeader, so all supplied buffer bytes belong to the contents of the File + cryptorDefinedData = Data() + continuationStream = MultipartInputStream(inputStreams: [InputStream(data: buffer), stream]) + case .v1(_, let dataLength): + // Detects whether it's safe to extract metadata + guard headerLength + dataLength < buffer.count else { + throw PubNubError(.decryptionFailure, additional: ["Cannot extract metadata for CryptorHeader v1"]) + } + // Extracts Cryptor-defined data from the supplied buffer + cryptorDefinedData = buffer.subdata(in: headerLength.. 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 + ) + guard numberOfBytesRead > 0 else { + break; + } + if buffer.count != numberOfBytesRead { + content += Array(buffer[0 ..< numberOfBytesRead]) + } else { + 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 73% rename from Sources/PubNub/Helpers/Streams/CryptoInputStream.swift rename to Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptoInputStream.swift index 2104466a..30e54c81 100644 --- a/Sources/PubNub/Helpers/Streams/CryptoInputStream.swift +++ b/Sources/PubNub/Helpers/Crypto/Miscellaneous/CryptoInputStream.swift @@ -26,11 +26,28 @@ // 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 + } + + public enum Operation { + case decrypt + case encrypt + } + /// Estimated size of the final crypted output public var estimatedCryptoCount: Int = 0 @@ -38,17 +55,16 @@ public class CryptoInputStream: InputStream { private var cipherStream: InputStream private var rawDataBufferRead: Int = -1 private var rawDataBuffer: [UInt8]? - private var rawDataLength: Int + private var rawDataLength: Int = 0 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]? private var cryptedBufferRead: Int = -1 - private var cryptedDataLength: Int = -1 private var cryptedDataFinalysed: Bool = false private var cryptedBytes: Int = 0 private var totalCrypted: Int = 0 @@ -57,38 +73,128 @@ 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 operation == .encrypt { + do { + cryptedBufferRead = 0 + let ivBuffer = crypto.iv.map { $0 } + + let cryptoStream = try CryptoStream( + operation: CCOperation(operation == .decrypt ? kCCDecrypt : kCCEncrypt), + algorithm: crypto.cipher.algorithm, + options: crypto.options, + keyBuffer: crypto.key.map { $0 }, + keyLength: crypto.key.count, + ivBuffer: ivBuffer + ) + if includeInitializationVectorInContent { + cryptedBuffer = ivBuffer + rawDataLength = contentLength + estimatedCryptoCount = crypto.cipher.blockSize + cryptoStream.getOutputLength(inputLength: rawDataLength, isFinal: true) + } else { + rawDataLength = contentLength + estimatedCryptoCount = cryptoStream.getOutputLength(inputLength: rawDataLength, isFinal: true) + } + self.cryptoStream = cryptoStream + + } catch { + _streamError = error + _streamStatus = .error + } + } else { + + // Create a buffer to store the IV in that matches the cipher block size + var initializationVectorBuffer = [UInt8](repeating: 0, count: crypto.cipher.blockSize) + + if includeInitializationVectorInContent { + switch input.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 = input.streamError + default: + // 0 represents end of the current buffer + break + } + } else { + initializationVectorBuffer = crypto.iv.map { $0 } + } + + // Init the Crypto Stream + do { + let decryptStream = try CryptoStream( + operation: CCOperation(operation == .decrypt ? kCCDecrypt : kCCEncrypt), + algorithm: crypto.cipher.algorithm, + options: crypto.options, + keyBuffer: crypto.key.map { $0 }, + keyLength: crypto.key.count, + ivBuffer: initializationVectorBuffer + ) + + if includeInitializationVectorInContent { + rawDataLength = contentLength - crypto.cipher.blockSize + // The estimated content length is the IV length plus the crypted length + estimatedCryptoCount = crypto.cipher.blockSize + decryptStream.getOutputLength(inputLength: rawDataLength, isFinal: true) + } else { + rawDataLength = contentLength + estimatedCryptoCount = decryptStream.getOutputLength(inputLength: rawDataLength, isFinal: true) + } + + self.cryptoStream = decryptStream + } catch { + _streamError = error + _streamStatus = .error + } + } + 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 { @@ -185,10 +291,11 @@ public class CryptoInputStream: InputStream { if finalyse { var finalBuffer = writeBuffer let append = cryptedBytesLength > 0 - + + writeBuffer = Array(writeBuffer[.., maxLength: Int) -> Int { - // Create Crypto if it does not exist - let encryptStream: CryptoStream - if let cryptoStream = cryptoStream { - encryptStream = cryptoStream - } 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 - - cryptedBufferRead = 0 - - encryptStream = try CryptoStream( - operation: operation, algorithm: crypto.cipher, options: Crypto.paddingLength, - keyBuffer: crypto.key.map { $0 }, keyLength: crypto.key.count, - ivBuffer: ivBuffer - ) - - cryptedDataLength = encryptStream.getOutputLength( - inputLength: rawDataLength, isFinal: true - ) + crypto.cipher.blockSize - } catch { - _streamError = error - _streamStatus = .error - return -1 - } - - // Assign the created stream to self so we retain it for future loops - cryptoStream = encryptStream - } - return crypt( outputBuffer: buffer, maxLength: maxLength, inputStream: cipherStream, readByteOffset: 0, - cryptoStream: encryptStream + cryptoStream: self.cryptoStream! ) } // MARK: - Decrypt func decrypt(_ buffer: UnsafeMutablePointer, maxLength: Int) -> Int { - // Create Crypto if it does not exist - let decryptStream: CryptoStream - if let cryptoStream = cryptoStream { - decryptStream = cryptoStream - } 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 - } - - // 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, - ivBuffer: initializationVectorBuffer - ) - } catch { - _streamError = error - _streamStatus = .error - return -1 - } - - // Assign the created stream to self so we retain it for future loops - cryptoStream = decryptStream - } - return crypt( outputBuffer: buffer, maxLength: maxLength, inputStream: cipherStream, - cryptoStream: decryptStream + cryptoStream: self.cryptoStream! ) } 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..db6ccfb9 100644 --- a/Sources/PubNub/Helpers/Streams/MultipartInputStream.swift +++ b/Sources/PubNub/Helpers/Streams/MultipartInputStream.swift @@ -30,14 +30,16 @@ import Foundation /// An `InputStream` that can combine multiple streams into a single stream class MultipartInputStream: InputStream { let inputStreams: [InputStream] - + let length: Int + private var currentIndex: Int private var _streamStatus: Stream.Status private var _streamError: Error? private weak var _delegate: StreamDelegate? - init(inputStreams: [InputStream]) { + init(inputStreams: [InputStream], length: Int = 0) { self.inputStreams = inputStreams + self.length = length currentIndex = 0 _streamStatus = .notOpen _streamError = nil @@ -124,7 +126,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..daf474c5 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..c0b0801f 100644 --- a/Sources/PubNub/PubNub.swift +++ b/Sources/PubNub/PubNub.swift @@ -1247,32 +1247,46 @@ 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 { + /// Encrypt some `Data` using the configuration `CryptorModule` value + /// - Parameter message: The plain text message to be encrypted + /// - Returns: A `Result` containing either the encryped Data (mapped to Base64-encoded data) or the Crypto Error + public func encrypt(message: String) -> Result { + guard let cryptorModule = configuration.cryptorModule 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) + + return cryptorModule.encrypt(data: dataMessage).map { + $0.base64EncodedData() + }.mapError { + $0 as Error + } } - /// Decrypt some `Data` using the configuration Cipher Key value + /// Decrypt some `Data` using the configuration CryptorModule 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 { + /// - Returns: A `Result` containing either the decrypted plain text message or the Crypto Error + public func decrypt(data: Data) -> Result { + guard let cryptorModule = configuration.cryptorModule else { PubNub.log.error(ErrorDescription.missingCryptoKey) return .failure(CryptoError.invalidKey) } - - return crypto.decrypt(encrypted: data) + guard let base64EncodedData = Data(base64Encoded: data) else { + PubNub.log.error("Cannot create Base64-encoded data") + return .failure(CryptoError.decodeError) + } + + return cryptorModule.decrypt(data: base64EncodedData) + .flatMap { + guard let string = String(data: $0, encoding: .utf8) else { + return .failure(PubNubError(.decryptionFailure, additional: ["Cannot create String from received bytes"])) + } + return .success(string) + } + .mapError { $0 as Error } } } diff --git a/Sources/PubNub/PubNubConfiguration.swift b/Sources/PubNub/PubNubConfiguration.swift index 11d27f09..fba28279 100644 --- a/Sources/PubNub/PubNubConfiguration.swift +++ b/Sources/PubNub/PubNubConfiguration.swift @@ -73,7 +73,8 @@ public struct PubNubConfiguration: Hashable { /// - publishKey: The PubNub Publish Key to be used when publishing data to a channel /// - subscribeKey: The PubNub Subscribe Key to be used when getting data from a channel /// - userId: The unique identifier to be used as a device identifier - /// - cipherKey: If set, all communication will be encrypted with this key + /// - cipherKey: ~~If set, all communication will be encrypted with this key~~ + /// - cryptorModule: If set, all communication will be encrypted with this module /// - authKey: If Access Manager (PAM) is enabled, client will use `authToken` instead of `authKey` on all requests /// - authToken: If Access Manager (PAM) is enabled, client will use `authToken` instead of `authKey` on all requests /// - useSecureConnections: The PubNub Publish Key to be used when publishing data to a channel @@ -92,6 +93,7 @@ public struct PubNubConfiguration: Hashable { subscribeKey: String, userId: String, cipherKey: Crypto? = nil, + cryptorModule: CryptorModule? = nil, authKey: String? = nil, authToken: String? = nil, useSecureConnections: Bool = true, @@ -109,10 +111,18 @@ public struct PubNubConfiguration: Hashable { guard userId.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 else { preconditionFailure("UserId should not be empty.") } + if let cryptorModule = cryptorModule { + self.cryptorModule = cryptorModule + } else { + if let cipherKey = cipherKey { + self.cryptorModule = CryptorModule.legacyCryptoModule(with: cipherKey.key, withRandomIV: cipherKey.randomizeIV) + // Preserves cipherKey for backward compatibility if anyone has already accessed it before + self.cipherKey = cipherKey + } + } self.publishKey = publishKey self.subscribeKey = subscribeKey - self.cipherKey = cipherKey self.authKey = authKey self.authToken = authToken self.userId = userId @@ -189,6 +199,9 @@ 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 cryptorModule: CryptorModule? + /// If set, all communication will be encrypted with this key + @available(*, deprecated, message: "Use 'cryptorModule' instead") public var cipherKey: Crypto? /// If Access Manager (PAM) is enabled, client will use `authKey` on all requests public var authKey: String? 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/PubNubContractTestCase.swift b/Tests/PubNubContractTest/PubNubContractTestCase.swift index b15aee7a..12f0b0d0 100644 --- a/Tests/PubNubContractTest/PubNubContractTestCase.swift +++ b/Tests/PubNubContractTest/PubNubContractTestCase.swift @@ -35,6 +35,9 @@ let defaultSubscribeKey = "demo-36" let defaultPublishKey = "demo-36" @objc public class PubNubContractTestCase: XCTestCase { + + fileprivate var listener: SubscriptionListener! + public var messageReceivedHandler: ((PubNubMessage, [PubNubMessage]) -> Void)? public var statusReceivedHandler: ((SubscriptionListener.StatusEvent, [SubscriptionListener.StatusEvent]) -> Void)? fileprivate static var _receivedErrorStatuses: [SubscriptionListener.StatusEvent] = [] @@ -225,7 +228,8 @@ let defaultPublishKey = "demo-36" PubNubPublishContractTestSteps().setup() PubNubSubscribeContractTestSteps().setup() PubNubTimeContractTestSteps().setup() - + PubNubCryptoModuleContractTestSteps().setup() + /// Objects acceptance testins. PubNubObjectsContractTests().setup() PubNubObjectsChannelMetadataContractTestSteps().setup() @@ -245,7 +249,7 @@ let defaultPublishKey = "demo-36" ) { let subscribeStatusExpect = expectation(description: "Subscribe statuses") subscribeStatusExpect.assertForOverFulfill = false - let listener = SubscriptionListener() + self.listener = SubscriptionListener() listener.didReceiveStatus = { [weak self] result in guard let strongSelf = self else { return } diff --git a/Tests/PubNubContractTest/Steps/CryptorModule/PubNubCryptoModuleContractTestSteps.swift b/Tests/PubNubContractTest/Steps/CryptorModule/PubNubCryptoModuleContractTestSteps.swift new file mode 100644 index 00000000..18498e8b --- /dev/null +++ b/Tests/PubNubContractTest/Steps/CryptorModule/PubNubCryptoModuleContractTestSteps.swift @@ -0,0 +1,224 @@ +// +// PubNubCryptoModuleContractTestSteps.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 Cucumberish +import Foundation +import XCTest +import CommonCrypto + +@testable import PubNub + +public class PubNubCryptoModuleContractTestSteps: PubNubContractTestCase { + var cryptorKind: String! + var cipherKey: String! + var randomIV: Bool = true + var otherCryptors: [String] = [] + var cryptorModule: CryptorModule! + + var encryptDataRes: Result! + var decryptDataRes: Result! + var encryptStreamRes: Result! + var decryptStreamRes: Result! + + var givenFileUrl: URL! + var outputPath: URL! + var encryptAsBinary: Bool! + var decryptAsBinary: Bool! + + override public func handleBeforeHook() { + cryptorKind = "" + cipherKey = "" + randomIV = true + otherCryptors = [] + cryptorModule = nil + + encryptDataRes = nil + decryptDataRes = nil + encryptStreamRes = nil + decryptStreamRes = nil + + givenFileUrl = nil + outputPath = nil + encryptAsBinary = nil + decryptAsBinary = nil + + super.handleBeforeHook() + } + + public override func setup() { + startCucumberHookEventsListening() + + Given("Crypto module with '(.*)' cryptor") { args, userInfo in + self.cryptorKind = args?.first as? String ?? "" + } + Given("Legacy code with '(.*)' cipher key and '(.*)' vector") { args, userInfo in + self.cipherKey = args?.first as? String ?? "" + self.randomIV = args?.first ?? "" == "random" + } + Given("Crypto module with default '(.*)' and additional '(.*)'") { args, userInfo in + self.cryptorKind = args?.first ?? "" + self.otherCryptors = [args?.last ?? ""] + } + Match(["*"], "with '(.*)' cipher key") { args, userInfo in + self.cipherKey = args?.first ?? "" + } + Match(["*"], "with '(.*)' vector") { args, userInfo in + self.randomIV = args?.first ?? "" == "random" + } + When("I decrypt '(.*)' file") { args, userInfo in + self.outputPath = self.generateTestOutputUrl() + self.givenFileUrl = self.localUrl(for: args?.first ?? "") + self.cryptorModule = self.createCryptorModule() + self.decryptAsBinary = false + + self.decryptStreamRes = self.cryptorModule.decrypt( + stream: InputStream(url: self.givenFileUrl)!, + contentLength: self.givenFileUrl.sizeOf, + to: self.outputPath + ) + } + + When("I decrypt '(.*)' file as '(.*)'") { args, _ in + self.givenFileUrl = self.localUrl(for: args?.first ?? "") + self.outputPath = self.generateTestOutputUrl() + self.cryptorModule = self.createCryptorModule() + self.decryptAsBinary = args?.last == "binary" + + if self.decryptAsBinary { + let dataToDecrypt = try! Data(contentsOf: self.givenFileUrl) + self.decryptDataRes = self.cryptorModule.decrypt(data: dataToDecrypt) + } else { + self.decryptStreamRes = self.cryptorModule.decrypt( + stream: InputStream(url: self.givenFileUrl)!, + contentLength: self.givenFileUrl.sizeOf, + to: self.outputPath + ) + } + } + + When("I encrypt '(.*)' file as '(.*)'") { args, userInfo in + self.givenFileUrl = self.localUrl(for: args?.first ?? "") + self.encryptAsBinary = args?.last == "binary" + self.outputPath = self.generateTestOutputUrl() + self.cryptorModule = self.createCryptorModule() + + if self.encryptAsBinary { + let dataToEncrypt = try! Data(contentsOf: self.givenFileUrl) + self.encryptDataRes = self.cryptorModule.encrypt(data: dataToEncrypt) + } else { + let streamToEncrypt = InputStream(url: self.givenFileUrl)! + let contentLength = self.givenFileUrl.sizeOf + self.encryptStreamRes = self.cryptorModule.encrypt(stream: streamToEncrypt, contentLength: contentLength) + } + } + + Then("I receive '(.*)'") { thenArgs, _ in + switch thenArgs?.first ?? "" { + case "decryption error": + if self.decryptAsBinary { + XCTAssertTrue(self.failureIfAny(from: self.decryptDataRes)?.reason == .decryptionFailure) + } else { + XCTAssertTrue(self.failureIfAny(from: self.decryptStreamRes)?.reason == .decryptionFailure) + } + case "encryption error": + if self.encryptAsBinary { + XCTAssertTrue(self.failureIfAny(from: self.encryptDataRes)?.reason == .encryptionFailure) + } else { + XCTAssertTrue(self.failureIfAny(from: self.encryptStreamRes)?.reason == .encryptionFailure) + } + case "unknown cryptor error": + XCTAssertTrue(self.failureIfAny(from: self.decryptStreamRes)?.reason == .unknownCryptorFailure) + case "success": + XCTAssertNotNil(try? self.decryptStreamRes?.get()) + default: + XCTFail("Unsupported outcome") + } + } + + Then("Successfully decrypt an encrypted file with legacy code") { _, _ in + let expectedData = try! Data(contentsOf: self.givenFileUrl) + + if self.encryptAsBinary { + let encryptedData = try! self.encryptDataRes.get() + let decryptedData = try! self.cryptorModule.decrypt(data: encryptedData).get() + XCTAssertEqual(expectedData, decryptedData) + } else { + let encryptedStream = try! self.encryptStreamRes.get() + let length = (encryptedStream as! MultipartInputStream).length + self.cryptorModule.decrypt(stream: encryptedStream, contentLength: length, to: self.outputPath) + let decryptedData = try! Data(contentsOf: self.outputPath) + XCTAssertEqual(expectedData, decryptedData) + } + } + + Then("Decrypted file content equal to the '(.*)' file content") { thenArgs, _ in + let fileNameUrlToCompare = self.localUrl(for: thenArgs?.first ?? "") + let expectedData = try! Data(contentsOf: fileNameUrlToCompare) + + if self.decryptAsBinary { + XCTAssertEqual(expectedData, try! self.decryptDataRes.get()) + } else { + XCTAssertEqual(expectedData, try! Data(contentsOf: self.outputPath)) + } + } + } +} + +fileprivate extension PubNubCryptoModuleContractTestSteps { + func localUrl(for fileName: String) -> URL { + let bundlePath = Bundle(for: Self.self).bundlePath + let finalPath = bundlePath.appending("/Features/encryption/assets/\(fileName)") + + return URL(fileURLWithPath: finalPath) + } + + func createCryptorModule() -> CryptorModule { + CryptorModule( + default: self.createCryptor(for: self.cryptorKind), + cryptors: self.otherCryptors.map { self.createCryptor(for: $0) } + ) + } + + func createCryptor(for stringIdentifier: String) -> Cryptor { + if stringIdentifier == "acrh" { + return AESCBCCryptor(key: self.cipherKey) + } else { + return LegacyCryptor(key: self.cipherKey, withRandomIV: self.randomIV) + } + } + + func failureIfAny(from result: Result?) -> Error? { + guard case .failure(let failure) = result else { + return nil + } + return failure + } + + func generateTestOutputUrl() -> URL { + FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + } +} 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..a14ef1d0 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) - - let encryptedStream = CryptoInputStream(.encrypt, url: plainTextURL, with: crypto) - let encryptedURL = try FileManager.default.temporaryFile( - using: "encryptedStream", - writing: encryptedStream, - purgeExisting: true - ) - - let decryptedStream = CryptoInputStream(.decrypt, url: encryptedURL, with: crypto) - let decryptedURL = try FileManager.default.temporaryFile( - using: "decryptedStream", - writing: decryptedStream, - purgeExisting: true + XCTAssertEqual(plainTextString.isEmpty, false) + + let data = try Data(contentsOf: plainTextURL) + let inputStream = InputStream(data: data) + + let encryptedStreamResult = try cryptorModule.encrypt( + stream: inputStream, + contentLength: data.count + ).get() as! MultipartInputStream + + let decryptedURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("decryptedStream") + try? FileManager.default.removeItem(at: decryptedURL) + + cryptorModule.decrypt( + stream: encryptedStreamResult, + contentLength: encryptedStreamResult.length, + 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)