Add SSKs to SessionProtocolKit

This commit is contained in:
nielsandriesse 2020-11-05 17:04:39 +11:00
parent 6ab4b64926
commit 10582e0381
104 changed files with 399 additions and 33 deletions

View File

@ -105,9 +105,11 @@ end
target 'SessionProtocolKit' do
pod 'CocoaLumberjack', :inhibit_warnings => true
pod 'CryptoSwift', :inhibit_warnings => true
pod 'Curve25519Kit', :inhibit_warnings => true
pod 'GRKOpenSSLFramework', :inhibit_warnings => true
pod 'HKDFKit', :inhibit_warnings => true
pod 'PromiseKit', :inhibit_warnings => true
pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true
end

View File

@ -332,6 +332,6 @@ SPEC CHECKSUMS:
YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 821988dba010cf44893aecf26652d7da531868f0
PODFILE CHECKSUM: 58acf63b2164dadf743187b6a52154721c1c01dd
COCOAPODS: 1.10.0.rc.1

2
Pods

@ -1 +1 @@
Subproject commit 24da46b65b9581625f18cd9dac30b310401e84f2
Subproject commit 03a39c064860412e9e69b99f19dd85cd5a9b8aad

View File

@ -0,0 +1,14 @@
public struct Configuration {
public let storage: SessionProtocolKitStorageProtocol
public let sharedSenderKeysDelegate: SharedSenderKeysDelegate
internal static var shared: Configuration!
}
public enum SessionProtocolKit { // Just to make the external API nice
public static func configure(with configuration: Configuration) {
Configuration.shared = configuration
}
}

View File

@ -0,0 +1,45 @@
import SessionUtilities
public final class ClosedGroupRatchet : NSObject, NSCoding {
public let chainKey: String
public let keyIndex: UInt
public let messageKeys: [String]
// MARK: Initialization
public init(chainKey: String, keyIndex: UInt, messageKeys: [String]) {
self.chainKey = chainKey
self.keyIndex = keyIndex
self.messageKeys = messageKeys
}
// MARK: Coding
public init?(coder: NSCoder) {
guard let chainKey = coder.decodeObject(forKey: "chainKey") as? String,
let keyIndex = coder.decodeObject(forKey: "keyIndex") as? UInt,
let messageKeys = coder.decodeObject(forKey: "messageKeys") as? [String] else { return nil }
self.chainKey = chainKey
self.keyIndex = UInt(keyIndex)
self.messageKeys = messageKeys
super.init()
}
public func encode(with coder: NSCoder) {
coder.encode(chainKey, forKey: "chainKey")
coder.encode(keyIndex, forKey: "keyIndex")
coder.encode(messageKeys, forKey: "messageKeys")
}
// MARK: Equality
override public func isEqual(_ other: Any?) -> Bool {
guard let other = other as? ClosedGroupRatchet else { return false }
return chainKey == other.chainKey && keyIndex == other.keyIndex && messageKeys == other.messageKeys
}
// MARK: Hashing
override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:)
return chainKey.hashValue ^ keyIndex.hashValue ^ messageKeys.hashValue
}
// MARK: Description
override public var description: String { return "[ chainKey : \(chainKey), keyIndex : \(keyIndex), messageKeys : \(messageKeys.prettifiedDescription) ]" }
}

View File

@ -0,0 +1,46 @@
public final class ClosedGroupSenderKey : NSObject, NSCoding {
public let chainKey: Data
public let keyIndex: UInt
public let publicKey: Data
// MARK: Initialization
init(chainKey: Data, keyIndex: UInt, publicKey: Data) {
self.chainKey = chainKey
self.keyIndex = keyIndex
self.publicKey = publicKey
}
// MARK: Coding
public init?(coder: NSCoder) {
guard let chainKey = coder.decodeObject(forKey: "chainKey") as? Data,
let keyIndex = coder.decodeObject(forKey: "keyIndex") as? UInt,
let publicKey = coder.decodeObject(forKey: "publicKey") as? Data else { return nil }
self.chainKey = chainKey
self.keyIndex = UInt(keyIndex)
self.publicKey = publicKey
super.init()
}
public func encode(with coder: NSCoder) {
coder.encode(chainKey, forKey: "chainKey")
coder.encode(keyIndex, forKey: "keyIndex")
coder.encode(publicKey, forKey: "publicKey")
}
// MARK: Equality
override public func isEqual(_ other: Any?) -> Bool {
guard let other = other as? ClosedGroupSenderKey else { return false }
return chainKey == other.chainKey && keyIndex == other.keyIndex && publicKey == other.publicKey
}
// MARK: Hashing
override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:)
return chainKey.hashValue ^ keyIndex.hashValue ^ publicKey.hashValue
}
// MARK: Description
override public var description: String {
return "[ chainKey : \(chainKey), keyIndex : \(keyIndex), publicKey: \(publicKey.toHexString()) ]"
}
}

View File

@ -0,0 +1,172 @@
import CryptoSwift
import PromiseKit
import SessionUtilities
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
internal func generateRatchet(for groupPublicKey: String, senderPublicKey: String, using transaction: Any) -> ClosedGroupRatchet {
let rootChainKey = Data.getSecureRandomData(ofSize: 32)!.toHexString()
let ratchet = ClosedGroupRatchet(chainKey: rootChainKey, keyIndex: 0, messageKeys: [])
Configuration.shared.storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: ratchet, in: .current, using: transaction)
return ratchet
}
private func step(_ ratchet: ClosedGroupRatchet) throws -> ClosedGroupRatchet {
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.
private func stepRatchetOnce(for groupPublicKey: String, senderPublicKey: String, using transaction: Any) throws -> ClosedGroupRatchet {
#if DEBUG
assert(!Thread.isMainThread)
#endif
guard let ratchet = Configuration.shared.storage.getClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, from: .current) else {
let error = RatchetingError.loadingFailed(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey)
print("[Loki] \(error.errorDescription!)")
throw error
}
do {
let result = try step(ratchet)
Configuration.shared.storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, in: .current, using: transaction)
return result
} catch {
print("[Loki] Couldn't step ratchet due to error: \(error).")
throw error
}
}
/// - Note: Sync. Don't call from the main thread.
private func stepRatchet(for groupPublicKey: String, senderPublicKey: String, until targetKeyIndex: UInt, using transaction: Any, isRetry: Bool = false) throws -> ClosedGroupRatchet {
#if DEBUG
assert(!Thread.isMainThread)
#endif
let collection: ClosedGroupRatchetCollectionType = (isRetry) ? .old : .current
guard let ratchet = Configuration.shared.storage.getClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, from: collection) else {
let error = RatchetingError.loadingFailed(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey)
print("[Loki] \(error.errorDescription!)")
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)
print("[Loki] \(error.errorDescription!)")
throw error
}
return ratchet
} else {
var currentKeyIndex = ratchet.keyIndex
var result = ratchet
while currentKeyIndex < targetKeyIndex {
do {
result = try step(result)
currentKeyIndex = result.keyIndex
} catch {
print("[Loki] Couldn't step ratchet due to error: \(error).")
throw error
}
}
let collection: ClosedGroupRatchetCollectionType = (isRetry) ? .old : .current
Configuration.shared.storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, in: collection, using: transaction)
return result
}
}
// MARK: Public API
public func encrypt(_ plaintext: Data, for groupPublicKey: String, senderPublicKey: String, using transaction: Any) throws -> (ivAndCiphertext: Data, keyIndex: UInt) {
let ratchet: ClosedGroupRatchet
do {
ratchet = try stepRatchetOnce(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction)
} catch {
// FIXME: It'd be cleaner to handle this in OWSMessageDecrypter (where all the other decryption errors are handled), but this was a lot more
// convenient because there's an easy way to get the sender public key from here.
if case RatchetingError.loadingFailed(_, _) = error {
Configuration.shared.sharedSenderKeysDelegate.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction)
}
throw error
}
let iv = Data.getSecureRandomData(ofSize: SharedSenderKeys.ivSize)!
let gcm = GCM(iv: iv.bytes, tagLength: Int(SharedSenderKeys.gcmTagSize), mode: .combined)
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)
}
public func decrypt(_ ivAndCiphertext: Data, for groupPublicKey: String, senderPublicKey: String, keyIndex: UInt, using transaction: Any, isRetry: Bool = false) throws -> Data {
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 {
// FIXME: It'd be cleaner to handle this in OWSMessageDecrypter (where all the other decryption errors are handled), but this was a lot more
// convenient because there's an easy way to get the sender public key from here.
if case RatchetingError.loadingFailed(_, _) = error {
Configuration.shared.sharedSenderKeysDelegate.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction)
}
throw error
}
}
let iv = ivAndCiphertext[0..<Int(SharedSenderKeys.ivSize)]
let ciphertext = ivAndCiphertext[Int(SharedSenderKeys.ivSize)...]
let gcm = GCM(iv: iv.bytes, tagLength: Int(SharedSenderKeys.gcmTagSize), mode: .combined)
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 {
Configuration.shared.sharedSenderKeysDelegate.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction)
throw error ?? RatchetingError.generic
}
}
public func isClosedGroup(_ publicKey: String) -> Bool {
return Configuration.shared.storage.getUserClosedGroupPublicKeys().contains(publicKey)
}
public func getKeyPair(forGroupWithPublicKey groupPublicKey: String) -> ECKeyPair {
let privateKey = Configuration.shared.storage.getClosedGroupPrivateKey(for: groupPublicKey)!
return ECKeyPair(publicKey: Data(hex: groupPublicKey.removing05PrefixIfNeeded()), privateKey: Data(hex: privateKey))
}
}

View File

@ -0,0 +1,5 @@
public protocol SharedSenderKeysDelegate {
func requestSenderKey(for groupPublicKey: String, senderPublicKey: String, using transaction: Any)
}

View File

@ -0,0 +1,17 @@
public enum ClosedGroupRatchetCollectionType {
case old, current
}
public protocol SessionProtocolKitStorageProtocol {
func with(_ work: (Any) -> Void)
func getClosedGroupRatchet(for groupPublicKey: String, senderPublicKey: String, from collection: ClosedGroupRatchetCollectionType) -> ClosedGroupRatchet?
func setClosedGroupRatchet(for groupPublicKey: String, senderPublicKey: String, ratchet: ClosedGroupRatchet, in collection: ClosedGroupRatchetCollectionType, using transaction: Any)
func getAllClosedGroupRatchets(for groupPublicKey: String, from collection: ClosedGroupRatchetCollectionType) -> [(senderPublicKey: String, ratchet: ClosedGroupRatchet)]
func getAllClosedGroupSenderKeys(for groupPublicKey: String, from collection: ClosedGroupRatchetCollectionType) -> Set<ClosedGroupSenderKey>
func removeAllClosedGroupRatchets(for groupPublicKey: String, from collection: ClosedGroupRatchetCollectionType, using transaction: Any)
func getUserClosedGroupPublicKeys() -> Set<String>
func getClosedGroupPrivateKey(for publicKey: String) -> String?
}

View File

@ -2,15 +2,6 @@ import Foundation
internal extension Data {
/// Returns `size` bytes of random data generated using the default secure random number generator. See
/// [SecRandomCopyBytes](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes) for more information.
static func getSecureRandomData(ofSize size: UInt) -> Data? {
var data = Data(count: Int(size))
let result = data.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, Int(size), $0.baseAddress!) }
guard result == errSecSuccess else { return nil }
return data
}
init(from inputStream: InputStream) throws {
self.init()
inputStream.open()

Some files were not shown because too many files have changed in this diff Show More