Skip to content

Commit

Permalink
Fixes for CryptoModule
Browse files Browse the repository at this point in the history
  • Loading branch information
jguz-pubnub committed Mar 14, 2024
1 parent 9d8766a commit fbb6055
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 51 deletions.
67 changes: 39 additions & 28 deletions Sources/PubNub/Helpers/Crypto/CryptoModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,31 @@ public class CryptorModule {
public static func aesCbcCryptoModule(with key: String, withRandomIV: Bool = true) -> CryptoModule {
preconditionFailure("This method is no longer available")
}

public static func legacyCryptoModule(with key: String, withRandomIV: Bool = true) -> CryptoModule {
preconditionFailure("This method is no longer available")
}
}

/// Object capable of encryption/decryption
public struct CryptoModule {
private let defaultCryptor: Cryptor
private let cryptors: [Cryptor]
private let defaultCryptor: any Cryptor
private let cryptors: [any Cryptor]
private let legacyCryptorId: CryptorId = []

typealias Base64EncodedString = String

/// Initializes `CryptoModule` 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 `CryptoModule` configured for you.
/// 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 `CryptoModule` 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] = []) {
public init(default cryptor: any Cryptor, cryptors: [any Cryptor] = []) {
self.defaultCryptor = cryptor
self.cryptors = cryptors
}
Expand All @@ -46,13 +48,15 @@ public struct CryptoModule {
///
/// - Parameters:
/// - data: Data to encrypt
/// - Returns: A success, storing encrypted `Data` if operation succeeds. Otherwise, a failure storing `PubNubError` is returned
/// - Returns:
/// - **Success**: An encrypted `Data` object
/// - **Failure**: `PubNubError` describing the reason of failure
public func encrypt(data: Data) -> Result<Data, PubNubError> {
guard !data.isEmpty else {
return .failure(PubNubError(
.encryptionFailure,
additional: ["Cannot encrypt empty Data"])
)
additional: ["Cannot encrypt empty Data"]
))
}
return defaultCryptor.encrypt(data: data).map {
if defaultCryptor.id == LegacyCryptor.ID {
Expand All @@ -71,13 +75,15 @@ public struct CryptoModule {
///
/// - Parameters:
/// - data: Data to decrypt
/// - Returns: A success, storing decrypted `Data` if operation succeeds. Otherwise, a failure storing `PubNubError` is returned
/// - Returns:
/// - **Success**: A decrypted `Data` object
/// - **Failure**: `PubNubError` describing the reason of failure
public func decrypt(data: Data) -> Result<Data, PubNubError> {
guard !data.isEmpty else {
return .failure(PubNubError(
.decryptionFailure,
additional: ["Cannot decrypt empty Data in \(String(describing: self))"])
)
additional: ["Cannot decrypt empty Data in \(String(describing: self))"]
))
}
do {
let header = try CryptorHeader.from(data: data)
Expand All @@ -87,7 +93,7 @@ public struct CryptoModule {
.unknownCryptorFailure,
additional: [
"Could not find matching Cryptor for \(header.cryptorId()) while decrypting Data. " +
"Ensure the corresponding instance is registered in \(String(describing: Self.self))"
"Ensure the corresponding instance is registered in \(String(describing: Self.self))"
]
))
}
Expand Down Expand Up @@ -115,8 +121,8 @@ public struct CryptoModule {
if $0.isEmpty {
return .failure(PubNubError(
.decryptionFailure,
additional: ["Decrypting resulted with empty Data"])
)
additional: ["Decrypting resulted with empty Data"]
))
}
return .success($0)
}
Expand All @@ -129,8 +135,8 @@ public struct CryptoModule {
return .failure(PubNubError(
.decryptionFailure,
underlying: error,
additional: ["Cannot decrypt InputStream"])
)
additional: ["Cannot decrypt InputStream"]
))
}
}

Expand All @@ -139,7 +145,9 @@ public struct CryptoModule {
/// - Parameters:
/// - stream: Stream to encrypt
/// - contentLength: Content length of encoded stream
/// - Returns: A success, storing an `InputStream` value if operation succeeds. Otherwise, a failure storing `PubNubError` is returned
/// - Returns:
/// - **Success**: An `InputStream` value
/// - **Failure**: `PubNubError` describing the reason of failure
public func encrypt(stream: InputStream, contentLength: Int) -> Result<InputStream, PubNubError> {
guard contentLength > 0 else {
return .failure(PubNubError(
Expand Down Expand Up @@ -179,7 +187,9 @@ public struct CryptoModule {
/// - 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
/// - Returns:
/// - **Success**: A decrypted `InputStream` object
/// - **Failure**: `PubNubError` describing the reason of failure
@discardableResult
public func decrypt(
stream: InputStream,
Expand All @@ -203,7 +213,7 @@ public struct CryptoModule {
.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))"
"Ensure the corresponding instance is registered in \(String(describing: Self.self))"
]
))
}
Expand All @@ -218,8 +228,8 @@ public struct CryptoModule {
if outputPath.sizeOf == 0 {
return .failure(PubNubError(
.decryptionFailure,
additional: ["Decrypting resulted with an empty File"])
)
additional: ["Decrypting resulted with an empty File"]
))
}
return .success($0)
}
Expand All @@ -237,7 +247,7 @@ public struct CryptoModule {
}
}

private func cryptor(matching header: CryptorHeader) -> Cryptor? {
private func cryptor(matching header: CryptorHeader) -> (any Cryptor)? {
header.cryptorId() == defaultCryptor.id ? defaultCryptor : cryptors.first(where: {
$0.id == header.cryptorId()
})
Expand All @@ -246,7 +256,6 @@ public struct CryptoModule {

/// Convenience methods for creating `CryptoModule`
public extension CryptoModule {

/// Returns **recommended** `CryptoModule` for encryption/decryption
///
/// - Parameters:
Expand Down Expand Up @@ -279,7 +288,9 @@ extension CryptoModule: Equatable {

extension CryptoModule: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(cryptors.map { $0.id })
for cryptor in cryptors {
hasher.combine(cryptor)
}
}
}

Expand All @@ -289,7 +300,7 @@ extension CryptoModule: CustomStringConvertible {
}
}

internal extension CryptoModule {
extension CryptoModule {
func encrypt(string: String) -> Result<Base64EncodedString, PubNubError> {
guard let data = string.data(using: .utf8) else {
return .failure(PubNubError(
Expand All @@ -309,8 +320,8 @@ internal extension CryptoModule {
} else {
return .failure(PubNubError(
.decryptionFailure,
additional: ["Cannot create String from provided Data"])
)
additional: ["Cannot create String from provided Data"]
))
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions Sources/PubNub/Helpers/Crypto/Cryptors/AESCBCCryptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,10 @@ public struct AESCBCCryptor: Cryptor {
}
}
}

extension AESCBCCryptor: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(id)
}
}
2 changes: 1 addition & 1 deletion Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public struct EncryptedStreamData {
public typealias CryptorId = [UInt8]

/// Protocol for all types that encapsulate concrete encryption/decryption operations
public protocol Cryptor {
public protocol Cryptor: Hashable {
/// Unique 4-byte identifier across all `Cryptor`
///
/// - Important: `[0x41, 0x43, 0x52, 0x48]` and `[0x00, 0x00, 0x00, 0x00]` values are reserved
Expand Down
8 changes: 8 additions & 0 deletions Sources/PubNub/Helpers/Crypto/Cryptors/LegacyCryptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,11 @@ public struct LegacyCryptor: Cryptor {
}
}
}

extension LegacyCryptor: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
hasher.combine(withRandomIV)
hasher.combine(LegacyCryptor.ID)
}
}
8 changes: 4 additions & 4 deletions Sources/PubNub/PubNub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1381,9 +1381,9 @@ public extension PubNub {
// MARK: - Crypto

extension PubNub {
/// Encrypt some `Data` using the configuration `CryptoModule` value
/// Encrypts the `Data` object using `CryptoModule` provided in configuration
/// - 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
/// - Returns: A `Result` containing either the encryped `Data` (mapped to Base64-encoded data) or the `CryptoError`
public func encrypt(message: String) -> Result<Data, Error> {
guard let cryptoModule = configuration.cryptoModule else {
PubNub.log.error(ErrorDescription.missingCryptoKey)
Expand All @@ -1400,9 +1400,9 @@ extension PubNub {
}
}

/// Decrypt some `Data` using the configuration CryptoModule value
/// Decrypts the given `Data` object using `CryptoModule` provided in `configuration`
/// - Parameter message: The encrypted `Data` to decrypt
/// - Returns: A `Result` containing either the decrypted plain text message or the Crypto Error
/// - Returns: A `Result` containing either the decrypted plain text message or the `CryptoError`
public func decrypt(data: Data) -> Result<String, Error> {
guard let cryptoModule = configuration.cryptoModule else {
PubNub.log.error(ErrorDescription.missingCryptoKey)
Expand Down
Loading

0 comments on commit fbb6055

Please sign in to comment.