Merge branch 'dev' of https://github.com/loki-project/loki-messenger-ios into sync-closed-group

This commit is contained in:
Ryan ZHAO 2020-02-17 14:22:23 +11:00
commit be20236a78
15 changed files with 98 additions and 232 deletions

2
Pods

@ -1 +1 @@
Subproject commit 6fae72d48c06c35c8219ebfc58116450c473b8f1
Subproject commit c171262c7dec75b47e89cde64b6ab2908b200c6b

View File

@ -142,13 +142,13 @@ public class SessionResetOperation: OWSOperation, DurableOperation {
* ================
*/
if (self.contactThread.sessionResetState != .requestReceived) {
if (self.contactThread.sessionResetStatus != .requestReceived) {
let message = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: self.contactThread, messageType: .typeLokiSessionResetInProgress)
message.save(with: transaction)
// Loki: We have initiated a session reset
print("[Loki] Session reset initiated.")
self.contactThread.sessionResetState = .initiated
self.contactThread.sessionResetStatus = .initiated
self.contactThread.save(with: transaction)
}
}

View File

@ -1227,7 +1227,7 @@ typedef enum : NSUInteger {
}
}
[[[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageType:TSInfoMessageTypeLokiSessionResetInProgress] save];
thread.sessionResetState = TSContactThreadSessionResetStateRequestReceived;
thread.sessionResetStatus = LKSessionResetStatusRequestReceived;
[thread save];
[thread removeAllSessionRestoreDevicesWithTransaction:nil];
}

View File

@ -3,25 +3,16 @@
//
#import "TSThread.h"
#import <SignalMetadataKit/SignalMetadataKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
// Loki: Session reset state
typedef NS_ENUM(NSInteger, TSContactThreadSessionResetState) {
// No ongoing session reset
TSContactThreadSessionResetStateNone,
// We initiated a session reset
TSContactThreadSessionResetStateInitiated,
// We received a session reset
TSContactThreadSessionResetStateRequestReceived,
};
extern NSString *const TSContactThreadPrefix;
@interface TSContactThread : TSThread
// Loki: The current session reset state for this thread
@property (atomic) TSContactThreadSessionResetState sessionResetState;
@property (atomic) LKSessionResetStatus sessionResetStatus;
@property (atomic, readonly) NSArray<NSString *> *sessionRestoreDevices;
@property (nonatomic) BOOL hasDismissedOffers;

View File

@ -26,7 +26,7 @@ NSString *const TSContactThreadPrefix = @"c";
self = [super initWithUniqueId:uniqueIdentifier];
// No session reset ongoing
_sessionResetState = TSContactThreadSessionResetStateNone;
_sessionResetStatus = LKSessionResetStatusNone;
_sessionRestoreDevices = @[];
return self;

View File

@ -1,4 +1,5 @@
import PromiseKit
import SignalMetadataKit
/// Base class for `LokiFileServerAPI` and `LokiPublicChatAPI`.
public class LokiDotNetAPI : NSObject {

View File

@ -1,4 +1,5 @@
import PromiseKit
import SignalMetadataKit
internal class LokiFileServerProxy : LokiHTTPClient {
private let server: String

View File

@ -1,4 +1,5 @@
import PromiseKit
import SignalMetadataKit
internal class LokiSnodeProxy : LokiHTTPClient {
private let target: LokiAPITarget

View File

@ -1,47 +0,0 @@
import CryptoSwift
import Curve25519Kit
@objc public final class DiffieHellman : NSObject {
@objc public class DiffieHellmanError : NSError { // Not called `Error` for Obj-C interoperablity
@objc public static let decryptionFailed = DiffieHellmanError(domain: "DiffieHellmanErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "Couldn't decrypt data." ])
}
public static let ivLength: Int32 = 16;
private override init() { }
public static func encrypt(_ plainTextData: Data, using symmetricKey: Data) throws -> Data {
let iv = Randomness.generateRandomBytes(ivLength)!
let ivBytes = [UInt8](iv)
let symmetricKeyBytes = [UInt8](symmetricKey)
let messageBytes = [UInt8](plainTextData)
let blockMode = CBC(iv: ivBytes)
let aes = try AES(key: symmetricKeyBytes, blockMode: blockMode)
let cipherText = try aes.encrypt(messageBytes)
let ivAndCipher = ivBytes + cipherText
return Data(bytes: ivAndCipher, count: ivAndCipher.count)
}
public static func encrypt(_ plainTextData: Data, publicKey: Data, privateKey: Data) throws -> Data {
let symmetricKey = try Curve25519.generateSharedSecret(fromPublicKey: publicKey, privateKey: privateKey)
return try encrypt(plainTextData, using: symmetricKey)
}
public static func decrypt(_ encryptedData: Data, using symmetricKey: Data) throws -> Data {
let symmetricKeyBytes = [UInt8](symmetricKey)
guard encryptedData.count >= ivLength else { throw DiffieHellmanError.decryptionFailed }
let ivBytes = [UInt8](encryptedData[..<ivLength])
let cipherBytes = [UInt8](encryptedData[ivLength...])
let blockMode = CBC(iv: ivBytes)
let aes = try AES(key: symmetricKeyBytes, blockMode: blockMode)
let decrypted = try aes.decrypt(cipherBytes)
return Data(bytes: decrypted, count: decrypted.count)
}
public static func decrypt(_ encryptedData: Data, publicKey: Data, privateKey: Data) throws -> Data {
let symmetricKey = try Curve25519.generateSharedSecret(fromPublicKey: publicKey, privateKey: privateKey)
return try decrypt(encryptedData, using: symmetricKey)
}
}

View File

@ -1,95 +0,0 @@
import CryptoSwift
import Curve25519Kit
private extension String {
// Convert hex string to Data
fileprivate var hexData: Data {
var hex = self
var data = Data()
while(hex.count > 0) {
let subIndex = hex.index(hex.startIndex, offsetBy: 2)
let c = String(hex[..<subIndex])
hex = String(hex[subIndex...])
var ch: UInt32 = 0
Scanner(string: c).scanHexInt32(&ch)
var char = UInt8(ch)
data.append(&char, count: 1)
}
return data
}
}
/// A fallback session cipher which uses the the recipients public key to encrypt data
@objc public final class FallBackSessionCipher : NSObject {
// The pubkey hex string of the recipient
private let recipientId: String
// The identity manager
private let identityKeyStore: OWSIdentityManager
// The length of the iv
private let ivLength: Int32 = 16;
// The pubkey representation of the hex id
private lazy var recipientPubKey: Data = {
var recipientId = self.recipientId
// We need to check here if the id is prefix with '05'
// We only need to do this if the length is 66
if (recipientId.count == 66 && recipientId.hasPrefix("05")) {
recipientId = recipientId.substring(from: 2)
}
return recipientId.hexData
}()
// Our identity key
private lazy var userIdentityKeyPair: ECKeyPair? = identityKeyStore.identityKeyPair()
// A symmetric key used for encryption and decryption
private lazy var symmetricKey: Data? = {
guard let userIdentityKeyPair = userIdentityKeyPair else { return nil }
return try? Curve25519.generateSharedSecret(fromPublicKey: recipientPubKey, privateKey: userIdentityKeyPair.privateKey)
}()
/// Create a FallBackSessionCipher.
/// This is a very basic cipher and should only be used in special cases such as Friend Requests.
///
/// - Parameters:
/// - recipientId: The pubkey string of the recipient
/// - identityKeyStore: The identity manager
@objc public init(recipientId: String, identityKeyStore: OWSIdentityManager) {
self.recipientId = recipientId
self.identityKeyStore = identityKeyStore
super.init()
}
/// Encrypt a message
///
/// - Parameter message: The message to encrypt
/// - Returns: The encypted message or `nil` if it failed
@objc public func encrypt(message: Data) -> Data? {
guard let symmetricKey = symmetricKey else { return nil }
do {
return try DiffieHellman.encrypt(message, using: symmetricKey)
} catch {
Logger.warn("FallBackSessionCipher: Failed to encrypt message")
return nil
}
}
/// Decrypt a message
///
/// - Parameter message: The message to decrypt
/// - Returns: The decrypted message or `nil` if it failed
@objc public func decrypt(message: Data) -> Data? {
guard let symmetricKey = symmetricKey else { return nil }
do {
return try DiffieHellman.decrypt(message, using: symmetricKey)
} catch {
Logger.warn("FallBackSessionCipher: Failed to decrypt message")
return nil
}
}
}

View File

@ -1,25 +0,0 @@
// Loki: Refer to Docs/SessionReset.md for explanations
#import <AxolotlKit/SessionCipher.h>
NS_ASSUME_NONNULL_BEGIN
extern NSString *const kNSNotificationName_SessionAdopted;
extern NSString *const kNSNotificationKey_ContactPubKey;
@interface SessionCipher (Loki)
/**
Decrypt the given `CipherMessage`.
This function is a wrapper around `throws_decrypt:protocolContext:` and adds on the custom Loki session handling.
Refer to SignalServiceKit/Loki/Docs/SessionReset.md for an overview of how it works.
@param whisperMessage The cipher message.
@param protocolContext The protocol context (a `YapDatabaseReadWriteTransaction`).
@return The decrypted data.
*/
- (NSData *)throws_lokiDecrypt:(id<CipherMessage>)whisperMessage protocolContext:(nullable id)protocolContext NS_SWIFT_UNAVAILABLE("throws Obj-C exceptions");
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,63 @@
import Foundation
import SignalMetadataKit
@objc(LKSessionResetImplementation)
public class LokiSessionResetImplementation : NSObject, SessionResetProtocol {
private let storage: OWSPrimaryStorage
@objc public init(storage: OWSPrimaryStorage) {
self.storage = storage
}
enum Errors : Error {
case invalidPreKey
case preKeyIDsDontMatch
}
public func validatePreKeyForFriendRequestAcceptance(for recipientID: String, whisperMessage: CipherMessage, protocolContext: Any?) throws {
guard let transaction = protocolContext as? YapDatabaseReadWriteTransaction else {
print("[Loki] Could not verify friend request acceptance pre key because an invalid transaction was provided.")
return
}
guard let preKeyMessage = whisperMessage as? PreKeyWhisperMessage else { return }
guard let storedPreKey = storage.getPreKey(forContact: recipientID, transaction: transaction) else {
print("[Loki] Received a friend request from a public key for which no pre key bundle was created.")
throw Errors.invalidPreKey
}
guard storedPreKey.id == preKeyMessage.prekeyID else {
print("[Loki] Received a `PreKeyWhisperMessage` (friend request acceptance) from an unknown source.")
throw Errors.preKeyIDsDontMatch
}
}
public func getSessionResetStatus(for recipientID: String, protocolContext: Any?) -> SessionResetStatus {
guard let transaction = protocolContext as? YapDatabaseReadWriteTransaction else {
print("[Loki] Could not get session reset status for \(recipientID) because an invalid transaction was provided.")
return .none
}
guard let thread = TSContactThread.getWithContactId(recipientID, transaction: transaction) else { return .none }
return thread.sessionResetStatus
}
public func onNewSessionAdopted(for recipientID: String, protocolContext: Any?) {
guard let transaction = protocolContext as? YapDatabaseReadWriteTransaction else {
Logger.warn("[Loki] Cannot handle new session adoption because an invalid transaction was provided.")
return
}
guard !recipientID.isEmpty else { return }
guard let thread = TSContactThread.getWithContactId(recipientID, transaction: transaction) else {
Logger.debug("[Loki] A new session was adopted but the thread couldn't be found for: \(recipientID).")
return
}
// If the current user initiated the reset then send back an empty message to acknowledge the completion of the session reset
if thread.sessionResetStatus == .initiated {
let emptyMessage = EphemeralMessage(in: thread)
SSKEnvironment.shared.messageSender.sendPromise(message: emptyMessage).retainUntilComplete()
}
// Show session reset done message
TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeLokiSessionResetDone).save(with: transaction)
// Clear the session reset status
thread.sessionResetStatus = .none
thread.save(with: transaction)
}
}

View File

@ -15,7 +15,6 @@
#import "OWSPrimaryStorage+SessionStore.h"
#import "OWSPrimaryStorage+SignedPreKeyStore.h"
#import "OWSPrimaryStorage.h"
#import "SessionCipher+Loki.h"
#import "SSKEnvironment.h"
#import "SignalRecipient.h"
#import "TSAccountManager.h"
@ -84,6 +83,7 @@ NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDes
@interface OWSMessageDecrypter ()
@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage;
@property (nonatomic, readonly) LKSessionResetImplementation *sessionResetImplementation;
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
@end
@ -101,7 +101,7 @@ NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDes
}
_primaryStorage = primaryStorage;
_sessionResetImplementation = [[LKSessionResetImplementation alloc] initWithStorage:primaryStorage];
_dbConnection = primaryStorage.newDatabaseConnection;
OWSSingletonAssert();
@ -336,7 +336,8 @@ NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDes
}
NSString *recipientId = envelope.source;
FallBackSessionCipher *cipher = [[FallBackSessionCipher alloc] initWithRecipientId:recipientId identityKeyStore:self.identityManager];
ECKeyPair *identityKeyPair = self.identityManager.identityKeyPair;
FallBackSessionCipher *cipher = [[FallBackSessionCipher alloc] initWithRecipientId:recipientId privateKey:identityKeyPair.privateKey];
NSData *_Nullable plaintextData = [[cipher decryptWithMessage:encryptedData] removePadding];
if (!plaintextData) {
@ -425,16 +426,22 @@ NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDes
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
@try {
id<CipherMessage> cipherMessage = cipherMessageBlock(encryptedData);
SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:self.primaryStorage
preKeyStore:self.primaryStorage
signedPreKeyStore:self.primaryStorage
identityKeyStore:self.identityManager
recipientId:recipientId
deviceId:deviceId];
LKSessionCipher *cipher = [[LKSessionCipher alloc]
initWithSessionResetImplementation:self.sessionResetImplementation
sessionStore:self.primaryStorage
preKeyStore:self.primaryStorage
signedPreKeyStore:self.primaryStorage
identityKeyStore:self.primaryStorage
recipientID:recipientId
deviceID:deviceId];
// plaintextData may be nil for some envelope types.
NSData *_Nullable plaintextData = [[cipher throws_lokiDecrypt:cipherMessage protocolContext:transaction] removePadding];
NSError *error = nil;
NSData *_Nullable decryptedData = [cipher decrypt:cipherMessage protocolContext:transaction error:&error];
// Throw if we got an error
SCKRaiseIfExceptionWrapperError(error);
NSData *_Nullable plaintextData = decryptedData != nil ? [decryptedData removePadding] : nil;
OWSMessageDecryptResult *result = [OWSMessageDecryptResult resultWithEnvelopeData:envelopeData
plaintextData:plaintextData
source:envelope.source
@ -482,11 +489,13 @@ NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDes
[self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSError *cipherError;
SMKSecretSessionCipher *_Nullable cipher =
[[SMKSecretSessionCipher alloc] initWithSessionStore:self.primaryStorage
[[SMKSecretSessionCipher alloc] initWithSessionResetImplementation:self.sessionResetImplementation
sessionStore:self.primaryStorage
preKeyStore:self.primaryStorage
signedPreKeyStore:self.primaryStorage
identityStore:self.identityManager
error:&cipherError];
if (cipherError || !cipher) {
OWSFailDebug(@"Could not create secret session cipher: %@.", cipherError);
cipherError = EnsureDecryptError(cipherError, @"Could not create secret session cipher.");

View File

@ -36,7 +36,6 @@
#import "OWSSyncGroupsMessage.h"
#import "OWSSyncGroupsRequestMessage.h"
#import "ProfileManagerProtocol.h"
#import "SessionCipher+Loki.h"
#import "SSKEnvironment.h"
#import "TSAccountManager.h"
#import "TSAttachment.h"
@ -53,11 +52,11 @@
#import "TSQuotedMessage.h"
#import <SignalCoreKit/Cryptography.h>
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalMetadataKit/SignalMetadataKit-Swift.h>
#import <SignalServiceKit/NSObject+Casting.h>
#import <SignalServiceKit/SignalRecipient.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import <YapDatabase/YapDatabase.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import "OWSDispatch.h"
#import "OWSBatchMessageProcessor.h"
#import "OWSQueues.h"
@ -94,9 +93,6 @@ NS_ASSUME_NONNULL_BEGIN
_primaryStorage = primaryStorage;
_dbConnection = primaryStorage.newDatabaseConnection;
_incomingMessageFinder = [[OWSIncomingMessageFinder alloc] initWithPrimaryStorage:primaryStorage];
// Loki: Observe session changes
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleNewSessionAdopted:) name:kNSNotificationName_SessionAdopted object:nil];
OWSSingletonAssert();
@ -1184,7 +1180,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.primaryStorage archiveAllSessionsForContact:hexEncodedPublicKey protocolContext:transaction];
// Loki: Set our session reset state
thread.sessionResetState = TSContactThreadSessionResetStateRequestReceived;
thread.sessionResetStatus = LKSessionResetStatusRequestReceived;
[thread saveWithTransaction:transaction];
// Loki: Send an empty message to trigger the session reset code for both parties
@ -2050,36 +2046,6 @@ NS_ASSUME_NONNULL_BEGIN
}
}
# pragma mark - Loki Session Handling
- (void)handleNewSessionAdopted:(NSNotification *)notification {
NSString *hexEncodedPublicKey = notification.userInfo[kNSNotificationKey_ContactPubKey];
if (hexEncodedPublicKey.length == 0) { return; }
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
TSContactThread *thread = [TSContactThread getThreadWithContactId:hexEncodedPublicKey transaction:transaction];
if (thread == nil) {
NSLog(@"[Loki] A new session was adopted but the thread couldn't be found for: %@.", hexEncodedPublicKey);
return;
}
// If the current user initiated the reset then send back an empty message to acknowledge the completion of the session reset
if (thread.sessionResetState == TSContactThreadSessionResetStateInitiated) {
LKEphemeralMessage *emptyMessage = [[LKEphemeralMessage alloc] initInThread:thread];
[self.messageSenderJobQueue addMessage:emptyMessage transaction:transaction];
}
// Show session reset done message
[[[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp
inThread:thread
messageType:TSInfoMessageTypeLokiSessionResetDone] saveWithTransaction:transaction];
// Clear the session reset state
thread.sessionResetState = TSContactThreadSessionResetStateNone;
[thread saveWithTransaction:transaction];
}];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1941,7 +1941,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
NSString *recipientId = recipient.recipientId;
TSOutgoingMessage *message = messageSend.message;
FallBackSessionCipher *cipher = [[FallBackSessionCipher alloc] initWithRecipientId:recipientId identityKeyStore:self.identityManager];
ECKeyPair *identityKeyPair = self.identityManager.identityKeyPair;
FallBackSessionCipher *cipher = [[FallBackSessionCipher alloc] initWithRecipientId:recipientId privateKey:identityKeyPair.privateKey];
// This will return nil if encryption failed
NSData *_Nullable serializedMessage = [cipher encryptWithMessage:[plainText paddedMessageBody]];