From a3382f41d4cec8248d5412a6bed4f36dd3158859 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 9 Nov 2020 10:58:47 +1100 Subject: [PATCH] Re-organize files --- Podfile | 2 +- Podfile.lock | 2 +- .../Jobs/AttachmentDownloadJob.swift | 2 +- .../Jobs/AttachmentUploadJob.swift | 2 +- SessionMessagingKit/Jobs/JobQueue.swift | 2 +- .../Jobs/MessageReceiveJob.swift | 2 +- SessionMessagingKit/Jobs/MessageSendJob.swift | 2 +- .../Jobs/NotifyPNServerJob.swift | 2 +- .../Control Message/ClosedGroupUpdate.swift | 2 +- .../ExpirationTimerUpdate.swift | 2 +- .../Control Message/ReadReceipt.swift | 2 +- .../Control Message/SessionRequest.swift | 2 +- .../Control Message/TypingIndicator.swift | 2 +- .../VisibleMessage+LinkPreview.swift | 2 +- .../VisibleMessage+Profile.swift | 2 +- .../VisibleMessage+Quote.swift | 2 +- .../Visible Message/VisibleMessage.swift | 2 +- .../MessageReceiver+Decryption.swift | 2 +- .../Sending & Receiving/MessageReceiver.swift | 2 +- .../MessageSender+Encryption.swift | 2 +- .../Sending & Receiving/MessageSender.swift | 2 +- .../Utilities/MessageWrapper.swift | 2 +- .../Utilities/ProofOfWork.swift | 2 +- .../ClosedGroupRatchet.swift | 2 +- .../Shared Sender Keys/SharedSenderKeys.swift | 2 +- .../Signal/FallbackSessionCipher.swift | 2 +- .../OnionRequestAPI+Encryption.swift | 2 +- SessionSnodeKit/OnionRequestAPI.swift | 2 +- SessionSnodeKit/SnodeAPI.swift | 2 +- SessionSnodeKit/SnodeMessage.swift | 2 +- SessionSnodeKit/Storage.swift | 2 +- SessionUtilities/Meta/SessionUtilities.h | 9 - .../AESGCM.swift | 0 .../Array+Description.swift | 0 .../Data+SecureRandom.swift | 0 .../Dictionary+Description.swift | 0 .../DiffieHellman.swift | 0 .../ECKeyPair+Utilities.h | 0 .../ECKeyPair+Utilities.m | 0 .../HTTP.swift | 0 .../JSON.swift | 0 .../Logging.swift | 0 .../Meta/Info.plist | 0 .../Meta/SessionUtilitiesKit.h | 9 + .../Mnemonic.swift | 3 +- .../NSDate+Timestamp.h | 0 .../NSDate+Timestamp.mm | 0 .../NSTimer+Proxying.h | 0 .../NSTimer+Proxying.m | 0 .../Promise+Retrying.swift | 0 .../String+Trimming.swift | 0 .../TSRequest.h | 0 .../TSRequest.m | 0 Signal.xcodeproj/project.pbxproj | 82 ++-- .../src/Loki/API/Deprecated/ProofOfWork.swift | 109 ----- .../src/Loki/API/LokiMessage.swift | 75 --- .../src/Loki/API/MessageWrapper.swift | 72 --- .../OnionRequestAPI+Encryption.swift | 72 --- .../API/Onion Requests/OnionRequestAPI.swift | 435 ------------------ .../src/Loki/API/SignalMessage.swift | 28 -- SignalServiceKit/src/Loki/API/Snode.swift | 66 --- SignalServiceKit/src/Loki/API/SnodeAPI.swift | 352 -------------- .../API/Utilities/DecryptionUtilities.swift | 18 - .../API/Utilities/EncryptionUtilities.swift | 38 -- .../src/Loki/API/Utilities/HTTP.swift | 107 ----- .../Closed Groups/ClosedGroupRatchet.swift | 44 -- .../Closed Groups/ClosedGroupSenderKey.swift | 51 -- .../ClosedGroupUpdateMessage.swift | 125 ----- .../Closed Groups/ClosedGroupUtilities.swift | 70 --- .../SharedSenderKeysImplementation.swift | 220 --------- 70 files changed, 86 insertions(+), 1961 deletions(-) delete mode 100644 SessionUtilities/Meta/SessionUtilities.h rename {SessionUtilities => SessionUtilitiesKit}/AESGCM.swift (100%) rename {SessionUtilities => SessionUtilitiesKit}/Array+Description.swift (100%) rename {SessionUtilities => SessionUtilitiesKit}/Data+SecureRandom.swift (100%) rename {SessionUtilities => SessionUtilitiesKit}/Dictionary+Description.swift (100%) rename {SessionUtilities => SessionUtilitiesKit}/DiffieHellman.swift (100%) rename {SessionUtilities => SessionUtilitiesKit}/ECKeyPair+Utilities.h (100%) rename {SessionUtilities => SessionUtilitiesKit}/ECKeyPair+Utilities.m (100%) rename {SessionUtilities => SessionUtilitiesKit}/HTTP.swift (100%) rename {SessionUtilities => SessionUtilitiesKit}/JSON.swift (100%) rename {SessionUtilities => SessionUtilitiesKit}/Logging.swift (100%) rename {SessionUtilities => SessionUtilitiesKit}/Meta/Info.plist (100%) create mode 100644 SessionUtilitiesKit/Meta/SessionUtilitiesKit.h rename {SignalServiceKit/src/Loki/Crypto => SessionUtilitiesKit}/Mnemonic.swift (97%) rename {SessionUtilities => SessionUtilitiesKit}/NSDate+Timestamp.h (100%) rename {SessionUtilities => SessionUtilitiesKit}/NSDate+Timestamp.mm (100%) rename {SessionUtilities => SessionUtilitiesKit}/NSTimer+Proxying.h (100%) rename {SessionUtilities => SessionUtilitiesKit}/NSTimer+Proxying.m (100%) rename {SessionUtilities => SessionUtilitiesKit}/Promise+Retrying.swift (100%) rename {SessionUtilities => SessionUtilitiesKit}/String+Trimming.swift (100%) rename {SessionUtilities => SessionUtilitiesKit}/TSRequest.h (100%) rename {SessionUtilities => SessionUtilitiesKit}/TSRequest.m (100%) delete mode 100644 SignalServiceKit/src/Loki/API/Deprecated/ProofOfWork.swift delete mode 100644 SignalServiceKit/src/Loki/API/LokiMessage.swift delete mode 100644 SignalServiceKit/src/Loki/API/MessageWrapper.swift delete mode 100644 SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift delete mode 100644 SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift delete mode 100644 SignalServiceKit/src/Loki/API/SignalMessage.swift delete mode 100644 SignalServiceKit/src/Loki/API/Snode.swift delete mode 100644 SignalServiceKit/src/Loki/API/SnodeAPI.swift delete mode 100644 SignalServiceKit/src/Loki/API/Utilities/DecryptionUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/API/Utilities/EncryptionUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/API/Utilities/HTTP.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift diff --git a/Podfile b/Podfile index fc9b74587..d4ec63eaf 100644 --- a/Podfile +++ b/Podfile @@ -121,7 +121,7 @@ target 'SessionSnodeKit' do pod 'PromiseKit', :inhibit_warnings => true end -target 'SessionUtilities' do +target 'SessionUtilitiesKit' do pod 'CryptoSwift', :inhibit_warnings => true pod 'Curve25519Kit', :inhibit_warnings => true pod 'PromiseKit', :inhibit_warnings => true diff --git a/Podfile.lock b/Podfile.lock index d341bb9b4..32abf08b8 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -332,6 +332,6 @@ SPEC CHECKSUMS: YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 2efffb9efc5ed9feaf8b2df90d8dee2d4685e7c2 +PODFILE CHECKSUM: 278b25019daa575575de0bf9baf371f7cdcd4fc4 COCOAPODS: 1.10.0.rc.1 diff --git a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift index f5d1b6f64..a8332088a 100644 --- a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit // TODO: Implementation diff --git a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift index ae185d8ca..4fb3ab4b1 100644 --- a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit // TODO: Implementation diff --git a/SessionMessagingKit/Jobs/JobQueue.swift b/SessionMessagingKit/Jobs/JobQueue.swift index 3fb56e705..410b925b9 100644 --- a/SessionMessagingKit/Jobs/JobQueue.swift +++ b/SessionMessagingKit/Jobs/JobQueue.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit public final class JobQueue : JobDelegate { diff --git a/SessionMessagingKit/Jobs/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/MessageReceiveJob.swift index 3b5ac0349..e15593475 100644 --- a/SessionMessagingKit/Jobs/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/MessageReceiveJob.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility public var delegate: JobDelegate? diff --git a/SessionMessagingKit/Jobs/MessageSendJob.swift b/SessionMessagingKit/Jobs/MessageSendJob.swift index 5f886803d..9e364c33a 100644 --- a/SessionMessagingKit/Jobs/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/MessageSendJob.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit public final class MessageSendJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility public var delegate: JobDelegate? diff --git a/SessionMessagingKit/Jobs/NotifyPNServerJob.swift b/SessionMessagingKit/Jobs/NotifyPNServerJob.swift index f4b9930ab..5e4bd1090 100644 --- a/SessionMessagingKit/Jobs/NotifyPNServerJob.swift +++ b/SessionMessagingKit/Jobs/NotifyPNServerJob.swift @@ -1,6 +1,6 @@ import PromiseKit import SessionSnodeKit -import SessionUtilities +import SessionUtilitiesKit public final class NotifyPNServerJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility public var delegate: JobDelegate? diff --git a/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift b/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift index 7d7a595d0..2d5fdf176 100644 --- a/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift +++ b/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift @@ -1,5 +1,5 @@ import SessionProtocolKit -import SessionUtilities +import SessionUtilitiesKit @objc(SNClosedGroupUpdate) public final class ClosedGroupUpdate : ControlMessage { diff --git a/SessionMessagingKit/Messages/Control Message/ExpirationTimerUpdate.swift b/SessionMessagingKit/Messages/Control Message/ExpirationTimerUpdate.swift index a8cd8d262..3abe0b232 100644 --- a/SessionMessagingKit/Messages/Control Message/ExpirationTimerUpdate.swift +++ b/SessionMessagingKit/Messages/Control Message/ExpirationTimerUpdate.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit @objc(SNExpirationTimerUpdate) public final class ExpirationTimerUpdate : ControlMessage { diff --git a/SessionMessagingKit/Messages/Control Message/ReadReceipt.swift b/SessionMessagingKit/Messages/Control Message/ReadReceipt.swift index 61b12200d..71cedf759 100644 --- a/SessionMessagingKit/Messages/Control Message/ReadReceipt.swift +++ b/SessionMessagingKit/Messages/Control Message/ReadReceipt.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit @objc(SNReadReceipt) public final class ReadReceipt : ControlMessage { diff --git a/SessionMessagingKit/Messages/Control Message/SessionRequest.swift b/SessionMessagingKit/Messages/Control Message/SessionRequest.swift index c80098e99..36d1667e2 100644 --- a/SessionMessagingKit/Messages/Control Message/SessionRequest.swift +++ b/SessionMessagingKit/Messages/Control Message/SessionRequest.swift @@ -1,5 +1,5 @@ import SessionProtocolKit -import SessionUtilities +import SessionUtilitiesKit @objc(SNSessionRequest) public final class SessionRequest : ControlMessage { diff --git a/SessionMessagingKit/Messages/Control Message/TypingIndicator.swift b/SessionMessagingKit/Messages/Control Message/TypingIndicator.swift index c37f57bb9..0e150ce73 100644 --- a/SessionMessagingKit/Messages/Control Message/TypingIndicator.swift +++ b/SessionMessagingKit/Messages/Control Message/TypingIndicator.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit @objc(SNTypingIndicator) public final class TypingIndicator : ControlMessage { diff --git a/SessionMessagingKit/Messages/Visible Message/VisibleMessage+LinkPreview.swift b/SessionMessagingKit/Messages/Visible Message/VisibleMessage+LinkPreview.swift index ba3dd26a1..d7e66a766 100644 --- a/SessionMessagingKit/Messages/Visible Message/VisibleMessage+LinkPreview.swift +++ b/SessionMessagingKit/Messages/Visible Message/VisibleMessage+LinkPreview.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit public extension VisibleMessage { diff --git a/SessionMessagingKit/Messages/Visible Message/VisibleMessage+Profile.swift b/SessionMessagingKit/Messages/Visible Message/VisibleMessage+Profile.swift index f450cb005..9ea0ff7e1 100644 --- a/SessionMessagingKit/Messages/Visible Message/VisibleMessage+Profile.swift +++ b/SessionMessagingKit/Messages/Visible Message/VisibleMessage+Profile.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit public extension VisibleMessage { diff --git a/SessionMessagingKit/Messages/Visible Message/VisibleMessage+Quote.swift b/SessionMessagingKit/Messages/Visible Message/VisibleMessage+Quote.swift index 00e6e1b2c..51bef0eb1 100644 --- a/SessionMessagingKit/Messages/Visible Message/VisibleMessage+Quote.swift +++ b/SessionMessagingKit/Messages/Visible Message/VisibleMessage+Quote.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit public extension VisibleMessage { diff --git a/SessionMessagingKit/Messages/Visible Message/VisibleMessage.swift b/SessionMessagingKit/Messages/Visible Message/VisibleMessage.swift index c564cf397..abed616f5 100644 --- a/SessionMessagingKit/Messages/Visible Message/VisibleMessage.swift +++ b/SessionMessagingKit/Messages/Visible Message/VisibleMessage.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit @objc(SNVisibleMessage) public final class VisibleMessage : Message { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift index 845efd95b..f73cfb5f7 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift @@ -1,6 +1,6 @@ import CryptoSwift import SessionProtocolKit -import SessionUtilities +import SessionUtilitiesKit internal extension MessageReceiver { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 68ca2f750..d57b47f74 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit internal enum MessageReceiver { diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift index 1a9fbbf6d..2f6794d32 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift @@ -1,5 +1,5 @@ import SessionProtocolKit -import SessionUtilities +import SessionUtilitiesKit internal extension MessageSender { diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 1fac964b7..b6e2dd074 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -1,6 +1,6 @@ import PromiseKit import SessionSnodeKit -import SessionUtilities +import SessionUtilitiesKit internal enum MessageSender { diff --git a/SessionMessagingKit/Utilities/MessageWrapper.swift b/SessionMessagingKit/Utilities/MessageWrapper.swift index 28c5f4603..b8d0ac071 100644 --- a/SessionMessagingKit/Utilities/MessageWrapper.swift +++ b/SessionMessagingKit/Utilities/MessageWrapper.swift @@ -1,5 +1,5 @@ import SessionSnodeKit -import SessionUtilities +import SessionUtilitiesKit public enum MessageWrapper { diff --git a/SessionMessagingKit/Utilities/ProofOfWork.swift b/SessionMessagingKit/Utilities/ProofOfWork.swift index 4271f4cf5..e3a21e778 100644 --- a/SessionMessagingKit/Utilities/ProofOfWork.swift +++ b/SessionMessagingKit/Utilities/ProofOfWork.swift @@ -1,5 +1,5 @@ import SessionSnodeKit -import SessionUtilities +import SessionUtilitiesKit enum ProofOfWork { diff --git a/SessionProtocolKit/Shared Sender Keys/ClosedGroupRatchet.swift b/SessionProtocolKit/Shared Sender Keys/ClosedGroupRatchet.swift index d6575022d..0764da3ab 100644 --- a/SessionProtocolKit/Shared Sender Keys/ClosedGroupRatchet.swift +++ b/SessionProtocolKit/Shared Sender Keys/ClosedGroupRatchet.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit public final class ClosedGroupRatchet : NSObject, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility public let chainKey: String diff --git a/SessionProtocolKit/Shared Sender Keys/SharedSenderKeys.swift b/SessionProtocolKit/Shared Sender Keys/SharedSenderKeys.swift index 875132474..600efe3ac 100644 --- a/SessionProtocolKit/Shared Sender Keys/SharedSenderKeys.swift +++ b/SessionProtocolKit/Shared Sender Keys/SharedSenderKeys.swift @@ -1,6 +1,6 @@ import CryptoSwift import PromiseKit -import SessionUtilities +import SessionUtilitiesKit public protocol SharedSenderKeysDelegate { diff --git a/SessionProtocolKit/Signal/FallbackSessionCipher.swift b/SessionProtocolKit/Signal/FallbackSessionCipher.swift index a91c09fe0..eaad92248 100644 --- a/SessionProtocolKit/Signal/FallbackSessionCipher.swift +++ b/SessionProtocolKit/Signal/FallbackSessionCipher.swift @@ -1,6 +1,6 @@ import CryptoSwift import Curve25519Kit -import SessionUtilities +import SessionUtilitiesKit /// A fallback session cipher which uses the the recipient's public key to encrypt data. @objc public final class FallBackSessionCipher : NSObject { diff --git a/SessionSnodeKit/OnionRequestAPI+Encryption.swift b/SessionSnodeKit/OnionRequestAPI+Encryption.swift index ed2dc326e..8b6f0093f 100644 --- a/SessionSnodeKit/OnionRequestAPI+Encryption.swift +++ b/SessionSnodeKit/OnionRequestAPI+Encryption.swift @@ -1,6 +1,6 @@ import CryptoSwift import PromiseKit -import SessionUtilities +import SessionUtilitiesKit internal extension OnionRequestAPI { diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index 76ba81159..f73da2b1b 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -1,6 +1,6 @@ import CryptoSwift import PromiseKit -import SessionUtilities +import SessionUtilitiesKit /// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information. public enum OnionRequestAPI { diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 946b9448a..9b59c153c 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -1,5 +1,5 @@ import PromiseKit -import SessionUtilities +import SessionUtilitiesKit public enum SnodeAPI { diff --git a/SessionSnodeKit/SnodeMessage.swift b/SessionSnodeKit/SnodeMessage.swift index 7fc3c21c1..80770be9c 100644 --- a/SessionSnodeKit/SnodeMessage.swift +++ b/SessionSnodeKit/SnodeMessage.swift @@ -1,5 +1,5 @@ import PromiseKit -import SessionUtilities +import SessionUtilitiesKit public final class SnodeMessage : NSObject, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility /// The hex encoded public key of the recipient. diff --git a/SessionSnodeKit/Storage.swift b/SessionSnodeKit/Storage.swift index 3e42f45ae..7fa26ed15 100644 --- a/SessionSnodeKit/Storage.swift +++ b/SessionSnodeKit/Storage.swift @@ -1,4 +1,4 @@ -import SessionUtilities +import SessionUtilitiesKit public protocol SessionSnodeKitStorageProtocol { diff --git a/SessionUtilities/Meta/SessionUtilities.h b/SessionUtilities/Meta/SessionUtilities.h deleted file mode 100644 index dda217359..000000000 --- a/SessionUtilities/Meta/SessionUtilities.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -FOUNDATION_EXPORT double SessionUtilitiesVersionNumber; -FOUNDATION_EXPORT const unsigned char SessionUtilitiesVersionString[]; - -#import -#import -#import -#import diff --git a/SessionUtilities/AESGCM.swift b/SessionUtilitiesKit/AESGCM.swift similarity index 100% rename from SessionUtilities/AESGCM.swift rename to SessionUtilitiesKit/AESGCM.swift diff --git a/SessionUtilities/Array+Description.swift b/SessionUtilitiesKit/Array+Description.swift similarity index 100% rename from SessionUtilities/Array+Description.swift rename to SessionUtilitiesKit/Array+Description.swift diff --git a/SessionUtilities/Data+SecureRandom.swift b/SessionUtilitiesKit/Data+SecureRandom.swift similarity index 100% rename from SessionUtilities/Data+SecureRandom.swift rename to SessionUtilitiesKit/Data+SecureRandom.swift diff --git a/SessionUtilities/Dictionary+Description.swift b/SessionUtilitiesKit/Dictionary+Description.swift similarity index 100% rename from SessionUtilities/Dictionary+Description.swift rename to SessionUtilitiesKit/Dictionary+Description.swift diff --git a/SessionUtilities/DiffieHellman.swift b/SessionUtilitiesKit/DiffieHellman.swift similarity index 100% rename from SessionUtilities/DiffieHellman.swift rename to SessionUtilitiesKit/DiffieHellman.swift diff --git a/SessionUtilities/ECKeyPair+Utilities.h b/SessionUtilitiesKit/ECKeyPair+Utilities.h similarity index 100% rename from SessionUtilities/ECKeyPair+Utilities.h rename to SessionUtilitiesKit/ECKeyPair+Utilities.h diff --git a/SessionUtilities/ECKeyPair+Utilities.m b/SessionUtilitiesKit/ECKeyPair+Utilities.m similarity index 100% rename from SessionUtilities/ECKeyPair+Utilities.m rename to SessionUtilitiesKit/ECKeyPair+Utilities.m diff --git a/SessionUtilities/HTTP.swift b/SessionUtilitiesKit/HTTP.swift similarity index 100% rename from SessionUtilities/HTTP.swift rename to SessionUtilitiesKit/HTTP.swift diff --git a/SessionUtilities/JSON.swift b/SessionUtilitiesKit/JSON.swift similarity index 100% rename from SessionUtilities/JSON.swift rename to SessionUtilitiesKit/JSON.swift diff --git a/SessionUtilities/Logging.swift b/SessionUtilitiesKit/Logging.swift similarity index 100% rename from SessionUtilities/Logging.swift rename to SessionUtilitiesKit/Logging.swift diff --git a/SessionUtilities/Meta/Info.plist b/SessionUtilitiesKit/Meta/Info.plist similarity index 100% rename from SessionUtilities/Meta/Info.plist rename to SessionUtilitiesKit/Meta/Info.plist diff --git a/SessionUtilitiesKit/Meta/SessionUtilitiesKit.h b/SessionUtilitiesKit/Meta/SessionUtilitiesKit.h new file mode 100644 index 000000000..f2aec9c6d --- /dev/null +++ b/SessionUtilitiesKit/Meta/SessionUtilitiesKit.h @@ -0,0 +1,9 @@ +#import + +FOUNDATION_EXPORT double SessionUtilitiesKitVersionNumber; +FOUNDATION_EXPORT const unsigned char SessionUtilitiesKitVersionString[]; + +#import +#import +#import +#import diff --git a/SignalServiceKit/src/Loki/Crypto/Mnemonic.swift b/SessionUtilitiesKit/Mnemonic.swift similarity index 97% rename from SignalServiceKit/src/Loki/Crypto/Mnemonic.swift rename to SessionUtilitiesKit/Mnemonic.swift index d7b01aa7c..9cbaa1588 100644 --- a/SignalServiceKit/src/Loki/Crypto/Mnemonic.swift +++ b/SessionUtilitiesKit/Mnemonic.swift @@ -24,8 +24,7 @@ public enum Mnemonic { if let cachedResult = Language.wordSetCache[self] { return cachedResult } else { - let bundleID = "org.cocoapods.SessionServiceKit" - let url = Bundle(identifier: bundleID)!.url(forResource: filename, withExtension: "txt")! + let url = Bundle.main.url(forResource: filename, withExtension: "txt")! let contents = try! String(contentsOf: url) let result = contents.split(separator: ",").map { String($0) } Language.wordSetCache[self] = result diff --git a/SessionUtilities/NSDate+Timestamp.h b/SessionUtilitiesKit/NSDate+Timestamp.h similarity index 100% rename from SessionUtilities/NSDate+Timestamp.h rename to SessionUtilitiesKit/NSDate+Timestamp.h diff --git a/SessionUtilities/NSDate+Timestamp.mm b/SessionUtilitiesKit/NSDate+Timestamp.mm similarity index 100% rename from SessionUtilities/NSDate+Timestamp.mm rename to SessionUtilitiesKit/NSDate+Timestamp.mm diff --git a/SessionUtilities/NSTimer+Proxying.h b/SessionUtilitiesKit/NSTimer+Proxying.h similarity index 100% rename from SessionUtilities/NSTimer+Proxying.h rename to SessionUtilitiesKit/NSTimer+Proxying.h diff --git a/SessionUtilities/NSTimer+Proxying.m b/SessionUtilitiesKit/NSTimer+Proxying.m similarity index 100% rename from SessionUtilities/NSTimer+Proxying.m rename to SessionUtilitiesKit/NSTimer+Proxying.m diff --git a/SessionUtilities/Promise+Retrying.swift b/SessionUtilitiesKit/Promise+Retrying.swift similarity index 100% rename from SessionUtilities/Promise+Retrying.swift rename to SessionUtilitiesKit/Promise+Retrying.swift diff --git a/SessionUtilities/String+Trimming.swift b/SessionUtilitiesKit/String+Trimming.swift similarity index 100% rename from SessionUtilities/String+Trimming.swift rename to SessionUtilitiesKit/String+Trimming.swift diff --git a/SessionUtilities/TSRequest.h b/SessionUtilitiesKit/TSRequest.h similarity index 100% rename from SessionUtilities/TSRequest.h rename to SessionUtilitiesKit/TSRequest.h diff --git a/SessionUtilities/TSRequest.m b/SessionUtilitiesKit/TSRequest.m similarity index 100% rename from SessionUtilities/TSRequest.m rename to SessionUtilitiesKit/TSRequest.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index a40264c36..b072d66ee 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -470,7 +470,7 @@ 7BDCFC092421894900641C39 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; }; 7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; 7BF3FF002505B8E400609570 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF3FEFF2505B8E400609570 /* PlaceholderIcon.swift */; }; - 9C9B845C8451114076E55902 /* Pods_SessionUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 686875887229AB29C07145BA /* Pods_SessionUtilities.framework */; }; + 945AA2B82B621254F69FA9E8 /* Pods_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9117261809D69B3D7C26B8F1 /* Pods_SessionUtilitiesKit.framework */; }; 9EE44C6B4D4A069B86112387 /* Pods_SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9559C3068280BA2383F547F7 /* Pods_SessionSnodeKit.framework */; }; A10FDF79184FB4BB007FF963 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; }; A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; }; @@ -652,7 +652,8 @@ C3A71D682558A0170043A11F /* LokiSessionCipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D652558A0170043A11F /* LokiSessionCipher.swift */; }; C3A71D742558A0F60043A11F /* SMKProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D722558A0F60043A11F /* SMKProto.swift */; }; C3A71D752558A0F60043A11F /* OWSUnidentifiedDelivery.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D732558A0F60043A11F /* OWSUnidentifiedDelivery.pb.swift */; }; - C3A71D862558A28A0043A11F /* DiffieHellman.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D662558A0170043A11F /* DiffieHellman.swift */; }; + C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71F882558BA9F0043A11F /* Mnemonic.swift */; }; + C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D662558A0170043A11F /* DiffieHellman.swift */; }; C3AABDDF2553ECF00042FF4C /* Array+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D12553860800C340D1 /* Array+Description.swift */; }; C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3BBE0752554CDA60050F1E3 /* Configuration.swift */; }; C3BBE0802554CDD70050F1E3 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3BBE07F2554CDD70050F1E3 /* Storage.swift */; }; @@ -679,10 +680,10 @@ C3C2A5DF2553860B00C340D1 /* Promise+Delaying.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D32553860900C340D1 /* Promise+Delaying.swift */; }; C3C2A5E02553860B00C340D1 /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D42553860A00C340D1 /* Threading.swift */; }; C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */; }; - C3C2A67D255388CC00C340D1 /* SessionUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A67B255388CC00C340D1 /* SessionUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C3C2A680255388CC00C340D1 /* SessionUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilities.framework */; }; - C3C2A681255388CC00C340D1 /* SessionUtilities.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilities.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C3C2A6C62553896A00C340D1 /* SessionUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilities.framework */; }; + C3C2A67D255388CC00C340D1 /* SessionUtilitiesKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A67B255388CC00C340D1 /* SessionUtilitiesKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A680255388CC00C340D1 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; + C3C2A681255388CC00C340D1 /* SessionUtilitiesKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C3C2A6C62553896A00C340D1 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; C3C2A6F425539DE700C340D1 /* SessionMessagingKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A6F225539DE700C340D1 /* SessionMessagingKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; C3C2A6F725539DE700C340D1 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; }; C3C2A6F825539DE700C340D1 /* SessionMessagingKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -794,7 +795,7 @@ C3C2ABF82553C8A300C340D1 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2ABF72553C8A300C340D1 /* Storage.swift */; }; C3C2AC0A2553C9A100C340D1 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2AC092553C9A100C340D1 /* Configuration.swift */; }; C3C2AC2E2553CBEB00C340D1 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2AC2D2553CBEB00C340D1 /* String+Trimming.swift */; }; - C3C2AC372553CCE600C340D1 /* SessionUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilities.framework */; }; + C3C2AC372553CCE600C340D1 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; C3C3CF8924D8EED300E1CCE7 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C3CF8824D8EED300E1CCE7 /* TextView.swift */; }; C3D0972B2510499C00F6E3E4 /* BackgroundPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D0972A2510499C00F6E3E4 /* BackgroundPoller.swift */; }; C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */; }; @@ -926,7 +927,7 @@ dstSubfolderSpec = 10; files = ( C3C2A86A2553B41A00C340D1 /* SessionProtocolKit.framework in Embed Frameworks */, - C3C2A681255388CC00C340D1 /* SessionUtilities.framework in Embed Frameworks */, + C3C2A681255388CC00C340D1 /* SessionUtilitiesKit.framework in Embed Frameworks */, C3C2A5A7255385C100C340D1 /* SessionSnodeKit.framework in Embed Frameworks */, 4535189A1FC63DBF00210559 /* SignalMessaging.framework in Embed Frameworks */, C3C2A6F825539DE700C340D1 /* SessionMessagingKit.framework in Embed Frameworks */, @@ -945,6 +946,7 @@ 1CE3CD5C23334683BDD3D78C /* Pods-Signal.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Signal.test.xcconfig"; path = "Pods/Target Support Files/Pods-Signal/Pods-Signal.test.xcconfig"; sourceTree = ""; }; 2183DCA28E0620BC73FCC554 /* Pods_SessionProtocolKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionProtocolKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2400888D239F30A600305217 /* SessionRestorationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRestorationView.swift; sourceTree = ""; }; + 264033E641846B67E0CB21B0 /* Pods-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUtilitiesKit/Pods-SessionUtilitiesKit.debug.xcconfig"; sourceTree = ""; }; 264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalMessaging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3303495F6651CE2F3CC9693B /* Pods-SessionUtilities.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUtilities.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUtilities/Pods-SessionUtilities.app store release.xcconfig"; sourceTree = ""; }; 3403B95B20EA9526001A1F44 /* OWSContactShareButtonsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactShareButtonsView.m; sourceTree = ""; }; @@ -1476,7 +1478,6 @@ 4CFD151C22415AA400F2450F /* CallVideoHintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVideoHintView.swift; sourceTree = ""; }; 4CFE6B6B21F92BA700006701 /* LegacyNotificationsAdaptee.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LegacyNotificationsAdaptee.swift; path = UserInterface/Notifications/LegacyNotificationsAdaptee.swift; sourceTree = ""; }; 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuActionsViewController.swift; sourceTree = ""; }; - 686875887229AB29C07145BA /* Pods_SessionUtilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionUtilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 69349DE607F5BA6036C9AC60 /* Pods-SignalShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.debug.xcconfig"; sourceTree = ""; }; 6A26D6558DE69AF455E571C1 /* Pods-SessionMessagingKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKit/Pods-SessionMessagingKit.debug.xcconfig"; sourceTree = ""; }; 70377AAA1918450100CAF501 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; @@ -1491,8 +1492,10 @@ 7BDCFC0424206E7300641C39 /* LokiPushNotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LokiPushNotificationService.entitlements; sourceTree = ""; }; 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = ""; }; 7BF3FEFF2505B8E400609570 /* PlaceholderIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderIcon.swift; sourceTree = ""; }; + 7DD180F770F8518B4E8796F2 /* Pods-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUtilitiesKit/Pods-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; 8981C8F64D94D3C52EB67A2C /* Pods-SignalTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.test.xcconfig"; sourceTree = ""; }; 8EEE74B0753448C085B48721 /* Pods-SignalMessaging.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.app store release.xcconfig"; sourceTree = ""; }; + 9117261809D69B3D7C26B8F1 /* Pods_SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 948239851C08032C842937CC /* Pods-SignalMessaging.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.test.xcconfig"; sourceTree = ""; }; 9559C3068280BA2383F547F7 /* Pods_SessionSnodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionSnodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9B533A9FA46206D3D99C9ADA /* Pods-SignalMessaging.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.debug.xcconfig"; sourceTree = ""; }; @@ -1689,6 +1692,7 @@ C3A71D662558A0170043A11F /* DiffieHellman.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffieHellman.swift; sourceTree = ""; }; C3A71D722558A0F60043A11F /* SMKProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SMKProto.swift; path = Protos/SMKProto.swift; sourceTree = ""; }; C3A71D732558A0F60043A11F /* OWSUnidentifiedDelivery.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSUnidentifiedDelivery.pb.swift; path = Protos/OWSUnidentifiedDelivery.pb.swift; sourceTree = ""; }; + C3A71F882558BA9F0043A11F /* Mnemonic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = ""; }; C3AA6BB824CE8F1B002358B6 /* Migrating Translations from Android.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Migrating Translations from Android.md"; sourceTree = ""; }; C3AECBEA24EF5244005743DE /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = translations/fa.lproj/Localizable.strings; sourceTree = ""; }; C3BBE0752554CDA60050F1E3 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; @@ -1719,8 +1723,8 @@ C3C2A5D72553860B00C340D1 /* AESGCM.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AESGCM.swift; sourceTree = ""; }; C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; C3C2A5D92553860B00C340D1 /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; - C3C2A679255388CC00C340D1 /* SessionUtilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionUtilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C3C2A67B255388CC00C340D1 /* SessionUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionUtilities.h; sourceTree = ""; }; + C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C3C2A67B255388CC00C340D1 /* SessionUtilitiesKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionUtilitiesKit.h; sourceTree = ""; }; C3C2A67C255388CC00C340D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionMessagingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C3C2A6F225539DE700C340D1 /* SessionMessagingKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionMessagingKit.h; sourceTree = ""; }; @@ -1901,7 +1905,7 @@ buildActionMask = 2147483647; files = ( 9EE44C6B4D4A069B86112387 /* Pods_SessionSnodeKit.framework in Frameworks */, - C3C2A6C62553896A00C340D1 /* SessionUtilities.framework in Frameworks */, + C3C2A6C62553896A00C340D1 /* SessionUtilitiesKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1909,7 +1913,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9C9B845C8451114076E55902 /* Pods_SessionUtilities.framework in Frameworks */, + 945AA2B82B621254F69FA9E8 /* Pods_SessionUtilitiesKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1928,7 +1932,7 @@ buildActionMask = 2147483647; files = ( A33A4BA9D050805FE156E3ED /* Pods_SessionProtocolKit.framework in Frameworks */, - C3C2AC372553CCE600C340D1 /* SessionUtilities.framework in Frameworks */, + C3C2AC372553CCE600C340D1 /* SessionUtilitiesKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1959,7 +1963,7 @@ A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */, A163E8AB16F3F6AA0094D68B /* Security.framework in Frameworks */, A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */, - C3C2A680255388CC00C340D1 /* SessionUtilities.framework in Frameworks */, + C3C2A680255388CC00C340D1 /* SessionUtilitiesKit.framework in Frameworks */, A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */, D2AEACDC16C426DA00C364C0 /* CFNetwork.framework in Frameworks */, D2179CFE16BB0B480006F3AB /* SystemConfiguration.framework in Frameworks */, @@ -2979,6 +2983,8 @@ FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */, AEA8083C060FF9BAFF6E0C9F /* Pods-SessionProtocolKit.debug.xcconfig */, 174BD0AE74771D02DAC2B7A9 /* Pods-SessionProtocolKit.app store release.xcconfig */, + 264033E641846B67E0CB21B0 /* Pods-SessionUtilitiesKit.debug.xcconfig */, + 7DD180F770F8518B4E8796F2 /* Pods-SessionUtilitiesKit.app store release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -3421,7 +3427,7 @@ path = Utilities; sourceTree = ""; }; - C3C2A67A255388CC00C340D1 /* SessionUtilities */ = { + C3C2A67A255388CC00C340D1 /* SessionUtilitiesKit */ = { isa = PBXGroup; children = ( C3C2A68B255388D500C340D1 /* Meta */, @@ -3435,6 +3441,7 @@ C3C2A5BC255385EE00C340D1 /* HTTP.swift */, C3C2A5D92553860B00C340D1 /* JSON.swift */, C3C2A5CE2553860700C340D1 /* Logging.swift */, + C3A71F882558BA9F0043A11F /* Mnemonic.swift */, C300A6302554B68200555489 /* NSDate+Timestamp.h */, C300A6312554B6D100555489 /* NSDate+Timestamp.mm */, C352A3762557859C00338F3E /* NSTimer+Proxying.h */, @@ -3444,13 +3451,13 @@ C352A3A42557B5F000338F3E /* TSRequest.h */, C352A3A52557B60D00338F3E /* TSRequest.m */, ); - path = SessionUtilities; + path = SessionUtilitiesKit; sourceTree = ""; }; C3C2A68B255388D500C340D1 /* Meta */ = { isa = PBXGroup; children = ( - C3C2A67B255388CC00C340D1 /* SessionUtilities.h */, + C3C2A67B255388CC00C340D1 /* SessionUtilitiesKit.h */, C3C2A67C255388CC00C340D1 /* Info.plist */, ); path = Meta; @@ -3702,7 +3709,7 @@ C3C2A6F125539DE700C340D1 /* SessionMessagingKit */, C3C2A8632553B41A00C340D1 /* SessionProtocolKit */, C3C2A5A0255385C100C340D1 /* SessionSnodeKit */, - C3C2A67A255388CC00C340D1 /* SessionUtilities */, + C3C2A67A255388CC00C340D1 /* SessionUtilitiesKit */, D221A08C169C9E5E00537ABF /* Frameworks */, D221A08A169C9E5E00537ABF /* Products */, 9404664EC513585B05DF1350 /* Pods */, @@ -3718,7 +3725,7 @@ 453518921FC63DBF00210559 /* SignalMessaging.framework */, 7BC01A3B241F40AB00BC7C55 /* LokiPushNotificationService.appex */, C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */, - C3C2A679255388CC00C340D1 /* SessionUtilities.framework */, + C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */, C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */, C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */, ); @@ -3769,9 +3776,9 @@ 264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */, 04912E453971FB16E5E78EC6 /* Pods_LokiPushNotificationService.framework */, 9559C3068280BA2383F547F7 /* Pods_SessionSnodeKit.framework */, - 686875887229AB29C07145BA /* Pods_SessionUtilities.framework */, FB523C549815DE935E98151E /* Pods_SessionMessagingKit.framework */, 2183DCA28E0620BC73FCC554 /* Pods_SessionProtocolKit.framework */, + 9117261809D69B3D7C26B8F1 /* Pods_SessionUtilitiesKit.framework */, ); name = Frameworks; sourceTree = ""; @@ -3905,7 +3912,7 @@ C300A63B2554B72200555489 /* NSDate+Timestamp.h in Headers */, C352A3772557864000338F3E /* NSTimer+Proxying.h in Headers */, C3471F5625553E1100297E91 /* ECKeyPair+Utilities.h in Headers */, - C3C2A67D255388CC00C340D1 /* SessionUtilities.h in Headers */, + C3C2A67D255388CC00C340D1 /* SessionUtilitiesKit.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4051,9 +4058,9 @@ productReference = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; productType = "com.apple.product-type.framework"; }; - C3C2A678255388CC00C340D1 /* SessionUtilities */ = { + C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */ = { isa = PBXNativeTarget; - buildConfigurationList = C3C2A684255388CC00C340D1 /* Build configuration list for PBXNativeTarget "SessionUtilities" */; + buildConfigurationList = C3C2A684255388CC00C340D1 /* Build configuration list for PBXNativeTarget "SessionUtilitiesKit" */; buildPhases = ( 83DABC75697364620557C68B /* [CP] Check Pods Manifest.lock */, C3C2A674255388CC00C340D1 /* Headers */, @@ -4065,9 +4072,9 @@ ); dependencies = ( ); - name = SessionUtilities; + name = SessionUtilitiesKit; productName = SessionUtilities; - productReference = C3C2A679255388CC00C340D1 /* SessionUtilities.framework */; + productReference = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; productType = "com.apple.product-type.framework"; }; C3C2A6EF25539DE700C340D1 /* SessionMessagingKit */ = { @@ -4299,7 +4306,7 @@ C3C2A6EF25539DE700C340D1 /* SessionMessagingKit */, C3C2A8612553B41A00C340D1 /* SessionProtocolKit */, C3C2A59E255385C100C340D1 /* SessionSnodeKit */, - C3C2A678255388CC00C340D1 /* SessionUtilities */, + C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */, ); }; /* End PBXProject section */ @@ -4708,7 +4715,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SessionUtilities-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SessionUtilitiesKit-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -5017,15 +5024,16 @@ C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */, C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */, C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */, + C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */, C3471F6825553E7600297E91 /* ECKeyPair+Utilities.m in Sources */, C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Description.swift in Sources */, C3C2AC2E2553CBEB00C340D1 /* String+Trimming.swift in Sources */, C352A3A62557B60D00338F3E /* TSRequest.m in Sources */, C3471ED42555386B00297E91 /* AESGCM.swift in Sources */, + C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */, C3BBE0A72554D4DE0050F1E3 /* Promise+Retrying.swift in Sources */, C3BBE0A92554D4DE0050F1E3 /* HTTP.swift in Sources */, C300A60D2554B31900555489 /* Logging.swift in Sources */, - C3A71D862558A28A0043A11F /* DiffieHellman.swift in Sources */, C300A6322554B6D100555489 /* NSDate+Timestamp.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -5477,7 +5485,7 @@ }; C3C2A67F255388CC00C340D1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C3C2A678255388CC00C340D1 /* SessionUtilities */; + target = C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */; targetProxy = C3C2A67E255388CC00C340D1 /* PBXContainerItemProxy */; }; C3C2A6F625539DE700C340D1 /* PBXTargetDependency */ = { @@ -6015,7 +6023,7 @@ }; C3C2A682255388CC00C340D1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E7E2FBF1546840C91B7E4879 /* Pods-SessionUtilities.debug.xcconfig */; + baseConfigurationReference = 264033E641846B67E0CB21B0 /* Pods-SessionUtilitiesKit.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -6047,7 +6055,7 @@ GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - INFOPLIST_FILE = SessionUtilities/Meta/Info.plist; + INFOPLIST_FILE = SessionUtilitiesKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 14.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -6062,7 +6070,7 @@ "-framework", "\"UIKit\"", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionUtilities"; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionUtilitiesKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -6076,7 +6084,7 @@ }; C3C2A683255388CC00C340D1 /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3303495F6651CE2F3CC9693B /* Pods-SessionUtilities.app store release.xcconfig */; + baseConfigurationReference = 7DD180F770F8518B4E8796F2 /* Pods-SessionUtilitiesKit.app store release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -6129,7 +6137,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = SessionUtilities/Meta/Info.plist; + INFOPLIST_FILE = SessionUtilitiesKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 14.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -6144,7 +6152,7 @@ "-framework", "\"UIKit\"", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionUtilities"; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionUtilitiesKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -6854,7 +6862,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = "App Store Release"; }; - C3C2A684255388CC00C340D1 /* Build configuration list for PBXNativeTarget "SessionUtilities" */ = { + C3C2A684255388CC00C340D1 /* Build configuration list for PBXNativeTarget "SessionUtilitiesKit" */ = { isa = XCConfigurationList; buildConfigurations = ( C3C2A682255388CC00C340D1 /* Debug */, diff --git a/SignalServiceKit/src/Loki/API/Deprecated/ProofOfWork.swift b/SignalServiceKit/src/Loki/API/Deprecated/ProofOfWork.swift deleted file mode 100644 index cbcfb5e4e..000000000 --- a/SignalServiceKit/src/Loki/API/Deprecated/ProofOfWork.swift +++ /dev/null @@ -1,109 +0,0 @@ -import CryptoSwift - -private extension UInt64 { - - fileprivate init(_ decimal: Decimal) { - self.init(truncating: decimal as NSDecimalNumber) - } - - // Convert a UInt8 array to a UInt64 - fileprivate init(_ bytes: [UInt8]) { - precondition(bytes.count <= MemoryLayout.size) - var value: UInt64 = 0 - for byte in bytes { - value <<= 8 - value |= UInt64(byte) - } - self.init(value) - } -} - -private extension MutableCollection where Element == UInt8, Index == Int { - - /// Increment every element by the given amount - /// - /// - Parameter amount: The amount to increment by - /// - Returns: The incremented collection - fileprivate func increment(by amount: Int) -> Self { - var result = self - var increment = amount - for i in (0.. 0 else { break } - let sum = Int(result[i]) + increment - result[i] = UInt8(sum % 256) - increment = sum / 256 - } - return result - } -} - -/** - * The main proof of work logic. - * - * This was copied from the desktop messenger. - * Ref: libloki/proof-of-work.js - */ -public enum ProofOfWork { - - // If this changes then we also have to use something other than UInt64 to support the new length - private static let nonceLength = 8 - - /// Calculate a proof of work with the given configuration - /// - /// Ref: https://bitmessage.org/wiki/Proof_of_work - /// - /// - Parameters: - /// - data: The message data - /// - pubKey: The message recipient - /// - timestamp: The timestamp - /// - ttl: The message time to live in milliseconds - /// - Returns: A nonce string or `nil` if it failed - public static func calculate(data: String, pubKey: String, timestamp: UInt64, ttl: UInt64) -> String? { - let payload = createPayload(pubKey: pubKey, data: data, timestamp: timestamp, ttl: ttl) - let target = calcTarget(ttl: ttl, payloadLength: payload.count, nonceTrials: Int(SnodeAPI.powDifficulty)) - - // Start with the max value - var trialValue = UInt64.max - - let initialHash = payload.sha512() - var nonce = [UInt8](repeating: 0, count: nonceLength) - - while trialValue > target { - nonce = nonce.increment(by: 1) - - // This is different to the bitmessage PoW - // resultHash = hash(nonce + hash(data)) ==> hash(nonce + initialHash) - let resultHash = (nonce + initialHash).sha512() - let trialValueArray = Array(resultHash[0..<8]) - trialValue = UInt64(trialValueArray) - } - - return nonce.toBase64() - } - - /// Get the proof of work payload - private static func createPayload(pubKey: String, data: String, timestamp: UInt64, ttl: UInt64) -> [UInt8] { - let timestampString = String(timestamp) - let ttlString = String(ttl) - let payloadString = timestampString + ttlString + pubKey + data - return payloadString.bytes - } - - /// Calculate the target we need to reach - private static func calcTarget(ttl: UInt64, payloadLength: Int, nonceTrials: Int) -> UInt64 { - let two16 = UInt64(pow(2, 16) - 1) - let two64 = UInt64(pow(2, 64) - 1) - - // Do all the calculations - let totalLength = UInt64(payloadLength + nonceLength) - let ttlInSeconds = ttl / 1000 - let ttlMult = ttlInSeconds * totalLength - - // UInt64 values - let innerFrac = ttlMult / two16 - let lenPlusInnerFrac = totalLength + innerFrac - let denominator = UInt64(nonceTrials) * lenPlusInnerFrac - - return two64 / denominator - } -} diff --git a/SignalServiceKit/src/Loki/API/LokiMessage.swift b/SignalServiceKit/src/Loki/API/LokiMessage.swift deleted file mode 100644 index 562a6870a..000000000 --- a/SignalServiceKit/src/Loki/API/LokiMessage.swift +++ /dev/null @@ -1,75 +0,0 @@ -import PromiseKit - -public struct LokiMessage { - /// The hex encoded public key of the recipient. - let recipientPublicKey: String - /// The content of the message. - let data: LosslessStringConvertible - /// The time to live for the message in milliseconds. - let ttl: UInt64 - /// Whether this message is a ping. - /// - /// - Note: The concept of pinging only applies to P2P messaging. - let isPing: Bool - /// When the proof of work was calculated, if applicable (P2P messages don't require proof of work). - /// - /// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970. - private(set) var timestamp: UInt64? = nil - /// The base 64 encoded proof of work, if applicable (P2P messages don't require proof of work). - private(set) var nonce: String? = nil - - private init(destination: String, data: LosslessStringConvertible, ttl: UInt64, isPing: Bool) { - self.recipientPublicKey = destination - self.data = data - self.ttl = ttl - self.isPing = isPing - } - - /// Construct a `LokiMessage` from a `SignalMessage`. - /// - /// - Note: `timestamp` is the original message timestamp (i.e. `TSOutgoingMessage.timestamp`). - public static func from(signalMessage: SignalMessage) -> LokiMessage? { - // To match the desktop application, we have to wrap the data in an envelope and then wrap that in a websocket object - do { - let wrappedMessage = try MessageWrapper.wrap(message: signalMessage) - let data = wrappedMessage.base64EncodedString() - let destination = signalMessage.recipientPublicKey - var ttl = TTLUtilities.fallbackMessageTTL - if let messageTTL = signalMessage.ttl, messageTTL > 0 { ttl = UInt64(messageTTL) } - let isPing = signalMessage.isPing - return LokiMessage(destination: destination, data: data, ttl: ttl, isPing: isPing) - } catch let error { - print("[Loki] Failed to convert Signal message to Loki message: \(signalMessage).") - return nil - } - } - - /// Calculate the proof of work for this message. - /// - /// - Returns: The promise of a new message with its `timestamp` and `nonce` set. - public func calculatePoW() -> Promise { - return Promise { seal in - DispatchQueue.global(qos: .userInitiated).async { - let now = NSDate.ows_millisecondTimeStamp() - let dataAsString = self.data as! String // Safe because of how from(signalMessage:with:) is implemented - if let nonce = ProofOfWork.calculate(data: dataAsString, pubKey: self.recipientPublicKey, timestamp: now, ttl: self.ttl) { - var result = self - result.timestamp = now - result.nonce = nonce - seal.fulfill(result) - } else { - seal.reject(SnodeAPI.SnodeAPIError.proofOfWorkCalculationFailed) - } - } - } - } - - public func toJSON() -> JSON { - var result = [ "pubKey" : recipientPublicKey, "data" : data.description, "ttl" : String(ttl) ] - if let timestamp = timestamp, let nonce = nonce { - result["timestamp"] = String(timestamp) - result["nonce"] = nonce - } - return result - } -} diff --git a/SignalServiceKit/src/Loki/API/MessageWrapper.swift b/SignalServiceKit/src/Loki/API/MessageWrapper.swift deleted file mode 100644 index f19626906..000000000 --- a/SignalServiceKit/src/Loki/API/MessageWrapper.swift +++ /dev/null @@ -1,72 +0,0 @@ - -public enum MessageWrapper { - - public enum Error : LocalizedError { - case failedToWrapData - case failedToWrapMessageInEnvelope - case failedToWrapEnvelopeInWebSocketMessage - case failedToUnwrapData - - public var errorDescription: String? { - switch self { - case .failedToWrapData: return "Failed to wrap data." - case .failedToWrapMessageInEnvelope: return "Failed to wrap message in envelope." - case .failedToWrapEnvelopeInWebSocketMessage: return "Failed to wrap envelope in web socket message." - case .failedToUnwrapData: return "Failed to unwrap data." - } - } - } - - /// Wraps `message` in an `SSKProtoEnvelope` and then a `WebSocketProtoWebSocketMessage` to match the desktop application. - public static func wrap(message: SignalMessage) throws -> Data { - do { - let envelope = try createEnvelope(around: message) - let webSocketMessage = try createWebSocketMessage(around: envelope) - return try webSocketMessage.serializedData() - } catch let error { - throw error as? Error ?? Error.failedToWrapData - } - } - - private static func createEnvelope(around message: SignalMessage) throws -> SSKProtoEnvelope { - do { - let builder = SSKProtoEnvelope.builder(type: message.type, timestamp: message.timestamp) - builder.setSource(message.senderPublicKey) - builder.setSourceDevice(message.senderDeviceID) - if let content = try Data(base64Encoded: message.content, options: .ignoreUnknownCharacters) { - builder.setContent(content) - } else { - throw Error.failedToWrapMessageInEnvelope - } - return try builder.build() - } catch let error { - print("[Loki] Failed to wrap message in envelope: \(error).") - throw Error.failedToWrapMessageInEnvelope - } - } - - private static func createWebSocketMessage(around envelope: SSKProtoEnvelope) throws -> WebSocketProtoWebSocketMessage { - do { - let requestBuilder = WebSocketProtoWebSocketRequestMessage.builder(verb: "PUT", path: "/api/v1/message", requestID: UInt64.random(in: 1.. SSKProtoEnvelope { - do { - let webSocketMessage = try WebSocketProtoWebSocketMessage.parseData(data) - let envelope = webSocketMessage.request!.body! - return try SSKProtoEnvelope.parseData(envelope) - } catch let error { - print("[Loki] Failed to unwrap data: \(error).") - throw Error.failedToUnwrapData - } - } -} diff --git a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift deleted file mode 100644 index 84a8da122..000000000 --- a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift +++ /dev/null @@ -1,72 +0,0 @@ -import CryptoSwift -import PromiseKit - -extension OnionRequestAPI { - - internal static func encode(ciphertext: Data, json: JSON) throws -> Data { - // The encoding of V2 onion requests looks like: | 4 bytes: size N of ciphertext | N bytes: ciphertext | json as utf8 | - guard JSONSerialization.isValidJSONObject(json) else { throw HTTP.Error.invalidJSON } - let jsonAsData = try JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) - let ciphertextSize = Int32(ciphertext.count).littleEndian - let ciphertextSizeAsData = withUnsafePointer(to: ciphertextSize) { Data(bytes: $0, count: MemoryLayout.size) } - return ciphertextSizeAsData + ciphertext + jsonAsData - } - - /// Encrypts `payload` for `destination` and returns the result. Use this to build the core of an onion request. - internal static func encrypt(_ payload: JSON, for destination: Destination) -> Promise { - let (promise, seal) = Promise.pending() - DispatchQueue.global(qos: .userInitiated).async { - do { - guard JSONSerialization.isValidJSONObject(payload) else { return seal.reject(HTTP.Error.invalidJSON) } - // Wrapping isn't needed for file server or open group onion requests - switch destination { - case .snode(let snode): - guard let snodeX25519PublicKey = snode.publicKeySet?.x25519Key else { return seal.reject(Error.snodePublicKeySetMissing) } - let payloadAsData = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ]) - let plaintext = try encode(ciphertext: payloadAsData, json: [ "headers" : "" ]) - let result = try EncryptionUtilities.encrypt(plaintext, using: snodeX25519PublicKey) - seal.fulfill(result) - case .server(_, let serverX25519PublicKey): - let plaintext = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ]) - let result = try EncryptionUtilities.encrypt(plaintext, using: serverX25519PublicKey) - seal.fulfill(result) - } - } catch (let error) { - seal.reject(error) - } - } - return promise - } - - /// Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request. - internal static func encryptHop(from lhs: Destination, to rhs: Destination, using previousEncryptionResult: EncryptionResult) -> Promise { - let (promise, seal) = Promise.pending() - DispatchQueue.global(qos: .userInitiated).async { - var parameters: JSON - switch rhs { - case .snode(let snode): - guard let snodeED25519PublicKey = snode.publicKeySet?.ed25519Key else { return seal.reject(Error.snodePublicKeySetMissing) } - parameters = [ "destination" : snodeED25519PublicKey ] - case .server(let host, _): - parameters = [ "host" : host, "target" : "/loki/v2/lsrpc", "method" : "POST" ] - } - parameters["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString() - let x25519PublicKey: String - switch lhs { - case .snode(let snode): - guard let snodeX25519PublicKey = snode.publicKeySet?.x25519Key else { return seal.reject(Error.snodePublicKeySetMissing) } - x25519PublicKey = snodeX25519PublicKey - case .server(_, let serverX25519PublicKey): - x25519PublicKey = serverX25519PublicKey - } - do { - let plaintext = try encode(ciphertext: previousEncryptionResult.ciphertext, json: parameters) - let result = try EncryptionUtilities.encrypt(plaintext, using: x25519PublicKey) - seal.fulfill(result) - } catch (let error) { - seal.reject(error) - } - } - return promise - } -} diff --git a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift deleted file mode 100644 index 66432ef61..000000000 --- a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift +++ /dev/null @@ -1,435 +0,0 @@ -import CryptoSwift -import PromiseKit - -/// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information. -public enum OnionRequestAPI { - private static var pathFailureCount: [Path:UInt] = [:] - private static var snodeFailureCount: [Snode:UInt] = [:] - public static var guardSnodes: Set = [] - public static var paths: [Path] = [] // Not a set to ensure we consistently show the same path to the user - - // MARK: Settings - /// The number of snodes (including the guard snode) in a path. - private static let pathSize: UInt = 3 - /// The number of times a path can fail before it's replaced. - private static let pathFailureThreshold: UInt = 3 - /// The number of times a snode can fail before it's replaced. - private static let snodeFailureThreshold: UInt = 3 - /// The number of paths to maintain. - public static let targetPathCount: UInt = 2 - - /// The number of guard snodes required to maintain `targetPathCount` paths. - private static var targetGuardSnodeCount: UInt { return targetPathCount } // One per path - - // MARK: Destination - internal enum Destination { - case snode(Snode) - case server(host: String, x25519PublicKey: String) - } - - // MARK: Error - public enum Error : LocalizedError { - case httpRequestFailedAtDestination(statusCode: UInt, json: JSON) - case insufficientSnodes - case invalidURL - case missingSnodeVersion - case snodePublicKeySetMissing - case unsupportedSnodeVersion(String) - - public var errorDescription: String? { - switch self { - case .httpRequestFailedAtDestination(let statusCode): return "HTTP request failed at destination with status code: \(statusCode)." - case .insufficientSnodes: return "Couldn't find enough snodes to build a path." - case .invalidURL: return "Invalid URL" - case .missingSnodeVersion: return "Missing snode version." - case .snodePublicKeySetMissing: return "Missing snode public key set." - case .unsupportedSnodeVersion(let version): return "Unsupported snode version: \(version)." - } - } - } - - // MARK: Path - public typealias Path = [Snode] - - // MARK: Onion Building Result - private typealias OnionBuildingResult = (guardSnode: Snode, finalEncryptionResult: EncryptionResult, destinationSymmetricKey: Data) - - // MARK: Private API - /// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise. - private static func testSnode(_ snode: Snode) -> Promise { - let (promise, seal) = Promise.pending() - DispatchQueue.global(qos: .userInitiated).async { - let url = "\(snode.address):\(snode.port)/get_stats/v1" - let timeout: TimeInterval = 3 // Use a shorter timeout for testing - HTTP.execute(.get, url, timeout: timeout).done2 { rawResponse in - guard let json = rawResponse as? JSON, let version = json["version"] as? String else { return seal.reject(Error.missingSnodeVersion) } - if version >= "2.0.7" { - seal.fulfill(()) - } else { - print("[Loki] [Onion Request API] Unsupported snode version: \(version).") - seal.reject(Error.unsupportedSnodeVersion(version)) - } - }.catch2 { error in - seal.reject(error) - } - } - return promise - } - - /// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with `Error.insufficientSnodes` - /// if not enough (reliable) snodes are available. - private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> Promise> { - if guardSnodes.count >= targetGuardSnodeCount { - return Promise> { $0.fulfill(guardSnodes) } - } else { - print("[Loki] [Onion Request API] Populating guard snode cache.") - return SnodeAPI.getRandomSnode().then2 { _ -> Promise> in // Just used to populate the snode pool - var unusedSnodes = SnodeAPI.snodePool.subtracting(reusableGuardSnodes) // Sync on LokiAPI.workQueue - let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) - guard unusedSnodes.count >= (targetGuardSnodeCount - reusableGuardSnodeCount) else { throw Error.insufficientSnodes } - func getGuardSnode() -> Promise { - // randomElement() uses the system's default random generator, which is cryptographically secure - guard let candidate = unusedSnodes.randomElement() else { return Promise { $0.reject(Error.insufficientSnodes) } } - unusedSnodes.remove(candidate) // All used snodes should be unique - print("[Loki] [Onion Request API] Testing guard snode: \(candidate).") - // Loop until a reliable guard snode is found - return testSnode(candidate).map2 { candidate }.recover(on: DispatchQueue.main) { _ in - withDelay(0.1, completionQueue: SnodeAPI.workQueue) { getGuardSnode() } - } - } - let promises = (0..<(targetGuardSnodeCount - reusableGuardSnodeCount)).map { _ in getGuardSnode() } - return when(fulfilled: promises).map2 { guardSnodes in - let guardSnodesAsSet = Set(guardSnodes + reusableGuardSnodes) - OnionRequestAPI.guardSnodes = guardSnodesAsSet - return guardSnodesAsSet - } - } - } - } - - /// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes` - /// if not enough (reliable) snodes are available. - private static func buildPaths(reusing reusablePaths: [Path]) -> Promise<[Path]> { - print("[Loki] [Onion Request API] Building onion request paths.") - DispatchQueue.main.async { - NotificationCenter.default.post(name: .buildingPaths, object: nil) - } - return SnodeAPI.getRandomSnode().then2 { _ -> Promise<[Path]> in // Just used to populate the snode pool - let reusableGuardSnodes = reusablePaths.map { $0[0] } - return getGuardSnodes(reusing: reusableGuardSnodes).map2 { guardSnodes -> [Path] in - var unusedSnodes = SnodeAPI.snodePool.subtracting(guardSnodes).subtracting(reusablePaths.flatMap { $0 }) - let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) - let pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount) - guard unusedSnodes.count >= pathSnodeCount else { throw Error.insufficientSnodes } - // Don't test path snodes as this would reveal the user's IP to them - return guardSnodes.subtracting(reusableGuardSnodes).map { guardSnode in - let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in - // randomElement() uses the system's default random generator, which is cryptographically secure - let pathSnode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above - unusedSnodes.remove(pathSnode) // All used snodes should be unique - return pathSnode - } - print("[Loki] [Onion Request API] Built new onion request path: \(result.prettifiedDescription).") - return result - } - }.map2 { paths in - OnionRequestAPI.paths = paths + reusablePaths - Storage.writeSync { transaction in - print("[Loki] Persisting onion request paths to database.") - Storage.setOnionRequestPaths(paths, using: transaction) - } - DispatchQueue.main.async { - NotificationCenter.default.post(name: .pathsBuilt, object: nil) - } - return paths - } - } - } - - /// Returns a `Path` to be used for building an onion request. Builds new paths as needed. - private static func getPath(excluding snode: Snode?) -> Promise { - guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") } - var paths = OnionRequestAPI.paths - if paths.isEmpty { - paths = Storage.getOnionRequestPaths() - OnionRequestAPI.paths = paths - if !paths.isEmpty { - guardSnodes.formUnion([ paths[0][0] ]) - if paths.count >= 2 { - guardSnodes.formUnion([ paths[1][0] ]) - } - } - } - // randomElement() uses the system's default random generator, which is cryptographically secure - if paths.count >= targetPathCount { - if let snode = snode { - return Promise { $0.fulfill(paths.filter { !$0.contains(snode) }.randomElement()!) } - } else { - return Promise { $0.fulfill(paths.randomElement()!) } - } - } else if !paths.isEmpty { - if let snode = snode { - if let path = paths.first(where: { !$0.contains(snode) }) { - buildPaths(reusing: paths).retainUntilComplete() // Re-build paths in the background - return Promise { $0.fulfill(path) } - } else { - return buildPaths(reusing: paths).map2 { paths in - return paths.filter { !$0.contains(snode) }.randomElement()! - } - } - } else { - buildPaths(reusing: paths).retainUntilComplete() // Re-build paths in the background - return Promise { $0.fulfill(paths.randomElement()!) } - } - } else { - return buildPaths(reusing: []).map2 { paths in - if let snode = snode { - return paths.filter { !$0.contains(snode) }.randomElement()! - } else { - return paths.randomElement()! - } - } - } - } - - private static func dropGuardSnode(_ snode: Snode) { - guardSnodes = guardSnodes.filter { $0 != snode } - } - - private static func drop(_ snode: Snode) throws { - // We repair the path here because we can do it sync. In the case where we drop a whole - // path we leave the re-building up to getPath(excluding:) because re-building the path - // in that case is async. - OnionRequestAPI.snodeFailureCount[snode] = 0 - var oldPaths = paths - guard let pathIndex = oldPaths.firstIndex(where: { $0.contains(snode) }) else { return } - var path = oldPaths[pathIndex] - guard let snodeIndex = path.firstIndex(of: snode) else { return } - path.remove(at: snodeIndex) - let unusedSnodes = SnodeAPI.snodePool.subtracting(oldPaths.flatMap { $0 }) - guard !unusedSnodes.isEmpty else { throw Error.insufficientSnodes } - // randomElement() uses the system's default random generator, which is cryptographically secure - path.append(unusedSnodes.randomElement()!) - // Don't test the new snode as this would reveal the user's IP - oldPaths.remove(at: pathIndex) - let newPaths = oldPaths + [ path ] - paths = newPaths - Storage.writeSync { transaction in - print("[Loki] Persisting onion request paths to database.") - Storage.setOnionRequestPaths(newPaths, using: transaction) - } - } - - private static func drop(_ path: Path) { - OnionRequestAPI.pathFailureCount[path] = 0 - var paths = OnionRequestAPI.paths - guard let pathIndex = paths.firstIndex(of: path) else { return } - paths.remove(at: pathIndex) - OnionRequestAPI.paths = paths - Storage.writeSync { transaction in - if !paths.isEmpty { - print("[Loki] Persisting onion request paths to database.") - Storage.setOnionRequestPaths(paths, using: transaction) - } else { - Storage.clearOnionRequestPaths(using: transaction) - } - } - } - - /// Builds an onion around `payload` and returns the result. - private static func buildOnion(around payload: JSON, targetedAt destination: Destination) -> Promise { - var guardSnode: Snode! - var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination - var encryptionResult: EncryptionResult! - var snodeToExclude: Snode? - if case .snode(let snode) = destination { snodeToExclude = snode } - return getPath(excluding: snodeToExclude).then2 { path -> Promise in - guardSnode = path.first! - // Encrypt in reverse order, i.e. the destination first - return encrypt(payload, for: destination).then2 { r -> Promise in - targetSnodeSymmetricKey = r.symmetricKey - // Recursively encrypt the layers of the onion (again in reverse order) - encryptionResult = r - var path = path - var rhs = destination - func addLayer() -> Promise { - if path.isEmpty { - return Promise { $0.fulfill(encryptionResult) } - } else { - let lhs = Destination.snode(path.removeLast()) - return OnionRequestAPI.encryptHop(from: lhs, to: rhs, using: encryptionResult).then2 { r -> Promise in - encryptionResult = r - rhs = lhs - return addLayer() - } - } - } - return addLayer() - } - }.map2 { _ in (guardSnode, encryptionResult, targetSnodeSymmetricKey) } - } - - // MARK: Internal API - /// Sends an onion request to `snode`. Builds new paths as needed. - internal static func sendOnionRequest(to snode: Snode, invoking method: Snode.Method, with parameters: JSON, associatedWith publicKey: String) -> Promise { - let payload: JSON = [ "method" : method.rawValue, "params" : parameters ] - return sendOnionRequest(with: payload, to: Destination.snode(snode)).recover2 { error -> Promise in - guard case OnionRequestAPI.Error.httpRequestFailedAtDestination(let statusCode, let json) = error else { throw error } - throw SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode, associatedWith: publicKey) ?? error - } - } - - /// Sends an onion request to `server`. Builds new paths as needed. - internal static func sendOnionRequest(_ request: NSURLRequest, to server: String, using x25519PublicKey: String, isJSONRequired: Bool = true) -> Promise { - var rawHeaders = request.allHTTPHeaderFields ?? [:] - rawHeaders.removeValue(forKey: "User-Agent") - var headers: JSON = rawHeaders.mapValues { value in - switch value.lowercased() { - case "true": return true - case "false": return false - default: return value - } - } - guard let url = request.url?.absoluteString, let host = request.url?.host else { return Promise(error: Error.invalidURL) } - var endpoint = "" - if server.count < url.count { - guard let serverEndIndex = url.range(of: server)?.upperBound else { return Promise(error: Error.invalidURL) } - let endpointStartIndex = url.index(after: serverEndIndex) - endpoint = String(url[endpointStartIndex.. Promise { - let (promise, seal) = Promise.pending() - var guardSnode: Snode! - SnodeAPI.workQueue.async { // Avoid race conditions on `guardSnodes` and `paths` - buildOnion(around: payload, targetedAt: destination).done2 { intermediate in - guardSnode = intermediate.guardSnode - let url = "\(guardSnode.address):\(guardSnode.port)/onion_req/v2" - let finalEncryptionResult = intermediate.finalEncryptionResult - let onion = finalEncryptionResult.ciphertext - if case Destination.server = destination, Double(onion.count) > 0.75 * Double(FileServerAPI.maxFileSize) { - print("[Loki] Approaching request size limit: ~\(onion.count) bytes.") - } - let parameters: JSON = [ - "ephemeral_key" : finalEncryptionResult.ephemeralPublicKey.toHexString() - ] - let body: Data - do { - body = try encode(ciphertext: onion, json: parameters) - } catch { - return seal.reject(error) - } - let destinationSymmetricKey = intermediate.destinationSymmetricKey - HTTP.execute(.post, url, body: body).done2 { rawResponse in - guard let json = rawResponse as? JSON, let base64EncodedIVAndCiphertext = json["result"] as? String, - let ivAndCiphertext = Data(base64Encoded: base64EncodedIVAndCiphertext), ivAndCiphertext.count >= EncryptionUtilities.ivSize else { return seal.reject(HTTP.Error.invalidJSON) } - do { - let data = try DecryptionUtilities.decrypt(ivAndCiphertext, usingAESGCMWithSymmetricKey: destinationSymmetricKey) - guard let json = try JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON, - let statusCode = json["status"] as? Int else { return seal.reject(HTTP.Error.invalidJSON) } - if statusCode == 406 { // Clock out of sync - print("[Loki] The user's clock is out of sync with the service node network.") - seal.reject(SnodeAPI.SnodeAPIError.clockOutOfSync) - } else if let bodyAsString = json["body"] as? String { - let body: JSON - if !isJSONRequired { - body = [ "result" : bodyAsString ] - } else { - guard let bodyAsData = bodyAsString.data(using: .utf8), - let b = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { return seal.reject(HTTP.Error.invalidJSON) } - body = b - } - guard 200...299 ~= statusCode else { return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: body)) } - seal.fulfill(body) - } else { - guard 200...299 ~= statusCode else { return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: json)) } - seal.fulfill(json) - } - } catch { - seal.reject(error) - } - }.catch2 { error in - seal.reject(error) - } - }.catch2 { error in - seal.reject(error) - } - } - promise.catch2 { error in // Must be invoked on LokiAPI.workQueue - guard case HTTP.Error.httpRequestFailed(let statusCode, let json) = error else { return } - let path = paths.first { $0.contains(guardSnode) } - func handleUnspecificError() { - guard let path = path else { return } - var pathFailureCount = OnionRequestAPI.pathFailureCount[path] ?? 0 - pathFailureCount += 1 - if pathFailureCount >= pathFailureThreshold { - dropGuardSnode(guardSnode) - path.forEach { snode in - SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode) // Intentionally don't throw - } - drop(path) - } else { - OnionRequestAPI.pathFailureCount[path] = pathFailureCount - } - } - let prefix = "Next node not found: " - if let message = json?["result"] as? String, message.hasPrefix(prefix) { - let ed25519PublicKey = message.substring(from: prefix.count) - if let path = path, let snode = path.first(where: { $0.publicKeySet?.ed25519Key == ed25519PublicKey }) { - var snodeFailureCount = OnionRequestAPI.snodeFailureCount[snode] ?? 0 - snodeFailureCount += 1 - if snodeFailureCount >= snodeFailureThreshold { - SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode) // Intentionally don't throw - do { - try drop(snode) - } catch { - handleUnspecificError() - } - } else { - OnionRequestAPI.snodeFailureCount[snode] = snodeFailureCount - } - } else { - handleUnspecificError() - } - } else if let message = json?["result"] as? String, message == "Loki Server error" { - // Do nothing - } else { - handleUnspecificError() - } - } - return promise - } -} diff --git a/SignalServiceKit/src/Loki/API/SignalMessage.swift b/SignalServiceKit/src/Loki/API/SignalMessage.swift deleted file mode 100644 index c4cb88249..000000000 --- a/SignalServiceKit/src/Loki/API/SignalMessage.swift +++ /dev/null @@ -1,28 +0,0 @@ - -@objc(LKSignalMessage) -public final class SignalMessage : NSObject { - @objc public let type: SSKProtoEnvelope.SSKProtoEnvelopeType - @objc public let timestamp: UInt64 - @objc public let senderPublicKey: String - @objc public let senderDeviceID: UInt32 - @objc public let content: String - @objc public let recipientPublicKey: String - @objc(ttl) - public let objc_ttl: UInt64 - @objc public let isPing: Bool - - public var ttl: UInt64? { return objc_ttl != 0 ? objc_ttl : nil } - - @objc public init(type: SSKProtoEnvelope.SSKProtoEnvelopeType, timestamp: UInt64, senderID: String, senderDeviceID: UInt32, - content: String, recipientID: String, ttl: UInt64, isPing: Bool) { - self.type = type - self.timestamp = timestamp - self.senderPublicKey = senderID - self.senderDeviceID = senderDeviceID - self.content = content - self.recipientPublicKey = recipientID - self.objc_ttl = ttl - self.isPing = isPing - super.init() - } -} diff --git a/SignalServiceKit/src/Loki/API/Snode.swift b/SignalServiceKit/src/Loki/API/Snode.swift deleted file mode 100644 index 1469a2291..000000000 --- a/SignalServiceKit/src/Loki/API/Snode.swift +++ /dev/null @@ -1,66 +0,0 @@ - -public final class Snode : NSObject, NSCoding { - public let address: String - public let port: UInt16 - internal let publicKeySet: KeySet? - - public var ip: String { - String(address[address.index(address.startIndex, offsetBy: 8).. Bool { - guard let other = other as? Snode else { return false } - return address == other.address && port == other.port - } - - // MARK: Hashing - override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:) - return address.hashValue ^ port.hashValue - } - - // MARK: Description - override public var description: String { return "\(address):\(port)" } -} diff --git a/SignalServiceKit/src/Loki/API/SnodeAPI.swift b/SignalServiceKit/src/Loki/API/SnodeAPI.swift deleted file mode 100644 index aa365a499..000000000 --- a/SignalServiceKit/src/Loki/API/SnodeAPI.swift +++ /dev/null @@ -1,352 +0,0 @@ -import PromiseKit - -@objc(LKSnodeAPI) -public final class SnodeAPI : NSObject { - internal static let workQueue = DispatchQueue(label: "SnodeAPI.workQueue", qos: .userInitiated) // It's important that this is a serial queue - - /// - Note: Should only be accessed from `LokiAPI.workQueue` to avoid race conditions. - internal static var snodeFailureCount: [Snode:UInt] = [:] - /// - Note: Should only be accessed from `LokiAPI.workQueue` to avoid race conditions. - internal static var snodePool: Set = [] - /// - Note: Should only be accessed from `LokiAPI.workQueue` to avoid race conditions. - internal static var swarmCache: [String:[Snode]] = [:] - - internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } - - // MARK: Settings - private static let maxRetryCount: UInt = 4 - private static let minimumSnodePoolCount = 64 - private static let minimumSwarmSnodeCount = 2 - private static let seedNodePool: Set = [ "https://storage.seed1.loki.network", "https://storage.seed3.loki.network", "https://public.loki.foundation" ] - private static let snodeFailureThreshold = 4 - private static let targetSwarmSnodeCount = 2 - - internal static var powDifficulty: UInt = 1 - /// - Note: Changing this on the fly is not recommended. - internal static var useOnionRequests = true - - // MARK: Error - @objc(LKSnodeAPIError) - public class SnodeAPIError : NSError { // Not called `Error` for Obj-C interoperablity - - @objc public static let proofOfWorkCalculationFailed = SnodeAPIError(domain: "LokiAPIErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "Failed to calculate proof of work." ]) - @objc public static let messageConversionFailed = SnodeAPIError(domain: "LokiAPIErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey : "Failed to construct message." ]) - @objc public static let clockOutOfSync = SnodeAPIError(domain: "LokiAPIErrorDomain", code: 3, userInfo: [ NSLocalizedDescriptionKey : "Your clock is out of sync with the service node network." ]) - @objc public static let randomSnodePoolUpdatingFailed = SnodeAPIError(domain: "LokiAPIErrorDomain", code: 4, userInfo: [ NSLocalizedDescriptionKey : "Failed to update random service node pool." ]) - @objc public static let missingSnodeVersion = SnodeAPIError(domain: "LokiAPIErrorDomain", code: 5, userInfo: [ NSLocalizedDescriptionKey : "Missing service node version." ]) - } - - // MARK: Type Aliases - public typealias MessageListPromise = Promise<[SSKProtoEnvelope]> - public typealias RawResponse = Any - public typealias RawResponsePromise = Promise - - // MARK: Lifecycle - override private init() { } - - // MARK: Core - internal static func invoke(_ method: Snode.Method, on snode: Snode, associatedWith publicKey: String, parameters: JSON) -> RawResponsePromise { - if useOnionRequests { - return OnionRequestAPI.sendOnionRequest(to: snode, invoking: method, with: parameters, associatedWith: publicKey).map2 { $0 as Any } - } else { - let url = "\(snode.address):\(snode.port)/storage_rpc/v1" - return HTTP.execute(.post, url, parameters: parameters).map2 { $0 as Any }.recover2 { error -> Promise in - guard case HTTP.Error.httpRequestFailed(let statusCode, let json) = error else { throw error } - throw SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode, associatedWith: publicKey) ?? error - } - } - } - - internal static func getRandomSnode() -> Promise { - if snodePool.count < minimumSnodePoolCount { - storage.dbReadConnection.read { transaction in - snodePool = storage.getSnodePool(in: transaction) - } - } - if snodePool.count < minimumSnodePoolCount { - let target = seedNodePool.randomElement()! - let url = "\(target)/json_rpc" - let parameters: JSON = [ - "method" : "get_n_service_nodes", - "params" : [ - "active_only" : true, - "fields" : [ - "public_ip" : true, "storage_port" : true, "pubkey_ed25519" : true, "pubkey_x25519" : true - ] - ] - ] - print("[Loki] Populating snode pool using: \(target).") - let (promise, seal) = Promise.pending() - attempt(maxRetryCount: 4, recoveringOn: SnodeAPI.workQueue) { - HTTP.execute(.post, url, parameters: parameters, useSeedNodeURLSession: true).map2 { json -> Snode in - guard let intermediate = json["result"] as? JSON, let rawSnodes = intermediate["service_node_states"] as? [JSON] else { throw SnodeAPIError.randomSnodePoolUpdatingFailed } - snodePool = try Set(rawSnodes.flatMap { rawSnode in - guard let address = rawSnode["public_ip"] as? String, let port = rawSnode["storage_port"] as? Int, - let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else { - print("[Loki] Failed to parse target from: \(rawSnode).") - return nil - } - return Snode(address: "https://\(address)", port: UInt16(port), publicKeySet: Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey)) - }) - // randomElement() uses the system's default random generator, which is cryptographically secure - if !snodePool.isEmpty { - return snodePool.randomElement()! - } else { - throw SnodeAPIError.randomSnodePoolUpdatingFailed - } - } - }.done2 { snode in - seal.fulfill(snode) - Storage.writeSync { transaction in - print("[Loki] Persisting snode pool to database.") - storage.setSnodePool(SnodeAPI.snodePool, in: transaction) - } - }.catch2 { error in - print("[Loki] Failed to contact seed node at: \(target).") - seal.reject(error) - } - return promise - } else { - return Promise { seal in - // randomElement() uses the system's default random generator, which is cryptographically secure - seal.fulfill(snodePool.randomElement()!) - } - } - } - - internal static func getSwarm(for publicKey: String, isForcedReload: Bool = false) -> Promise<[Snode]> { - if swarmCache[publicKey] == nil { - storage.dbReadConnection.read { transaction in - swarmCache[publicKey] = storage.getSwarm(for: publicKey, in: transaction) - } - } - if let cachedSwarm = swarmCache[publicKey], cachedSwarm.count >= minimumSwarmSnodeCount && !isForcedReload { - return Promise<[Snode]> { $0.fulfill(cachedSwarm) } - } else { - print("[Loki] Getting swarm for: \(publicKey == getUserHexEncodedPublicKey() ? "self" : publicKey).") - let parameters: [String:Any] = [ "pubKey" : publicKey ] - return getRandomSnode().then2 { snode in - attempt(maxRetryCount: 4, recoveringOn: SnodeAPI.workQueue) { - invoke(.getSwarm, on: snode, associatedWith: publicKey, parameters: parameters) - } - }.map2 { rawSnodes in - let swarm = parseSnodes(from: rawSnodes) - swarmCache[publicKey] = swarm - Storage.writeSync { transaction in - storage.setSwarm(swarm, for: publicKey, in: transaction) - } - return swarm - } - } - } - - internal static func getTargetSnodes(for publicKey: String) -> Promise<[Snode]> { - // shuffled() uses the system's default random generator, which is cryptographically secure - return getSwarm(for: publicKey).map2 { Array($0.shuffled().prefix(targetSwarmSnodeCount)) } - } - - internal static func dropSnodeFromSnodePool(_ snode: Snode) { - SnodeAPI.snodePool.remove(snode) - Storage.writeSync { transaction in - storage.dropSnodeFromSnodePool(snode, in: transaction) - } - } - - @objc public static func clearSnodePool() { - snodePool.removeAll() - Storage.writeSync { transaction in - storage.clearSnodePool(in: transaction) - } - } - - internal static func dropSnodeFromSwarmIfNeeded(_ snode: Snode, publicKey: String) { - let swarm = SnodeAPI.swarmCache[publicKey] - if var swarm = swarm, let index = swarm.firstIndex(of: snode) { - swarm.remove(at: index) - SnodeAPI.swarmCache[publicKey] = swarm - Storage.writeSync { transaction in - storage.setSwarm(swarm, for: publicKey, in: transaction) - } - } - } - - // MARK: Receiving - internal static func getRawMessages(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { - Storage.writeSync { transaction in - Storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey, using: transaction) - } - let lastHash = Storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? "" - let parameters = [ "pubKey" : publicKey, "lastHash" : lastHash ] - return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) - } - - public static func getMessages(for publicKey: String) -> Promise> { - return attempt(maxRetryCount: maxRetryCount, recoveringOn: SnodeAPI.workQueue) { - getTargetSnodes(for: publicKey).mapValues2 { targetSnode in - getRawMessages(from: targetSnode, associatedWith: publicKey).map2 { - parseRawMessagesResponse($0, from: targetSnode, associatedWith: publicKey) - } - }.map2 { Set($0) } - } - } - - // MARK: Sending - @objc(sendSignalMessage:) - public static func objc_sendSignalMessage(_ signalMessage: SignalMessage) -> AnyPromise { - let promise = sendSignalMessage(signalMessage).mapValues2 { AnyPromise.from($0) }.map2 { Set($0) } - return AnyPromise.from(promise) - } - - public static func sendSignalMessage(_ signalMessage: SignalMessage) -> Promise> { - // Convert the message to a Loki message - guard let lokiMessage = LokiMessage.from(signalMessage: signalMessage) else { return Promise(error: SnodeAPIError.messageConversionFailed) } - let publicKey = lokiMessage.recipientPublicKey - let notificationCenter = NotificationCenter.default - notificationCenter.post(name: .calculatingPoW, object: NSNumber(value: signalMessage.timestamp)) - // Calculate proof of work - return lokiMessage.calculatePoW().then2 { lokiMessageWithPoW -> Promise> in - notificationCenter.post(name: .routing, object: NSNumber(value: signalMessage.timestamp)) - // Get the target snodes - return getTargetSnodes(for: publicKey).map2 { snodes in - notificationCenter.post(name: .messageSending, object: NSNumber(value: signalMessage.timestamp)) - let parameters = lokiMessageWithPoW.toJSON() - return Set(snodes.map { snode in - // Send the message to the target snode - return attempt(maxRetryCount: maxRetryCount, recoveringOn: SnodeAPI.workQueue) { - invoke(.sendMessage, on: snode, associatedWith: publicKey, parameters: parameters) - }.map2 { rawResponse in - if let json = rawResponse as? JSON, let powDifficulty = json["difficulty"] as? Int { - guard powDifficulty != SnodeAPI.powDifficulty, powDifficulty < 100 else { return rawResponse } - print("[Loki] Setting proof of work difficulty to \(powDifficulty).") - SnodeAPI.powDifficulty = UInt(powDifficulty) - } else { - print("[Loki] Failed to update proof of work difficulty from: \(rawResponse).") - } - return rawResponse - } - }) - } - } - } - - // MARK: Parsing - - // The parsing utilities below use a best attempt approach to parsing; they warn for parsing failures but don't throw exceptions. - - private static func parseSnodes(from rawResponse: Any) -> [Snode] { - guard let json = rawResponse as? JSON, let rawSnodes = json["snodes"] as? [JSON] else { - print("[Loki] Failed to parse targets from: \(rawResponse).") - return [] - } - return rawSnodes.flatMap { rawSnode in - guard let address = rawSnode["ip"] as? String, let portAsString = rawSnode["port"] as? String, let port = UInt16(portAsString), let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else { - print("[Loki] Failed to parse target from: \(rawSnode).") - return nil - } - return Snode(address: "https://\(address)", port: port, publicKeySet: Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey)) - } - } - - internal static func parseRawMessagesResponse(_ rawResponse: Any, from snode: Snode, associatedWith publicKey: String) -> [SSKProtoEnvelope] { - guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return [] } - updateLastMessageHashValueIfPossible(for: snode, associatedWith: publicKey, from: rawMessages) - let rawNewMessages = removeDuplicates(from: rawMessages, associatedWith: publicKey) - let newMessages = parseProtoEnvelopes(from: rawNewMessages) - return newMessages - } - - private static func updateLastMessageHashValueIfPossible(for snode: Snode, associatedWith publicKey: String, from rawMessages: [JSON]) { - if let lastMessage = rawMessages.last, let lastHash = lastMessage["hash"] as? String, let expirationDate = lastMessage["expiration"] as? UInt64 { - Storage.writeSync { transaction in - Storage.setLastMessageHashInfo(for: snode, associatedWith: publicKey, to: [ "hash" : lastHash, "expirationDate" : NSNumber(value: expirationDate) ], using: transaction) - } - } else if (!rawMessages.isEmpty) { - print("[Loki] Failed to update last message hash value from: \(rawMessages).") - } - } - - private static func removeDuplicates(from rawMessages: [JSON], associatedWith publicKey: String) -> [JSON] { - var receivedMessages = Storage.getReceivedMessages(for: publicKey) ?? [] - return rawMessages.filter { rawMessage in - guard let hash = rawMessage["hash"] as? String else { - print("[Loki] Missing hash value for message: \(rawMessage).") - return false - } - let isDuplicate = receivedMessages.contains(hash) - receivedMessages.insert(hash) - Storage.writeSync { transaction in - Storage.setReceivedMessages(to: receivedMessages, for: publicKey, using: transaction) - } - return !isDuplicate - } - } - - private static func parseProtoEnvelopes(from rawMessages: [JSON]) -> [SSKProtoEnvelope] { - return rawMessages.compactMap { rawMessage in - guard let base64EncodedData = rawMessage["data"] as? String, let data = Data(base64Encoded: base64EncodedData) else { - print("[Loki] Failed to decode data for message: \(rawMessage).") - return nil - } - guard let envelope = try? MessageWrapper.unwrap(data: data) else { - print("[Loki] Failed to unwrap data for message: \(rawMessage).") - return nil - } - return envelope - } - } - - // MARK: Error Handling - /// - Note: Should only be invoked from `LokiAPI.workQueue` to avoid race conditions. - internal static func handleError(withStatusCode statusCode: UInt, json: JSON?, forSnode snode: Snode, associatedWith publicKey: String? = nil) -> Error? { - #if DEBUG - assertOnQueue(SnodeAPI.workQueue) - #endif - func handleBadSnode() { - let oldFailureCount = SnodeAPI.snodeFailureCount[snode] ?? 0 - let newFailureCount = oldFailureCount + 1 - SnodeAPI.snodeFailureCount[snode] = newFailureCount - print("[Loki] Couldn't reach snode at: \(snode); setting failure count to \(newFailureCount).") - if newFailureCount >= SnodeAPI.snodeFailureThreshold { - print("[Loki] Failure threshold reached for: \(snode); dropping it.") - if let publicKey = publicKey { - SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey) - } - SnodeAPI.dropSnodeFromSnodePool(snode) - print("[Loki] Snode pool count: \(snodePool.count).") - SnodeAPI.snodeFailureCount[snode] = 0 - } - } - switch statusCode { - case 0, 400, 500, 503: - // The snode is unreachable - handleBadSnode() - case 406: - print("[Loki] The user's clock is out of sync with the service node network.") - return SnodeAPI.SnodeAPIError.clockOutOfSync - case 421: - // The snode isn't associated with the given public key anymore - if let publicKey = publicKey { - print("[Loki] Invalidating swarm for: \(publicKey).") - SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey) - } else { - print("[Loki] Got a 421 without an associated public key.") - } - case 432: - // The proof of work difficulty is too low - if let powDifficulty = json?["difficulty"] as? UInt { - if powDifficulty < 100 { - print("[Loki] Setting proof of work difficulty to \(powDifficulty).") - SnodeAPI.powDifficulty = UInt(powDifficulty) - } else { - handleBadSnode() - } - } else { - print("[Loki] Failed to update proof of work difficulty.") - } - default: - handleBadSnode() - print("[Loki] Unhandled response code: \(statusCode).") - } - return nil - } -} diff --git a/SignalServiceKit/src/Loki/API/Utilities/DecryptionUtilities.swift b/SignalServiceKit/src/Loki/API/Utilities/DecryptionUtilities.swift deleted file mode 100644 index 974451235..000000000 --- a/SignalServiceKit/src/Loki/API/Utilities/DecryptionUtilities.swift +++ /dev/null @@ -1,18 +0,0 @@ -import CryptoSwift - -enum DecryptionUtilities { - - /// - Note: Sync. Don't call from the main thread. - internal static func decrypt(_ ivAndCiphertext: Data, usingAESGCMWithSymmetricKey symmetricKey: Data) throws -> Data { - if Thread.isMainThread { - #if DEBUG - preconditionFailure("It's illegal to call decrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") - #endif - } - let iv = ivAndCiphertext[0.. Data { - if Thread.isMainThread { - #if DEBUG - preconditionFailure("It's illegal to call encrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") - #endif - } - let iv = Data.getSecureRandomData(ofSize: ivSize)! - let gcm = GCM(iv: iv.bytes, tagLength: Int(gcmTagSize), mode: .combined) - let aes = try AES(key: symmetricKey.bytes, blockMode: gcm, padding: .noPadding) - let ciphertext = try aes.encrypt(plaintext.bytes) - return iv + Data(bytes: ciphertext) - } - - /// - Note: Sync. Don't call from the main thread. - internal static func encrypt(_ plaintext: Data, using hexEncodedX25519PublicKey: String) throws -> EncryptionResult { - if Thread.isMainThread { - #if DEBUG - preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") - #endif - } - let x25519PublicKey = Data(hex: hexEncodedX25519PublicKey) - let ephemeralKeyPair = Curve25519.generateKeyPair() - let ephemeralSharedSecret = try Curve25519.generateSharedSecret(fromPublicKey: x25519PublicKey, privateKey: ephemeralKeyPair.privateKey) - let salt = "LOKI" - let symmetricKey = try HMAC(key: salt.bytes, variant: .sha256).authenticate(ephemeralSharedSecret.bytes) - let ciphertext = try encrypt(plaintext, usingAESGCMWithSymmetricKey: Data(bytes: symmetricKey)) - return (ciphertext, Data(bytes: symmetricKey), ephemeralKeyPair.publicKey) - } -} diff --git a/SignalServiceKit/src/Loki/API/Utilities/HTTP.swift b/SignalServiceKit/src/Loki/API/Utilities/HTTP.swift deleted file mode 100644 index a6c908714..000000000 --- a/SignalServiceKit/src/Loki/API/Utilities/HTTP.swift +++ /dev/null @@ -1,107 +0,0 @@ -import PromiseKit - -public enum HTTP { - private static let seedNodeURLSession = URLSession(configuration: .ephemeral) - private static let defaultURLSession = URLSession(configuration: .ephemeral, delegate: defaultURLSessionDelegate, delegateQueue: nil) - private static let defaultURLSessionDelegate = DefaultURLSessionDelegateImplementation() - - // MARK: Settings - public static let timeout: TimeInterval = 10 - - // MARK: URL Session Delegate Implementation - private final class DefaultURLSessionDelegateImplementation : NSObject, URLSessionDelegate { - - func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - // Snode to snode communication uses self-signed certificates but clients can safely ignore this - completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!)) - } - } - - // MARK: Verb - public enum Verb : String { - case get = "GET" - case put = "PUT" - case post = "POST" - case delete = "DELETE" - } - - // MARK: Error - public enum Error : LocalizedError { - case generic - case httpRequestFailed(statusCode: UInt, json: JSON?) - case invalidJSON - - public var errorDescription: String? { - switch self { - case .generic: return "An error occurred." - case .httpRequestFailed(let statusCode, _): return "HTTP request failed with status code: \(statusCode)." - case .invalidJSON: return "Invalid JSON." - } - } - } - - // MARK: Main - public static func execute(_ verb: Verb, _ url: String, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { - return execute(verb, url, body: nil, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession) - } - - public static func execute(_ verb: Verb, _ url: String, parameters: JSON?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { - if let parameters = parameters { - do { - guard JSONSerialization.isValidJSONObject(parameters) else { return Promise(error: Error.invalidJSON) } - let body = try JSONSerialization.data(withJSONObject: parameters, options: [ .fragmentsAllowed ]) - return execute(verb, url, body: body, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession) - } catch (let error) { - return Promise(error: error) - } - } else { - return execute(verb, url, body: nil, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession) - } - } - - public static func execute(_ verb: Verb, _ url: String, body: Data?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { - var request = URLRequest(url: URL(string: url)!) - request.httpMethod = verb.rawValue - request.httpBody = body - request.timeoutInterval = timeout - request.allHTTPHeaderFields?.removeValue(forKey: "User-Agent") - let (promise, seal) = Promise.pending() - let urlSession = useSeedNodeURLSession ? seedNodeURLSession : defaultURLSession - let task = urlSession.dataTask(with: request) { data, response, error in - guard let data = data, let response = response as? HTTPURLResponse else { - if let error = error { - print("[Loki] \(verb.rawValue) request to \(url) failed due to error: \(error).") - } else { - print("[Loki] \(verb.rawValue) request to \(url) failed.") - } - // Override the actual error so that we can correctly catch failed requests in sendOnionRequest(invoking:on:with:) - return seal.reject(Error.httpRequestFailed(statusCode: 0, json: nil)) - } - if let error = error { - print("[Loki] \(verb.rawValue) request to \(url) failed due to error: \(error).") - // Override the actual error so that we can correctly catch failed requests in sendOnionRequest(invoking:on:with:) - return seal.reject(Error.httpRequestFailed(statusCode: 0, json: nil)) - } - let statusCode = UInt(response.statusCode) - var json: JSON? = nil - if let j = try? JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON { - json = j - } else if let result = String(data: data, encoding: .utf8) { - json = [ "result" : result ] - } - guard 200...299 ~= statusCode else { - let jsonDescription = json?.prettifiedDescription ?? "no debugging info provided" - print("[Loki] \(verb.rawValue) request to \(url) failed with status code: \(statusCode) (\(jsonDescription)).") - return seal.reject(Error.httpRequestFailed(statusCode: statusCode, json: json)) - } - if let json = json { - seal.fulfill(json) - } else { - print("[Loki] Couldn't parse JSON returned by \(verb.rawValue) request to \(url).") - return seal.reject(Error.invalidJSON) - } - } - task.resume() - return promise - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift deleted file mode 100644 index 07fbf3ede..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift +++ /dev/null @@ -1,44 +0,0 @@ - -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) ]" } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift deleted file mode 100644 index e29fda01e..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift +++ /dev/null @@ -1,51 +0,0 @@ - -internal final class ClosedGroupSenderKey : NSObject, NSCoding { - internal let chainKey: Data - internal let keyIndex: UInt - internal 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: Proto Conversion - internal func toProto() throws -> SSKProtoDataMessageClosedGroupUpdateSenderKey { - return try SSKProtoDataMessageClosedGroupUpdateSenderKey.builder(chainKey: chainKey, keyIndex: UInt32(keyIndex), publicKey: publicKey).build() - } - - // 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()) ]" - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift deleted file mode 100644 index dfa11d0c1..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift +++ /dev/null @@ -1,125 +0,0 @@ - -@objc(LKClosedGroupUpdateMessage) -internal final class ClosedGroupUpdateMessage : TSOutgoingMessage { - private let kind: Kind - - // MARK: Settings - @objc internal override var ttl: UInt32 { return UInt32(TTLUtilities.getTTL(for: .closedGroupUpdate)) } - - @objc internal override func shouldBeSaved() -> Bool { return false } - @objc internal override func shouldSyncTranscript() -> Bool { return false } - - // MARK: Kind - internal enum Kind { - case new(groupPublicKey: Data, name: String, groupPrivateKey: Data, senderKeys: [ClosedGroupSenderKey], members: [Data], admins: [Data]) - case info(groupPublicKey: Data, name: String, senderKeys: [ClosedGroupSenderKey], members: [Data], admins: [Data]) - case senderKeyRequest(groupPublicKey: Data) - case senderKey(groupPublicKey: Data, senderKey: ClosedGroupSenderKey) - } - - // MARK: Initialization - internal init(thread: TSThread, kind: Kind) { - self.kind = kind - super.init(outgoingMessageWithTimestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageBody: "", - attachmentIds: NSMutableArray(), expiresInSeconds: 0, expireStartedAt: 0, isVoiceMessage: false, - groupMetaMessage: .unspecified, quotedMessage: nil, contactShare: nil, linkPreview: nil) - } - - required init(dictionary: [String:Any]) throws { - preconditionFailure("Use init(thread:kind:) instead.") - } - - // MARK: Coding - internal required init?(coder: NSCoder) { - guard let thread = coder.decodeObject(forKey: "thread") as? TSThread, - let timestamp = coder.decodeObject(forKey: "timestamp") as? UInt64, - let groupPublicKey = coder.decodeObject(forKey: "groupPublicKey") as? Data, - let rawKind = coder.decodeObject(forKey: "kind") as? String else { return nil } - switch rawKind { - case "new": - guard let name = coder.decodeObject(forKey: "name") as? String, - let groupPrivateKey = coder.decodeObject(forKey: "groupPrivateKey") as? Data, - let senderKeys = coder.decodeObject(forKey: "senderKeys") as? [ClosedGroupSenderKey], - let members = coder.decodeObject(forKey: "members") as? [Data], - let admins = coder.decodeObject(forKey: "admins") as? [Data] else { return nil } - self.kind = .new(groupPublicKey: groupPublicKey, name: name, groupPrivateKey: groupPrivateKey, senderKeys: senderKeys, members: members, admins: admins) - case "info": - guard let name = coder.decodeObject(forKey: "name") as? String, - let senderKeys = coder.decodeObject(forKey: "senderKeys") as? [ClosedGroupSenderKey], - let members = coder.decodeObject(forKey: "members") as? [Data], - let admins = coder.decodeObject(forKey: "admins") as? [Data] else { return nil } - self.kind = .info(groupPublicKey: groupPublicKey, name: name, senderKeys: senderKeys, members: members, admins: admins) - case "senderKeyRequest": - self.kind = .senderKeyRequest(groupPublicKey: groupPublicKey) - case "senderKey": - guard let senderKeys = coder.decodeObject(forKey: "senderKeys") as? [ClosedGroupSenderKey], - let senderKey = senderKeys.first else { return nil } - self.kind = .senderKey(groupPublicKey: groupPublicKey, senderKey: senderKey) - default: return nil - } - super.init(outgoingMessageWithTimestamp: timestamp, in: thread, messageBody: "", - attachmentIds: NSMutableArray(), expiresInSeconds: 0, expireStartedAt: 0, isVoiceMessage: false, - groupMetaMessage: .unspecified, quotedMessage: nil, contactShare: nil, linkPreview: nil) - } - - internal override func encode(with coder: NSCoder) { - coder.encode(thread, forKey: "thread") - coder.encode(timestamp, forKey: "timestamp") - switch kind { - case .new(let groupPublicKey, let name, let groupPrivateKey, let senderKeys, let members, let admins): - coder.encode("new", forKey: "kind") - coder.encode(groupPublicKey, forKey: "groupPublicKey") - coder.encode(name, forKey: "name") - coder.encode(groupPrivateKey, forKey: "groupPrivateKey") - coder.encode(senderKeys, forKey: "senderKeys") - coder.encode(members, forKey: "members") - coder.encode(admins, forKey: "admins") - case .info(let groupPublicKey, let name, let senderKeys, let members, let admins): - coder.encode("info", forKey: "kind") - coder.encode(groupPublicKey, forKey: "groupPublicKey") - coder.encode(name, forKey: "name") - coder.encode(senderKeys, forKey: "senderKeys") - coder.encode(members, forKey: "members") - coder.encode(admins, forKey: "admins") - case .senderKeyRequest(let groupPublicKey): - coder.encode(groupPublicKey, forKey: "groupPublicKey") - case .senderKey(let groupPublicKey, let senderKey): - coder.encode("senderKey", forKey: "kind") - coder.encode(groupPublicKey, forKey: "groupPublicKey") - coder.encode([ senderKey ], forKey: "senderKeys") - } - } - - // MARK: Building - @objc internal override func dataMessageBuilder() -> Any? { - guard let builder = super.dataMessageBuilder() as? SSKProtoDataMessage.SSKProtoDataMessageBuilder else { return nil } - do { - let closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate.SSKProtoDataMessageClosedGroupUpdateBuilder - switch kind { - case .new(let groupPublicKey, let name, let groupPrivateKey, let senderKeys, let members, let admins): - closedGroupUpdate = SSKProtoDataMessageClosedGroupUpdate.builder(groupPublicKey: groupPublicKey, type: .new) - closedGroupUpdate.setName(name) - closedGroupUpdate.setGroupPrivateKey(groupPrivateKey) - closedGroupUpdate.setSenderKeys(try senderKeys.map { try $0.toProto() }) - closedGroupUpdate.setMembers(members) - closedGroupUpdate.setAdmins(admins) - case .info(let groupPublicKey, let name, let senderKeys, let members, let admins): - closedGroupUpdate = SSKProtoDataMessageClosedGroupUpdate.builder(groupPublicKey: groupPublicKey, type: .info) - closedGroupUpdate.setName(name) - closedGroupUpdate.setSenderKeys(try senderKeys.map { try $0.toProto() }) - closedGroupUpdate.setMembers(members) - closedGroupUpdate.setAdmins(admins) - case .senderKeyRequest(let groupPublicKey): - closedGroupUpdate = SSKProtoDataMessageClosedGroupUpdate.builder(groupPublicKey: groupPublicKey, type: .senderKeyRequest) - case .senderKey(let groupPublicKey, let senderKey): - closedGroupUpdate = SSKProtoDataMessageClosedGroupUpdate.builder(groupPublicKey: groupPublicKey, type: .senderKey) - closedGroupUpdate.setSenderKeys([ try senderKey.toProto() ]) - } - builder.setClosedGroupUpdate(try closedGroupUpdate.build()) - } catch { - owsFailDebug("Failed to build closed group update due to error: \(error).") - return nil - } - return builder - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUtilities.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUtilities.swift deleted file mode 100644 index 4fb7e8bbf..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUtilities.swift +++ /dev/null @@ -1,70 +0,0 @@ -import CryptoSwift -import SessionMetadataKit - -@objc(LKClosedGroupUtilities) -public final class ClosedGroupUtilities : NSObject { - - @objc(LKSSKDecryptionError) - public class SSKDecryptionError : NSError { // Not called `Error` for Obj-C interoperablity - - @objc public static let invalidGroupPublicKey = SSKDecryptionError(domain: "SSKErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "Invalid group public key." ]) - @objc public static let noData = SSKDecryptionError(domain: "SSKErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey : "Received an empty envelope." ]) - @objc public static let noGroupPrivateKey = SSKDecryptionError(domain: "SSKErrorDomain", code: 3, userInfo: [ NSLocalizedDescriptionKey : "Missing group private key." ]) - @objc public static let selfSend = SSKDecryptionError(domain: "SSKErrorDomain", code: 4, userInfo: [ NSLocalizedDescriptionKey : "Message addressed at self." ]) - } - - @objc(encryptData:usingGroupPublicKey:transaction:error:) - public static func encrypt(data: Data, groupPublicKey: String, transaction: YapDatabaseReadWriteTransaction) throws -> Data { - // 1. ) Encrypt the data with the user's sender key - guard let userPublicKey = OWSIdentityManager.shared().identityKeyPair()?.hexEncodedPublicKey else { - throw SMKError.assertionError(description: "[Loki] Couldn't find user key pair.") - } - let ciphertextAndKeyIndex = try SharedSenderKeysImplementation.shared.encrypt(data, forGroupWithPublicKey: groupPublicKey, - senderPublicKey: userPublicKey, protocolContext: transaction) - let ivAndCiphertext = ciphertextAndKeyIndex[0] as! Data - let keyIndex = ciphertextAndKeyIndex[1] as! UInt - let encryptedMessage = ClosedGroupCiphertextMessage(_throws_withIVAndCiphertext: ivAndCiphertext, senderPublicKey: Data(hex: userPublicKey), keyIndex: UInt32(keyIndex)) - // 2. ) Encrypt the result for the group's public key to hide the sender public key and key index - let (ciphertext, _, ephemeralPublicKey) = try EncryptionUtilities.encrypt(encryptedMessage.serialized, using: groupPublicKey.removing05PrefixIfNeeded()) - // 3. ) Wrap the result - return try SSKProtoClosedGroupCiphertextMessageWrapper.builder(ciphertext: ciphertext, ephemeralPublicKey: ephemeralPublicKey).build().serializedData() - } - - @objc(decryptEnvelope:transaction:error:) - public static func decrypt(envelope: SSKProtoEnvelope, transaction: YapDatabaseReadWriteTransaction) throws -> [Any] { - let (plaintext, senderPublicKey) = try decrypt(envelope: envelope, transaction: transaction) - return [ plaintext, senderPublicKey ] - } - - public static func decrypt(envelope: SSKProtoEnvelope, transaction: YapDatabaseReadWriteTransaction) throws -> (plaintext: Data, senderPublicKey: String) { - // 1. ) Check preconditions - guard let groupPublicKey = envelope.source, SharedSenderKeysImplementation.shared.isClosedGroup(groupPublicKey) else { - throw SSKDecryptionError.invalidGroupPublicKey - } - guard let data = envelope.content else { - throw SSKDecryptionError.noData - } - guard let hexEncodedGroupPrivateKey = Storage.getClosedGroupPrivateKey(for: groupPublicKey) else { - throw SSKDecryptionError.noGroupPrivateKey - } - let groupPrivateKey = Data(hex: hexEncodedGroupPrivateKey) - // 2. ) Parse the wrapper - let wrapper = try SSKProtoClosedGroupCiphertextMessageWrapper.parseData(data) - let ivAndCiphertext = wrapper.ciphertext - let ephemeralPublicKey = wrapper.ephemeralPublicKey - // 3. ) Decrypt the data inside - let ephemeralSharedSecret = try Curve25519.generateSharedSecret(fromPublicKey: ephemeralPublicKey, privateKey: groupPrivateKey) - let salt = "LOKI" - let symmetricKey = try HMAC(key: salt.bytes, variant: .sha256).authenticate(ephemeralSharedSecret.bytes) - let closedGroupCiphertextMessageAsData = try DecryptionUtilities.decrypt(ivAndCiphertext, usingAESGCMWithSymmetricKey: Data(symmetricKey)) - // 4. ) Parse the closed group ciphertext message - let closedGroupCiphertextMessage = ClosedGroupCiphertextMessage(_throws_with: closedGroupCiphertextMessageAsData) - let senderPublicKey = closedGroupCiphertextMessage.senderPublicKey.toHexString() - guard senderPublicKey != getUserHexEncodedPublicKey() else { throw SSKDecryptionError.selfSend } - // 5. ) Use the info inside the closed group ciphertext message to decrypt the actual message content - let plaintext = try SharedSenderKeysImplementation.shared.decrypt(closedGroupCiphertextMessage.ivAndCiphertext, forGroupWithPublicKey: groupPublicKey, - senderPublicKey: senderPublicKey, keyIndex: UInt(closedGroupCiphertextMessage.keyIndex), protocolContext: transaction) - // 6. ) Return - return (plaintext, senderPublicKey) - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift deleted file mode 100644 index 21724bf07..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift +++ /dev/null @@ -1,220 +0,0 @@ -import CryptoSwift -import PromiseKit -import SessionMetadataKit - -@objc(LKSharedSenderKeysImplementation) -public final class SharedSenderKeysImplementation : NSObject { - private static let gcmTagSize: UInt = 16 - private static let ivSize: UInt = 12 - - // MARK: Documentation - // A quick overview of how shared sender key based closed groups work: - // - // • When a user creates a group, they generate a key pair for the group along with a ratchet for - // every member of the group. They bundle this together with some other group info such as the group - // name in a `ClosedGroupUpdateMessage` and send that using established channels to every member of - // the group. Note that because a user can only pick from their existing contacts when selecting - // the group members they shouldn't need to establish sessions before being able to send the - // `ClosedGroupUpdateMessage`. Another way to optimize the performance of the group creation process - // is to batch fetch the device links of all members involved ahead of time, rather than letting - // the sending pipeline do it separately for every user the `ClosedGroupUpdateMessage` is sent to. - // • After the group is created, every user polls for the public key associated with the group. - // • Upon receiving a `ClosedGroupUpdateMessage` of type `.new`, a user sends session requests to all - // other members of the group they don't yet have a session with for reasons outlined below. - // • When a user sends a message they step their ratchet and use the resulting message key to encrypt - // the message. - // • When another user receives that message, they step the ratchet associated with the sender and - // use the resulting message key to decrypt the message. - // • When a user leaves or is kicked from a group, all members must generate new ratchets to ensure that - // removed users can't decrypt messages going forward. To this end every user deletes all ratchets - // associated with the group in question upon receiving a group update message that indicates that - // a user left. They then generate a new ratchet for themselves and send it out to all members of - // the group (again fetching device links ahead of time). The user should already have established - // sessions with all other members at this point because of the behavior outlined a few points above. - // • When a user adds a new member to the group, they generate a ratchet for that new member and - // send that bundled in a `ClosedGroupUpdateMessage` to the group. They send a - // `ClosedGroupUpdateMessage` with the newly generated ratchet but also the existing ratchets of - // every other member of the group to the user that joined. - - // 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: Initialization - @objc public static let shared = SharedSenderKeysImplementation() - - private override init() { } - - // MARK: Private/Internal API - internal func generateRatchet(for groupPublicKey: String, senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) -> ClosedGroupRatchet { - let rootChainKey = Data.getSecureRandomData(ofSize: 32)!.toHexString() - let ratchet = ClosedGroupRatchet(chainKey: rootChainKey, keyIndex: 0, messageKeys: []) - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: ratchet, 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: YapDatabaseReadWriteTransaction) throws -> ClosedGroupRatchet { - #if DEBUG - assert(!Thread.isMainThread) - #endif - guard let ratchet = Storage.getClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey) else { - let error = RatchetingError.loadingFailed(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) - print("[Loki] \(error.errorDescription!)") - throw error - } - do { - let result = try step(ratchet) - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, 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: YapDatabaseReadWriteTransaction, isRetry: Bool = false) throws -> ClosedGroupRatchet { - #if DEBUG - assert(!Thread.isMainThread) - #endif - let collection: Storage.ClosedGroupRatchetCollectionType = (isRetry) ? .old : .current - guard let ratchet = 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: Storage.ClosedGroupRatchetCollectionType = (isRetry) ? .old : .current - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, in: collection, using: transaction) - return result - } - } - - // MARK: Public API - @objc(encrypt:forGroupWithPublicKey:senderPublicKey:protocolContext:error:) - public func encrypt(_ plaintext: Data, forGroupWithPublicKey groupPublicKey: String, senderPublicKey: String, protocolContext: Any) throws -> [Any] { - let transaction = protocolContext as! YapDatabaseReadWriteTransaction - let (ivAndCiphertext, keyIndex) = try encrypt(plaintext, for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction) - return [ ivAndCiphertext, NSNumber(value: keyIndex) ] - } - - public func encrypt(_ plaintext: Data, for groupPublicKey: String, senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) 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 { - ClosedGroupsProtocol.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction) - } - throw error - } - let iv = Data.getSecureRandomData(ofSize: SharedSenderKeysImplementation.ivSize)! - let gcm = GCM(iv: iv.bytes, tagLength: Int(SharedSenderKeysImplementation.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(bytes: ciphertext), ratchet.keyIndex) - } - - @objc(decrypt:forGroupWithPublicKey:senderPublicKey:keyIndex:protocolContext:error:) - public func decrypt(_ ivAndCiphertext: Data, forGroupWithPublicKey groupPublicKey: String, senderPublicKey: String, keyIndex: UInt, protocolContext: Any) throws -> Data { - let transaction = protocolContext as! YapDatabaseReadWriteTransaction - return try decrypt(ivAndCiphertext, for: groupPublicKey, senderPublicKey: senderPublicKey, keyIndex: keyIndex, using: transaction) - } - - public func decrypt(_ ivAndCiphertext: Data, for groupPublicKey: String, senderPublicKey: String, keyIndex: UInt, using transaction: YapDatabaseReadWriteTransaction, 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 { - ClosedGroupsProtocol.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction) - } - throw error - } - } - let iv = ivAndCiphertext[0.. 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).. Bool { - return Storage.getUserClosedGroupPublicKeys().contains(publicKey) - } - - public func getKeyPair(forGroupWithPublicKey groupPublicKey: String) -> ECKeyPair { - let privateKey = Storage.getClosedGroupPrivateKey(for: groupPublicKey)! - return ECKeyPair(publicKey: Data(hex: groupPublicKey.removing05PrefixIfNeeded()), privateKey: Data(hex: privateKey))! - } -}