mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
8.9 KiB
Swift
165 lines
8.9 KiB
Swift
5 years ago
|
import CryptoSwift
|
||
|
import PromiseKit
|
||
5 years ago
|
import SessionUtilitiesKit
|
||
5 years ago
|
|
||
5 years ago
|
public protocol SharedSenderKeysDelegate {
|
||
|
|
||
|
func requestSenderKey(for groupPublicKey: String, senderPublicKey: String, using transaction: Any)
|
||
|
}
|
||
|
|
||
5 years ago
|
public enum SharedSenderKeys {
|
||
|
private static let gcmTagSize: UInt = 16
|
||
|
private static let ivSize: UInt = 12
|
||
|
|
||
|
// MARK: Ratcheting Error
|
||
|
public enum RatchetingError : LocalizedError {
|
||
|
case loadingFailed(groupPublicKey: String, senderPublicKey: String)
|
||
|
case messageKeyMissing(targetKeyIndex: UInt, groupPublicKey: String, senderPublicKey: String)
|
||
|
case generic
|
||
|
|
||
|
public var errorDescription: String? {
|
||
|
switch self {
|
||
|
case .loadingFailed(let groupPublicKey, let senderPublicKey): return "Couldn't get ratchet for closed group with public key: \(groupPublicKey), sender public key: \(senderPublicKey)."
|
||
|
case .messageKeyMissing(let targetKeyIndex, let groupPublicKey, let senderPublicKey): return "Couldn't find message key for old key index: \(targetKeyIndex), public key: \(groupPublicKey), sender public key: \(senderPublicKey)."
|
||
|
case .generic: return "An error occurred"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MARK: Private/Internal API
|
||
4 years ago
|
public static func generateRatchet(for groupPublicKey: String, senderPublicKey: String, using transaction: Any) -> ClosedGroupRatchet {
|
||
5 years ago
|
let rootChainKey = Data.getSecureRandomData(ofSize: 32)!.toHexString()
|
||
|
let ratchet = ClosedGroupRatchet(chainKey: rootChainKey, keyIndex: 0, messageKeys: [])
|
||
4 years ago
|
SNProtocolKitConfiguration.shared.storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: ratchet, in: .current, using: transaction)
|
||
5 years ago
|
return ratchet
|
||
|
}
|
||
|
|
||
5 years ago
|
private static func step(_ ratchet: ClosedGroupRatchet) throws -> ClosedGroupRatchet {
|
||
5 years ago
|
let nextMessageKey = try HMAC(key: Data(hex: ratchet.chainKey).bytes, variant: .sha256).authenticate([ UInt8(1) ])
|
||
|
let nextChainKey = try HMAC(key: Data(hex: ratchet.chainKey).bytes, variant: .sha256).authenticate([ UInt8(2) ])
|
||
|
let nextKeyIndex = ratchet.keyIndex + 1
|
||
|
let messageKeys = ratchet.messageKeys + [ nextMessageKey.toHexString() ]
|
||
|
return ClosedGroupRatchet(chainKey: nextChainKey.toHexString(), keyIndex: nextKeyIndex, messageKeys: messageKeys)
|
||
|
}
|
||
|
|
||
|
/// - Note: Sync. Don't call from the main thread.
|
||
5 years ago
|
private static func stepRatchetOnce(for groupPublicKey: String, senderPublicKey: String, using transaction: Any) throws -> ClosedGroupRatchet {
|
||
5 years ago
|
#if DEBUG
|
||
|
assert(!Thread.isMainThread)
|
||
|
#endif
|
||
4 years ago
|
guard let ratchet = SNProtocolKitConfiguration.shared.storage.getClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, from: .current) else {
|
||
5 years ago
|
let error = RatchetingError.loadingFailed(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey)
|
||
5 years ago
|
SNLog("\(error.errorDescription!)")
|
||
5 years ago
|
throw error
|
||
|
}
|
||
|
do {
|
||
|
let result = try step(ratchet)
|
||
4 years ago
|
SNProtocolKitConfiguration.shared.storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, in: .current, using: transaction)
|
||
5 years ago
|
return result
|
||
|
} catch {
|
||
5 years ago
|
SNLog("Couldn't step ratchet due to error: \(error).")
|
||
5 years ago
|
throw error
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// - Note: Sync. Don't call from the main thread.
|
||
5 years ago
|
private static func stepRatchet(for groupPublicKey: String, senderPublicKey: String, until targetKeyIndex: UInt, using transaction: Any, isRetry: Bool = false) throws -> ClosedGroupRatchet {
|
||
5 years ago
|
#if DEBUG
|
||
|
assert(!Thread.isMainThread)
|
||
|
#endif
|
||
|
let collection: ClosedGroupRatchetCollectionType = (isRetry) ? .old : .current
|
||
4 years ago
|
guard let ratchet = SNProtocolKitConfiguration.shared.storage.getClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, from: collection) else {
|
||
5 years ago
|
let error = RatchetingError.loadingFailed(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey)
|
||
5 years ago
|
SNLog("\(error.errorDescription!)")
|
||
5 years ago
|
throw error
|
||
|
}
|
||
|
if targetKeyIndex < ratchet.keyIndex {
|
||
|
// There's no need to advance the ratchet if this is invoked for an old key index
|
||
|
guard ratchet.messageKeys.count > targetKeyIndex else {
|
||
|
let error = RatchetingError.messageKeyMissing(targetKeyIndex: targetKeyIndex, groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey)
|
||
5 years ago
|
SNLog("\(error.errorDescription!)")
|
||
5 years ago
|
throw error
|
||
|
}
|
||
|
return ratchet
|
||
|
} else {
|
||
|
var currentKeyIndex = ratchet.keyIndex
|
||
|
var result = ratchet
|
||
|
while currentKeyIndex < targetKeyIndex {
|
||
|
do {
|
||
|
result = try step(result)
|
||
|
currentKeyIndex = result.keyIndex
|
||
|
} catch {
|
||
5 years ago
|
SNLog("Couldn't step ratchet due to error: \(error).")
|
||
5 years ago
|
throw error
|
||
|
}
|
||
|
}
|
||
|
let collection: ClosedGroupRatchetCollectionType = (isRetry) ? .old : .current
|
||
4 years ago
|
SNProtocolKitConfiguration.shared.storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, in: collection, using: transaction)
|
||
5 years ago
|
return result
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MARK: Public API
|
||
5 years ago
|
public static func encrypt(_ plaintext: Data, for groupPublicKey: String, senderPublicKey: String, using transaction: Any) throws -> (ivAndCiphertext: Data, keyIndex: UInt) {
|
||
5 years ago
|
let ratchet: ClosedGroupRatchet
|
||
|
do {
|
||
|
ratchet = try stepRatchetOnce(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction)
|
||
|
} catch {
|
||
|
if case RatchetingError.loadingFailed(_, _) = error {
|
||
4 years ago
|
SNProtocolKitConfiguration.shared.sharedSenderKeysDelegate.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction)
|
||
5 years ago
|
}
|
||
|
throw error
|
||
|
}
|
||
5 years ago
|
let iv = Data.getSecureRandomData(ofSize: ivSize)!
|
||
|
let gcm = GCM(iv: iv.bytes, tagLength: Int(gcmTagSize), mode: .combined)
|
||
5 years ago
|
let messageKey = ratchet.messageKeys.last!
|
||
|
let aes = try AES(key: Data(hex: messageKey).bytes, blockMode: gcm, padding: .noPadding)
|
||
|
let ciphertext = try aes.encrypt(plaintext.bytes)
|
||
|
return (ivAndCiphertext: iv + Data(ciphertext), ratchet.keyIndex)
|
||
|
}
|
||
|
|
||
5 years ago
|
public static func decrypt(_ ivAndCiphertext: Data, for groupPublicKey: String, senderPublicKey: String, keyIndex: UInt, using transaction: Any, isRetry: Bool = false) throws -> Data {
|
||
5 years ago
|
let ratchet: ClosedGroupRatchet
|
||
|
do {
|
||
|
ratchet = try stepRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, until: keyIndex, using: transaction, isRetry: isRetry)
|
||
|
} catch {
|
||
|
if !isRetry {
|
||
|
return try decrypt(ivAndCiphertext, for: groupPublicKey, senderPublicKey: senderPublicKey, keyIndex: keyIndex, using: transaction, isRetry: true)
|
||
|
} else {
|
||
|
if case RatchetingError.loadingFailed(_, _) = error {
|
||
4 years ago
|
SNProtocolKitConfiguration.shared.sharedSenderKeysDelegate.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction)
|
||
5 years ago
|
}
|
||
|
throw error
|
||
|
}
|
||
|
}
|
||
5 years ago
|
let iv = ivAndCiphertext[0..<Int(ivSize)]
|
||
|
let ciphertext = ivAndCiphertext[Int(ivSize)...]
|
||
|
let gcm = GCM(iv: iv.bytes, tagLength: Int(gcmTagSize), mode: .combined)
|
||
5 years ago
|
let messageKeys = ratchet.messageKeys
|
||
|
let lastNMessageKeys: [String]
|
||
|
if messageKeys.count > 16 { // Pick an arbitrary number of message keys to try; this helps resolve issues caused by messages arriving out of order
|
||
|
lastNMessageKeys = [String](messageKeys[messageKeys.index(messageKeys.endIndex, offsetBy: -16)..<messageKeys.endIndex])
|
||
|
} else {
|
||
|
lastNMessageKeys = messageKeys
|
||
|
}
|
||
|
guard !lastNMessageKeys.isEmpty else {
|
||
|
throw RatchetingError.messageKeyMissing(targetKeyIndex: keyIndex, groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey)
|
||
|
}
|
||
|
var error: Error?
|
||
|
for messageKey in lastNMessageKeys.reversed() { // Reversed because most likely the last one is the one we need
|
||
|
let aes = try AES(key: Data(hex: messageKey).bytes, blockMode: gcm, padding: .noPadding)
|
||
|
do {
|
||
|
return Data(try aes.decrypt(ciphertext.bytes))
|
||
|
} catch (let e) {
|
||
|
error = e
|
||
|
}
|
||
|
}
|
||
|
if !isRetry {
|
||
|
return try decrypt(ivAndCiphertext, for: groupPublicKey, senderPublicKey: senderPublicKey, keyIndex: keyIndex, using: transaction, isRetry: true)
|
||
|
} else {
|
||
4 years ago
|
SNProtocolKitConfiguration.shared.sharedSenderKeysDelegate.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction)
|
||
5 years ago
|
throw error ?? RatchetingError.generic
|
||
|
}
|
||
|
}
|
||
|
}
|