diff --git a/Podfile b/Podfile index b9868d936..552a0ab1c 100644 --- a/Podfile +++ b/Podfile @@ -16,9 +16,6 @@ def shared_pods pod 'SessionAxolotlKit', git: 'https://github.com/loki-project/session-ios-protocol-kit.git', branch: 'master', testspecs: ["Tests"] # Fork of AxolotlKit # pod 'AxolotlKit', path: '../SignalProtocolKit', testspecs: ["Tests"] - pod 'SessionHKDFKit', git: 'https://github.com/nielsandriesse/session-ios-hkdf-kit.git', testspecs: ["Tests"] # Fork of HKDFKit - # pod 'HKDFKit', path: '../HKDFKit', testspecs: ["Tests"] - pod 'SessionCurve25519Kit', git: 'https://github.com/loki-project/session-ios-curve-25519-kit', testspecs: ["Tests"] # Fork of Curve25519Kit # pod 'Curve25519Kit', path: '../Curve25519Kit', testspecs: ["Tests"] @@ -47,10 +44,6 @@ def shared_pods pod 'YapDatabase/SQLCipher', :git => 'https://github.com/signalapp/YapDatabase.git', branch: 'signal-release' # pod 'YapDatabase/SQLCipher', path: '../YapDatabase' - # Forked to incorporate our self-built binary artifact. - pod 'GRKOpenSSLFramework', git: 'https://github.com/signalapp/GRKOpenSSLFramework' - #pod 'GRKOpenSSLFramework', path: '../GRKOpenSSLFramework' - pod 'Starscream', git: 'https://github.com/signalapp/Starscream.git', branch: 'signal-release' # pod 'Starscream', path: '../Starscream' @@ -110,6 +103,14 @@ target 'SessionMessagingKit' do pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true end +target 'SessionProtocolKit' do + pod 'CocoaLumberjack', :inhibit_warnings => true + pod 'Curve25519Kit', :inhibit_warnings => true + pod 'GRKOpenSSLFramework', :inhibit_warnings => true + pod 'HKDFKit', :inhibit_warnings => true + pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true +end + target 'SessionSnodeKit' do pod 'CryptoSwift', :inhibit_warnings => true pod 'Curve25519Kit', :inhibit_warnings => true diff --git a/Podfile.lock b/Podfile.lock index 6764fdd27..47689c383 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -20,7 +20,8 @@ PODS: - CryptoSwift (1.3.2) - Curve25519Kit (2.1.0) - FeedKit (8.1.1) - - GRKOpenSSLFramework (1.0.2.12) + - GRKOpenSSLFramework (1.0.2.20) + - HKDFKit (0.0.3) - libPhoneNumber-iOS (0.9.15) - Mantle (2.1.0): - Mantle/extobjc (= 2.1.0) @@ -67,9 +68,6 @@ PODS: - SessionHKDFKit (0.0.5): - CocoaLumberjack - SessionCoreKit - - SessionHKDFKit/Tests (0.0.5): - - CocoaLumberjack - - SessionCoreKit - SessionMetadataKit (1.0.9): - CocoaLumberjack - CryptoSwift (~> 1.3) @@ -200,11 +198,13 @@ PODS: DEPENDENCIES: - AFNetworking (~> 3.2.1) + - CocoaLumberjack - CryptoSwift - CryptoSwift (~> 1.3) - Curve25519Kit - FeedKit (~> 8.1) - - GRKOpenSSLFramework (from `https://github.com/signalapp/GRKOpenSSLFramework`) + - GRKOpenSSLFramework + - HKDFKit - Mantle (from `https://github.com/signalapp/Mantle`, branch `signal-master`) - NVActivityIndicatorView (~> 4.7) - PromiseKit @@ -217,8 +217,6 @@ DEPENDENCIES: - SessionCoreKit/Tests (from `https://github.com/loki-project/session-ios-core-kit.git`) - SessionCurve25519Kit (from `https://github.com/loki-project/session-ios-curve-25519-kit`) - SessionCurve25519Kit/Tests (from `https://github.com/loki-project/session-ios-curve-25519-kit`) - - SessionHKDFKit (from `https://github.com/nielsandriesse/session-ios-hkdf-kit.git`) - - SessionHKDFKit/Tests (from `https://github.com/nielsandriesse/session-ios-hkdf-kit.git`) - SessionMetadataKit (from `https://github.com/loki-project/session-ios-metadata-kit`) - SessionMetadataKit/Tests (from `https://github.com/loki-project/session-ios-metadata-kit`) - SessionServiceKit (from `.`) @@ -239,12 +237,15 @@ SPEC REPOS: - CryptoSwift - Curve25519Kit - FeedKit + - GRKOpenSSLFramework + - HKDFKit - libPhoneNumber-iOS - NVActivityIndicatorView - PromiseKit - PureLayout - Reachability - SAMKeychain + - SessionHKDFKit - Sodium - SQLCipher - SSZipArchive @@ -252,8 +253,6 @@ SPEC REPOS: - ZXingObjC EXTERNAL SOURCES: - GRKOpenSSLFramework: - :git: https://github.com/signalapp/GRKOpenSSLFramework Mantle: :branch: signal-master :git: https://github.com/signalapp/Mantle @@ -264,8 +263,6 @@ EXTERNAL SOURCES: :git: https://github.com/loki-project/session-ios-core-kit.git SessionCurve25519Kit: :git: https://github.com/loki-project/session-ios-curve-25519-kit - SessionHKDFKit: - :git: https://github.com/nielsandriesse/session-ios-hkdf-kit.git SessionMetadataKit: :git: https://github.com/loki-project/session-ios-metadata-kit SessionServiceKit: @@ -280,9 +277,6 @@ EXTERNAL SOURCES: :git: https://github.com/signalapp/YYImage CHECKOUT OPTIONS: - GRKOpenSSLFramework: - :commit: b799c27e7927e5304ec1e4ad53c6d33c6fd1cae7 - :git: https://github.com/signalapp/GRKOpenSSLFramework Mantle: :commit: b72c2d1e6132501db906de2cffa8ded7803c54f4 :git: https://github.com/signalapp/Mantle @@ -295,9 +289,6 @@ CHECKOUT OPTIONS: SessionCurve25519Kit: :commit: c3bc075d1e1c8339eebe2af184869de1a007d855 :git: https://github.com/loki-project/session-ios-curve-25519-kit - SessionHKDFKit: - :commit: 0dcf8cf8a7995ef8663146f7063e6c1d7f5a3274 - :git: https://github.com/nielsandriesse/session-ios-hkdf-kit.git SessionMetadataKit: :commit: df787d84bb8adb23c10df669296dee8d7988e410 :git: https://github.com/loki-project/session-ios-metadata-kit @@ -317,7 +308,8 @@ SPEC CHECKSUMS: CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060 Curve25519Kit: 76d0859ecb34704f7732847812363f83b23a6a59 FeedKit: 3418eed25f0b493b205b4de1b8511ac21d413fa9 - GRKOpenSSLFramework: 8a3735ad41e7dc1daff460467bccd32ca5d6ae3e + GRKOpenSSLFramework: dc635b0a9d4cd8af2a9ff80a61e779e21b69dfd8 + HKDFKit: c058305d6f64b84f28c50bd7aa89574625bcb62a libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b NVActivityIndicatorView: d24b7ebcf80af5dcd994adb650e2b6c93379270f @@ -340,6 +332,6 @@ SPEC CHECKSUMS: YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 2834fe9a900f54e92e43b8f13c0df20409683f26 +PODFILE CHECKSUM: 821988dba010cf44893aecf26652d7da531868f0 COCOAPODS: 1.10.0.rc.1 diff --git a/Pods b/Pods index ceaefa107..24da46b65 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit ceaefa107175deb5bddd8c8556491f4ebd548046 +Subproject commit 24da46b65b9581625f18cd9dac30b310401e84f2 diff --git a/SessionMessagingKit/ControlMessage.swift b/SessionMessagingKit/ControlMessage.swift index 6b7b5b2d9..a910776f7 100644 --- a/SessionMessagingKit/ControlMessage.swift +++ b/SessionMessagingKit/ControlMessage.swift @@ -3,10 +3,12 @@ public class ControlMessage : Message { public enum Kind { - case sessionRequest + case sessionRequest(preKeyBundle: PreKeyBundle) } - func foo() { - + public override class func fromProto(_ proto: SNProtoContent) -> ControlMessage? { + if let preKeyBundle = proto.prekeyBundleMessage { + + } } } diff --git a/SessionMessagingKit/Message.swift b/SessionMessagingKit/Message.swift index d9e89eaf1..a8ce58677 100644 --- a/SessionMessagingKit/Message.swift +++ b/SessionMessagingKit/Message.swift @@ -7,6 +7,8 @@ public class Message : NSObject, NSCoding { // Not a protocol for YapDatabase co public var sentTimestamp: UInt64? public var receivedTimestamp: UInt64? + public override init() { } + public required init?(coder: NSCoder) { preconditionFailure("init?(coder:) is abstract and must be overridden.") } @@ -14,4 +16,12 @@ public class Message : NSObject, NSCoding { // Not a protocol for YapDatabase co public func encode(with coder: NSCoder) { preconditionFailure("encode(with:) is abstract and must be overridden.") } + + public class func fromProto(_ proto: SNProtoContent) -> Self? { + preconditionFailure("fromProto(_:) is abstract and must be overridden.") + } + + public func toProto() -> Any? { + preconditionFailure("toProto() is abstract and must be overridden.") + } } diff --git a/SessionMessagingKit/VisibleMessage+Contact.swift b/SessionMessagingKit/VisibleMessage+Contact.swift index f0f6ef6f6..548070cec 100644 --- a/SessionMessagingKit/VisibleMessage+Contact.swift +++ b/SessionMessagingKit/VisibleMessage+Contact.swift @@ -2,7 +2,7 @@ public extension VisibleMessage { @objc(SNContact) - public class Contact : NSObject, NSCoding { + class Contact : NSObject, NSCoding { public required init?(coder: NSCoder) { fatalError("Not implemented.") diff --git a/SessionMessagingKit/VisibleMessage+LinkPreview.swift b/SessionMessagingKit/VisibleMessage+LinkPreview.swift index 34f843ae2..68c931510 100644 --- a/SessionMessagingKit/VisibleMessage+LinkPreview.swift +++ b/SessionMessagingKit/VisibleMessage+LinkPreview.swift @@ -2,7 +2,7 @@ public extension VisibleMessage { @objc(SNLinkPreview) - public class LinkPreview : NSObject, NSCoding { + class LinkPreview : NSObject, NSCoding { public required init?(coder: NSCoder) { fatalError("Not implemented.") diff --git a/SessionMessagingKit/VisibleMessage+Quote.swift b/SessionMessagingKit/VisibleMessage+Quote.swift index ca467773b..e50eddd59 100644 --- a/SessionMessagingKit/VisibleMessage+Quote.swift +++ b/SessionMessagingKit/VisibleMessage+Quote.swift @@ -2,7 +2,7 @@ public extension VisibleMessage { @objc(SNQuote) - public class Quote : NSObject, NSCoding { + class Quote : NSObject, NSCoding { public required init?(coder: NSCoder) { fatalError("Not implemented.") diff --git a/SessionMessagingKit/VisibleMessage.swift b/SessionMessagingKit/VisibleMessage.swift index 3bf3754cd..d30c4427a 100644 --- a/SessionMessagingKit/VisibleMessage.swift +++ b/SessionMessagingKit/VisibleMessage.swift @@ -7,4 +7,11 @@ public final class VisibleMessage : Message { public var linkPreview: LinkPreview? public var contact: Contact? + public override class func fromProto(_ proto: SNProtoContent) -> VisibleMessage? { + guard let data = proto.dataMessage, + let text = data.body else { return nil } + let result = VisibleMessage() + result.text = text + return result + } } diff --git a/SessionProtocolKit/AxolotlExceptions.h b/SessionProtocolKit/AxolotlExceptions.h new file mode 100644 index 000000000..a93421cbb --- /dev/null +++ b/SessionProtocolKit/AxolotlExceptions.h @@ -0,0 +1,72 @@ +// +// AxolotlExceptions.h +// AxolotlKit +// +// Created by Frederic Jacobs on 23/07/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#ifndef AxolotlKit_AxolotlExceptions_h +#define AxolotlKit_AxolotlExceptions_h + +/** + * Thrown when the user is: + + 1) Sending a message with a PreKeyBundle that contains a different identity key than the previously known one. + 2) Receiving a new PreKeyWhisperMessage that has a different identity key than the previously known one. + */ + +static NSString *UntrustedIdentityKeyException = @"AxolotlUnstrustedIdentityKeyException"; + +/** + * Thrown thrown when a message is received with an unknown PreKeyID. + */ + +static NSString *InvalidKeyIdException = @"AxolotlInvalidKeyIdException"; + +/** + * Thrown when: + + 1) Signature of Prekeys are not correctly signed. + 2) We received a key type that is not compatible with this version. (All keys should be Curve25519). + */ + +static NSString *InvalidKeyException = @"AxolotlInvalidKeyException"; + +/** + * Thrown when receiving a message with no associated session for decryption. + */ + +static NSString *NoSessionException = @"AxolotlNoSessionException"; + +/** + * Thrown when receiving a malformatted message. + */ + +static NSString *InvalidMessageException = @"AxolotlInvalidMessageException"; + +/** + * Thrown when experiencing issues encrypting/decrypting a message symetrically. + */ + +static NSString *CipherException = @"AxolotlCipherIssue"; + +/** + * Thrown when detecting a message being sent a second time. (Replay attacks/bugs) + */ + +static NSString *DuplicateMessageException = @"AxolotlDuplicateMessage"; + +/** + * Thrown when receiving a message send with a non-supported version of the TextSecure protocol. + */ + +static NSString *LegacyMessageException = @"AxolotlLegacyMessageException"; + +/** + * Thrown when a client tries to initiate a session with a non-supported version. + */ + +static NSString *InvalidVersionException = @"AxolotlInvalidVersionException"; + +#endif diff --git a/SessionProtocolKit/CipherMessage/CipherMessage.h b/SessionProtocolKit/CipherMessage/CipherMessage.h new file mode 100644 index 000000000..2b0d85415 --- /dev/null +++ b/SessionProtocolKit/CipherMessage/CipherMessage.h @@ -0,0 +1,24 @@ +// +// CipherMessage.h +// AxolotlKit +// +// Created by Frederic Jacobs on 26/10/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import + +typedef NS_ENUM(NSUInteger, CipherMessageType) { + CipherMessageType_Prekey = 0, + CipherMessageType_Whisper, + CipherMessageType_Fallback = 3, + CipherMessageType_ClosedGroupCiphertext = 4 +}; + +@protocol CipherMessage + +- (NSData *)serialized; + +@property (nonatomic, readonly) CipherMessageType cipherMessageType; + +@end diff --git a/SessionProtocolKit/CipherMessage/ClosedGroupCiphertextMessage.h b/SessionProtocolKit/CipherMessage/ClosedGroupCiphertextMessage.h new file mode 100644 index 000000000..a145c288c --- /dev/null +++ b/SessionProtocolKit/CipherMessage/ClosedGroupCiphertextMessage.h @@ -0,0 +1,18 @@ +#import "CipherMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ClosedGroupCiphertextMessage : NSObject + +@property (nonatomic, readonly) NSData *serialized; +@property (nonatomic, readonly) NSData *ivAndCiphertext; +@property (nonatomic, readonly) NSData *senderPublicKey; +@property (nonatomic, readonly) uint32_t keyIndex; + +- (instancetype)init_throws_withIVAndCiphertext:(NSData *)ivAndCiphertext senderPublicKey:(NSData *)senderPublicKey keyIndex:(uint32_t)keyIndex; +- (instancetype)init_throws_withData:(NSData *)serialized; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/SessionProtocolKit/CipherMessage/ClosedGroupCiphertextMessage.m b/SessionProtocolKit/CipherMessage/ClosedGroupCiphertextMessage.m new file mode 100644 index 000000000..9ad1b9c42 --- /dev/null +++ b/SessionProtocolKit/CipherMessage/ClosedGroupCiphertextMessage.m @@ -0,0 +1,60 @@ +#import "ClosedGroupCiphertextMessage.h" +#import "AxolotlExceptions.h" +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@implementation ClosedGroupCiphertextMessage + +- (instancetype)init_throws_withIVAndCiphertext:(NSData *)ivAndCiphertext senderPublicKey:(NSData *)senderPublicKey keyIndex:(uint32_t)keyIndex +{ + if (self = [super init]) { + _ivAndCiphertext = ivAndCiphertext; + _senderPublicKey = senderPublicKey; + _keyIndex = keyIndex; + + SPKProtoClosedGroupCiphertextMessageBuilder *builder = [SPKProtoClosedGroupCiphertextMessage builderWithCiphertext:ivAndCiphertext + senderPublicKey:senderPublicKey + keyIndex:keyIndex]; + + NSError *error; + NSData *_Nullable serialized = [builder buildSerializedDataAndReturnError:&error]; + if (serialized == nil || error != nil) { + OWSFailDebug(@"Couldn't serialize proto due to error: %@.", error); + OWSRaiseException(InvalidMessageException, @"Couldn't serialize proto."); + } + + _serialized = serialized; + } + + return self; +} + +- (instancetype)init_throws_withData:(NSData *)serialized +{ + if (self = [super init]) { + NSError *error; + SPKProtoClosedGroupCiphertextMessage *_Nullable ciphertextMessage = [SPKProtoClosedGroupCiphertextMessage parseData:serialized error:&error]; + if (ciphertextMessage == nil || error != nil) { + OWSFailDebug(@"Couldn't parse proto due to error: %@.", error); + OWSRaiseException(InvalidMessageException, @"Couldn't parse proto."); + } + + _serialized = serialized; + _ivAndCiphertext = ciphertextMessage.ciphertext; + _senderPublicKey = ciphertextMessage.senderPublicKey; + _keyIndex = ciphertextMessage.keyIndex; + } + + return self; +} + +- (CipherMessageType)cipherMessageType +{ + return CipherMessageType_ClosedGroupCiphertext; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/CipherMessage/FallbackMessage.h b/SessionProtocolKit/CipherMessage/FallbackMessage.h new file mode 100644 index 000000000..d55c14bf0 --- /dev/null +++ b/SessionProtocolKit/CipherMessage/FallbackMessage.h @@ -0,0 +1,13 @@ +#import "CipherMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FallbackMessage : NSObject + +@property (nonatomic, readonly) NSData *serialized; + +- (instancetype)init_throws_withData:(NSData *)serialized; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/CipherMessage/FallbackMessage.m b/SessionProtocolKit/CipherMessage/FallbackMessage.m new file mode 100644 index 000000000..87d1f2ae2 --- /dev/null +++ b/SessionProtocolKit/CipherMessage/FallbackMessage.m @@ -0,0 +1,18 @@ +#import "FallbackMessage.h" + +@implementation FallbackMessage + +- (instancetype)init_throws_withData:(NSData *)serialized +{ + if (self = [super init]) { + _serialized = serialized; + } + return self; +} + +- (CipherMessageType)cipherMessageType +{ + return CipherMessageType_Fallback; +} + +@end diff --git a/SessionProtocolKit/CipherMessage/PreKeyWhisperMessage.h b/SessionProtocolKit/CipherMessage/PreKeyWhisperMessage.h new file mode 100644 index 000000000..d64aaa289 --- /dev/null +++ b/SessionProtocolKit/CipherMessage/PreKeyWhisperMessage.h @@ -0,0 +1,32 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "WhisperMessage.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface PreKeyWhisperMessage : NSObject + +- (instancetype)init_throws_withData:(NSData *)serialized NS_SWIFT_UNAVAILABLE("throws objc exceptions"); +- (nullable instancetype)initWithData:(NSData *)serialized error:(NSError **)outError; + +- (instancetype)init_throws_withWhisperMessage:(WhisperMessage *)whisperMessage + registrationId:(int)registrationId + prekeyId:(int)prekeyId + signedPrekeyId:(int)signedPrekeyId + baseKey:(NSData *)baseKey + identityKey:(NSData *)identityKey NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +@property (nonatomic, readonly) int registrationId; +@property (nonatomic, readonly) int version; +@property (nonatomic, readonly) int prekeyID; +@property (nonatomic, readonly) int signedPrekeyId; +@property (nonatomic, readonly) NSData *baseKey; +@property (nonatomic, readonly) NSData *identityKey; +@property (nonatomic, readonly) WhisperMessage *message; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/CipherMessage/PreKeyWhisperMessage.m b/SessionProtocolKit/CipherMessage/PreKeyWhisperMessage.m new file mode 100644 index 000000000..69b78a856 --- /dev/null +++ b/SessionProtocolKit/CipherMessage/PreKeyWhisperMessage.m @@ -0,0 +1,149 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "PreKeyWhisperMessage.h" +#import "AxolotlExceptions.h" +#import "Constants.h" +#import "SerializationUtilities.h" +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface PreKeyWhisperMessage () + +@property (nonatomic, readwrite) NSData *identityKey; +@property (nonatomic, readwrite) NSData *baseKey; +@property (nonatomic, readwrite) NSData *serialized; + +@end + +#pragma mark - + +@implementation PreKeyWhisperMessage + +- (instancetype)init_throws_withWhisperMessage:(WhisperMessage *)whisperMessage + registrationId:(int)registrationId + prekeyId:(int)prekeyId + signedPrekeyId:(int)signedPrekeyId + baseKey:(NSData *)baseKey + identityKey:(NSData *)identityKey +{ + OWSAssert(whisperMessage); + OWSAssert(baseKey); + OWSAssert(identityKey); + + if (self = [super init]) { + _registrationId = registrationId; + _version = whisperMessage.version; + _prekeyID = prekeyId; + _signedPrekeyId = signedPrekeyId; + _baseKey = baseKey; + _identityKey = identityKey; + _message = whisperMessage; + + SPKProtoTSProtoPreKeyWhisperMessageBuilder *messageBuilder = [SPKProtoTSProtoPreKeyWhisperMessage builderWithSignedPreKeyID:signedPrekeyId + baseKey:baseKey + identityKey:identityKey + message:whisperMessage.serialized]; + [messageBuilder setRegistrationID:registrationId]; + + if (prekeyId != -1) { + [messageBuilder setPreKeyID:prekeyId]; + } + + Byte versionByte = [SerializationUtilities intsToByteHigh:_version low:CURRENT_VERSION]; + NSMutableData *serialized = [NSMutableData dataWithBytes:&versionByte length:1]; + + NSError *error; + NSData *_Nullable messageData = [messageBuilder buildSerializedDataAndReturnError:&error]; + if (!messageData || error) { + OWSFailDebug(@"Could not serialize proto: %@.", error); + OWSRaiseException(InvalidMessageException, @"Could not serialize proto."); + } + [serialized appendData:messageData]; + + _serialized = [serialized copy]; + } + + return self; +} + +- (nullable instancetype)initWithData:(NSData *)serialized error:(NSError **)outError +{ + @try { + self = [self init_throws_withData:serialized]; + return self; + } @catch (NSException *exception) { + *outError = SCKExceptionWrapperErrorMake(exception); + return nil; + } +} + +- (instancetype)init_throws_withData:(NSData *)serialized +{ + if (self = [super init]) { + if (serialized.length < 1) { + OWSFailDebug(@"Empty data"); + OWSRaiseException(InvalidMessageException, @"Empty data"); + } + + Byte version; + [serialized getBytes:&version length:1]; + _version = [SerializationUtilities highBitsToIntFromByte:version]; + + if (_version > CURRENT_VERSION && _version < MINIMUM_SUPPORTED_VERSION) { + @throw [NSException exceptionWithName:InvalidVersionException + reason:@"Unknown version" + userInfo:@{ @"version" : [NSNumber numberWithInt:_version] }]; + } + + NSUInteger messageDataLength; + ows_sub_overflow(serialized.length, 1, &messageDataLength); + NSData *messageData = [serialized subdataWithRange:NSMakeRange(1, messageDataLength)]; + + NSError *error; + SPKProtoTSProtoPreKeyWhisperMessage *_Nullable preKeyWhisperMessage = + [SPKProtoTSProtoPreKeyWhisperMessage parseData:messageData error:&error]; + if (!preKeyWhisperMessage || error) { + OWSFailDebug(@"Could not parse proto: %@.", error); + OWSRaiseException(InvalidMessageException, @"Could not parse proto."); + } + + _serialized = serialized; + _registrationId = preKeyWhisperMessage.registrationID; + + // This method is called when decrypting a received PreKeyMessage, but to be symmetrical with + // encrypting a PreKeyWhisperMessage before sending, we use "-1" to indicate *no* unsigned prekey was + // included. + _prekeyID = preKeyWhisperMessage.hasPreKeyID ? preKeyWhisperMessage.preKeyID : -1; + _signedPrekeyId = preKeyWhisperMessage.signedPreKeyID; + _baseKey = preKeyWhisperMessage.baseKey; + _identityKey = preKeyWhisperMessage.identityKey; + _message = [[WhisperMessage alloc] init_throws_withData:preKeyWhisperMessage.message]; + } + + return self; +} + +- (CipherMessageType)cipherMessageType { + return CipherMessageType_Prekey; +} + +#pragma mark - Logging + ++ (NSString *)logTag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)logTag +{ + return self.class.logTag; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/CipherMessage/WhisperMessage.h b/SessionProtocolKit/CipherMessage/WhisperMessage.h new file mode 100644 index 000000000..7ab354448 --- /dev/null +++ b/SessionProtocolKit/CipherMessage/WhisperMessage.h @@ -0,0 +1,40 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "CipherMessage.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@class ECKeyPair; + +@interface WhisperMessage : NSObject + +@property (nonatomic, readonly) int version; +@property (nonatomic, readonly) NSData *senderRatchetKey; +@property (nonatomic, readonly) int previousCounter; +@property (nonatomic, readonly) int counter; +@property (nonatomic, readonly) NSData *cipherText; +@property (nonatomic, readonly) NSData *serialized; + +- (instancetype)init_throws_withData:(NSData *)serialized NS_SWIFT_UNAVAILABLE("throws objc exceptions"); +- (nullable instancetype)initWithData:(NSData *)serialized error:(NSError **)outError; + +- (instancetype)init_throws_withVersion:(int)version + macKey:(NSData *)macKey + senderRatchetKey:(NSData *)senderRatchetKey + counter:(int)counter + previousCounter:(int)previousCounter + cipherText:(NSData *)cipherText + senderIdentityKey:(NSData *)senderIdentityKey + receiverIdentityKey:(NSData *)receiverIdentityKey NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +- (void)throws_verifyMacWithVersion:(int)messageVersion + senderIdentityKey:(NSData *)senderIdentityKey + receiverIdentityKey:(NSData *)receiverIdentityKey + macKey:(NSData *)macKey NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/CipherMessage/WhisperMessage.m b/SessionProtocolKit/CipherMessage/WhisperMessage.m new file mode 100644 index 000000000..1489ccecf --- /dev/null +++ b/SessionProtocolKit/CipherMessage/WhisperMessage.m @@ -0,0 +1,201 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "WhisperMessage.h" +#import "AxolotlExceptions.h" +#import "Constants.h" +#import "NSData+keyVersionByte.h" +#import "SerializationUtilities.h" +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +#define VERSION_LENGTH 1 + +@implementation WhisperMessage + +- (instancetype)init_throws_withVersion:(int)version + macKey:(NSData *)macKey + senderRatchetKey:(NSData *)senderRatchetKey + counter:(int)counter + previousCounter:(int)previousCounter + cipherText:(NSData *)cipherText + senderIdentityKey:(NSData *)senderIdentityKey + receiverIdentityKey:(NSData *)receiverIdentityKey +{ + OWSAssert(macKey); + OWSAssert(senderRatchetKey); + OWSAssert(cipherText); + OWSAssert(cipherText); + OWSAssert(senderIdentityKey); + OWSAssert(receiverIdentityKey); + + if (self = [super init]) { + Byte versionByte = [SerializationUtilities intsToByteHigh:version low:CURRENT_VERSION]; + NSMutableData *serialized = [NSMutableData dataWithBytes:&versionByte length:1]; + + SPKProtoTSProtoWhisperMessageBuilder *messageBuilder = [SPKProtoTSProtoWhisperMessage builderWithRatchetKey:senderRatchetKey + counter:counter + ciphertext:cipherText]; + [messageBuilder setPreviousCounter:previousCounter]; + NSError *error; + NSData *_Nullable messageData = [messageBuilder buildSerializedDataAndReturnError:&error]; + if (!messageData || error) { + OWSFailDebug(@"Could not serialize proto: %@.", error); + OWSRaiseException(InvalidMessageException, @"Could not serialize proto."); + } + [serialized appendData:messageData]; + + NSData *mac = [SerializationUtilities throws_macWithVersion:version + identityKey:[senderIdentityKey prependKeyType] + receiverIdentityKey:[receiverIdentityKey prependKeyType] + macKey:macKey + serialized:serialized]; + + [serialized appendData:mac]; + + _version = version; + _senderRatchetKey = senderRatchetKey; + _previousCounter = previousCounter; + _counter = counter; + _cipherText = cipherText; + _serialized = [serialized copy]; + } + + return self; +} + +- (nullable instancetype)initWithData:(NSData *)serialized error:(NSError **)outError +{ + @try { + self = [self init_throws_withData:serialized]; + return self; + } @catch (NSException *exception) { + *outError = SCKExceptionWrapperErrorMake(exception); + return nil; + } +} + +- (instancetype)init_throws_withData:(NSData *)serialized +{ + if (self = [super init]) { + if (serialized.length <= (VERSION_LENGTH + MAC_LENGTH)) { + @throw [NSException exceptionWithName:InvalidMessageException + reason:@"Message size is too short to have content" + userInfo:@{}]; + } + + Byte version; + [serialized getBytes:&version length:VERSION_LENGTH]; + + NSUInteger messageAndMacLength; + ows_sub_overflow(serialized.length, VERSION_LENGTH, &messageAndMacLength); + NSData *messageAndMac = [serialized subdataWithRange:NSMakeRange(VERSION_LENGTH, messageAndMacLength)]; + + NSUInteger messageLength; + ows_sub_overflow(messageAndMac.length, MAC_LENGTH, &messageLength); + NSData *messageData = [messageAndMac subdataWithRange:NSMakeRange(0, messageLength)]; + + if ([SerializationUtilities highBitsToIntFromByte:version] < MINIMUM_SUPPORTED_VERSION) { + @throw [NSException + exceptionWithName:LegacyMessageException + reason:@"Message was sent with an unsupported version of the TextSecure protocol." + userInfo:@{}]; + } + + if ([SerializationUtilities highBitsToIntFromByte:version] > CURRENT_VERSION) { + @throw [NSException exceptionWithName:InvalidMessageException + reason:@"Unknown Version" + userInfo:@{ + @"Version" : [NSNumber + numberWithChar:[SerializationUtilities highBitsToIntFromByte:version]] + }]; + } + + NSError *error; + SPKProtoTSProtoWhisperMessage *_Nullable whisperMessage = + [SPKProtoTSProtoWhisperMessage parseData:messageData error:&error]; + if (!whisperMessage || error) { + OWSFailDebug(@"Could not parse proto: %@.", error); + OWSRaiseException(InvalidMessageException, @"Could not parse proto."); + } + + _serialized = serialized; + _senderRatchetKey = [whisperMessage.ratchetKey throws_removeKeyType]; + _version = [SerializationUtilities highBitsToIntFromByte:version]; + _counter = whisperMessage.counter; + _previousCounter = whisperMessage.previousCounter; + _cipherText = whisperMessage.ciphertext; + } + + return self; +} + +- (void)throws_verifyMacWithVersion:(int)messageVersion + senderIdentityKey:(NSData *)senderIdentityKey + receiverIdentityKey:(NSData *)receiverIdentityKey + macKey:(NSData *)macKey +{ + OWSAssert(senderIdentityKey); + OWSAssert(receiverIdentityKey); + OWSAssert(macKey); + + OWSDataParser *dataParser = [[OWSDataParser alloc] initWithData:self.serialized]; + NSError *error; + + NSUInteger messageLength; + if (__builtin_sub_overflow(self.serialized.length, MAC_LENGTH, &messageLength)) { + OWSFailDebug(@"Data too short"); + OWSRaiseException(InvalidMessageException, @"Data too short"); + } + NSData *_Nullable data = [dataParser nextDataWithLength:messageLength + name:@"message data" + error:&error]; + if (!data || error) { + OWSFailDebug(@"Could not parse data: %@.", error); + OWSRaiseException(InvalidMessageException, @"Could not parse data."); + } + NSData *_Nullable theirMac = [dataParser nextDataWithLength:MAC_LENGTH + name:@"mac data" + error:&error]; + if (!theirMac || error) { + OWSFailDebug(@"Could not parse their mac: %@.", error); + OWSRaiseException(InvalidMessageException, @"Could not parse their mac."); + } + + NSData *ourMac = [SerializationUtilities throws_macWithVersion:messageVersion + identityKey:[senderIdentityKey prependKeyType] + receiverIdentityKey:[receiverIdentityKey prependKeyType] + macKey:macKey + serialized:data]; + + if (![theirMac ows_constantTimeIsEqualToData:ourMac]) { + OWSFailDebug(@"Bad Mac! Their Mac: %@ Our Mac: %@", theirMac, ourMac); + OWSRaiseException(InvalidMessageException, @"Bad Mac!"); + } +} + +- (CipherMessageType)cipherMessageType { + return CipherMessageType_Whisper; +} + +#pragma mark - Logging + ++ (NSString *)logTag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)logTag +{ + return self.class.logTag; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Constants.h b/SessionProtocolKit/Constants.h new file mode 100644 index 000000000..edc957b3e --- /dev/null +++ b/SessionProtocolKit/Constants.h @@ -0,0 +1,15 @@ +// +// Constants.h +// AxolotlKit +// +// Created by Frederic Jacobs on 14/10/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#ifndef AxolotlKit_Constants_h +#define AxolotlKit_Constants_h + +#define MINIMUM_SUPPORTED_VERSION 3 +#define CURRENT_VERSION 3 + +#endif diff --git a/SessionProtocolKit/Crypto/AES-CBC.h b/SessionProtocolKit/Crypto/AES-CBC.h new file mode 100644 index 000000000..7862f0915 --- /dev/null +++ b/SessionProtocolKit/Crypto/AES-CBC.h @@ -0,0 +1,41 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AES_CBC : NSObject + +/** + * Encrypts with AES in CBC mode + * + * @param data data to encrypt + * @param key AES key + * @param iv Initialization vector for CBC + * + * @return ciphertext + */ + ++ (NSData *)throws_encryptCBCMode:(NSData *)data + withKey:(NSData *)key + withIV:(NSData *)iv NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +/** + * Decrypts with AES in CBC mode + * + * @param data data to decrypt + * @param key AES key + * @param iv Initialization vector for CBC + * + * @return plaintext + */ + ++ (NSData *)throws_decryptCBCMode:(NSData *)data + withKey:(NSData *)key + withIV:(NSData *)iv NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Crypto/AES-CBC.m b/SessionProtocolKit/Crypto/AES-CBC.m new file mode 100644 index 000000000..84612ff47 --- /dev/null +++ b/SessionProtocolKit/Crypto/AES-CBC.m @@ -0,0 +1,105 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "AES-CBC.h" +#import "AxolotlExceptions.h" +#import "MessageKeys.h" +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@implementation AES_CBC + +#pragma mark AESCBC Mode + ++ (NSData *)throws_encryptCBCMode:(NSData *)data withKey:(NSData *)key withIV:(NSData *)iv +{ + if (!data) { + @throw [NSException exceptionWithName:CipherException reason:@"Missing data to encrypt." userInfo:nil]; + } + if (data.length >= SIZE_MAX - kCCBlockSizeAES128) { + @throw [NSException exceptionWithName:CipherException reason:@"Oversize data." userInfo:nil]; + } + if (key.length != 32) { + @throw [NSException exceptionWithName:CipherException reason:@"AES key should be 256 bits." userInfo:nil]; + } + if (iv.length != 16) { + @throw [NSException exceptionWithName:CipherException reason:@"AES-CBC IV should be 128 bits." userInfo:nil]; + } + + size_t bufferSize; + ows_add_overflow(data.length, kCCBlockSizeAES128, &bufferSize); + NSMutableData *_Nullable bufferData = [NSMutableData dataWithLength:bufferSize]; + OWSAssert(bufferData != nil); + + size_t bytesEncrypted = 0; + CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, + kCCAlgorithmAES128, + kCCOptionPKCS7Padding, + [key bytes], + [key length], + [iv bytes], + [data bytes], + [data length], + bufferData.mutableBytes, + bufferSize, + &bytesEncrypted); + + if (cryptStatus == kCCSuccess) { + return [bufferData subdataWithRange:NSMakeRange(0, bytesEncrypted)]; + } else { + @throw [NSException exceptionWithName:CipherException + reason:@"We encountered an issue while encrypting." + userInfo:nil]; + } +} + ++ (NSData *)throws_decryptCBCMode:(NSData *)data withKey:(NSData *)key withIV:(NSData *)iv +{ + if (!data) { + @throw [NSException exceptionWithName:CipherException reason:@"Missing data to decrypt." userInfo:nil]; + } + if (data.length >= SIZE_MAX - kCCBlockSizeAES128) { + @throw [NSException exceptionWithName:CipherException reason:@"Oversize data." userInfo:nil]; + } + if (key.length != 32) { + @throw [NSException exceptionWithName:CipherException reason:@"AES key should be 256 bits." userInfo:nil]; + } + if (iv.length != 16) { + @throw [NSException exceptionWithName:CipherException reason:@"AES-CBC IV should be 128 bits." userInfo:nil]; + } + + size_t bufferSize; + ows_add_overflow(data.length, kCCBlockSizeAES128, &bufferSize); + NSMutableData *_Nullable bufferData = [NSMutableData dataWithLength:bufferSize]; + OWSAssert(bufferData != nil); + + size_t bytesDecrypted = 0; + CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, + kCCAlgorithmAES128, + kCCOptionPKCS7Padding, + [key bytes], + [key length], + [iv bytes], + [data bytes], + [data length], + bufferData.mutableBytes, + bufferSize, + &bytesDecrypted); + + if (cryptStatus == kCCSuccess) { + return [bufferData subdataWithRange:NSMakeRange(0, bytesDecrypted)]; + } else { + @throw [NSException exceptionWithName:CipherException + reason:@"We encountered an issue while decrypting." + userInfo:nil]; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Meta/Info.plist b/SessionProtocolKit/Meta/Info.plist new file mode 100644 index 000000000..9bcb24442 --- /dev/null +++ b/SessionProtocolKit/Meta/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/SessionProtocolKit/Meta/SessionProtocolKit.h b/SessionProtocolKit/Meta/SessionProtocolKit.h new file mode 100644 index 000000000..737bbbafd --- /dev/null +++ b/SessionProtocolKit/Meta/SessionProtocolKit.h @@ -0,0 +1,12 @@ +#import + +FOUNDATION_EXPORT double SessionProtocolKitVersionNumber; +FOUNDATION_EXPORT const unsigned char SessionProtocolKitVersionString[]; + +#import +#import +#import +#import +#import +#import +#import diff --git a/SessionProtocolKit/Prekeys/PreKeyBundle.h b/SessionProtocolKit/Prekeys/PreKeyBundle.h new file mode 100644 index 000000000..b81791123 --- /dev/null +++ b/SessionProtocolKit/Prekeys/PreKeyBundle.h @@ -0,0 +1,31 @@ +// +// AxolotlKeyFetch.h +// AxolotlKit +// +// Created by Frederic Jacobs on 21/07/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import + +@interface PreKeyBundle : NSObject + +@property (nonatomic, readonly) NSData *identityKey; +@property (nonatomic, readonly) int registrationId; +@property (nonatomic, readonly) int deviceId; +@property (nonatomic, readonly) NSData *signedPreKeyPublic; +@property (nonatomic, readonly) NSData *preKeyPublic; +@property (nonatomic, readonly) int preKeyId; +@property (nonatomic, readonly) int signedPreKeyId; +@property (nonatomic, readonly) NSData *signedPreKeySignature; + +- (nullable instancetype)initWithRegistrationId:(int)registrationId + deviceId:(int)deviceId + preKeyId:(int)preKeyId + preKeyPublic:(NSData *)preKeyPublic + signedPreKeyPublic:(NSData *)signedPreKeyPublic + signedPreKeyId:(int)signedPreKeyId + signedPreKeySignature:(NSData *)signedPreKeySignature + identityKey:(NSData *)identityKey; + +@end diff --git a/SessionProtocolKit/Prekeys/PreKeyBundle.m b/SessionProtocolKit/Prekeys/PreKeyBundle.m new file mode 100644 index 000000000..d45cb2e8b --- /dev/null +++ b/SessionProtocolKit/Prekeys/PreKeyBundle.m @@ -0,0 +1,106 @@ +// +// AxolotlKeyFetch.m +// AxolotlKit +// +// Created by Frederic Jacobs on 21/07/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import "PreKeyBundle.h" +#import + + +static NSString* const kCoderPKBIdentityKey = @"kCoderPKBIdentityKey"; +static NSString* const kCoderPKBregistrationId = @"kCoderPKBregistrationId"; +static NSString* const kCoderPKBdeviceId = @"kCoderPKBdeviceId"; +static NSString* const kCoderPKBsignedPreKeyPublic = @"kCoderPKBsignedPreKeyPublic"; +static NSString* const kCoderPKBpreKeyPublic = @"kCoderPKBpreKeyPublic"; +static NSString* const kCoderPKBpreKeyId = @"kCoderPKBpreKeyId"; +static NSString* const kCoderPKBsignedPreKeyId = @"kCoderPKBsignedPreKeyId"; +static NSString* const kCoderPKBsignedPreKeySignature = @"kCoderPKBsignedPreKeySignature"; + +@implementation PreKeyBundle + +- (nullable instancetype)initWithRegistrationId:(int)registrationId + deviceId:(int)deviceId + preKeyId:(int)preKeyId + preKeyPublic:(NSData *)preKeyPublic + signedPreKeyPublic:(NSData *)signedPreKeyPublic + signedPreKeyId:(int)signedPreKeyId + signedPreKeySignature:(NSData *)signedPreKeySignature + identityKey:(NSData *)identityKey +{ + if (preKeyPublic && preKeyPublic.length != 33) { + OWSFailDebug(@"preKeyPublic && preKeyPublic.length != 33"); + return nil; + } + if (signedPreKeyPublic.length != 33) { + OWSFailDebug(@"signedPreKeyPublic.length != 33"); + return nil; + } + if (!signedPreKeySignature) { + OWSFailDebug(@"!signedPreKeySignature"); + return nil; + } + if (identityKey.length != 33) { + OWSFailDebug(@"identityKey.length != 33"); + return nil; + } + + self = [super init]; + + if (self) { + _identityKey = identityKey; + _registrationId = registrationId; + _deviceId = deviceId; + _preKeyPublic = preKeyPublic; + _preKeyId = preKeyId; + _signedPreKeyPublic = signedPreKeyPublic; + _signedPreKeyId = signedPreKeyId; + _signedPreKeySignature = signedPreKeySignature; + } + + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder{ + int registrationId = [aDecoder decodeIntForKey:kCoderPKBregistrationId]; + int deviceId = [aDecoder decodeIntForKey:kCoderPKBdeviceId]; + int preKeyId = [aDecoder decodeIntForKey:kCoderPKBpreKeyId]; + int signedPreKeyId = [aDecoder decodeIntForKey:kCoderPKBsignedPreKeyId]; + + NSData *preKeyPublic = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderPKBpreKeyPublic]; + NSData *signedPreKeyPublic = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderPKBsignedPreKeyPublic]; + NSData *signedPreKeySignature = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderPKBsignedPreKeySignature]; + NSData *identityKey = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderPKBIdentityKey]; + + + self = [self initWithRegistrationId:registrationId + deviceId:deviceId + preKeyId:preKeyId + preKeyPublic:preKeyPublic + signedPreKeyPublic:signedPreKeyPublic + signedPreKeyId:signedPreKeyId + signedPreKeySignature:signedPreKeySignature + identityKey:identityKey]; + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder{ + [aCoder encodeInt:_registrationId forKey:kCoderPKBregistrationId]; + [aCoder encodeInt:_deviceId forKey:kCoderPKBdeviceId]; + [aCoder encodeInt:_preKeyId forKey:kCoderPKBpreKeyId]; + [aCoder encodeInt:_signedPreKeyId forKey:kCoderPKBsignedPreKeyId]; + + [aCoder encodeObject:_preKeyPublic forKey:kCoderPKBpreKeyPublic]; + [aCoder encodeObject:_signedPreKeyPublic forKey:kCoderPKBsignedPreKeyPublic]; + [aCoder encodeObject:_signedPreKeySignature forKey:kCoderPKBsignedPreKeySignature]; + [aCoder encodeObject:_identityKey forKey:kCoderPKBIdentityKey]; +} + ++(BOOL)supportsSecureCoding{ + return YES; +} + +@end diff --git a/SessionProtocolKit/Prekeys/PreKeyRecord.h b/SessionProtocolKit/Prekeys/PreKeyRecord.h new file mode 100644 index 000000000..ddd58647d --- /dev/null +++ b/SessionProtocolKit/Prekeys/PreKeyRecord.h @@ -0,0 +1,16 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import +#import + + +@interface PreKeyRecord : NSObject + +@property (nonatomic, readonly) int Id; +@property (nonatomic, readonly) ECKeyPair *keyPair; + +- (instancetype)initWithId:(int)identifier keyPair:(ECKeyPair*)keyPair; + +@end diff --git a/SessionProtocolKit/Prekeys/PreKeyRecord.m b/SessionProtocolKit/Prekeys/PreKeyRecord.m new file mode 100644 index 000000000..7ee04c837 --- /dev/null +++ b/SessionProtocolKit/Prekeys/PreKeyRecord.m @@ -0,0 +1,45 @@ +// +// PreKeyRecord.m +// AxolotlKit +// +// Created by Frederic Jacobs on 26/07/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import "PreKeyRecord.h" +#import + +static NSString* const kCoderPreKeyId = @"kCoderPreKeyId"; +static NSString* const kCoderPreKeyPair = @"kCoderPreKeyPair"; + +@implementation PreKeyRecord + ++ (BOOL)supportsSecureCoding{ + return YES; +} + +- (instancetype)initWithId:(int)identifier keyPair:(ECKeyPair*)keyPair{ + OWSAssert(keyPair); + + self = [super init]; + + if (self) { + _Id = identifier; + _keyPair = keyPair; + } + + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder{ + return [self initWithId:[aDecoder decodeIntForKey:kCoderPreKeyId] keyPair:[aDecoder decodeObjectOfClass:[ECKeyPair class] forKey:kCoderPreKeyPair]]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder{ + [aCoder encodeInteger:_Id forKey:kCoderPreKeyId]; + [aCoder encodeObject:_keyPair forKey:kCoderPreKeyPair]; +} + + + +@end diff --git a/SessionProtocolKit/Prekeys/SignedPrekeyRecord.h b/SessionProtocolKit/Prekeys/SignedPrekeyRecord.h new file mode 100644 index 000000000..6bf27ce39 --- /dev/null +++ b/SessionProtocolKit/Prekeys/SignedPrekeyRecord.h @@ -0,0 +1,21 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import +#import "PreKeyRecord.h" +#import + +@interface SignedPreKeyRecord : PreKeyRecord + +@property (nonatomic, readonly) NSData *signature; +@property (nonatomic, readonly) NSDate *generatedAt; +// Defaults to NO. Should only be set after the service accepts this record. +@property (nonatomic, readonly) BOOL wasAcceptedByService; + +- (instancetype)initWithId:(int)identifier keyPair:(ECKeyPair *)keyPair signature:(NSData*)signature generatedAt:(NSDate*)generatedAt; +- (instancetype)initWithId:(int)identifier keyPair:(ECKeyPair *)keyPair NS_UNAVAILABLE; + +- (void)markAsAcceptedByService; + +@end diff --git a/SessionProtocolKit/Prekeys/SignedPrekeyRecord.m b/SessionProtocolKit/Prekeys/SignedPrekeyRecord.m new file mode 100644 index 000000000..1fbed9994 --- /dev/null +++ b/SessionProtocolKit/Prekeys/SignedPrekeyRecord.m @@ -0,0 +1,78 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "SignedPrekeyRecord.h" +#import + +static NSString* const kCoderPreKeyId = @"kCoderPreKeyId"; +static NSString* const kCoderPreKeyPair = @"kCoderPreKeyPair"; +static NSString* const kCoderPreKeyDate = @"kCoderPreKeyDate"; +static NSString* const kCoderPreKeySignature = @"kCoderPreKeySignature"; +static NSString *const kCoderPreKeyWasAcceptedByService = @"kCoderPreKeyWasAcceptedByService"; + +@implementation SignedPreKeyRecord + ++ (BOOL)supportsSecureCoding{ + return YES; +} + +- (instancetype)initWithId:(int)identifier + keyPair:(ECKeyPair *)keyPair + signature:(NSData *)signature + generatedAt:(NSDate *)generatedAt + wasAcceptedByService:(BOOL)wasAcceptedByService +{ + OWSAssert(keyPair); + OWSAssert(signature); + OWSAssert(generatedAt); + + self = [super initWithId:identifier keyPair:keyPair]; + + if (self) { + _signature = signature; + _generatedAt = generatedAt; + _wasAcceptedByService = wasAcceptedByService; + } + + return self; +} + +- (instancetype)initWithId:(int)identifier keyPair:(ECKeyPair *)keyPair signature:(NSData*)signature generatedAt:(NSDate *)generatedAt{ + self = [super initWithId:identifier keyPair:keyPair]; + + if (self) { + _signature = signature; + _generatedAt = generatedAt; + } + + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder{ + return [self initWithId:[aDecoder decodeIntForKey:kCoderPreKeyId] + keyPair:[aDecoder decodeObjectOfClass:[ECKeyPair class] forKey:kCoderPreKeyPair] + signature:[aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderPreKeySignature] + generatedAt:[aDecoder decodeObjectOfClass:[NSDate class] forKey:kCoderPreKeyDate] + wasAcceptedByService:[aDecoder decodeBoolForKey:kCoderPreKeyWasAcceptedByService]]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder{ + [aCoder encodeInt:self.Id forKey:kCoderPreKeyId]; + [aCoder encodeObject:self.keyPair forKey:kCoderPreKeyPair]; + [aCoder encodeObject:self.signature forKey:kCoderPreKeySignature]; + [aCoder encodeObject:self.generatedAt forKey:kCoderPreKeyDate]; + [aCoder encodeBool:self.wasAcceptedByService forKey:kCoderPreKeyWasAcceptedByService]; +} + +- (instancetype)initWithId:(int)identifier keyPair:(ECKeyPair*)keyPair{ + OWSAbstractMethod(); + return nil; +} + +- (void)markAsAcceptedByService +{ + _wasAcceptedByService = YES; +} + +@end diff --git a/SessionProtocolKit/Protos/Makefile b/SessionProtocolKit/Protos/Makefile new file mode 100644 index 000000000..24dd640b2 --- /dev/null +++ b/SessionProtocolKit/Protos/Makefile @@ -0,0 +1,12 @@ +PROTOC=protoc \ + --proto_path='./' +WRAPPER_SCRIPT=../../../../session-ios/Scripts/ProtoWrappers.py \ + --proto-dir='./' --verbose --add-log-tag + +all: webrtc_data_proto + +webrtc_data_proto: WhisperTextProtocol.proto + $(PROTOC) --swift_out=. \ + WhisperTextProtocol.proto + $(WRAPPER_SCRIPT) --dst-dir=. \ + --wrapper-prefix=SPKProto --proto-prefix=SPKProtos --proto-file=WhisperTextProtocol.proto diff --git a/SessionProtocolKit/Protos/SPKProto.swift b/SessionProtocolKit/Protos/SPKProto.swift new file mode 100644 index 000000000..5de7e84b6 --- /dev/null +++ b/SessionProtocolKit/Protos/SPKProto.swift @@ -0,0 +1,869 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +// WARNING: This code is generated. Only edit within the markers. + +public enum SPKProtoError: Error { + case invalidProtobuf(description: String) +} + +// MARK: - SPKProtoTSProtoWhisperMessage + +@objc public class SPKProtoTSProtoWhisperMessage: NSObject { + + // MARK: - SPKProtoTSProtoWhisperMessageBuilder + + @objc public class func builder(ratchetKey: Data, counter: UInt32, ciphertext: Data) -> SPKProtoTSProtoWhisperMessageBuilder { + return SPKProtoTSProtoWhisperMessageBuilder(ratchetKey: ratchetKey, counter: counter, ciphertext: ciphertext) + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> SPKProtoTSProtoWhisperMessageBuilder { + let builder = SPKProtoTSProtoWhisperMessageBuilder(ratchetKey: ratchetKey, counter: counter, ciphertext: ciphertext) + if hasPreviousCounter { + builder.setPreviousCounter(previousCounter) + } + return builder + } + + @objc public class SPKProtoTSProtoWhisperMessageBuilder: NSObject { + + private var proto = SPKProtos_TSProtoWhisperMessage() + + @objc fileprivate override init() {} + + @objc fileprivate init(ratchetKey: Data, counter: UInt32, ciphertext: Data) { + super.init() + + setRatchetKey(ratchetKey) + setCounter(counter) + setCiphertext(ciphertext) + } + + @objc public func setRatchetKey(_ valueParam: Data) { + proto.ratchetKey = valueParam + } + + @objc public func setCounter(_ valueParam: UInt32) { + proto.counter = valueParam + } + + @objc public func setPreviousCounter(_ valueParam: UInt32) { + proto.previousCounter = valueParam + } + + @objc public func setCiphertext(_ valueParam: Data) { + proto.ciphertext = valueParam + } + + @objc public func build() throws -> SPKProtoTSProtoWhisperMessage { + return try SPKProtoTSProtoWhisperMessage.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try SPKProtoTSProtoWhisperMessage.parseProto(proto).serializedData() + } + } + + fileprivate let proto: SPKProtos_TSProtoWhisperMessage + + @objc public let ratchetKey: Data + + @objc public let counter: UInt32 + + @objc public let ciphertext: Data + + @objc public var previousCounter: UInt32 { + return proto.previousCounter + } + @objc public var hasPreviousCounter: Bool { + return proto.hasPreviousCounter + } + + private init(proto: SPKProtos_TSProtoWhisperMessage, + ratchetKey: Data, + counter: UInt32, + ciphertext: Data) { + self.proto = proto + self.ratchetKey = ratchetKey + self.counter = counter + self.ciphertext = ciphertext + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> SPKProtoTSProtoWhisperMessage { + let proto = try SPKProtos_TSProtoWhisperMessage(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: SPKProtos_TSProtoWhisperMessage) throws -> SPKProtoTSProtoWhisperMessage { + guard proto.hasRatchetKey else { + throw SPKProtoError.invalidProtobuf(description: "\(logTag) missing required field: ratchetKey") + } + let ratchetKey = proto.ratchetKey + + guard proto.hasCounter else { + throw SPKProtoError.invalidProtobuf(description: "\(logTag) missing required field: counter") + } + let counter = proto.counter + + guard proto.hasCiphertext else { + throw SPKProtoError.invalidProtobuf(description: "\(logTag) missing required field: ciphertext") + } + let ciphertext = proto.ciphertext + + // MARK: - Begin Validation Logic for SPKProtoTSProtoWhisperMessage - + + // MARK: - End Validation Logic for SPKProtoTSProtoWhisperMessage - + + let result = SPKProtoTSProtoWhisperMessage(proto: proto, + ratchetKey: ratchetKey, + counter: counter, + ciphertext: ciphertext) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension SPKProtoTSProtoWhisperMessage { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension SPKProtoTSProtoWhisperMessage.SPKProtoTSProtoWhisperMessageBuilder { + @objc public func buildIgnoringErrors() -> SPKProtoTSProtoWhisperMessage? { + return try! self.build() + } +} + +#endif + +// MARK: - SPKProtoTSProtoPreKeyWhisperMessage + +@objc public class SPKProtoTSProtoPreKeyWhisperMessage: NSObject { + + // MARK: - SPKProtoTSProtoPreKeyWhisperMessageBuilder + + @objc public class func builder(signedPreKeyID: UInt32, baseKey: Data, identityKey: Data, message: Data) -> SPKProtoTSProtoPreKeyWhisperMessageBuilder { + return SPKProtoTSProtoPreKeyWhisperMessageBuilder(signedPreKeyID: signedPreKeyID, baseKey: baseKey, identityKey: identityKey, message: message) + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> SPKProtoTSProtoPreKeyWhisperMessageBuilder { + let builder = SPKProtoTSProtoPreKeyWhisperMessageBuilder(signedPreKeyID: signedPreKeyID, baseKey: baseKey, identityKey: identityKey, message: message) + if hasRegistrationID { + builder.setRegistrationID(registrationID) + } + if hasPreKeyID { + builder.setPreKeyID(preKeyID) + } + return builder + } + + @objc public class SPKProtoTSProtoPreKeyWhisperMessageBuilder: NSObject { + + private var proto = SPKProtos_TSProtoPreKeyWhisperMessage() + + @objc fileprivate override init() {} + + @objc fileprivate init(signedPreKeyID: UInt32, baseKey: Data, identityKey: Data, message: Data) { + super.init() + + setSignedPreKeyID(signedPreKeyID) + setBaseKey(baseKey) + setIdentityKey(identityKey) + setMessage(message) + } + + @objc public func setRegistrationID(_ valueParam: UInt32) { + proto.registrationID = valueParam + } + + @objc public func setPreKeyID(_ valueParam: UInt32) { + proto.preKeyID = valueParam + } + + @objc public func setSignedPreKeyID(_ valueParam: UInt32) { + proto.signedPreKeyID = valueParam + } + + @objc public func setBaseKey(_ valueParam: Data) { + proto.baseKey = valueParam + } + + @objc public func setIdentityKey(_ valueParam: Data) { + proto.identityKey = valueParam + } + + @objc public func setMessage(_ valueParam: Data) { + proto.message = valueParam + } + + @objc public func build() throws -> SPKProtoTSProtoPreKeyWhisperMessage { + return try SPKProtoTSProtoPreKeyWhisperMessage.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try SPKProtoTSProtoPreKeyWhisperMessage.parseProto(proto).serializedData() + } + } + + fileprivate let proto: SPKProtos_TSProtoPreKeyWhisperMessage + + @objc public let signedPreKeyID: UInt32 + + @objc public let baseKey: Data + + @objc public let identityKey: Data + + @objc public let message: Data + + @objc public var registrationID: UInt32 { + return proto.registrationID + } + @objc public var hasRegistrationID: Bool { + return proto.hasRegistrationID + } + + @objc public var preKeyID: UInt32 { + return proto.preKeyID + } + @objc public var hasPreKeyID: Bool { + return proto.hasPreKeyID + } + + private init(proto: SPKProtos_TSProtoPreKeyWhisperMessage, + signedPreKeyID: UInt32, + baseKey: Data, + identityKey: Data, + message: Data) { + self.proto = proto + self.signedPreKeyID = signedPreKeyID + self.baseKey = baseKey + self.identityKey = identityKey + self.message = message + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> SPKProtoTSProtoPreKeyWhisperMessage { + let proto = try SPKProtos_TSProtoPreKeyWhisperMessage(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: SPKProtos_TSProtoPreKeyWhisperMessage) throws -> SPKProtoTSProtoPreKeyWhisperMessage { + guard proto.hasSignedPreKeyID else { + throw SPKProtoError.invalidProtobuf(description: "\(logTag) missing required field: signedPreKeyID") + } + let signedPreKeyID = proto.signedPreKeyID + + guard proto.hasBaseKey else { + throw SPKProtoError.invalidProtobuf(description: "\(logTag) missing required field: baseKey") + } + let baseKey = proto.baseKey + + guard proto.hasIdentityKey else { + throw SPKProtoError.invalidProtobuf(description: "\(logTag) missing required field: identityKey") + } + let identityKey = proto.identityKey + + guard proto.hasMessage else { + throw SPKProtoError.invalidProtobuf(description: "\(logTag) missing required field: message") + } + let message = proto.message + + // MARK: - Begin Validation Logic for SPKProtoTSProtoPreKeyWhisperMessage - + + // MARK: - End Validation Logic for SPKProtoTSProtoPreKeyWhisperMessage - + + let result = SPKProtoTSProtoPreKeyWhisperMessage(proto: proto, + signedPreKeyID: signedPreKeyID, + baseKey: baseKey, + identityKey: identityKey, + message: message) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension SPKProtoTSProtoPreKeyWhisperMessage { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension SPKProtoTSProtoPreKeyWhisperMessage.SPKProtoTSProtoPreKeyWhisperMessageBuilder { + @objc public func buildIgnoringErrors() -> SPKProtoTSProtoPreKeyWhisperMessage? { + return try! self.build() + } +} + +#endif + +// MARK: - SPKProtoTSProtoKeyExchangeMessage + +@objc public class SPKProtoTSProtoKeyExchangeMessage: NSObject { + + // MARK: - SPKProtoTSProtoKeyExchangeMessageBuilder + + @objc public class func builder() -> SPKProtoTSProtoKeyExchangeMessageBuilder { + return SPKProtoTSProtoKeyExchangeMessageBuilder() + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> SPKProtoTSProtoKeyExchangeMessageBuilder { + let builder = SPKProtoTSProtoKeyExchangeMessageBuilder() + if hasID { + builder.setId(id) + } + if let _value = baseKey { + builder.setBaseKey(_value) + } + if let _value = ratchetKey { + builder.setRatchetKey(_value) + } + if let _value = identityKey { + builder.setIdentityKey(_value) + } + if let _value = baseKeySignature { + builder.setBaseKeySignature(_value) + } + return builder + } + + @objc public class SPKProtoTSProtoKeyExchangeMessageBuilder: NSObject { + + private var proto = SPKProtos_TSProtoKeyExchangeMessage() + + @objc fileprivate override init() {} + + @objc public func setId(_ valueParam: UInt32) { + proto.id = valueParam + } + + @objc public func setBaseKey(_ valueParam: Data) { + proto.baseKey = valueParam + } + + @objc public func setRatchetKey(_ valueParam: Data) { + proto.ratchetKey = valueParam + } + + @objc public func setIdentityKey(_ valueParam: Data) { + proto.identityKey = valueParam + } + + @objc public func setBaseKeySignature(_ valueParam: Data) { + proto.baseKeySignature = valueParam + } + + @objc public func build() throws -> SPKProtoTSProtoKeyExchangeMessage { + return try SPKProtoTSProtoKeyExchangeMessage.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try SPKProtoTSProtoKeyExchangeMessage.parseProto(proto).serializedData() + } + } + + fileprivate let proto: SPKProtos_TSProtoKeyExchangeMessage + + @objc public var id: UInt32 { + return proto.id + } + @objc public var hasID: Bool { + return proto.hasID + } + + @objc public var baseKey: Data? { + guard proto.hasBaseKey else { + return nil + } + return proto.baseKey + } + @objc public var hasBaseKey: Bool { + return proto.hasBaseKey + } + + @objc public var ratchetKey: Data? { + guard proto.hasRatchetKey else { + return nil + } + return proto.ratchetKey + } + @objc public var hasRatchetKey: Bool { + return proto.hasRatchetKey + } + + @objc public var identityKey: Data? { + guard proto.hasIdentityKey else { + return nil + } + return proto.identityKey + } + @objc public var hasIdentityKey: Bool { + return proto.hasIdentityKey + } + + @objc public var baseKeySignature: Data? { + guard proto.hasBaseKeySignature else { + return nil + } + return proto.baseKeySignature + } + @objc public var hasBaseKeySignature: Bool { + return proto.hasBaseKeySignature + } + + private init(proto: SPKProtos_TSProtoKeyExchangeMessage) { + self.proto = proto + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> SPKProtoTSProtoKeyExchangeMessage { + let proto = try SPKProtos_TSProtoKeyExchangeMessage(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: SPKProtos_TSProtoKeyExchangeMessage) throws -> SPKProtoTSProtoKeyExchangeMessage { + // MARK: - Begin Validation Logic for SPKProtoTSProtoKeyExchangeMessage - + + // MARK: - End Validation Logic for SPKProtoTSProtoKeyExchangeMessage - + + let result = SPKProtoTSProtoKeyExchangeMessage(proto: proto) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension SPKProtoTSProtoKeyExchangeMessage { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension SPKProtoTSProtoKeyExchangeMessage.SPKProtoTSProtoKeyExchangeMessageBuilder { + @objc public func buildIgnoringErrors() -> SPKProtoTSProtoKeyExchangeMessage? { + return try! self.build() + } +} + +#endif + +// MARK: - SPKProtoTSProtoSenderKeyMessage + +@objc public class SPKProtoTSProtoSenderKeyMessage: NSObject { + + // MARK: - SPKProtoTSProtoSenderKeyMessageBuilder + + @objc public class func builder() -> SPKProtoTSProtoSenderKeyMessageBuilder { + return SPKProtoTSProtoSenderKeyMessageBuilder() + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> SPKProtoTSProtoSenderKeyMessageBuilder { + let builder = SPKProtoTSProtoSenderKeyMessageBuilder() + if hasID { + builder.setId(id) + } + if hasIteration { + builder.setIteration(iteration) + } + if let _value = ciphertext { + builder.setCiphertext(_value) + } + return builder + } + + @objc public class SPKProtoTSProtoSenderKeyMessageBuilder: NSObject { + + private var proto = SPKProtos_TSProtoSenderKeyMessage() + + @objc fileprivate override init() {} + + @objc public func setId(_ valueParam: UInt32) { + proto.id = valueParam + } + + @objc public func setIteration(_ valueParam: UInt32) { + proto.iteration = valueParam + } + + @objc public func setCiphertext(_ valueParam: Data) { + proto.ciphertext = valueParam + } + + @objc public func build() throws -> SPKProtoTSProtoSenderKeyMessage { + return try SPKProtoTSProtoSenderKeyMessage.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try SPKProtoTSProtoSenderKeyMessage.parseProto(proto).serializedData() + } + } + + fileprivate let proto: SPKProtos_TSProtoSenderKeyMessage + + @objc public var id: UInt32 { + return proto.id + } + @objc public var hasID: Bool { + return proto.hasID + } + + @objc public var iteration: UInt32 { + return proto.iteration + } + @objc public var hasIteration: Bool { + return proto.hasIteration + } + + @objc public var ciphertext: Data? { + guard proto.hasCiphertext else { + return nil + } + return proto.ciphertext + } + @objc public var hasCiphertext: Bool { + return proto.hasCiphertext + } + + private init(proto: SPKProtos_TSProtoSenderKeyMessage) { + self.proto = proto + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> SPKProtoTSProtoSenderKeyMessage { + let proto = try SPKProtos_TSProtoSenderKeyMessage(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: SPKProtos_TSProtoSenderKeyMessage) throws -> SPKProtoTSProtoSenderKeyMessage { + // MARK: - Begin Validation Logic for SPKProtoTSProtoSenderKeyMessage - + + // MARK: - End Validation Logic for SPKProtoTSProtoSenderKeyMessage - + + let result = SPKProtoTSProtoSenderKeyMessage(proto: proto) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension SPKProtoTSProtoSenderKeyMessage { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension SPKProtoTSProtoSenderKeyMessage.SPKProtoTSProtoSenderKeyMessageBuilder { + @objc public func buildIgnoringErrors() -> SPKProtoTSProtoSenderKeyMessage? { + return try! self.build() + } +} + +#endif + +// MARK: - SPKProtoTSProtoSenderKeyDistributionMessage + +@objc public class SPKProtoTSProtoSenderKeyDistributionMessage: NSObject { + + // MARK: - SPKProtoTSProtoSenderKeyDistributionMessageBuilder + + @objc public class func builder() -> SPKProtoTSProtoSenderKeyDistributionMessageBuilder { + return SPKProtoTSProtoSenderKeyDistributionMessageBuilder() + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> SPKProtoTSProtoSenderKeyDistributionMessageBuilder { + let builder = SPKProtoTSProtoSenderKeyDistributionMessageBuilder() + if hasID { + builder.setId(id) + } + if hasIteration { + builder.setIteration(iteration) + } + if let _value = chainKey { + builder.setChainKey(_value) + } + if let _value = signingKey { + builder.setSigningKey(_value) + } + return builder + } + + @objc public class SPKProtoTSProtoSenderKeyDistributionMessageBuilder: NSObject { + + private var proto = SPKProtos_TSProtoSenderKeyDistributionMessage() + + @objc fileprivate override init() {} + + @objc public func setId(_ valueParam: UInt32) { + proto.id = valueParam + } + + @objc public func setIteration(_ valueParam: UInt32) { + proto.iteration = valueParam + } + + @objc public func setChainKey(_ valueParam: Data) { + proto.chainKey = valueParam + } + + @objc public func setSigningKey(_ valueParam: Data) { + proto.signingKey = valueParam + } + + @objc public func build() throws -> SPKProtoTSProtoSenderKeyDistributionMessage { + return try SPKProtoTSProtoSenderKeyDistributionMessage.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try SPKProtoTSProtoSenderKeyDistributionMessage.parseProto(proto).serializedData() + } + } + + fileprivate let proto: SPKProtos_TSProtoSenderKeyDistributionMessage + + @objc public var id: UInt32 { + return proto.id + } + @objc public var hasID: Bool { + return proto.hasID + } + + @objc public var iteration: UInt32 { + return proto.iteration + } + @objc public var hasIteration: Bool { + return proto.hasIteration + } + + @objc public var chainKey: Data? { + guard proto.hasChainKey else { + return nil + } + return proto.chainKey + } + @objc public var hasChainKey: Bool { + return proto.hasChainKey + } + + @objc public var signingKey: Data? { + guard proto.hasSigningKey else { + return nil + } + return proto.signingKey + } + @objc public var hasSigningKey: Bool { + return proto.hasSigningKey + } + + private init(proto: SPKProtos_TSProtoSenderKeyDistributionMessage) { + self.proto = proto + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> SPKProtoTSProtoSenderKeyDistributionMessage { + let proto = try SPKProtos_TSProtoSenderKeyDistributionMessage(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: SPKProtos_TSProtoSenderKeyDistributionMessage) throws -> SPKProtoTSProtoSenderKeyDistributionMessage { + // MARK: - Begin Validation Logic for SPKProtoTSProtoSenderKeyDistributionMessage - + + // MARK: - End Validation Logic for SPKProtoTSProtoSenderKeyDistributionMessage - + + let result = SPKProtoTSProtoSenderKeyDistributionMessage(proto: proto) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension SPKProtoTSProtoSenderKeyDistributionMessage { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension SPKProtoTSProtoSenderKeyDistributionMessage.SPKProtoTSProtoSenderKeyDistributionMessageBuilder { + @objc public func buildIgnoringErrors() -> SPKProtoTSProtoSenderKeyDistributionMessage? { + return try! self.build() + } +} + +#endif + +// MARK: - SPKProtoClosedGroupCiphertextMessage + +@objc public class SPKProtoClosedGroupCiphertextMessage: NSObject { + + // MARK: - SPKProtoClosedGroupCiphertextMessageBuilder + + @objc public class func builder(ciphertext: Data, senderPublicKey: Data, keyIndex: UInt32) -> SPKProtoClosedGroupCiphertextMessageBuilder { + return SPKProtoClosedGroupCiphertextMessageBuilder(ciphertext: ciphertext, senderPublicKey: senderPublicKey, keyIndex: keyIndex) + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> SPKProtoClosedGroupCiphertextMessageBuilder { + let builder = SPKProtoClosedGroupCiphertextMessageBuilder(ciphertext: ciphertext, senderPublicKey: senderPublicKey, keyIndex: keyIndex) + return builder + } + + @objc public class SPKProtoClosedGroupCiphertextMessageBuilder: NSObject { + + private var proto = SPKProtos_ClosedGroupCiphertextMessage() + + @objc fileprivate override init() {} + + @objc fileprivate init(ciphertext: Data, senderPublicKey: Data, keyIndex: UInt32) { + super.init() + + setCiphertext(ciphertext) + setSenderPublicKey(senderPublicKey) + setKeyIndex(keyIndex) + } + + @objc public func setCiphertext(_ valueParam: Data) { + proto.ciphertext = valueParam + } + + @objc public func setSenderPublicKey(_ valueParam: Data) { + proto.senderPublicKey = valueParam + } + + @objc public func setKeyIndex(_ valueParam: UInt32) { + proto.keyIndex = valueParam + } + + @objc public func build() throws -> SPKProtoClosedGroupCiphertextMessage { + return try SPKProtoClosedGroupCiphertextMessage.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try SPKProtoClosedGroupCiphertextMessage.parseProto(proto).serializedData() + } + } + + fileprivate let proto: SPKProtos_ClosedGroupCiphertextMessage + + @objc public let ciphertext: Data + + @objc public let senderPublicKey: Data + + @objc public let keyIndex: UInt32 + + private init(proto: SPKProtos_ClosedGroupCiphertextMessage, + ciphertext: Data, + senderPublicKey: Data, + keyIndex: UInt32) { + self.proto = proto + self.ciphertext = ciphertext + self.senderPublicKey = senderPublicKey + self.keyIndex = keyIndex + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> SPKProtoClosedGroupCiphertextMessage { + let proto = try SPKProtos_ClosedGroupCiphertextMessage(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: SPKProtos_ClosedGroupCiphertextMessage) throws -> SPKProtoClosedGroupCiphertextMessage { + guard proto.hasCiphertext else { + throw SPKProtoError.invalidProtobuf(description: "\(logTag) missing required field: ciphertext") + } + let ciphertext = proto.ciphertext + + guard proto.hasSenderPublicKey else { + throw SPKProtoError.invalidProtobuf(description: "\(logTag) missing required field: senderPublicKey") + } + let senderPublicKey = proto.senderPublicKey + + guard proto.hasKeyIndex else { + throw SPKProtoError.invalidProtobuf(description: "\(logTag) missing required field: keyIndex") + } + let keyIndex = proto.keyIndex + + // MARK: - Begin Validation Logic for SPKProtoClosedGroupCiphertextMessage - + + // MARK: - End Validation Logic for SPKProtoClosedGroupCiphertextMessage - + + let result = SPKProtoClosedGroupCiphertextMessage(proto: proto, + ciphertext: ciphertext, + senderPublicKey: senderPublicKey, + keyIndex: keyIndex) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension SPKProtoClosedGroupCiphertextMessage { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension SPKProtoClosedGroupCiphertextMessage.SPKProtoClosedGroupCiphertextMessageBuilder { + @objc public func buildIgnoringErrors() -> SPKProtoClosedGroupCiphertextMessage? { + return try! self.build() + } +} + +#endif diff --git a/SessionProtocolKit/Protos/WhisperTextProtocol.pb.swift b/SessionProtocolKit/Protos/WhisperTextProtocol.pb.swift new file mode 100644 index 000000000..8edcccf6d --- /dev/null +++ b/SessionProtocolKit/Protos/WhisperTextProtocol.pb.swift @@ -0,0 +1,642 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: WhisperTextProtocol.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +/// iOS - since we use a modern proto-compiler, we must specify +/// the legacy proto format. + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +struct SPKProtos_TSProtoWhisperMessage { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// @required + var ratchetKey: Data { + get {return _ratchetKey ?? SwiftProtobuf.Internal.emptyData} + set {_ratchetKey = newValue} + } + /// Returns true if `ratchetKey` has been explicitly set. + var hasRatchetKey: Bool {return self._ratchetKey != nil} + /// Clears the value of `ratchetKey`. Subsequent reads from it will return its default value. + mutating func clearRatchetKey() {self._ratchetKey = nil} + + /// @required + var counter: UInt32 { + get {return _counter ?? 0} + set {_counter = newValue} + } + /// Returns true if `counter` has been explicitly set. + var hasCounter: Bool {return self._counter != nil} + /// Clears the value of `counter`. Subsequent reads from it will return its default value. + mutating func clearCounter() {self._counter = nil} + + var previousCounter: UInt32 { + get {return _previousCounter ?? 0} + set {_previousCounter = newValue} + } + /// Returns true if `previousCounter` has been explicitly set. + var hasPreviousCounter: Bool {return self._previousCounter != nil} + /// Clears the value of `previousCounter`. Subsequent reads from it will return its default value. + mutating func clearPreviousCounter() {self._previousCounter = nil} + + /// @required + var ciphertext: Data { + get {return _ciphertext ?? SwiftProtobuf.Internal.emptyData} + set {_ciphertext = newValue} + } + /// Returns true if `ciphertext` has been explicitly set. + var hasCiphertext: Bool {return self._ciphertext != nil} + /// Clears the value of `ciphertext`. Subsequent reads from it will return its default value. + mutating func clearCiphertext() {self._ciphertext = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _ratchetKey: Data? = nil + fileprivate var _counter: UInt32? = nil + fileprivate var _previousCounter: UInt32? = nil + fileprivate var _ciphertext: Data? = nil +} + +struct SPKProtos_TSProtoPreKeyWhisperMessage { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var registrationID: UInt32 { + get {return _registrationID ?? 0} + set {_registrationID = newValue} + } + /// Returns true if `registrationID` has been explicitly set. + var hasRegistrationID: Bool {return self._registrationID != nil} + /// Clears the value of `registrationID`. Subsequent reads from it will return its default value. + mutating func clearRegistrationID() {self._registrationID = nil} + + var preKeyID: UInt32 { + get {return _preKeyID ?? 0} + set {_preKeyID = newValue} + } + /// Returns true if `preKeyID` has been explicitly set. + var hasPreKeyID: Bool {return self._preKeyID != nil} + /// Clears the value of `preKeyID`. Subsequent reads from it will return its default value. + mutating func clearPreKeyID() {self._preKeyID = nil} + + /// @required + var signedPreKeyID: UInt32 { + get {return _signedPreKeyID ?? 0} + set {_signedPreKeyID = newValue} + } + /// Returns true if `signedPreKeyID` has been explicitly set. + var hasSignedPreKeyID: Bool {return self._signedPreKeyID != nil} + /// Clears the value of `signedPreKeyID`. Subsequent reads from it will return its default value. + mutating func clearSignedPreKeyID() {self._signedPreKeyID = nil} + + /// @required + var baseKey: Data { + get {return _baseKey ?? SwiftProtobuf.Internal.emptyData} + set {_baseKey = newValue} + } + /// Returns true if `baseKey` has been explicitly set. + var hasBaseKey: Bool {return self._baseKey != nil} + /// Clears the value of `baseKey`. Subsequent reads from it will return its default value. + mutating func clearBaseKey() {self._baseKey = nil} + + /// @required + var identityKey: Data { + get {return _identityKey ?? SwiftProtobuf.Internal.emptyData} + set {_identityKey = newValue} + } + /// Returns true if `identityKey` has been explicitly set. + var hasIdentityKey: Bool {return self._identityKey != nil} + /// Clears the value of `identityKey`. Subsequent reads from it will return its default value. + mutating func clearIdentityKey() {self._identityKey = nil} + + /// @required + var message: Data { + get {return _message ?? SwiftProtobuf.Internal.emptyData} + set {_message = newValue} + } + /// Returns true if `message` has been explicitly set. + var hasMessage: Bool {return self._message != nil} + /// Clears the value of `message`. Subsequent reads from it will return its default value. + mutating func clearMessage() {self._message = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _registrationID: UInt32? = nil + fileprivate var _preKeyID: UInt32? = nil + fileprivate var _signedPreKeyID: UInt32? = nil + fileprivate var _baseKey: Data? = nil + fileprivate var _identityKey: Data? = nil + fileprivate var _message: Data? = nil +} + +struct SPKProtos_TSProtoKeyExchangeMessage { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var id: UInt32 { + get {return _id ?? 0} + set {_id = newValue} + } + /// Returns true if `id` has been explicitly set. + var hasID: Bool {return self._id != nil} + /// Clears the value of `id`. Subsequent reads from it will return its default value. + mutating func clearID() {self._id = nil} + + var baseKey: Data { + get {return _baseKey ?? SwiftProtobuf.Internal.emptyData} + set {_baseKey = newValue} + } + /// Returns true if `baseKey` has been explicitly set. + var hasBaseKey: Bool {return self._baseKey != nil} + /// Clears the value of `baseKey`. Subsequent reads from it will return its default value. + mutating func clearBaseKey() {self._baseKey = nil} + + var ratchetKey: Data { + get {return _ratchetKey ?? SwiftProtobuf.Internal.emptyData} + set {_ratchetKey = newValue} + } + /// Returns true if `ratchetKey` has been explicitly set. + var hasRatchetKey: Bool {return self._ratchetKey != nil} + /// Clears the value of `ratchetKey`. Subsequent reads from it will return its default value. + mutating func clearRatchetKey() {self._ratchetKey = nil} + + var identityKey: Data { + get {return _identityKey ?? SwiftProtobuf.Internal.emptyData} + set {_identityKey = newValue} + } + /// Returns true if `identityKey` has been explicitly set. + var hasIdentityKey: Bool {return self._identityKey != nil} + /// Clears the value of `identityKey`. Subsequent reads from it will return its default value. + mutating func clearIdentityKey() {self._identityKey = nil} + + var baseKeySignature: Data { + get {return _baseKeySignature ?? SwiftProtobuf.Internal.emptyData} + set {_baseKeySignature = newValue} + } + /// Returns true if `baseKeySignature` has been explicitly set. + var hasBaseKeySignature: Bool {return self._baseKeySignature != nil} + /// Clears the value of `baseKeySignature`. Subsequent reads from it will return its default value. + mutating func clearBaseKeySignature() {self._baseKeySignature = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _id: UInt32? = nil + fileprivate var _baseKey: Data? = nil + fileprivate var _ratchetKey: Data? = nil + fileprivate var _identityKey: Data? = nil + fileprivate var _baseKeySignature: Data? = nil +} + +struct SPKProtos_TSProtoSenderKeyMessage { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var id: UInt32 { + get {return _id ?? 0} + set {_id = newValue} + } + /// Returns true if `id` has been explicitly set. + var hasID: Bool {return self._id != nil} + /// Clears the value of `id`. Subsequent reads from it will return its default value. + mutating func clearID() {self._id = nil} + + var iteration: UInt32 { + get {return _iteration ?? 0} + set {_iteration = newValue} + } + /// Returns true if `iteration` has been explicitly set. + var hasIteration: Bool {return self._iteration != nil} + /// Clears the value of `iteration`. Subsequent reads from it will return its default value. + mutating func clearIteration() {self._iteration = nil} + + var ciphertext: Data { + get {return _ciphertext ?? SwiftProtobuf.Internal.emptyData} + set {_ciphertext = newValue} + } + /// Returns true if `ciphertext` has been explicitly set. + var hasCiphertext: Bool {return self._ciphertext != nil} + /// Clears the value of `ciphertext`. Subsequent reads from it will return its default value. + mutating func clearCiphertext() {self._ciphertext = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _id: UInt32? = nil + fileprivate var _iteration: UInt32? = nil + fileprivate var _ciphertext: Data? = nil +} + +struct SPKProtos_TSProtoSenderKeyDistributionMessage { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var id: UInt32 { + get {return _id ?? 0} + set {_id = newValue} + } + /// Returns true if `id` has been explicitly set. + var hasID: Bool {return self._id != nil} + /// Clears the value of `id`. Subsequent reads from it will return its default value. + mutating func clearID() {self._id = nil} + + var iteration: UInt32 { + get {return _iteration ?? 0} + set {_iteration = newValue} + } + /// Returns true if `iteration` has been explicitly set. + var hasIteration: Bool {return self._iteration != nil} + /// Clears the value of `iteration`. Subsequent reads from it will return its default value. + mutating func clearIteration() {self._iteration = nil} + + var chainKey: Data { + get {return _chainKey ?? SwiftProtobuf.Internal.emptyData} + set {_chainKey = newValue} + } + /// Returns true if `chainKey` has been explicitly set. + var hasChainKey: Bool {return self._chainKey != nil} + /// Clears the value of `chainKey`. Subsequent reads from it will return its default value. + mutating func clearChainKey() {self._chainKey = nil} + + var signingKey: Data { + get {return _signingKey ?? SwiftProtobuf.Internal.emptyData} + set {_signingKey = newValue} + } + /// Returns true if `signingKey` has been explicitly set. + var hasSigningKey: Bool {return self._signingKey != nil} + /// Clears the value of `signingKey`. Subsequent reads from it will return its default value. + mutating func clearSigningKey() {self._signingKey = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _id: UInt32? = nil + fileprivate var _iteration: UInt32? = nil + fileprivate var _chainKey: Data? = nil + fileprivate var _signingKey: Data? = nil +} + +struct SPKProtos_ClosedGroupCiphertextMessage { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// @required + var ciphertext: Data { + get {return _ciphertext ?? SwiftProtobuf.Internal.emptyData} + set {_ciphertext = newValue} + } + /// Returns true if `ciphertext` has been explicitly set. + var hasCiphertext: Bool {return self._ciphertext != nil} + /// Clears the value of `ciphertext`. Subsequent reads from it will return its default value. + mutating func clearCiphertext() {self._ciphertext = nil} + + /// @required + var senderPublicKey: Data { + get {return _senderPublicKey ?? SwiftProtobuf.Internal.emptyData} + set {_senderPublicKey = newValue} + } + /// Returns true if `senderPublicKey` has been explicitly set. + var hasSenderPublicKey: Bool {return self._senderPublicKey != nil} + /// Clears the value of `senderPublicKey`. Subsequent reads from it will return its default value. + mutating func clearSenderPublicKey() {self._senderPublicKey = nil} + + /// @required + var keyIndex: UInt32 { + get {return _keyIndex ?? 0} + set {_keyIndex = newValue} + } + /// Returns true if `keyIndex` has been explicitly set. + var hasKeyIndex: Bool {return self._keyIndex != nil} + /// Clears the value of `keyIndex`. Subsequent reads from it will return its default value. + mutating func clearKeyIndex() {self._keyIndex = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _ciphertext: Data? = nil + fileprivate var _senderPublicKey: Data? = nil + fileprivate var _keyIndex: UInt32? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "SPKProtos" + +extension SPKProtos_TSProtoWhisperMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".TSProtoWhisperMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "ratchetKey"), + 2: .same(proto: "counter"), + 3: .same(proto: "previousCounter"), + 4: .same(proto: "ciphertext"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularBytesField(value: &self._ratchetKey) + case 2: try decoder.decodeSingularUInt32Field(value: &self._counter) + case 3: try decoder.decodeSingularUInt32Field(value: &self._previousCounter) + case 4: try decoder.decodeSingularBytesField(value: &self._ciphertext) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if let v = self._ratchetKey { + try visitor.visitSingularBytesField(value: v, fieldNumber: 1) + } + if let v = self._counter { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } + if let v = self._previousCounter { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + } + if let v = self._ciphertext { + try visitor.visitSingularBytesField(value: v, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SPKProtos_TSProtoWhisperMessage, rhs: SPKProtos_TSProtoWhisperMessage) -> Bool { + if lhs._ratchetKey != rhs._ratchetKey {return false} + if lhs._counter != rhs._counter {return false} + if lhs._previousCounter != rhs._previousCounter {return false} + if lhs._ciphertext != rhs._ciphertext {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SPKProtos_TSProtoPreKeyWhisperMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".TSProtoPreKeyWhisperMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 5: .same(proto: "registrationId"), + 1: .same(proto: "preKeyId"), + 6: .same(proto: "signedPreKeyId"), + 2: .same(proto: "baseKey"), + 3: .same(proto: "identityKey"), + 4: .same(proto: "message"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularUInt32Field(value: &self._preKeyID) + case 2: try decoder.decodeSingularBytesField(value: &self._baseKey) + case 3: try decoder.decodeSingularBytesField(value: &self._identityKey) + case 4: try decoder.decodeSingularBytesField(value: &self._message) + case 5: try decoder.decodeSingularUInt32Field(value: &self._registrationID) + case 6: try decoder.decodeSingularUInt32Field(value: &self._signedPreKeyID) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if let v = self._preKeyID { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } + if let v = self._baseKey { + try visitor.visitSingularBytesField(value: v, fieldNumber: 2) + } + if let v = self._identityKey { + try visitor.visitSingularBytesField(value: v, fieldNumber: 3) + } + if let v = self._message { + try visitor.visitSingularBytesField(value: v, fieldNumber: 4) + } + if let v = self._registrationID { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5) + } + if let v = self._signedPreKeyID { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 6) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SPKProtos_TSProtoPreKeyWhisperMessage, rhs: SPKProtos_TSProtoPreKeyWhisperMessage) -> Bool { + if lhs._registrationID != rhs._registrationID {return false} + if lhs._preKeyID != rhs._preKeyID {return false} + if lhs._signedPreKeyID != rhs._signedPreKeyID {return false} + if lhs._baseKey != rhs._baseKey {return false} + if lhs._identityKey != rhs._identityKey {return false} + if lhs._message != rhs._message {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SPKProtos_TSProtoKeyExchangeMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".TSProtoKeyExchangeMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "id"), + 2: .same(proto: "baseKey"), + 3: .same(proto: "ratchetKey"), + 4: .same(proto: "identityKey"), + 5: .same(proto: "baseKeySignature"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularUInt32Field(value: &self._id) + case 2: try decoder.decodeSingularBytesField(value: &self._baseKey) + case 3: try decoder.decodeSingularBytesField(value: &self._ratchetKey) + case 4: try decoder.decodeSingularBytesField(value: &self._identityKey) + case 5: try decoder.decodeSingularBytesField(value: &self._baseKeySignature) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if let v = self._id { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } + if let v = self._baseKey { + try visitor.visitSingularBytesField(value: v, fieldNumber: 2) + } + if let v = self._ratchetKey { + try visitor.visitSingularBytesField(value: v, fieldNumber: 3) + } + if let v = self._identityKey { + try visitor.visitSingularBytesField(value: v, fieldNumber: 4) + } + if let v = self._baseKeySignature { + try visitor.visitSingularBytesField(value: v, fieldNumber: 5) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SPKProtos_TSProtoKeyExchangeMessage, rhs: SPKProtos_TSProtoKeyExchangeMessage) -> Bool { + if lhs._id != rhs._id {return false} + if lhs._baseKey != rhs._baseKey {return false} + if lhs._ratchetKey != rhs._ratchetKey {return false} + if lhs._identityKey != rhs._identityKey {return false} + if lhs._baseKeySignature != rhs._baseKeySignature {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SPKProtos_TSProtoSenderKeyMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".TSProtoSenderKeyMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "id"), + 2: .same(proto: "iteration"), + 3: .same(proto: "ciphertext"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularUInt32Field(value: &self._id) + case 2: try decoder.decodeSingularUInt32Field(value: &self._iteration) + case 3: try decoder.decodeSingularBytesField(value: &self._ciphertext) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if let v = self._id { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } + if let v = self._iteration { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } + if let v = self._ciphertext { + try visitor.visitSingularBytesField(value: v, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SPKProtos_TSProtoSenderKeyMessage, rhs: SPKProtos_TSProtoSenderKeyMessage) -> Bool { + if lhs._id != rhs._id {return false} + if lhs._iteration != rhs._iteration {return false} + if lhs._ciphertext != rhs._ciphertext {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SPKProtos_TSProtoSenderKeyDistributionMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".TSProtoSenderKeyDistributionMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "id"), + 2: .same(proto: "iteration"), + 3: .same(proto: "chainKey"), + 4: .same(proto: "signingKey"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularUInt32Field(value: &self._id) + case 2: try decoder.decodeSingularUInt32Field(value: &self._iteration) + case 3: try decoder.decodeSingularBytesField(value: &self._chainKey) + case 4: try decoder.decodeSingularBytesField(value: &self._signingKey) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if let v = self._id { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } + if let v = self._iteration { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } + if let v = self._chainKey { + try visitor.visitSingularBytesField(value: v, fieldNumber: 3) + } + if let v = self._signingKey { + try visitor.visitSingularBytesField(value: v, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SPKProtos_TSProtoSenderKeyDistributionMessage, rhs: SPKProtos_TSProtoSenderKeyDistributionMessage) -> Bool { + if lhs._id != rhs._id {return false} + if lhs._iteration != rhs._iteration {return false} + if lhs._chainKey != rhs._chainKey {return false} + if lhs._signingKey != rhs._signingKey {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SPKProtos_ClosedGroupCiphertextMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".ClosedGroupCiphertextMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "ciphertext"), + 2: .same(proto: "senderPublicKey"), + 3: .same(proto: "keyIndex"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularBytesField(value: &self._ciphertext) + case 2: try decoder.decodeSingularBytesField(value: &self._senderPublicKey) + case 3: try decoder.decodeSingularUInt32Field(value: &self._keyIndex) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if let v = self._ciphertext { + try visitor.visitSingularBytesField(value: v, fieldNumber: 1) + } + if let v = self._senderPublicKey { + try visitor.visitSingularBytesField(value: v, fieldNumber: 2) + } + if let v = self._keyIndex { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SPKProtos_ClosedGroupCiphertextMessage, rhs: SPKProtos_ClosedGroupCiphertextMessage) -> Bool { + if lhs._ciphertext != rhs._ciphertext {return false} + if lhs._senderPublicKey != rhs._senderPublicKey {return false} + if lhs._keyIndex != rhs._keyIndex {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/SessionProtocolKit/Protos/WhisperTextProtocol.proto b/SessionProtocolKit/Protos/WhisperTextProtocol.proto new file mode 100644 index 000000000..310cbcf2e --- /dev/null +++ b/SessionProtocolKit/Protos/WhisperTextProtocol.proto @@ -0,0 +1,59 @@ +// iOS - since we use a modern proto-compiler, we must specify +// the legacy proto format. +syntax = "proto2"; + +// iOS - package name determines class prefix +package SPKProtos; + +message TSProtoWhisperMessage { + // @required + optional bytes ratchetKey = 1; + // @required + optional uint32 counter = 2; + optional uint32 previousCounter = 3; + // @required + optional bytes ciphertext = 4; +} + +message TSProtoPreKeyWhisperMessage { + optional uint32 registrationId = 5; + optional uint32 preKeyId = 1; + // @required + optional uint32 signedPreKeyId = 6; + // @required + optional bytes baseKey = 2; + // @required + optional bytes identityKey = 3; + // @required + optional bytes message = 4; // WhisperMessage +} + +message TSProtoKeyExchangeMessage { + optional uint32 id = 1; + optional bytes baseKey = 2; + optional bytes ratchetKey = 3; + optional bytes identityKey = 4; + optional bytes baseKeySignature = 5; +} + +message TSProtoSenderKeyMessage { + optional uint32 id = 1; + optional uint32 iteration = 2; + optional bytes ciphertext = 3; +} + +message TSProtoSenderKeyDistributionMessage { + optional uint32 id = 1; + optional uint32 iteration = 2; + optional bytes chainKey = 3; + optional bytes signingKey = 4; +} + +message ClosedGroupCiphertextMessage { + // @required + optional bytes ciphertext = 1; + // @required + optional bytes senderPublicKey = 2; + // @required + optional uint32 keyIndex = 3; +} diff --git a/SessionProtocolKit/Ratchet/AliceAxolotlParameters.h b/SessionProtocolKit/Ratchet/AliceAxolotlParameters.h new file mode 100644 index 000000000..1623371f5 --- /dev/null +++ b/SessionProtocolKit/Ratchet/AliceAxolotlParameters.h @@ -0,0 +1,22 @@ +// +// AliceAxolotlParameters.h +// AxolotlKit +// +// Created by Frederic Jacobs on 26/07/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import + +#import "AxolotlParameters.h" + +@interface AliceAxolotlParameters : NSObject + +@property (nonatomic, readonly)ECKeyPair *ourBaseKey; +@property (nonatomic, readonly)NSData* theirSignedPreKey; +@property (nonatomic, readonly)NSData* theirRatchetKey; +@property (nonatomic, readonly)NSData* theirOneTimePrekey; + +- (instancetype)initWithIdentityKey:(ECKeyPair*)myIdentityKey theirIdentityKey:(NSData*)theirIdentityKey ourBaseKey:(ECKeyPair*)ourBaseKey theirSignedPreKey:(NSData*)theirSignedPreKey theirOneTimePreKey:(NSData*)theirOneTimePreKey theirRatchetKey:(NSData*)theirRatchetKey; + +@end diff --git a/SessionProtocolKit/Ratchet/AliceAxolotlParameters.m b/SessionProtocolKit/Ratchet/AliceAxolotlParameters.m new file mode 100644 index 000000000..979b0cc2d --- /dev/null +++ b/SessionProtocolKit/Ratchet/AliceAxolotlParameters.m @@ -0,0 +1,39 @@ +// +// AliceAxolotlParameters.m +// AxolotlKit +// +// Created by Frederic Jacobs on 26/07/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import "AliceAxolotlParameters.h" +#import + +@implementation AliceAxolotlParameters + +@synthesize ourIdentityKeyPair=_ourIdentityKeyPair, theirIdentityKey=_theirIdentityKey; + +- (instancetype)initWithIdentityKey:(ECKeyPair*)myIdentityKey theirIdentityKey:(NSData*)theirIdentityKey ourBaseKey:(ECKeyPair*)ourBaseKey theirSignedPreKey:(NSData*)theirSignedPreKey theirOneTimePreKey:(NSData*)theirOneTimePreKey theirRatchetKey:(NSData*)theirRatchetKey{ + + OWSAssert(myIdentityKey); + OWSAssert(theirIdentityKey); + OWSAssert(ourBaseKey); + OWSAssert(theirSignedPreKey); + OWSAssert(theirRatchetKey); + + self = [super init]; + + if (self) { + _ourIdentityKeyPair = myIdentityKey; + _theirIdentityKey = theirIdentityKey; + _ourBaseKey = ourBaseKey; + _theirSignedPreKey = theirSignedPreKey; + _theirOneTimePrekey = theirOneTimePreKey; + _theirRatchetKey = theirRatchetKey; + } + + return self; +} + + +@end diff --git a/SessionProtocolKit/Ratchet/AxolotlParameters.h b/SessionProtocolKit/Ratchet/AxolotlParameters.h new file mode 100644 index 000000000..5d83e9fa2 --- /dev/null +++ b/SessionProtocolKit/Ratchet/AxolotlParameters.h @@ -0,0 +1,13 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import +#import + +@protocol AxolotlParameters + +@property (nonatomic) ECKeyPair *ourIdentityKeyPair; +@property (nonatomic) NSData *theirIdentityKey; + +@end diff --git a/SessionProtocolKit/Ratchet/BobAxolotlParameters.h b/SessionProtocolKit/Ratchet/BobAxolotlParameters.h new file mode 100644 index 000000000..14ec22b7b --- /dev/null +++ b/SessionProtocolKit/Ratchet/BobAxolotlParameters.h @@ -0,0 +1,22 @@ +// +// BobAxolotlParameters.h +// AxolotlKit +// +// Created by Frederic Jacobs on 26/07/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import +#import "AxolotlParameters.h" + +@interface BobAxolotlParameters : NSObject + +@property (nonatomic, readonly)ECKeyPair *ourSignedPrekey; +@property (nonatomic, readonly)ECKeyPair *ourRatchetKey; +@property (nonatomic, readonly)ECKeyPair *ourOneTimePrekey; + +@property (nonatomic, readonly)NSData *theirBaseKey; + +- (instancetype)initWithMyIdentityKeyPair:(ECKeyPair*)ourIdentityKeyPair theirIdentityKey:(NSData*)theirIdentityKey ourSignedPrekey:(ECKeyPair*)ourSignedPrekey ourRatchetKey:(ECKeyPair*)ourRatchetKey ourOneTimePrekey:(ECKeyPair*)ourOneTimeKeyPair theirBaseKey:(NSData*)theirBaseKey; + +@end diff --git a/SessionProtocolKit/Ratchet/BobAxolotlParameters.m b/SessionProtocolKit/Ratchet/BobAxolotlParameters.m new file mode 100644 index 000000000..def81109f --- /dev/null +++ b/SessionProtocolKit/Ratchet/BobAxolotlParameters.m @@ -0,0 +1,37 @@ +// +// BobAxolotlParameters.m +// AxolotlKit +// +// Created by Frederic Jacobs on 26/07/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import "BobAxolotlParameters.h" +#import + +@implementation BobAxolotlParameters + +@synthesize theirIdentityKey=_theirIdentityKey, ourIdentityKeyPair=_ourIdentityKeyPair; + +- (instancetype)initWithMyIdentityKeyPair:(ECKeyPair*)ourIdentityKeyPair theirIdentityKey:(NSData*)theirIdentityKey ourSignedPrekey:(ECKeyPair*)ourSignedPrekey ourRatchetKey:(ECKeyPair*)ourRatchetKey ourOneTimePrekey:(ECKeyPair*)ourOneTimeKeyPair theirBaseKey:(NSData*)theirBaseKey{ + + OWSAssert(ourIdentityKeyPair); + OWSAssert(theirIdentityKey); + OWSAssert(ourSignedPrekey); + OWSAssert(ourRatchetKey); + OWSAssert(theirBaseKey); + + self = [super init]; + + if (self) { + _ourIdentityKeyPair = ourIdentityKeyPair; + _theirIdentityKey = theirIdentityKey; + _ourSignedPrekey = ourSignedPrekey; + _ourRatchetKey = ourRatchetKey; + _ourOneTimePrekey = ourOneTimeKeyPair; + _theirBaseKey = theirBaseKey; + } + return self; +} + +@end diff --git a/SessionProtocolKit/Ratchet/Chain.h b/SessionProtocolKit/Ratchet/Chain.h new file mode 100644 index 000000000..1fa3249e3 --- /dev/null +++ b/SessionProtocolKit/Ratchet/Chain.h @@ -0,0 +1,17 @@ +// +// Chain.h +// AxolotlKit +// +// Created by Frederic Jacobs on 02/09/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import +@class ChainKey; + +@protocol Chain + +-(ChainKey*)chainKey; +-(void)setChainKey:(ChainKey*)chainKey; + +@end diff --git a/SessionProtocolKit/Ratchet/ChainAndIndex.h b/SessionProtocolKit/Ratchet/ChainAndIndex.h new file mode 100644 index 000000000..57b82f945 --- /dev/null +++ b/SessionProtocolKit/Ratchet/ChainAndIndex.h @@ -0,0 +1,17 @@ +// +// ChainAndIndex.h +// AxolotlKit +// +// Created by Frederic Jacobs on 21/09/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import +#import "ChainKey.h" + +@interface ChainAndIndex : NSObject + +@property id chain; +@property int index; + +@end diff --git a/SessionProtocolKit/Ratchet/ChainAndIndex.m b/SessionProtocolKit/Ratchet/ChainAndIndex.m new file mode 100644 index 000000000..f326f4c1c --- /dev/null +++ b/SessionProtocolKit/Ratchet/ChainAndIndex.m @@ -0,0 +1,13 @@ +// +// ChainAndIndex.m +// AxolotlKit +// +// Created by Frederic Jacobs on 21/09/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import "ChainAndIndex.h" + +@implementation ChainAndIndex + +@end diff --git a/SessionProtocolKit/Ratchet/ChainKey.h b/SessionProtocolKit/Ratchet/ChainKey.h new file mode 100644 index 000000000..aad87afe3 --- /dev/null +++ b/SessionProtocolKit/Ratchet/ChainKey.h @@ -0,0 +1,25 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "Chain.h" +#import "MessageKeys.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ChainKey : NSObject + +@property (nonatomic, readonly) int index; +@property (nonatomic, readonly) NSData *key; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithData:(NSData *)chainKey index:(int)index NS_DESIGNATED_INITIALIZER; + +- (instancetype)nextChainKey; + +- (MessageKeys *)throws_messageKeys NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Ratchet/ChainKey.m b/SessionProtocolKit/Ratchet/ChainKey.m new file mode 100644 index 000000000..1cafdec7d --- /dev/null +++ b/SessionProtocolKit/Ratchet/ChainKey.m @@ -0,0 +1,96 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "ChainKey.h" +#import "TSDerivedSecrets.h" +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@implementation ChainKey + +static NSString *const kCoderKey = @"kCoderKey"; +static NSString *const kCoderIndex = @"kCoderIndex"; + +#define kTSKeySeedLength 1 + +static uint8_t kMessageKeySeed[kTSKeySeedLength] = { 01 }; +static uint8_t kChainKeySeed[kTSKeySeedLength] = { 02 }; + ++ (BOOL)supportsSecureCoding +{ + return YES; +} + +- (nullable id)initWithCoder:(NSCoder *)aDecoder +{ + NSData *key = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderKey]; + int index = [aDecoder decodeIntForKey:kCoderIndex]; + + return [self initWithData:key index:index]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder +{ + [aCoder encodeObject:_key forKey:kCoderKey]; + [aCoder encodeInt:_index forKey:kCoderIndex]; +} + +- (instancetype)initWithData:(NSData *)chainKey index:(int)index +{ + OWSAssert(chainKey.length == 32); + OWSAssert(index >= 0); + + self = [super init]; + + if (self) { + _key = chainKey; + _index = index; + } + + return self; +} + +- (instancetype)nextChainKey +{ + NSData *nextCK = [self baseMaterial:[NSData dataWithBytes:kChainKeySeed length:kTSKeySeedLength]]; + OWSAssert(nextCK.length == 32); + + int nextIndex; + ows_add_overflow(self.index, 1, &nextIndex); + return [[ChainKey alloc] initWithData:nextCK index:nextIndex]; +} + +- (MessageKeys *)throws_messageKeys +{ + NSData *inputKeyMaterial = [self baseMaterial:[NSData dataWithBytes:kMessageKeySeed length:kTSKeySeedLength]]; + TSDerivedSecrets *derivedSecrets = [TSDerivedSecrets throws_derivedMessageKeysWithData:inputKeyMaterial]; + return [[MessageKeys alloc] initWithCipherKey:derivedSecrets.cipherKey + macKey:derivedSecrets.macKey + iv:derivedSecrets.iv + index:self.index]; +} + +- (NSData *)baseMaterial:(NSData *)seed +{ + OWSAssert(self.key); + OWSAssert(self.key.length == 32); + OWSAssert(seed); + OWSAssert(seed.length == kTSKeySeedLength); + + NSMutableData *_Nullable bufferData = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; + OWSAssert(bufferData); + + CCHmacContext ctx; + CCHmacInit(&ctx, kCCHmacAlgSHA256, [self.key bytes], [self.key length]); + CCHmacUpdate(&ctx, [seed bytes], [seed length]); + CCHmacFinal(&ctx, bufferData.mutableBytes); + return [bufferData copy]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Ratchet/MessageKeys.h b/SessionProtocolKit/Ratchet/MessageKeys.h new file mode 100644 index 000000000..6ce8bad41 --- /dev/null +++ b/SessionProtocolKit/Ratchet/MessageKeys.h @@ -0,0 +1,20 @@ +// +// TSMessageKeys.h +// AxolotlKit +// +// Created by Frederic Jacobs on 09/03/14. +// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// + +#import + +@interface MessageKeys : NSObject + +- (instancetype)initWithCipherKey:(NSData*)cipherKey macKey:(NSData*)macKey iv:(NSData*)data index:(int)index; + +@property (readonly)NSData *cipherKey; +@property (readonly)NSData *macKey; +@property (readonly)NSData *iv; +@property (readonly)int index; + +@end diff --git a/SessionProtocolKit/Ratchet/MessageKeys.m b/SessionProtocolKit/Ratchet/MessageKeys.m new file mode 100644 index 000000000..cc14fff0b --- /dev/null +++ b/SessionProtocolKit/Ratchet/MessageKeys.m @@ -0,0 +1,62 @@ +// +// TSMessageKeys.m +// AxolotlKit +// +// Created by Frederic Jacobs on 09/03/14. +// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// + +#import "MessageKeys.h" +#import + +static NSString* const kCoderMessageKeysCipherKey = @"kCoderMessageKeysCipherKey"; +static NSString* const kCoderMessageKeysMacKey = @"kCoderMessageKeysMacKey"; +static NSString* const kCoderMessageKeysIVKey = @"kCoderMessageKeysIVKey"; +static NSString* const kCoderMessageKeysIndex = @"kCoderMessageKeysIndex"; + + +@implementation MessageKeys + ++ (BOOL)supportsSecureCoding{ + return YES; +} + +- (id)initWithCoder:(NSCoder *)aDecoder{ + self = [self initWithCipherKey:[aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderMessageKeysCipherKey] + macKey:[aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderMessageKeysMacKey] + iv:[aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderMessageKeysIVKey] + index:[aDecoder decodeIntForKey:kCoderMessageKeysIndex]]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder{ + [aCoder encodeObject:self.cipherKey forKey:kCoderMessageKeysCipherKey]; + [aCoder encodeObject:self.macKey forKey:kCoderMessageKeysMacKey]; + [aCoder encodeObject:self.iv forKey:kCoderMessageKeysIVKey]; + [aCoder encodeInt:self.index forKey:kCoderMessageKeysIndex]; +} + + +- (instancetype)initWithCipherKey:(NSData*)cipherKey macKey:(NSData*)macKey iv:(NSData *)data index:(int)index{ + + OWSAssert(cipherKey); + OWSAssert(macKey); + OWSAssert(data); + + self = [super init]; + + if (self) { + _cipherKey = cipherKey; + _macKey = macKey; + _iv = data; + _index = index; + } + + return self; +} + +-(NSString*) debugDescription { + return [NSString stringWithFormat:@"cipherKey: %@\n macKey %@\n",self.cipherKey,self.macKey]; +} + +@end diff --git a/SessionProtocolKit/Ratchet/RKCK.h b/SessionProtocolKit/Ratchet/RKCK.h new file mode 100644 index 000000000..895717124 --- /dev/null +++ b/SessionProtocolKit/Ratchet/RKCK.h @@ -0,0 +1,19 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "Chain.h" +#import "RootKey.h" +#import "SessionState.h" +#import + +@class ECKeyPair; + +@interface RKCK : NSObject + +@property (nonatomic,strong) RootKey *rootKey; +@property (nonatomic,strong) ChainKey *chainKey; + +-(instancetype) initWithRK:(RootKey*)rootKey CK:(ChainKey*)chainKey; + +@end diff --git a/SessionProtocolKit/Ratchet/RKCK.m b/SessionProtocolKit/Ratchet/RKCK.m new file mode 100644 index 000000000..e1c8c1506 --- /dev/null +++ b/SessionProtocolKit/Ratchet/RKCK.m @@ -0,0 +1,22 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "RKCK.h" +#import +#import "TSDerivedSecrets.h" +#import + +@implementation RKCK + +- (instancetype)initWithRK:(RootKey*)rootKey CK:(ChainKey*)chainKey{ + OWSAssert(rootKey); + OWSAssert(chainKey); + + self = [super init]; + self.rootKey = rootKey; + self.chainKey = chainKey; + return self; +} + +@end diff --git a/SessionProtocolKit/Ratchet/RatchetingSession.h b/SessionProtocolKit/Ratchet/RatchetingSession.h new file mode 100644 index 000000000..d141123c6 --- /dev/null +++ b/SessionProtocolKit/Ratchet/RatchetingSession.h @@ -0,0 +1,41 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +@class AliceAxolotlParameters; +@class BobAxolotlParameters; +@class ECKeyPair; +@class SessionState; + +@interface RatchetingSession : NSObject + ++ (void)throws_initializeSession:(SessionState *)session + sessionVersion:(int)sessionVersion + AliceParameters:(AliceAxolotlParameters *)parameters NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + ++ (BOOL)initializeSession:(SessionState *)session + sessionVersion:(int)sessionVersion + aliceParameters:(AliceAxolotlParameters *)aliceParameters + error:(NSError **)outError; + ++ (void)throws_initializeSession:(SessionState *)session + sessionVersion:(int)sessionVersion + BobParameters:(BobAxolotlParameters *)parameters NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + ++ (BOOL)initializeSession:(SessionState *)session + sessionVersion:(int)sessionVersion + bobParameters:(BobAxolotlParameters *)bobParameters + error:(NSError **)outError; + +/** + * For testing purposes + */ + ++ (void)throws_initializeSession:(SessionState *)session + sessionVersion:(int)sessionVersion + AliceParameters:(AliceAxolotlParameters *)parameters + senderRatchet:(ECKeyPair *)ratchet NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +@end diff --git a/SessionProtocolKit/Ratchet/RatchetingSession.m b/SessionProtocolKit/Ratchet/RatchetingSession.m new file mode 100644 index 000000000..82d602f88 --- /dev/null +++ b/SessionProtocolKit/Ratchet/RatchetingSession.m @@ -0,0 +1,188 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "RatchetingSession.h" +#import "AliceAxolotlParameters.h" +#import "BobAxolotlParameters.h" +#import "ChainKey.h" +#import "RootKey.h" +#import "SessionState.h" +#import +#import +#import +#import + +@interface DHEResult : NSObject + +@property (nonatomic, readonly) RootKey *rootKey; +@property (nonatomic, readonly) NSData *chainKey; + +- (instancetype)init_throws_withMasterKey:(NSData *)data NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +@end + +@implementation DHEResult + +- (instancetype)init_throws_withMasterKey:(NSData *)data +{ + // DHE Result is expected to be the result of 3 or 4 DHEs outputting 32 bytes each, + // plus the 32 discontinuity bytes added to make V3 incompatible with V2 + OWSAssert([data length] == 32 * 4 || [data length] == 32 * 5); + + self = [super init]; + const char *HKDFDefaultSalt[4] = {0}; + NSData *salt = [NSData dataWithBytes:HKDFDefaultSalt length:sizeof(HKDFDefaultSalt)]; + NSData *info = [@"WhisperText" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *derivedMaterial = [HKDFKit deriveKey:data info:info salt:salt outputSize:64]; + OWSAssert(derivedMaterial.length == 64); + _rootKey = [[RootKey alloc] initWithData:[derivedMaterial subdataWithRange:NSMakeRange(0, 32)]]; + _chainKey = [derivedMaterial subdataWithRange:NSMakeRange(32, 32)]; + + return self; +} + +@end + + +@implementation RatchetingSession + ++ (void)throws_initializeSession:(SessionState *)session + sessionVersion:(int)sessionVersion + AliceParameters:(AliceAxolotlParameters *)parameters +{ + OWSAssert(session); + OWSAssert(parameters); + + ECKeyPair *sendingRatchetKey = [Curve25519 generateKeyPair]; + OWSAssert(sendingRatchetKey); + [self throws_initializeSession:session + sessionVersion:sessionVersion + AliceParameters:parameters + senderRatchet:sendingRatchetKey]; +} + ++ (BOOL)initializeSession:(SessionState *)session + sessionVersion:(int)sessionVersion + bobParameters:(BobAxolotlParameters *)bobParameters + error:(NSError **)outError +{ + return [SCKExceptionWrapper + tryBlock:^{ + [self throws_initializeSession:session sessionVersion:sessionVersion BobParameters:bobParameters]; + } + error:outError]; +} + ++ (void)throws_initializeSession:(SessionState *)session + sessionVersion:(int)sessionVersion + BobParameters:(BobAxolotlParameters *)parameters +{ + OWSAssert(session); + OWSAssert(parameters); + + [session setVersion:sessionVersion]; + [session setRemoteIdentityKey:parameters.theirIdentityKey]; + [session setLocalIdentityKey:parameters.ourIdentityKeyPair.publicKey]; + + DHEResult *result = [self throws_DHEKeyAgreement:parameters]; + OWSAssert(result); + + [session setSenderChain:parameters.ourRatchetKey chainKey:[[ChainKey alloc]initWithData:result.chainKey index:0]]; + [session setRootKey:result.rootKey]; +} + ++ (BOOL)initializeSession:(SessionState *)session + sessionVersion:(int)sessionVersion + aliceParameters:(AliceAxolotlParameters *)aliceParameters + error:(NSError **)outError +{ + return [SCKExceptionWrapper + tryBlock:^{ + [self throws_initializeSession:session sessionVersion:sessionVersion AliceParameters:aliceParameters]; + } + error:outError]; +} + ++ (void)throws_initializeSession:(SessionState *)session + sessionVersion:(int)sessionVersion + AliceParameters:(AliceAxolotlParameters *)parameters + senderRatchet:(ECKeyPair *)sendingRatchet +{ + + OWSAssert(session); + OWSAssert(parameters); + OWSAssert(sendingRatchet); + + [session setVersion:sessionVersion]; + [session setRemoteIdentityKey:parameters.theirIdentityKey]; + [session setLocalIdentityKey:parameters.ourIdentityKeyPair.publicKey]; + + DHEResult *result = [self throws_DHEKeyAgreement:parameters]; + OWSAssert(result); + RKCK *sendingChain = + [result.rootKey throws_createChainWithTheirEphemeral:parameters.theirRatchetKey ourEphemeral:sendingRatchet]; + OWSAssert(sendingChain); + + [session addReceiverChain:parameters.theirRatchetKey chainKey:[[ChainKey alloc]initWithData:result.chainKey index:0]]; + [session setSenderChain:sendingRatchet chainKey:sendingChain.chainKey]; + [session setRootKey:sendingChain.rootKey]; +} + ++ (DHEResult *)throws_DHEKeyAgreement:(id)parameters +{ + OWSAssert(parameters); + + NSMutableData *masterKey = [NSMutableData data]; + + [masterKey appendData:[self discontinuityBytes]]; + + if ([parameters isKindOfClass:[AliceAxolotlParameters class]]) { + AliceAxolotlParameters *params = (AliceAxolotlParameters*)parameters; + + [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirSignedPreKey + andKeyPair:params.ourIdentityKeyPair]]; + [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirIdentityKey + andKeyPair:params.ourBaseKey]]; + [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirSignedPreKey + andKeyPair:params.ourBaseKey]]; + if (params.theirOneTimePrekey) { + [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirOneTimePrekey + andKeyPair:params.ourBaseKey]]; + } + } else if ([parameters isKindOfClass:[BobAxolotlParameters class]]){ + BobAxolotlParameters *params = (BobAxolotlParameters*)parameters; + + [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirIdentityKey + andKeyPair:params.ourSignedPrekey]]; + [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirBaseKey + andKeyPair:params.ourIdentityKeyPair]]; + [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirBaseKey + andKeyPair:params.ourSignedPrekey]]; + if (params.ourOneTimePrekey) { + [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirBaseKey + andKeyPair:params.ourOneTimePrekey]]; + } + } + + return [[DHEResult alloc] init_throws_withMasterKey:masterKey]; +} + +/** + * The discontinuity bytes enforce that the session initialization is different between protocol V2 and V3. + * + * @return Returns 32-bytes of 0xFF + */ + ++ (NSData*)discontinuityBytes{ + NSMutableData *discontinuity = [NSMutableData data]; + int8_t byte = 0xFF; + + for (int i = 0; i < 32; i++) { + [discontinuity appendBytes:&byte length:sizeof(int8_t)]; + } + return [NSData dataWithData:discontinuity]; +} + + +@end diff --git a/SessionProtocolKit/Ratchet/ReceivingChain.h b/SessionProtocolKit/Ratchet/ReceivingChain.h new file mode 100644 index 000000000..9904bfd26 --- /dev/null +++ b/SessionProtocolKit/Ratchet/ReceivingChain.h @@ -0,0 +1,16 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import +#import "Chain.h" +#import + +@interface ReceivingChain : NSObject + +- (instancetype)initWithChainKey:(ChainKey*)chainKey senderRatchetKey:(NSData*)senderRatchet; + +@property NSMutableArray *messageKeysList; +@property NSData *senderRatchetKey; + +@end diff --git a/SessionProtocolKit/Ratchet/ReceivingChain.m b/SessionProtocolKit/Ratchet/ReceivingChain.m new file mode 100644 index 000000000..012113851 --- /dev/null +++ b/SessionProtocolKit/Ratchet/ReceivingChain.m @@ -0,0 +1,57 @@ +// +// ReceivingChain.m +// AxolotlKit +// +// Created by Frederic Jacobs on 02/09/14. +// Copyright (c) 2014 Frederic Jacobs. All rights reserved. +// + +#import "ReceivingChain.h" +#import + +@interface ReceivingChain () + +@property (nonatomic)ChainKey *chainKey; + +@end + +@implementation ReceivingChain + +static NSString* const kCoderChainKey = @"kCoderChainKey"; +static NSString* const kCoderSenderRatchet = @"kCoderSenderRatchet"; +static NSString* const kCoderMessageKeys = @"kCoderMessageKeys"; + ++ (BOOL)supportsSecureCoding{ + return YES; +} + +- (id)initWithCoder:(NSCoder *)aDecoder{ + self = [self initWithChainKey:[aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderChainKey] + senderRatchetKey:[aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderSenderRatchet]]; + if (self) { + self.messageKeysList = [aDecoder decodeObjectOfClass:[NSMutableArray class] forKey:kCoderMessageKeys]; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder{ + [aCoder encodeObject:self.chainKey forKey:kCoderChainKey]; + [aCoder encodeObject:self.senderRatchetKey forKey:kCoderSenderRatchet]; + [aCoder encodeObject:self.messageKeysList forKey:kCoderMessageKeys]; +} + +- (instancetype)initWithChainKey:(ChainKey *)chainKey senderRatchetKey:(NSData *)senderRatchet{ + OWSAssert(chainKey); + OWSAssert(senderRatchet); + + self = [super init]; + + self.chainKey = chainKey; + self.senderRatchetKey = senderRatchet; + self.messageKeysList = [NSMutableArray array]; + + return self; +} + +@end diff --git a/SessionProtocolKit/Ratchet/RootKey.h b/SessionProtocolKit/Ratchet/RootKey.h new file mode 100644 index 000000000..7882d7baa --- /dev/null +++ b/SessionProtocolKit/Ratchet/RootKey.h @@ -0,0 +1,18 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +@class ECKeyPair; +@class RKCK; + +@interface RootKey : NSObject + +- (instancetype)initWithData:(NSData *)data; +- (RKCK *)throws_createChainWithTheirEphemeral:(NSData *)theirEphemeral + ourEphemeral:(ECKeyPair *)ourEphemeral NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +@property (nonatomic, readonly) NSData *keyData; + +@end diff --git a/SessionProtocolKit/Ratchet/RootKey.m b/SessionProtocolKit/Ratchet/RootKey.m new file mode 100644 index 000000000..170794138 --- /dev/null +++ b/SessionProtocolKit/Ratchet/RootKey.m @@ -0,0 +1,64 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "RootKey.h" +#import "ChainKey.h" +#import "RKCK.h" +#import "TSDerivedSecrets.h" +#import +#import + +static NSString* const kCoderData = @"kCoderData"; + +@implementation RootKey + ++(BOOL)supportsSecureCoding{ + return YES; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder{ + [aCoder encodeObject:_keyData forKey:kCoderData]; +} + +- (id)initWithCoder:(NSCoder *)aDecoder{ + self = [super init]; + + if (self) { + _keyData = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderData]; + } + + return self; +} + +- (instancetype)initWithData:(NSData *)data{ + self = [super init]; + + OWSAssert(data.length == 32); + + if (self) { + _keyData = data; + } + + return self; +} + +- (RKCK *)throws_createChainWithTheirEphemeral:(NSData *)theirEphemeral ourEphemeral:(ECKeyPair *)ourEphemeral +{ + OWSAssert(theirEphemeral); + OWSAssert(ourEphemeral); + + NSData *sharedSecret = [Curve25519 generateSharedSecretFromPublicKey:theirEphemeral andKeyPair:ourEphemeral]; + OWSAssert(sharedSecret.length == 32); + + TSDerivedSecrets *secrets = + [TSDerivedSecrets throws_derivedRatchetedSecretsWithSharedSecret:sharedSecret rootKey:_keyData]; + OWSAssert(secrets); + + RKCK *newRKCK = [[RKCK alloc] initWithRK:[[RootKey alloc] initWithData:secrets.cipherKey] + CK:[[ChainKey alloc] initWithData:secrets.macKey index:0]]; + + return newRKCK; +} + +@end diff --git a/SessionProtocolKit/Ratchet/SendingChain.h b/SessionProtocolKit/Ratchet/SendingChain.h new file mode 100644 index 000000000..c6e787d09 --- /dev/null +++ b/SessionProtocolKit/Ratchet/SendingChain.h @@ -0,0 +1,16 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import +#import "Chain.h" + +#import + +@interface SendingChain : NSObject + +-(instancetype)initWithChainKey:(ChainKey*)chainKey senderRatchetKeyPair:(ECKeyPair*)keyPair; + +@property ECKeyPair *senderRatchetKeyPair; + +@end diff --git a/SessionProtocolKit/Ratchet/SendingChain.m b/SessionProtocolKit/Ratchet/SendingChain.m new file mode 100644 index 000000000..768c634d0 --- /dev/null +++ b/SessionProtocolKit/Ratchet/SendingChain.m @@ -0,0 +1,54 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "SendingChain.h" +#import "ChainKey.h" +#import + +@interface SendingChain () + +@property (nonatomic)ChainKey *chainKey; + +@end + +@implementation SendingChain + +static NSString* const kCoderChainKey = @"kCoderChainKey"; +static NSString* const kCoderSenderRatchet = @"kCoderSenderRatchet"; + ++ (BOOL)supportsSecureCoding{ + return YES; +} + +- (id)initWithCoder:(NSCoder *)aDecoder{ + self = [self initWithChainKey:[aDecoder decodeObjectOfClass:[ChainKey class] forKey:kCoderChainKey] + senderRatchetKeyPair:[aDecoder decodeObjectOfClass:[ECKeyPair class] forKey:kCoderSenderRatchet]]; + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder{ + [aCoder encodeObject:self.chainKey forKey:kCoderChainKey]; + [aCoder encodeObject:self.senderRatchetKeyPair forKey:kCoderSenderRatchet]; +} + +- (instancetype)initWithChainKey:(ChainKey *)chainKey senderRatchetKeyPair:(ECKeyPair *)keyPair{ + self = [super init]; + + OWSAssert(chainKey.key.length == 32); + OWSAssert(keyPair); + + if (self) { + _chainKey = chainKey; + _senderRatchetKeyPair = keyPair; + } + + return self; +} + +-(ChainKey *)chainKey{ + return _chainKey; +} + +@end diff --git a/SessionProtocolKit/Ratchet/TSDerivedSecrets.h b/SessionProtocolKit/Ratchet/TSDerivedSecrets.h new file mode 100644 index 000000000..9497dfe83 --- /dev/null +++ b/SessionProtocolKit/Ratchet/TSDerivedSecrets.h @@ -0,0 +1,20 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +@interface TSDerivedSecrets : NSData + ++ (instancetype)throws_derivedInitialSecretsWithMasterKey:(NSData *)masterKey + NS_SWIFT_UNAVAILABLE("throws objc exceptions"); ++ (instancetype)throws_derivedRatchetedSecretsWithSharedSecret:(NSData *)masterKey + rootKey:(NSData *)rootKey + NS_SWIFT_UNAVAILABLE("throws objc exceptions"); ++ (instancetype)throws_derivedMessageKeysWithData:(NSData *)data NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +@property NSData *cipherKey; +@property NSData *macKey; +@property NSData *iv; + +@end diff --git a/SessionProtocolKit/Ratchet/TSDerivedSecrets.m b/SessionProtocolKit/Ratchet/TSDerivedSecrets.m new file mode 100644 index 000000000..0aa8eda75 --- /dev/null +++ b/SessionProtocolKit/Ratchet/TSDerivedSecrets.m @@ -0,0 +1,67 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "TSDerivedSecrets.h" +#import +#import +#import + +@implementation TSDerivedSecrets + ++ (instancetype)throws_derivedSecretsWithSeed:(NSData *)masterKey salt:(NSData *)salt info:(NSData *)info +{ + OWSAssert(masterKey.length == 32); + OWSAssert(info); + + TSDerivedSecrets *secrets = [[TSDerivedSecrets alloc] init]; + OWSAssert(secrets); + + if (!salt) { + const char *HKDFDefaultSalt[4] = {0}; + salt = [NSData dataWithBytes:HKDFDefaultSalt length:sizeof(HKDFDefaultSalt)]; + } + + @try { + NSData *derivedMaterial = [HKDFKit deriveKey:masterKey info:info salt:salt outputSize:96]; + secrets.cipherKey = [derivedMaterial subdataWithRange:NSMakeRange(0, 32)]; + secrets.macKey = [derivedMaterial subdataWithRange:NSMakeRange(32, 32)]; + secrets.iv = [derivedMaterial subdataWithRange:NSMakeRange(64, 16)]; + } + @catch (NSException *exception) { + @throw NSInvalidArgumentException; + } + + OWSAssert(secrets.cipherKey.length == 32); + OWSAssert(secrets.macKey.length == 32); + OWSAssert(secrets.iv.length == 16); + + return secrets; +} + ++ (instancetype)throws_derivedInitialSecretsWithMasterKey:(NSData *)masterKey +{ + OWSAssert(masterKey); + + NSData *info = [@"WhisperText" dataUsingEncoding:NSUTF8StringEncoding]; + return [self throws_derivedSecretsWithSeed:masterKey salt:nil info:info]; +} + ++ (instancetype)throws_derivedRatchetedSecretsWithSharedSecret:(NSData *)masterKey rootKey:(NSData *)rootKey +{ + OWSAssert(masterKey); + OWSAssert(rootKey); + + NSData *info = [@"WhisperRatchet" dataUsingEncoding:NSUTF8StringEncoding]; + return [self throws_derivedSecretsWithSeed:masterKey salt:rootKey info:info]; +} + ++ (instancetype)throws_derivedMessageKeysWithData:(NSData *)data +{ + OWSAssert(data); + + NSData *info = [@"WhisperMessageKeys" dataUsingEncoding:NSUTF8StringEncoding]; + return [self throws_derivedSecretsWithSeed:data salt:nil info:info]; +} + +@end diff --git a/SessionProtocolKit/SessionCipher.h b/SessionProtocolKit/SessionCipher.h new file mode 100644 index 000000000..80e330969 --- /dev/null +++ b/SessionProtocolKit/SessionCipher.h @@ -0,0 +1,44 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "AxolotlStore.h" +#import "IdentityKeyStore.h" +#import "PreKeyStore.h" +#import "PreKeyWhisperMessage.h" +#import "SessionState.h" +#import "SessionStore.h" +#import "SignedPreKeyStore.h" +#import "WhisperMessage.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SessionCipher : NSObject + +- (instancetype)initWithAxolotlStore:(id)sessionStore recipientId:(NSString*)recipientId deviceId:(int)deviceId; + +- (instancetype)initWithSessionStore:(id)sessionStore preKeyStore:(id)preKeyStore signedPreKeyStore:(id)signedPreKeyStore identityKeyStore:(id)identityKeyStore recipientId:(NSString*)recipientId deviceId:(int)deviceId; + +// protocolContext is an optional parameter that can be used to ensure that all +// identity and session store writes are coordinated and/or occur within a single +// transaction. +- (id)throws_encryptMessage:(NSData *)paddedMessage + protocolContext:(nullable id)protocolContext NS_SWIFT_UNAVAILABLE("throws objc exceptions"); +- (nullable id)encryptMessage:(NSData *)paddedMessage + protocolContext:(nullable id)protocolContext + error:(NSError **)outError; + +- (NSData *)throws_decrypt:(id)whisperMessage + protocolContext:(nullable id)protocolContext NS_SWIFT_UNAVAILABLE("throws objc exceptions"); +- (nullable NSData *)decrypt:(id)whisperMessage + protocolContext:(nullable id)protocolContext + error:(NSError **)outError; + +- (int)throws_remoteRegistrationId:(nullable id)protocolContext NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +- (int)throws_sessionVersion:(nullable id)protocolContext NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/SessionCipher.m b/SessionProtocolKit/SessionCipher.m new file mode 100644 index 000000000..7b2cbeece --- /dev/null +++ b/SessionProtocolKit/SessionCipher.m @@ -0,0 +1,527 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "SessionCipher.h" +#import "AES-CBC.h" +#import "AxolotlExceptions.h" +#import "AxolotlParameters.h" +#import "ChainKey.h" +#import "MessageKeys.h" +#import "NSData+keyVersionByte.h" +#import "PreKeyStore.h" +#import "RootKey.h" +#import "SessionBuilder.h" +#import "SessionState.h" +#import "SessionStore.h" +#import "SignedPreKeyStore.h" +#import "WhisperMessage.h" +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SessionCipher () + +@property (nonatomic, readonly) NSString *recipientId; +@property (nonatomic, readonly) int deviceId; + +@property (nonatomic, readonly) id identityKeyStore; +@property (nonatomic, readonly) id sessionStore; +@property (nonatomic, readonly) SessionBuilder *sessionBuilder; +@property (nonatomic, readonly) id prekeyStore; + +@end + +#pragma mark - + +@implementation SessionCipher + +- (instancetype)initWithAxolotlStore:(id)sessionStore recipientId:(NSString*)recipientId deviceId:(int)deviceId{ + OWSAssert(sessionStore); + OWSAssert(recipientId); + return [self initWithSessionStore:sessionStore + preKeyStore:sessionStore + signedPreKeyStore:sessionStore + identityKeyStore:sessionStore + recipientId:recipientId + deviceId:deviceId]; +} + +- (instancetype)initWithSessionStore:(id)sessionStore + preKeyStore:(id)preKeyStore + signedPreKeyStore:(id)signedPreKeyStore + identityKeyStore:(id)identityKeyStore + recipientId:(NSString*)recipientId + deviceId:(int)deviceId{ + OWSAssert(sessionStore); + OWSAssert(preKeyStore); + OWSAssert(signedPreKeyStore); + OWSAssert(identityKeyStore); + OWSAssert(recipientId); + + self = [super init]; + + if (self){ + _recipientId = recipientId; + _deviceId = deviceId; + _sessionStore = sessionStore; + _identityKeyStore = identityKeyStore; + _prekeyStore = preKeyStore; + _sessionBuilder = [[SessionBuilder alloc] initWithSessionStore:sessionStore + preKeyStore:preKeyStore + signedPreKeyStore:signedPreKeyStore + identityKeyStore:identityKeyStore + recipientId:recipientId + deviceId:deviceId]; + } + + return self; +} + +- (nullable id)encryptMessage:(NSData *)paddedMessage + protocolContext:(nullable id)protocolContext + error:(NSError **)outError +{ + __block id result; + [SCKExceptionWrapper + tryBlock:^{ + result = [self throws_encryptMessage:paddedMessage protocolContext:protocolContext]; + } + error:outError]; + + return result; +} + +- (id)throws_encryptMessage:(NSData *)paddedMessage protocolContext:(nullable id)protocolContext +{ + OWSAssert(paddedMessage); + + SessionRecord *sessionRecord = + [self.sessionStore loadSession:self.recipientId deviceId:self.deviceId protocolContext:protocolContext]; + SessionState *sessionState = sessionRecord.sessionState; + ChainKey *chainKey = sessionState.senderChainKey; + MessageKeys *messageKeys = [chainKey throws_messageKeys]; + NSData *senderRatchetKey = sessionState.senderRatchetKey; + int previousCounter = sessionState.previousCounter; + int sessionVersion = sessionState.version; + + if (![self.identityKeyStore isTrustedIdentityKey:sessionState.remoteIdentityKey + recipientId:self.recipientId + direction:TSMessageDirectionOutgoing + protocolContext:protocolContext]) { + DDLogWarn( + @"%@ Previously known identity key for while encrypting for recipient: %@", self.tag, self.recipientId); + @throw [NSException exceptionWithName:UntrustedIdentityKeyException + reason:@"There is a previously known identity key." + userInfo:@{}]; + } + + [self.identityKeyStore saveRemoteIdentity:sessionState.remoteIdentityKey + recipientId:self.recipientId + protocolContext:protocolContext]; + + NSData *ciphertextBody = + [AES_CBC throws_encryptCBCMode:paddedMessage withKey:messageKeys.cipherKey withIV:messageKeys.iv]; + + id cipherMessage = + [[WhisperMessage alloc] init_throws_withVersion:sessionVersion + macKey:messageKeys.macKey + senderRatchetKey:senderRatchetKey.prependKeyType + counter:chainKey.index + previousCounter:previousCounter + cipherText:ciphertextBody + senderIdentityKey:sessionState.localIdentityKey.prependKeyType + receiverIdentityKey:sessionState.remoteIdentityKey.prependKeyType]; + + if ([sessionState hasUnacknowledgedPreKeyMessage]) { + PendingPreKey *items = [sessionState unacknowledgedPreKeyMessageItems]; + int localRegistrationId = [sessionState localRegistrationId]; + + DDLogInfo(@"Building PreKeyWhisperMessage for: %@ with preKeyId: %d", self.recipientId, items.preKeyId); + + cipherMessage = + [[PreKeyWhisperMessage alloc] init_throws_withWhisperMessage:cipherMessage + registrationId:localRegistrationId + prekeyId:items.preKeyId + signedPrekeyId:items.signedPreKeyId + baseKey:items.baseKey.prependKeyType + identityKey:sessionState.localIdentityKey.prependKeyType]; + } + + [sessionState setSenderChainKey:[chainKey nextChainKey]]; + [self.sessionStore storeSession:self.recipientId + deviceId:self.deviceId + session:sessionRecord + protocolContext:protocolContext]; + + return cipherMessage; +} + +- (nullable NSData *)decrypt:(id)whisperMessage + protocolContext:(nullable id)protocolContext + error:(NSError **)outError +{ + __block NSData *_Nullable result; + [SCKExceptionWrapper + tryBlock:^{ + result = [self throws_decrypt:whisperMessage protocolContext:protocolContext]; + } + error:outError]; + + return result; +} + +- (NSData *)throws_decrypt:(id)whisperMessage protocolContext:(nullable id)protocolContext +{ + OWSAssert(whisperMessage); + + switch (whisperMessage.cipherMessageType) { + case CipherMessageType_Whisper: + if (![whisperMessage isKindOfClass:[WhisperMessage class]]) { + OWSFail(@"Unexpected message type: %@", [whisperMessage class]); + return nil; + } + return [self throws_decryptWhisperMessage:(WhisperMessage *)whisperMessage protocolContext:protocolContext]; + case CipherMessageType_Prekey: + if (![whisperMessage isKindOfClass:[PreKeyWhisperMessage class]]) { + OWSFail(@"Unexpected message type: %@", [whisperMessage class]); + return nil; + } + return [self throws_decryptPreKeyWhisperMessage:(PreKeyWhisperMessage *)whisperMessage + protocolContext:protocolContext]; + default: + OWSFailDebug(@"Unexpected message type: %@", [whisperMessage class]); + return [NSData new]; + } +} + +- (NSData *)throws_decryptPreKeyWhisperMessage:(PreKeyWhisperMessage *)preKeyWhisperMessage + protocolContext:(nullable id)protocolContext +{ + OWSAssert(preKeyWhisperMessage); + + SessionRecord *sessionRecord = + [self.sessionStore loadSession:self.recipientId deviceId:self.deviceId protocolContext:protocolContext]; + int unsignedPreKeyId = [self.sessionBuilder throws_processPrekeyWhisperMessage:preKeyWhisperMessage + withSession:sessionRecord + protocolContext:protocolContext]; + NSData *plaintext = [self throws_decryptWithSessionRecord:sessionRecord + whisperMessage:preKeyWhisperMessage.message + protocolContext:protocolContext]; + + [self.sessionStore storeSession:self.recipientId + deviceId:self.deviceId + session:sessionRecord + protocolContext:protocolContext]; + + // If there was an unsigned PreKey + if (unsignedPreKeyId >= 0) { + [self.prekeyStore removePreKey:unsignedPreKeyId protocolContext:protocolContext]; + } + + return plaintext; +} + +- (NSData *)throws_decryptWhisperMessage:(WhisperMessage *)whisperMessage protocolContext:(nullable id)protocolContext +{ + OWSAssert(whisperMessage); + + SessionRecord *sessionRecord = + [self.sessionStore loadSession:self.recipientId deviceId:self.deviceId protocolContext:protocolContext]; + NSData *plaintext = [self throws_decryptWithSessionRecord:sessionRecord + whisperMessage:whisperMessage + protocolContext:protocolContext]; + + if (![self.identityKeyStore isTrustedIdentityKey:sessionRecord.sessionState.remoteIdentityKey + recipientId:self.recipientId + direction:TSMessageDirectionIncoming + protocolContext:protocolContext]) { + DDLogWarn( + @"%@ Previously known identity key for while decrypting from recipient: %@", self.tag, self.recipientId); + @throw [NSException exceptionWithName:UntrustedIdentityKeyException + reason:@"There is a previously known identity key." + userInfo:@{}]; + } + + [self.identityKeyStore saveRemoteIdentity:sessionRecord.sessionState.remoteIdentityKey + recipientId:self.recipientId + protocolContext:protocolContext]; + [self.sessionStore storeSession:self.recipientId + deviceId:self.deviceId + session:sessionRecord + protocolContext:protocolContext]; + + return plaintext; +} + +- (NSData *)throws_decryptWithSessionRecord:(SessionRecord *)sessionRecord + whisperMessage:(WhisperMessage *)whisperMessage + protocolContext:(nullable id)protocolContext +{ + OWSAssert(sessionRecord); + OWSAssert(whisperMessage); + + SessionState *sessionState = [sessionRecord sessionState]; + NSMutableArray *exceptions = [NSMutableArray array]; + + @try { + NSData *decryptedData = [self throws_decryptWithSessionState:sessionState + whisperMessage:whisperMessage + protocolContext:protocolContext]; + DDLogDebug(@"%@ successfully decrypted with current session state: %@", self.tag, sessionState); + return decryptedData; + } + @catch (NSException *exception) { + if ([exception.name isEqualToString:InvalidMessageException]) { + [exceptions addObject:exception]; + } else { + @throw exception; + } + } + + // If we can decrypt the message with an "old" session state, that means the sender is using an "old" session. + // In which case, we promote that session to "active" so as to converge on a single session for sending/receiving. + __block NSUInteger stateToPromoteIdx; + __block NSData *decryptedData; + [[sessionRecord previousSessionStates] + enumerateObjectsUsingBlock:^(SessionState *_Nonnull previousState, NSUInteger idx, BOOL *_Nonnull stop) { + @try { + decryptedData = [self throws_decryptWithSessionState:previousState + whisperMessage:whisperMessage + protocolContext:protocolContext]; + DDLogInfo(@"%@ successfully decrypted with PREVIOUS session state: %@", self.tag, previousState); + OWSAssert(decryptedData != nil); + stateToPromoteIdx = idx; + *stop = YES; + } @catch (NSException *exception) { + [exceptions addObject:exception]; + } + }]; + + if (decryptedData) { + SessionState *sessionStateToPromote = [sessionRecord previousSessionStates][stateToPromoteIdx]; + OWSAssert(sessionStateToPromote != nil); + DDLogInfo(@"%@ promoting session: %@", self.tag, sessionStateToPromote); + [[sessionRecord previousSessionStates] removeObjectAtIndex:stateToPromoteIdx]; + [sessionRecord promoteState:sessionStateToPromote]; + + return decryptedData; + } + + BOOL containsActiveSession = + [self.sessionStore containsSession:self.recipientId deviceId:self.deviceId protocolContext:protocolContext]; + DDLogError(@"%@ No valid session for recipient: %@ containsActiveSession: %@, previousStates: %lu", + self.tag, + self.recipientId, + (containsActiveSession ? @"YES" : @"NO"), + (unsigned long)sessionRecord.previousSessionStates.count); + + if (containsActiveSession) { + @throw [NSException exceptionWithName:InvalidMessageException + reason:@"No valid sessions" + userInfo:@{ + @"Exceptions" : exceptions + }]; + } else { + @throw [NSException + exceptionWithName:NoSessionException + reason:[NSString stringWithFormat:@"No session for: %@, %d", self.recipientId, self.deviceId] + userInfo:nil]; + } +} + +- (NSData *)throws_decryptWithSessionState:(SessionState *)sessionState + whisperMessage:(WhisperMessage *)whisperMessage + protocolContext:(nullable id)protocolContext +{ + OWSAssert(sessionState); + OWSAssert(whisperMessage); + + if (![sessionState hasSenderChain]) { + @throw [NSException exceptionWithName:InvalidMessageException reason:@"Uninitialized session!" userInfo:nil]; + } + + if (whisperMessage.version != sessionState.version) { + @throw [NSException exceptionWithName:InvalidMessageException + reason:[NSString stringWithFormat:@"Got message version %d but was expecting %d", + whisperMessage.version, + sessionState.version] + userInfo:nil]; + } + + int messageVersion = whisperMessage.version; + NSData *theirEphemeral = whisperMessage.senderRatchetKey.throws_removeKeyType; + int counter = whisperMessage.counter; + ChainKey *chainKey = [self throws_getOrCreateChainKeys:sessionState theirEphemeral:theirEphemeral]; + OWSAssert(chainKey); + MessageKeys *messageKeys = [self throws_getOrCreateMessageKeysForSession:sessionState + theirEphemeral:theirEphemeral + chainKey:chainKey + counter:counter]; + OWSAssert(messageKeys); + + [whisperMessage throws_verifyMacWithVersion:messageVersion + senderIdentityKey:sessionState.remoteIdentityKey + receiverIdentityKey:sessionState.localIdentityKey + macKey:messageKeys.macKey]; + + NSData *plaintext = + [AES_CBC throws_decryptCBCMode:whisperMessage.cipherText withKey:messageKeys.cipherKey withIV:messageKeys.iv]; + + [sessionState clearUnacknowledgedPreKeyMessage]; + + return plaintext; +} + +- (ChainKey *)throws_getOrCreateChainKeys:(SessionState *)sessionState theirEphemeral:(NSData *)theirEphemeral +{ + OWSAssert(sessionState); + OWSGuardWithException(theirEphemeral, InvalidMessageException); + OWSGuardWithException(theirEphemeral.length == 32, InvalidMessageException); + + @try { + if ([sessionState hasReceiverChain:theirEphemeral]) { + DDLogInfo(@"%@ %@.%d has existing receiver chain.", self.tag, self.recipientId, self.deviceId); + return [sessionState receiverChainKey:theirEphemeral]; + } else{ + DDLogInfo(@"%@ %@.%d creating new chains.", self.tag, self.recipientId, self.deviceId); + RootKey *rootKey = [sessionState rootKey]; + OWSAssert(rootKey.keyData.length == 32); + + ECKeyPair *ourEphemeral = [sessionState senderRatchetKeyPair]; + OWSAssert(ourEphemeral.publicKey.length == 32); + + RKCK *receiverChain = + [rootKey throws_createChainWithTheirEphemeral:theirEphemeral ourEphemeral:ourEphemeral]; + + ECKeyPair *ourNewEphemeral = [Curve25519 generateKeyPair]; + OWSAssert(ourNewEphemeral.publicKey.length == 32); + + RKCK *senderChain = [receiverChain.rootKey throws_createChainWithTheirEphemeral:theirEphemeral + ourEphemeral:ourNewEphemeral]; + + OWSAssert(senderChain.rootKey.keyData.length == 32); + [sessionState setRootKey:senderChain.rootKey]; + + OWSAssert(receiverChain.chainKey.key.length == 32); + [sessionState addReceiverChain:theirEphemeral chainKey:receiverChain.chainKey]; + + int previousCounter; + ows_sub_overflow(sessionState.senderChainKey.index, 1, &previousCounter); + [sessionState setPreviousCounter:MAX(previousCounter, 0)]; + [sessionState setSenderChain:ourNewEphemeral chainKey:senderChain.chainKey]; + + return receiverChain.chainKey; + } + } + @catch (NSException *exception) { + @throw [NSException exceptionWithName:InvalidMessageException reason:@"Chainkeys couldn't be derived" userInfo:nil]; + } +} + +- (MessageKeys *)throws_getOrCreateMessageKeysForSession:(SessionState *)sessionState + theirEphemeral:(NSData *)theirEphemeral + chainKey:(ChainKey *)chainKey + counter:(int)counter +{ + OWSAssert(sessionState); + OWSGuardWithException(theirEphemeral, InvalidMessageException); + OWSGuardWithException(theirEphemeral.length == 32, InvalidMessageException); + OWSAssert(chainKey); + + if (chainKey.index > counter) { + if ([sessionState hasMessageKeys:theirEphemeral counter:counter]) { + return [sessionState removeMessageKeys:theirEphemeral counter:counter]; + } else { + OWSLogInfo( + @"%@ %@.%d Duplicate message for counter: %d", self.tag, self.recipientId, self.deviceId, counter); + OWSLogFlush(); + @throw [NSException exceptionWithName:DuplicateMessageException reason:@"Received message with old counter!" userInfo:@{}]; + } + } + + NSUInteger kCounterLimit = 2000; + int counterOffset; + if (__builtin_sub_overflow(counter, chainKey.index, &counterOffset)) { + OWSFailDebug(@"Overflow while calculating counter offset"); + OWSRaiseException(InvalidMessageException, @"Overflow while calculating counter offset"); + } + if (counterOffset > kCounterLimit) { + OWSLogError(@"%@ %@.%d Exceeded future message limit: %lu, index: %d, counter: %d)", + self.tag, + self.recipientId, + self.deviceId, + (unsigned long)kCounterLimit, + chainKey.index, + counter); + OWSLogFlush(); + @throw [NSException exceptionWithName:InvalidMessageException + reason:@"Exceeded message keys chain length limit" + userInfo:@{}]; + } + + while (chainKey.index < counter) { + MessageKeys *messageKeys = [chainKey throws_messageKeys]; + [sessionState setMessageKeys:theirEphemeral messageKeys:messageKeys]; + chainKey = chainKey.nextChainKey; + } + + [sessionState setReceiverChainKey:theirEphemeral chainKey:[chainKey nextChainKey]]; + return [chainKey throws_messageKeys]; +} + +/** + * The current version data. First 4 bits are the current version and the last 4 ones are the lowest version we support. + * + * @return Current version data + */ + ++ (NSData*)currentProtocolVersion{ + NSUInteger index = 0b00100010; + NSData *versionByte = [NSData dataWithBytes:&index length:1]; + return versionByte; +} + +- (int)throws_remoteRegistrationId:(nullable id)protocolContext +{ + SessionRecord *_Nullable record = + [self.sessionStore loadSession:self.recipientId deviceId:_deviceId protocolContext:protocolContext]; + + if (!record) { + @throw [NSException exceptionWithName:NoSessionException reason:@"Trying to get registration Id of a non-existing session." userInfo:nil]; + } + + return record.sessionState.remoteRegistrationId; +} + +- (int)throws_sessionVersion:(nullable id)protocolContext +{ + SessionRecord *_Nullable record = + [self.sessionStore loadSession:self.recipientId deviceId:_deviceId protocolContext:protocolContext]; + + if (!record) { + @throw [NSException exceptionWithName:NoSessionException reason:@"Trying to get the version of a non-existing session." userInfo:nil]; + } + + return record.sessionState.version; +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Sessions/SessionBuilder.h b/SessionProtocolKit/Sessions/SessionBuilder.h new file mode 100644 index 000000000..80dde718f --- /dev/null +++ b/SessionProtocolKit/Sessions/SessionBuilder.h @@ -0,0 +1,44 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "AxolotlStore.h" +#import "IdentityKeyStore.h" +#import "PreKeyBundle.h" +#import "PreKeyStore.h" +#import "SessionStore.h" +#import "SignedPreKeyStore.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@class PreKeyWhisperMessage; + +extern const int kPreKeyOfLastResortId; + +@interface SessionBuilder : NSObject + +- (instancetype)initWithAxolotlStore:(id)sessionStore + recipientId:(NSString *)recipientId + deviceId:(int)deviceId; + +- (instancetype)initWithSessionStore:(id)sessionStore + preKeyStore:(id)preKeyStore + signedPreKeyStore:(id)signedPreKeyStore + identityKeyStore:(id)identityKeyStore + recipientId:(NSString *)recipientId + deviceId:(int)deviceId; + +- (void)throws_processPrekeyBundle:(PreKeyBundle *)preKeyBundle + protocolContext:(nullable id)protocolContext NS_SWIFT_UNAVAILABLE("throws objc exceptions"); +- (BOOL)processPrekeyBundle:(PreKeyBundle *)preKeyBundle + protocolContext:(nullable id)protocolContext + error:(NSError **)outError; + +- (int)throws_processPrekeyWhisperMessage:(PreKeyWhisperMessage *)message + withSession:(SessionRecord *)sessionRecord + protocolContext:(nullable id)protocolContext NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Sessions/SessionBuilder.m b/SessionProtocolKit/Sessions/SessionBuilder.m new file mode 100644 index 000000000..f50bd1faf --- /dev/null +++ b/SessionProtocolKit/Sessions/SessionBuilder.m @@ -0,0 +1,263 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "SessionBuilder.h" +#import "AliceAxolotlParameters.h" +#import "AxolotlExceptions.h" +#import "AxolotlParameters.h" +#import "AxolotlStore.h" +#import "BobAxolotlParameters.h" +#import "NSData+keyVersionByte.h" +#import "PreKeyWhisperMessage.h" +#import "PrekeyBundle.h" +#import "RatchetingSession.h" +#import "SessionState.h" +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +#define CURRENT_VERSION 3 +#define MINUMUM_VERSION 3 + +const int kPreKeyOfLastResortId = 0xFFFFFF; + +@interface SessionBuilder () + +@property (nonatomic, readonly)NSString* recipientId; +@property (nonatomic, readonly)int deviceId; + +@property(nonatomic, readonly)id sessionStore; +@property(nonatomic, readonly)id prekeyStore ; +@property(nonatomic, readonly)id signedPreKeyStore; +@property(nonatomic, readonly)id identityStore; + + +@end + +@implementation SessionBuilder + +- (instancetype)initWithAxolotlStore:(id)sessionStore recipientId:(NSString*)recipientId deviceId:(int)deviceId{ + OWSAssert(sessionStore); + OWSAssert(recipientId); + + return [self initWithSessionStore:sessionStore + preKeyStore:sessionStore + signedPreKeyStore:sessionStore + identityKeyStore:sessionStore + recipientId:recipientId + deviceId:deviceId]; +} + +- (instancetype)initWithSessionStore:(id)sessionStore + preKeyStore:(id)preKeyStore + signedPreKeyStore:(id)signedPreKeyStore + identityKeyStore:(id)identityKeyStore + recipientId:(NSString*)recipientId + deviceId:(int)deviceId{ + + OWSAssert(sessionStore); + OWSAssert(preKeyStore); + OWSAssert(signedPreKeyStore); + OWSAssert(identityKeyStore); + OWSAssert(recipientId); + + self = [super init]; + + if (self) { + _sessionStore = sessionStore; + _prekeyStore = preKeyStore; + _signedPreKeyStore = signedPreKeyStore; + _identityStore = identityKeyStore; + _recipientId = recipientId; + _deviceId = deviceId; + } + + return self; +} + +- (BOOL)processPrekeyBundle:(PreKeyBundle *)preKeyBundle + protocolContext:(nullable id)protocolContext + error:(NSError **)outError +{ + return [SCKExceptionWrapper + tryBlock:^{ + [self throws_processPrekeyBundle:preKeyBundle protocolContext:protocolContext]; + } + error:outError]; +} + +- (void)throws_processPrekeyBundle:(PreKeyBundle *)preKeyBundle protocolContext:(nullable id)protocolContext +{ + OWSAssert(preKeyBundle); + + NSData *theirIdentityKey = preKeyBundle.identityKey.throws_removeKeyType; + NSData *theirSignedPreKey = preKeyBundle.signedPreKeyPublic.throws_removeKeyType; + + if (![self.identityStore isTrustedIdentityKey:theirIdentityKey + recipientId:self.recipientId + direction:TSMessageDirectionOutgoing + protocolContext:protocolContext]) { + @throw [NSException exceptionWithName:UntrustedIdentityKeyException reason:@"Identity key is not valid" userInfo:@{}]; + } + + // NOTE: we use preKeyBundle.signedPreKeyPublic which has the key type byte. + if (![Ed25519 verifySignature:preKeyBundle.signedPreKeySignature + publicKey:theirIdentityKey + data:preKeyBundle.signedPreKeyPublic]) { + @throw [NSException exceptionWithName:InvalidKeyException reason:@"KeyIsNotValidlySigned" userInfo:nil]; + } + + SessionRecord *sessionRecord = + [self.sessionStore loadSession:self.recipientId deviceId:preKeyBundle.deviceId protocolContext:protocolContext]; + ECKeyPair *ourBaseKey = [Curve25519 generateKeyPair]; + NSData *theirOneTimePreKey = preKeyBundle.preKeyPublic.throws_removeKeyType; + int theirOneTimePreKeyId = preKeyBundle.preKeyId; + int theirSignedPreKeyId = preKeyBundle.signedPreKeyId; + + + AliceAxolotlParameters *params = + [[AliceAxolotlParameters alloc] initWithIdentityKey:[self.identityStore identityKeyPair:protocolContext] + theirIdentityKey:theirIdentityKey + ourBaseKey:ourBaseKey + theirSignedPreKey:theirSignedPreKey + theirOneTimePreKey:theirOneTimePreKey + theirRatchetKey:theirSignedPreKey]; + + if (!sessionRecord.isFresh) { + [sessionRecord archiveCurrentState]; + } + + [RatchetingSession throws_initializeSession:[sessionRecord sessionState] + sessionVersion:CURRENT_VERSION + AliceParameters:params]; + + DDLogInfo(@"setUnacknowledgedPreKeyMessage for: %@ with preKeyId: %d", self.recipientId, theirOneTimePreKeyId); + + [sessionRecord.sessionState setUnacknowledgedPreKeyMessage:theirOneTimePreKeyId signedPreKey:theirSignedPreKeyId baseKey:ourBaseKey.publicKey]; + [sessionRecord.sessionState setLocalRegistrationId:[self.identityStore localRegistrationId:protocolContext]]; + [sessionRecord.sessionState setRemoteRegistrationId:preKeyBundle.registrationId]; + [sessionRecord.sessionState setAliceBaseKey:ourBaseKey.publicKey]; + + // Saving invalidates any existing sessions, so be sure to save *before* storing the new session. + BOOL previousIdentityExisted = [self.identityStore saveRemoteIdentity:theirIdentityKey + recipientId:self.recipientId + protocolContext:protocolContext]; + if (previousIdentityExisted) { + DDLogInfo(@"%@ PKBundle removing previous session states for changed identity for recipient:%@", + self.tag, + self.recipientId); + [sessionRecord removePreviousSessionStates]; + } + + [self.sessionStore storeSession:self.recipientId + deviceId:self.deviceId + session:sessionRecord + protocolContext:protocolContext]; +} + +- (int)throws_processPrekeyWhisperMessage:(PreKeyWhisperMessage *)message + withSession:(SessionRecord *)sessionRecord + protocolContext:(nullable id)protocolContext +{ + OWSAssert(message); + OWSAssert(sessionRecord); + + int messageVersion = message.version; + NSData *theirIdentityKey = message.identityKey.throws_removeKeyType; + + if (![self.identityStore isTrustedIdentityKey:theirIdentityKey + recipientId:self.recipientId + direction:TSMessageDirectionIncoming + protocolContext:protocolContext]) { + @throw [NSException exceptionWithName:UntrustedIdentityKeyException reason:@"There is a previously known identity key." userInfo:@{}]; + } + + int unSignedPrekeyId = -1; + + switch (messageVersion) { + case 3: + unSignedPrekeyId = + [self throws_processPrekeyV3:message withSession:sessionRecord protocolContext:protocolContext]; + break; + default: + @throw [NSException exceptionWithName:InvalidVersionException reason:@"Trying to initialize with unknown version" userInfo:@{}]; + break; + } + + [self.identityStore saveRemoteIdentity:theirIdentityKey + recipientId:self.recipientId + protocolContext:protocolContext]; + + return unSignedPrekeyId; +} + +- (int)throws_processPrekeyV3:(PreKeyWhisperMessage *)message + withSession:(SessionRecord *)sessionRecord + protocolContext:(nullable id)protocolContext +{ + OWSAssert(message); + OWSAssert(sessionRecord); + + NSData *baseKey = message.baseKey.throws_removeKeyType; + + if ([sessionRecord hasSessionState:message.version baseKey:baseKey]) { + return -1; + } + + ECKeyPair *ourSignedPrekey = [self.signedPreKeyStore throws_loadSignedPrekey:message.signedPrekeyId].keyPair; + + ECKeyPair *_Nullable ourOneTimePreKey; + if (message.prekeyID >= 0) { + ourOneTimePreKey = [self.prekeyStore throws_loadPreKey:message.prekeyID].keyPair; + } else { + DDLogWarn(@"%@ Processing PreKey message which had no one-time prekey.", self.tag); + } + + BobAxolotlParameters *params = + [[BobAxolotlParameters alloc] initWithMyIdentityKeyPair:[self.identityStore identityKeyPair:protocolContext] + theirIdentityKey:message.identityKey.throws_removeKeyType + ourSignedPrekey:ourSignedPrekey + ourRatchetKey:ourSignedPrekey + ourOneTimePrekey:ourOneTimePreKey + theirBaseKey:baseKey]; + + if (!sessionRecord.isFresh) { + [sessionRecord archiveCurrentState]; + } + + [RatchetingSession throws_initializeSession:sessionRecord.sessionState + sessionVersion:message.version + BobParameters:params]; + + [sessionRecord.sessionState setLocalRegistrationId:[self.identityStore localRegistrationId:protocolContext]]; + [sessionRecord.sessionState setRemoteRegistrationId:message.registrationId]; + [sessionRecord.sessionState setAliceBaseKey:baseKey]; + + // If we used a prekey and it wasn't the prekey of last resort + if (message.prekeyID >= 0 && message.prekeyID != kPreKeyOfLastResortId) { + return message.prekeyID; + } else { + return -1; + } +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Sessions/SessionRecord.h b/SessionProtocolKit/Sessions/SessionRecord.h new file mode 100644 index 000000000..d695c8057 --- /dev/null +++ b/SessionProtocolKit/Sessions/SessionRecord.h @@ -0,0 +1,23 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import +#import "SessionState.h" + +@interface SessionRecord : NSObject + +- (instancetype)init; + +- (BOOL)hasSessionState:(int)version baseKey:(NSData*)aliceBaseKey; +- (SessionState*)sessionState; +- (NSMutableArray *)previousSessionStates; + +- (void)removePreviousSessionStates; +- (BOOL)isFresh; +- (void)markAsUnFresh; +- (void)archiveCurrentState; +- (void)promoteState:(SessionState*)promotedState; +- (void)setState:(SessionState*)sessionState; + +@end diff --git a/SessionProtocolKit/Sessions/SessionRecord.m b/SessionProtocolKit/Sessions/SessionRecord.m new file mode 100644 index 000000000..54d8267fb --- /dev/null +++ b/SessionProtocolKit/Sessions/SessionRecord.m @@ -0,0 +1,115 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "SessionRecord.h" +#import + +#define ARCHIVED_STATES_MAX_LENGTH 40 + +@interface SessionRecord() + +@property (nonatomic, retain) SessionState* sessionState; +@property (nonatomic, retain) NSMutableArray* previousStates; +@property (nonatomic) BOOL fresh; + +@end + +#define currentSessionStateKey @"currentSessionStateKey" +#define previousSessionsStateKey @"previousSessionStateKeys" + +@implementation SessionRecord + +- (instancetype)init{ + self = [super init]; + + if (self) { + _fresh = YES; + _sessionState = [SessionState new]; + _previousStates = [NSMutableArray new]; + } + + return self; +} + +#pragma mark Serialization + ++ (BOOL)supportsSecureCoding{ + return YES; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder{ + [aCoder encodeObject:self.previousStates forKey:previousSessionsStateKey]; + [aCoder encodeObject:self.sessionState forKey:currentSessionStateKey]; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder{ + self = [self init]; + + self.fresh = false; + + self.previousStates = [aDecoder decodeObjectOfClass:[NSMutableArray class] forKey:previousSessionsStateKey]; + self.sessionState = [aDecoder decodeObjectOfClass:[SessionState class] forKey:currentSessionStateKey]; + + return self; +} + +- (BOOL)hasSessionState:(int)version baseKey:(NSData *)aliceBaseKey{ + if (self.sessionState.version == version && [aliceBaseKey isEqualToData:self.sessionState.aliceBaseKey]) { + return YES; + } + + for (SessionState *state in self.previousStates) { + if (state.version == version && [aliceBaseKey isEqualToData:self.sessionState.aliceBaseKey]) { + return YES; + } + } + + return NO; +} + +- (SessionState*)sessionState{ + return _sessionState; +} + +- (NSMutableArray *)previousSessionStates +{ + return _previousStates; +} + +- (void)removePreviousSessionStates +{ + [_previousStates removeAllObjects]; +} + +- (BOOL)isFresh{ + return _fresh; +} + +- (void)markAsUnFresh +{ + self.fresh = false; +} + +- (void)archiveCurrentState{ + [self promoteState:[SessionState new]]; +} + +- (void)promoteState:(SessionState *)promotedState{ + [self.previousStates insertObject:self.sessionState atIndex:0]; + self.sessionState = promotedState; + + if (self.previousStates.count > ARCHIVED_STATES_MAX_LENGTH) { + NSUInteger deleteCount; + ows_sub_overflow(self.previousStates.count, ARCHIVED_STATES_MAX_LENGTH, &deleteCount); + NSIndexSet *indexesToDelete = + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(ARCHIVED_STATES_MAX_LENGTH, deleteCount)]; + [self.previousSessionStates removeObjectsAtIndexes:indexesToDelete]; + } +} + +- (void)setState:(SessionState *)sessionState{ + self.sessionState = sessionState; +} + +@end diff --git a/SessionProtocolKit/Sessions/SessionState.h b/SessionProtocolKit/Sessions/SessionState.h new file mode 100644 index 000000000..2344b055f --- /dev/null +++ b/SessionProtocolKit/Sessions/SessionState.h @@ -0,0 +1,71 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import +@class ECKeyPair; +#import "RKCK.h" +#import "MessageKeys.h" +#import "Chain.h" +#import "RootKey.h" + +/** + * Pending PreKeys + */ + +@interface PendingPreKey : NSObject + +@property (readonly) int preKeyId; +@property (readonly) int signedPreKeyId; +@property (readonly) NSData *baseKey; + +-(instancetype)initWithBaseKey:(NSData*)baseKey preKeyId:(int)preKeyId signedPreKeyId:(int)signedPrekeyId; + +@end + +@interface SessionState : NSObject + +/** + * AxolotlSessions are either retreived from the database or initiated on new discussions. They are serialized before being stored to make storing abstractions significantly simpler. Because we propose no abstraction for a contact and TextSecure has multi-device (multiple sessions with same identity key) support, the identityKeys need to be added manually. + */ + +@property(nonatomic) int version; +@property(nonatomic, copy) NSData *aliceBaseKey; +@property(nonatomic) NSData *remoteIdentityKey; +@property(nonatomic) NSData *localIdentityKey; +@property(nonatomic) int previousCounter; +@property(nonatomic) RootKey *rootKey; + +@property(nonatomic)int remoteRegistrationId; +@property(nonatomic)int localRegistrationId; + +- (NSData*)senderRatchetKey; +- (ECKeyPair*)senderRatchetKeyPair; + +- (BOOL)hasReceiverChain:(NSData *)senderEphemeral; +- (BOOL)hasSenderChain; + +- (ChainKey *)receiverChainKey:(NSData *)senderEphemeral; + +- (void)setReceiverChainKey:(NSData*)senderEphemeral chainKey:(ChainKey*)chainKey; + +- (void)addReceiverChain:(NSData*)senderRatchetKey chainKey:(ChainKey*)chainKey; + +- (void)setSenderChain:(ECKeyPair*)senderRatcherKeyPair chainKey:(ChainKey*)chainKey; + +- (ChainKey*)senderChainKey; + +- (void)setSenderChainKey:(ChainKey*)nextChainKey; + +- (BOOL)hasMessageKeys:(NSData*)senderRatchetKey counter:(int)counter; + +- (MessageKeys*)removeMessageKeys:(NSData*)senderRatcherKey counter:(int)counter; + +- (void)setMessageKeys:(NSData*)senderRatchetKey messageKeys:(MessageKeys*)messageKeys; + +- (void)setUnacknowledgedPreKeyMessage:(int)preKeyId signedPreKey:(int)signedPreKeyId baseKey:(NSData*)baseKey; +- (BOOL)hasUnacknowledgedPreKeyMessage; +- (PendingPreKey*)unacknowledgedPreKeyMessageItems; +- (void)clearUnacknowledgedPreKeyMessage; + +@end diff --git a/SessionProtocolKit/Sessions/SessionState.m b/SessionProtocolKit/Sessions/SessionState.m new file mode 100644 index 000000000..5807d6c9f --- /dev/null +++ b/SessionProtocolKit/Sessions/SessionState.m @@ -0,0 +1,313 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import +#import "SessionState.h" +#import "ReceivingChain.h" +#import "SendingChain.h" +#import "ChainAndIndex.h" +#import + +@implementation PendingPreKey + +static NSString* const kCoderPreKeyId = @"kCoderPreKeyId"; +static NSString* const kCoderSignedPreKeyId = @"kCoderSignedPreKeyId"; +static NSString* const kCoderBaseKey = @"kCoderBaseKey"; + + ++ (BOOL)supportsSecureCoding{ + return YES; +} + +-(instancetype)initWithBaseKey:(NSData*)baseKey preKeyId:(int)preKeyId signedPreKeyId:(int)signedPrekeyId{ + OWSAssert(baseKey); + + self = [super init]; + if (self) { + _preKeyId = preKeyId; + _signedPreKeyId = signedPrekeyId; + _baseKey = baseKey; + } + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder{ + self = [self initWithBaseKey:[aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderBaseKey] + preKeyId:[aDecoder decodeIntForKey:kCoderPreKeyId] + signedPreKeyId:[aDecoder decodeIntForKey:kCoderSignedPreKeyId]]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder{ + [aCoder encodeObject:_baseKey forKey:kCoderBaseKey]; + [aCoder encodeInt:_preKeyId forKey:kCoderPreKeyId]; + [aCoder encodeInt:_signedPreKeyId forKey:kCoderSignedPreKeyId]; +} + +@end + +@interface SessionState () + +@property SendingChain *sendingChain; // The outgoing sending chain +@property NSMutableArray *receivingChains; // NSArray of ReceivingChains +@property PendingPreKey *pendingPreKey; + +@end + +#pragma mark Keys for coder + +static NSString* const kCoderVersion = @"kCoderVersion"; +static NSString* const kCoderAliceBaseKey = @"kCoderAliceBaseKey"; +static NSString* const kCoderRemoteIDKey = @"kCoderRemoteIDKey"; +static NSString* const kCoderLocalIDKey = @"kCoderLocalIDKey"; +static NSString* const kCoderPreviousCounter = @"kCoderPreviousCounter"; +static NSString* const kCoderRootKey = @"kCoderRoot"; +static NSString* const kCoderLocalRegID = @"kCoderLocalRegID"; +static NSString* const kCoderRemoteRegID = @"kCoderRemoteRegID"; +static NSString* const kCoderReceiverChains = @"kCoderReceiverChains"; +static NSString* const kCoderSendingChain = @"kCoderSendingChain"; +static NSString* const kCoderPendingPrekey = @"kCoderPendingPrekey"; + +@implementation SessionState + ++ (BOOL)supportsSecureCoding{ + return YES; +} + +- (instancetype)init{ + self = [super init]; + + if (self) { + self.receivingChains = [NSMutableArray array]; + } + + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder{ + self = [self init]; + + if (self) { + self.version = [aDecoder decodeIntForKey:kCoderVersion]; + self.aliceBaseKey = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderAliceBaseKey]; + self.remoteIdentityKey = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderRemoteIDKey]; + self.localIdentityKey = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderLocalIDKey]; + self.previousCounter = [aDecoder decodeIntForKey:kCoderPreviousCounter]; + self.rootKey = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCoderRootKey]; + self.remoteRegistrationId = [[aDecoder decodeObjectOfClass:[NSNumber class] forKey:kCoderRemoteRegID] intValue]; + self.localRegistrationId = [[aDecoder decodeObjectOfClass:[NSNumber class] forKey:kCoderLocalRegID] intValue]; + self.sendingChain = [aDecoder decodeObjectOfClass:[SendingChain class] forKey:kCoderSendingChain]; + self.receivingChains = [aDecoder decodeObjectOfClass:[NSArray class] forKey:kCoderReceiverChains]; + self.pendingPreKey = [aDecoder decodeObjectOfClass:[PendingPreKey class] forKey:kCoderPendingPrekey]; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder{ + [aCoder encodeInt:self.version forKey:kCoderVersion]; + [aCoder encodeObject:self.aliceBaseKey forKey:kCoderAliceBaseKey]; + [aCoder encodeObject:self.remoteIdentityKey forKey:kCoderRemoteIDKey]; + [aCoder encodeObject:self.localIdentityKey forKey:kCoderLocalIDKey]; + [aCoder encodeInt:self.previousCounter forKey:kCoderPreviousCounter]; + [aCoder encodeObject:self.rootKey forKey:kCoderRootKey]; + [aCoder encodeObject:[NSNumber numberWithInt:self.remoteRegistrationId] forKey:kCoderRemoteRegID]; + [aCoder encodeObject:[NSNumber numberWithInt:self.localRegistrationId] forKey:kCoderLocalRegID]; + [aCoder encodeObject:self.sendingChain forKey:kCoderSendingChain]; + [aCoder encodeObject:[self.receivingChains mutableCopy] forKey:kCoderReceiverChains]; + [aCoder encodeObject:self.pendingPreKey forKey:kCoderPendingPrekey]; +} + +- (NSData*)senderRatchetKey{ + return [[self senderRatchetKeyPair] publicKey]; +} + +- (ECKeyPair*)senderRatchetKeyPair{ + return [[self sendingChain] senderRatchetKeyPair]; +} + +- (BOOL)hasReceiverChain:(NSData *)senderEphemeral +{ + return [self receiverChain:senderEphemeral] != nil; +} + +- (BOOL)hasSenderChain{ + return self.sendingChain != nil; +} + +- (ChainAndIndex *)receiverChain:(NSData *)senderEphemeral +{ + int index = 0; + + for (ReceivingChain *receiverChain in self.receivingChains) { + NSData *chainSenderRatchetKey = receiverChain.senderRatchetKey; + + if ([chainSenderRatchetKey isEqualToData:senderEphemeral]) { + ChainAndIndex *cai = [[ChainAndIndex alloc] init]; + cai.chain = receiverChain; + cai.index = index; + return cai; + } + ows_add_overflow(index, 1, &index); + } + + return nil; +} + +- (ChainKey *)receiverChainKey:(NSData *)senderEphemeral +{ + OWSAssert(senderEphemeral); + + ChainAndIndex *receiverChainAndIndex = [self receiverChain:senderEphemeral]; + ReceivingChain *receiverChain = (ReceivingChain*)receiverChainAndIndex.chain; + + if (receiverChain == nil) { + return nil; + } else{ + OWSAssert(receiverChain.chainKey.key); + return [[ChainKey alloc] initWithData:receiverChain.chainKey.key index:receiverChain.chainKey.index]; + } +} + +- (void)setReceiverChainKey:(NSData*)senderEphemeral chainKey:(ChainKey*)nextChainKey{ + OWSAssert(senderEphemeral); + OWSAssert(nextChainKey); + + ChainAndIndex *chainAndIndex = [self receiverChain:senderEphemeral]; + ReceivingChain *chain = (ReceivingChain*)chainAndIndex.chain; + + ReceivingChain *newChain = chain; + newChain.chainKey = nextChainKey; + + [self.receivingChains replaceObjectAtIndex:chainAndIndex.index withObject:newChain]; +} + +- (void)addReceiverChain:(NSData*)senderRatchetKey chainKey:(ChainKey*)chainKey{ + OWSAssert(senderRatchetKey); + OWSAssert(chainKey); + ReceivingChain *receivingChain = [[ReceivingChain alloc] initWithChainKey:chainKey senderRatchetKey:senderRatchetKey]; + + [self.receivingChains addObject:receivingChain]; + + if ([self.receivingChains count] > 5) { + DDLogInfo( + @"%@ Trimming excessive receivingChain count: %lu", self.tag, (unsigned long)self.receivingChains.count); + // We keep 5 receiving chains to be able to decrypt out of order messages. + [self.receivingChains removeObjectAtIndex:0]; + } +} + +- (void)setSenderChain:(ECKeyPair*)senderRatchetKeyPair chainKey:(ChainKey*)chainKey{ + OWSAssert(senderRatchetKeyPair); + OWSAssert(chainKey); + + self.sendingChain = [[SendingChain alloc]initWithChainKey:chainKey senderRatchetKeyPair:senderRatchetKeyPair]; +} + +- (ChainKey*)senderChainKey{ + return self.sendingChain.chainKey; +} + +- (void)setSenderChainKey:(ChainKey*)nextChainKey{ + OWSAssert(nextChainKey); + + SendingChain *sendingChain = self.sendingChain; + sendingChain.chainKey = nextChainKey; + + self.sendingChain = sendingChain; +} + +- (BOOL)hasMessageKeys:(NSData*)senderRatchetKey counter:(int)counter{ + OWSAssert(senderRatchetKey); + ChainAndIndex *chainAndIndex = [self receiverChain:senderRatchetKey]; + ReceivingChain *receivingChain = (ReceivingChain*)chainAndIndex.chain; + + if (!receivingChain) { + return NO; + } + + NSArray *messageKeyArray = receivingChain.messageKeysList; + + for (MessageKeys *keys in messageKeyArray) { + if (keys.index == counter) { + return YES; + } + } + + return NO; +} + +- (MessageKeys*)removeMessageKeys:(NSData*)senderRatcherKey counter:(int)counter{ + ChainAndIndex *chainAndIndex = [self receiverChain:senderRatcherKey]; + ReceivingChain *receivingChain = (ReceivingChain*)chainAndIndex.chain; + + if (!receivingChain) { + return nil; + } + + NSMutableArray *messageList = receivingChain.messageKeysList; + + MessageKeys *result; + + for(MessageKeys *messageKeys in messageList){ + if (messageKeys.index == counter) { + result = messageKeys; + break; + } + } + + [messageList removeObject:result]; + + return result; +} + +-(void)setReceiverChain:(int)index updatedChain:(ReceivingChain*)recvchain{ + OWSAssert(recvchain); + + [self.receivingChains replaceObjectAtIndex:index withObject:recvchain]; +} + +- (void)setMessageKeys:(NSData*)senderRatchetKey messageKeys:(MessageKeys*)messageKeys{ + OWSAssert(senderRatchetKey); + OWSAssert(messageKeys); + + ChainAndIndex *chainAndIndex = [self receiverChain:senderRatchetKey]; + ReceivingChain *chain = (ReceivingChain*)chainAndIndex.chain; + [chain.messageKeysList addObject:messageKeys]; + + [self setReceiverChain:chainAndIndex.index updatedChain:chain]; +} + +- (void)setUnacknowledgedPreKeyMessage:(int)preKeyId signedPreKey:(int)signedPreKeyId baseKey:(NSData*)baseKey{ + OWSAssert(baseKey); + + PendingPreKey *pendingPreKey = [[PendingPreKey alloc] initWithBaseKey:baseKey preKeyId:preKeyId signedPreKeyId:signedPreKeyId]; + + self.pendingPreKey = pendingPreKey; +} + +- (BOOL)hasUnacknowledgedPreKeyMessage{ + return self.pendingPreKey?YES:NO; +} + +- (PendingPreKey*)unacknowledgedPreKeyMessageItems{ + return self.pendingPreKey; +} +- (void)clearUnacknowledgedPreKeyMessage{ + self.pendingPreKey = nil; +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + +@end diff --git a/SessionProtocolKit/State/AxolotlStore.h b/SessionProtocolKit/State/AxolotlStore.h new file mode 100644 index 000000000..357598460 --- /dev/null +++ b/SessionProtocolKit/State/AxolotlStore.h @@ -0,0 +1,21 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "IdentityKeyStore.h" +#import "PreKeyStore.h" +#import "SessionStore.h" +#import "SignedPreKeyStore.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The Session Store defines the interface of the storage of sesssions. + */ + +@protocol AxolotlStore + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/State/IdentityKeyStore.h b/SessionProtocolKit/State/IdentityKeyStore.h new file mode 100644 index 000000000..7dc88859b --- /dev/null +++ b/SessionProtocolKit/State/IdentityKeyStore.h @@ -0,0 +1,57 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class ECKeyPair; + +typedef NS_ENUM(NSInteger, TSMessageDirection) { + TSMessageDirectionUnknown = 0, + TSMessageDirectionIncoming, + TSMessageDirectionOutgoing +}; + +// See a discussion of the protocolContext in SessionCipher.h. +@protocol IdentityKeyStore + +- (nullable ECKeyPair *)identityKeyPair:(nullable id)protocolContext; + +- (int)localRegistrationId:(nullable id)protocolContext; + +/** + * Record a recipients identity key + * + * @param identityKey key data used to identify the recipient + * @param recipientId unique stable identifier for the recipient, e.g. e164 phone number + * + * @returns YES if we are replacing an existing known identity key for recipientId. + * NO if there was no previously stored identity key for the recipient. + */ +- (BOOL)saveRemoteIdentity:(NSData *)identityKey + recipientId:(NSString *)recipientId + protocolContext:(nullable id)protocolContext; + +/** + * @param identityKey key data used to identify the recipient + * @param recipientId unique stable identifier for the recipient, e.g. e164 phone number + * @param direction whether the key is being used in a sending or receiving context, as this could affect the + * decision to trust the key. + * + * @returns YES if the key is trusted + * NO if the key is not trusted + */ +- (BOOL)isTrustedIdentityKey:(NSData *)identityKey + recipientId:(NSString *)recipientId + direction:(TSMessageDirection)direction + protocolContext:(nullable id)protocolContext; + +- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId; + +- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId protocolContext:(nullable id)protocolContext; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/State/PreKeyStore.h b/SessionProtocolKit/State/PreKeyStore.h new file mode 100644 index 000000000..863222908 --- /dev/null +++ b/SessionProtocolKit/State/PreKeyStore.h @@ -0,0 +1,22 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "PreKeyRecord.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol PreKeyStore + +- (PreKeyRecord *)throws_loadPreKey:(int)preKeyId NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +- (void)storePreKey:(int)preKeyId preKeyRecord:(PreKeyRecord *)record; + +- (BOOL)containsPreKey:(int)preKeyId; + +- (void)removePreKey:(int)preKeyId protocolContext:(nullable id)protocolContext; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/State/SessionStore.h b/SessionProtocolKit/State/SessionStore.h new file mode 100644 index 000000000..513cdcb07 --- /dev/null +++ b/SessionProtocolKit/State/SessionStore.h @@ -0,0 +1,44 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "SessionRecord.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +// See a discussion of the protocolContext in SessionCipher.h. +@protocol SessionStore + +/** + * Returns a copy of the SessionRecord corresponding to the recipientId + deviceId tuple or a new SessionRecord if one does not currently exist. + * + * @param contactIdentifier The recipientId of the remote client. + * @param deviceId The deviceId of the remote client. + * + * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple. + */ +- (SessionRecord *)loadSession:(NSString *)contactIdentifier + deviceId:(int)deviceId + protocolContext:(nullable id)protocolContext; + +- (NSArray *)subDevicesSessions:(NSString *)contactIdentifier protocolContext:(nullable id)protocolContext __attribute__((deprecated)); + +- (void)storeSession:(NSString *)contactIdentifier + deviceId:(int)deviceId + session:(SessionRecord *)session + protocolContext:(nullable id)protocolContext; + +- (BOOL)containsSession:(NSString *)contactIdentifier + deviceId:(int)deviceId + protocolContext:(nullable id)protocolContext; + +- (void)deleteSessionForContact:(NSString *)contactIdentifier + deviceId:(int)deviceId + protocolContext:(nullable id)protocolContext; + +- (void)deleteAllSessionsForContact:(NSString *)contactIdentifier protocolContext:(nullable id)protocolContext; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/State/SignedPreKeyStore.h b/SessionProtocolKit/State/SignedPreKeyStore.h new file mode 100644 index 000000000..1efb143c0 --- /dev/null +++ b/SessionProtocolKit/State/SignedPreKeyStore.h @@ -0,0 +1,26 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "SignedPrekeyRecord.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol SignedPreKeyStore + +- (SignedPreKeyRecord *)throws_loadSignedPrekey:(int)signedPreKeyId NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +- (nullable SignedPreKeyRecord *)loadSignedPrekeyOrNil:(int)signedPreKeyId; + +- (NSArray *)loadSignedPreKeys; + +- (void)storeSignedPreKey:(int)signedPreKeyId signedPreKeyRecord:(SignedPreKeyRecord *)signedPreKeyRecord; + +- (BOOL)containsSignedPreKey:(int)signedPreKeyId; + +- (void)removeSignedPreKey:(int)signedPrekeyId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/Cryptography.h b/SessionProtocolKit/Utility/Cryptography.h new file mode 100644 index 000000000..f7ea09bfe --- /dev/null +++ b/SessionProtocolKit/Utility/Cryptography.h @@ -0,0 +1,161 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +extern const NSUInteger kAES256_KeyByteLength; +extern const NSUInteger kAESGCM256_IVLength; +extern const NSUInteger kAES256CTR_IVLength; + +extern const NSUInteger SCKErrorCodeFailedToDecryptMessage; + +/// Key appropriate for use in AES256-GCM +@interface OWSAES256Key : NSObject + +/// Generates new secure random key +- (instancetype)init; ++ (instancetype)generateRandomKey; + +/** + * @param data representing the raw key bytes + * + * @returns a new instance if key is of appropriate length for AES256-GCM + * else returns nil. + */ ++ (nullable instancetype)keyWithData:(NSData *)data; + +/// The raw key material +@property (nonatomic, readonly) NSData *keyData; + +@end + +#pragma mark - + +// TODO: This class should probably be renamed to: AES256GCMEncryptionResult +// (note the missing 6 in 256). +@interface AES25GCMEncryptionResult : NSObject + +@property (nonatomic, readonly) NSData *ciphertext; +@property (nonatomic, readonly) NSData *initializationVector; +@property (nonatomic, readonly) NSData *authTag; + +- (instancetype)init NS_UNAVAILABLE; +- (nullable instancetype)initWithCipherText:(NSData *)cipherText + initializationVector:(NSData *)initializationVector + authTag:(NSData *)authTag NS_DESIGNATED_INITIALIZER; + +@end + +#pragma mark - + +@interface AES256CTREncryptionResult : NSObject + +@property (nonatomic, readonly) NSData *ciphertext; +@property (nonatomic, readonly) NSData *initializationVector; + +- (instancetype)init NS_UNAVAILABLE; +- (nullable instancetype)initWithCiphertext:(NSData *)ciphertext + initializationVector:(NSData *)initializationVector NS_DESIGNATED_INITIALIZER; + +@end + +#pragma mark - + +@interface Cryptography : NSObject + +typedef NS_ENUM(NSInteger, TSMACType) { + TSHMACSHA256Truncated10Bytes = 2, + TSHMACSHA256AttachementType = 3 +}; + ++ (NSData *)generateRandomBytes:(NSUInteger)numberBytes; + ++ (uint32_t)randomUInt32; ++ (uint64_t)randomUInt64; ++ (unsigned)randomUnsigned; + +#pragma mark - SHA and HMAC methods + +// Full length SHA256 digest for `data` ++ (nullable NSData *)computeSHA256Digest:(NSData *)data; + +// Truncated SHA256 digest for `data` ++ (nullable NSData *)computeSHA256Digest:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes; + ++ (nullable NSString *)truncatedSHA1Base64EncodedWithoutPadding:(NSString *)string; + ++ (nullable NSData *)decryptAppleMessagePayload:(NSData *)payload withSignalingKey:(NSString *)signalingKeyString; + ++ (nullable NSData *)computeSHA256HMAC:(NSData *)data withHMACKey:(NSData *)HMACKey; + ++ (nullable NSData *)truncatedSHA256HMAC:(NSData *)dataToHMAC + withHMACKey:(NSData *)HMACKey + truncation:(NSUInteger)truncation; + +#pragma mark - Attachments & Stickers + +// Though digest can and will be nil for legacy clients, we now reject attachments lacking a digest. ++ (nullable NSData *)decryptAttachment:(NSData *)dataToDecrypt + withKey:(NSData *)key + digest:(nullable NSData *)digest + unpaddedSize:(UInt32)unpaddedSize + error:(NSError **)error; + ++ (nullable NSData *)decryptStickerData:(NSData *)dataToDecrypt + withKey:(NSData *)key + error:(NSError **)error; + ++ (nullable NSData *)encryptAttachmentData:(NSData *)attachmentData + outKey:(NSData *_Nonnull *_Nullable)outKey + outDigest:(NSData *_Nonnull *_Nullable)outDigest; + +#pragma mark - AES-GCM + ++ (nullable AES25GCMEncryptionResult *)encryptAESGCMWithData:(NSData *)plaintext + additionalAuthenticatedData:(nullable NSData *)additionalAuthenticatedData + key:(OWSAES256Key *)key + NS_SWIFT_NAME(encryptAESGCM(plainTextData:additionalAuthenticatedData:key:)); + ++ (nullable AES25GCMEncryptionResult *)encryptAESGCMWithData:(NSData *)plaintext + initializationVector:(NSData *)initializationVector + additionalAuthenticatedData:(nullable NSData *)additionalAuthenticatedData + key:(OWSAES256Key *)key + NS_SWIFT_NAME(encryptAESGCM(plainTextData:initializationVector:additionalAuthenticatedData:key:)); + ++ (nullable NSData *)decryptAESGCMWithInitializationVector:(NSData *)initializationVector + ciphertext:(NSData *)ciphertext + additionalAuthenticatedData:(nullable NSData *)additionalAuthenticatedData + authTag:(NSData *)authTagFromEncrypt + key:(OWSAES256Key *)key + NS_SWIFT_NAME(decryptAESGCM(withInitializationVector:ciphertext:additionalAuthenticatedData:authTag:key:)); + +#pragma mark - Profiles + ++ (nullable NSData *)encryptAESGCMWithProfileData:(NSData *)plaintextData key:(OWSAES256Key *)key + NS_SWIFT_NAME(encryptAESGCMProfileData(plainTextData:key:)); + ++ (nullable NSData *)decryptAESGCMWithProfileData:(NSData *)encryptedData key:(OWSAES256Key *)key + NS_SWIFT_NAME(decryptAESGCMProfileData(encryptedData:key:)); + +#pragma mark - AES-CTR + ++ (nullable AES256CTREncryptionResult *)encryptAESCTRWithData:(NSData *)plaintext + initializationVector:(NSData *)initializationVector + key:(OWSAES256Key *)key + NS_SWIFT_NAME(encryptAESCTR(plaintextData:initializationVector:key:)); + ++ (nullable NSData *)decryptAESCTRWithCipherText:(NSData *)cipherText + initializationVector:(NSData *)initializationVector + key:(OWSAES256Key *)key + NS_SWIFT_NAME(decryptAESCTR(cipherText:initializationVector:key:)); + +#pragma mark - + ++ (void)seedRandom; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/Cryptography.m b/SessionProtocolKit/Utility/Cryptography.m new file mode 100644 index 000000000..b6a84350e --- /dev/null +++ b/SessionProtocolKit/Utility/Cryptography.m @@ -0,0 +1,1074 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import +#import "Cryptography.h" +#import "NSData+OWS.h" +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +#define HMAC256_KEY_LENGTH 32 +#define HMAC256_OUTPUT_LENGTH 32 +#define AES_CBC_IV_LENGTH 16 +#define AES_KEY_SIZE 32 + +NSString *const SignalCoreKitErrorDomain = @"SignalCoreKitErrorDomain"; + +const NSUInteger SCKErrorCodeFailedToDecryptMessage = 100; + +NSError *SCKErrorWithCodeDescription(NSUInteger code, NSString *description) +{ + return [NSError errorWithDomain:SignalCoreKitErrorDomain + code:code + userInfo:@{ NSLocalizedDescriptionKey: description }]; +} + +// Returned by many OpenSSL functions - indicating success +const int kOpenSSLSuccess = 1; + +// length of initialization nonce for AES256-GCM +const NSUInteger kAESGCM256_IVLength = 12; + +const NSUInteger kAES256CTR_IVLength = 16; + +// length of authentication tag for AES256-GCM +static const NSUInteger kAESGCM256_TagLength = 16; + +// length of key used for websocket envelope authentication +static const NSUInteger kHMAC256_EnvelopeKeyLength = 20; + +const NSUInteger kAES256_KeyByteLength = 32; + +@implementation OWSAES256Key + ++ (nullable instancetype)keyWithData:(NSData *)data +{ + if (data.length != kAES256_KeyByteLength) { + OWSLogError(@"Invalid key length: %lu", (unsigned long)data.length); + return nil; + } + + return [[self alloc] initWithData:data]; +} + ++ (instancetype)generateRandomKey +{ + return [self new]; +} + +- (instancetype)init +{ + return [self initWithData:[Cryptography generateRandomBytes:kAES256_KeyByteLength]]; +} + +- (instancetype)initWithData:(NSData *)data +{ + self = [super init]; + if (!self) { + return self; + } + + _keyData = data; + + return self; +} + +#pragma mark - SecureCoding + ++ (BOOL)supportsSecureCoding +{ + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder +{ + self = [super init]; + if (!self) { + return self; + } + + NSData *keyData = [aDecoder decodeObjectOfClass:[NSData class] forKey:@"keyData"]; + if (keyData.length != kAES256_KeyByteLength) { + OWSFailDebug(@"Invalid key length: %lu", (unsigned long)keyData.length); + return nil; + } + + _keyData = keyData; + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder +{ + [aCoder encodeObject:_keyData forKey:@"keyData"]; +} + +@end + +#pragma mark - + +@implementation AES25GCMEncryptionResult + +- (nullable instancetype)initWithCipherText:(NSData *)cipherText + initializationVector:(NSData *)initializationVector + authTag:(NSData *)authTag +{ + self = [super init]; + if (!self) { + return self; + } + + _ciphertext = [cipherText copy]; + _initializationVector = [initializationVector copy]; + _authTag = [authTag copy]; + + if (_ciphertext == nil || _initializationVector.length != kAESGCM256_IVLength + || _authTag.length != kAESGCM256_TagLength) { + return nil; + } + + return self; +} + +@end + +#pragma mark - + +@implementation AES256CTREncryptionResult + +- (nullable instancetype)initWithCiphertext:(NSData *)ciphertext initializationVector:(NSData *)initializationVector +{ + self = [super init]; + if (!self) { + return self; + } + + _ciphertext = [ciphertext copy]; + _initializationVector = [initializationVector copy]; + + if (_ciphertext == nil) { + OWSFail(@"Missing cipher text."); + return nil; + } + if (_initializationVector.length != kAES256CTR_IVLength) { + OWSFail(@"Invalid initialization vector."); + return nil; + } + + return self; +} + +@end + +#pragma mark - + +@implementation Cryptography + ++ (NSString *)logTag +{ + return @"AES25GCMEncryptionResult"; +} + +#pragma mark - random bytes methods + ++ (NSData *)generateRandomBytes:(NSUInteger)numberBytes +{ + return [Randomness generateRandomBytes:(int)numberBytes]; +} + ++ (uint32_t)randomUInt32 +{ + size_t size = sizeof(uint32_t); + NSData *data = [self generateRandomBytes:size]; + uint32_t result = 0; + [data getBytes:&result range:NSMakeRange(0, size)]; + return result; +} + ++ (uint64_t)randomUInt64 +{ + size_t size = sizeof(uint64_t); + NSData *data = [self generateRandomBytes:size]; + uint64_t result = 0; + [data getBytes:&result range:NSMakeRange(0, size)]; + return result; +} + ++ (unsigned)randomUnsigned +{ + size_t size = sizeof(unsigned); + NSData *data = [self generateRandomBytes:size]; + unsigned result = 0; + [data getBytes:&result range:NSMakeRange(0, size)]; + return result; +} + +#pragma mark - SHA1 + +// Used by TSContactManager to send hashed/truncated contact list to server. ++ (nullable NSString *)truncatedSHA1Base64EncodedWithoutPadding:(NSString *)string +{ + NSData *_Nullable stringData = [string dataUsingEncoding:NSUTF8StringEncoding]; + if (!stringData) { + OWSFailDebug(@"could not convert string to utf-8."); + return nil; + } + if (stringData.length >= UINT32_MAX) { + OWSFailDebug(@"string data is too long."); + return nil; + } + uint32_t dataLength = (uint32_t)stringData.length; + + NSMutableData *_Nullable hashData = [NSMutableData dataWithLength:20]; + if (!hashData) { + OWSFail(@"Could not allocate buffer."); + } + CC_SHA1(stringData.bytes, dataLength, hashData.mutableBytes); + + NSData *truncatedData = [hashData subdataWithRange:NSMakeRange(0, 10)]; + return [[truncatedData base64EncodedString] stringByReplacingOccurrencesOfString:@"=" withString:@""]; +} + +#pragma mark - SHA256 Digest + ++ (nullable NSData *)computeSHA256Digest:(NSData *)data +{ + return [self computeSHA256Digest:data truncatedToBytes:CC_SHA256_DIGEST_LENGTH]; +} + ++ (nullable NSData *)computeSHA256Digest:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes +{ + if (data.length >= UINT32_MAX) { + OWSFailDebug(@"data is too long."); + return nil; + } + uint32_t dataLength = (uint32_t)data.length; + + NSMutableData *_Nullable digestData = [[NSMutableData alloc] initWithLength:CC_SHA256_DIGEST_LENGTH]; + if (!digestData) { + OWSFailDebug(@"could not allocate buffer."); + return nil; + } + CC_SHA256(data.bytes, dataLength, digestData.mutableBytes); + return [digestData subdataWithRange:NSMakeRange(0, truncatedBytes)]; +} + +#pragma mark - HMAC/SHA256 + ++ (nullable NSData *)computeSHA256HMAC:(NSData *)data withHMACKey:(NSData *)HMACKey +{ + if (data.length >= SIZE_MAX) { + OWSFailDebug(@"data is too long."); + return nil; + } + size_t dataLength = (size_t)data.length; + if (HMACKey.length >= SIZE_MAX) { + OWSFailDebug(@"HMAC key is too long."); + return nil; + } + size_t hmacKeyLength = (size_t)HMACKey.length; + + NSMutableData *_Nullable ourHmacData = [[NSMutableData alloc] initWithLength:CC_SHA256_DIGEST_LENGTH]; + if (!ourHmacData) { + OWSFailDebug(@"could not allocate buffer."); + return nil; + } + CCHmac(kCCHmacAlgSHA256, [HMACKey bytes], hmacKeyLength, [data bytes], dataLength, ourHmacData.mutableBytes); + return [ourHmacData copy]; +} + ++ (nullable NSData *)truncatedSHA256HMAC:(NSData *)dataToHMAC + withHMACKey:(NSData *)HMACKey + truncation:(NSUInteger)truncation +{ + OWSAssert(truncation <= CC_SHA256_DIGEST_LENGTH); + OWSAssert(dataToHMAC); + OWSAssert(HMACKey); + + return + [[Cryptography computeSHA256HMAC:dataToHMAC withHMACKey:HMACKey] subdataWithRange:NSMakeRange(0, truncation)]; +} + +#pragma mark - AES CBC Mode + +/** + * AES256 CBC encrypt then mac. Used to decrypt both signal messages and attachment blobs + * + * @return decrypted data or nil if hmac invalid/decryption fails + */ ++ (nullable NSData *)decryptCBCMode:(NSData *)dataToDecrypt + key:(NSData *)key + IV:(NSData *)iv + version:(nullable NSData *)version + HMACKey:(NSData *)hmacKey + HMACType:(TSMACType)hmacType + matchingHMAC:(NSData *)hmac + digest:(nullable NSData *)digest +{ + OWSAssert(dataToDecrypt); + OWSAssert(key); + if (key.length != kCCKeySizeAES256) { + OWSFailDebug(@"key had wrong size."); + return nil; + } + OWSAssert(iv); + if (iv.length != kCCBlockSizeAES128) { + OWSFailDebug(@"iv had wrong size."); + return nil; + } + OWSAssert(hmacKey); + OWSAssert(hmac); + + size_t bufferSize; + BOOL didOverflow = __builtin_add_overflow(dataToDecrypt.length, kCCBlockSizeAES128, &bufferSize); + if (didOverflow) { + OWSFailDebug(@"bufferSize was too large."); + return nil; + } + + // Verify hmac of: version? || iv || encrypted data + + NSUInteger dataToAuthLength = 0; + if (__builtin_add_overflow(dataToDecrypt.length, iv.length, &dataToAuthLength)) { + OWSFailDebug(@"dataToAuth was too large."); + return nil; + } + if (version != nil && __builtin_add_overflow(dataToAuthLength, version.length, &dataToAuthLength)) { + OWSFailDebug(@"dataToAuth was too large."); + return nil; + } + + NSMutableData *dataToAuth = [NSMutableData data]; + if (version != nil) { + [dataToAuth appendData:version]; + } + [dataToAuth appendData:iv]; + [dataToAuth appendData:dataToDecrypt]; + + NSData *_Nullable ourHmacData; + + if (hmacType == TSHMACSHA256Truncated10Bytes) { + // used to authenticate envelope from websocket + OWSAssert(hmacKey.length == kHMAC256_EnvelopeKeyLength); + ourHmacData = [Cryptography truncatedSHA256HMAC:dataToAuth withHMACKey:hmacKey truncation:10]; + OWSAssert(ourHmacData.length == 10); + } else if (hmacType == TSHMACSHA256AttachementType) { + OWSAssert(hmacKey.length == HMAC256_KEY_LENGTH); + ourHmacData = + [Cryptography truncatedSHA256HMAC:dataToAuth withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH]; + OWSAssert(ourHmacData.length == HMAC256_OUTPUT_LENGTH); + } else { + OWSFail(@"unknown HMAC scheme: %ld", (long)hmacType); + } + + if (hmac == nil || ![ourHmacData ows_constantTimeIsEqualToData:hmac]) { + OWSLogError(@"Bad HMAC on decrypting payload."); + // Don't log HMAC in prod + OWSLogDebug(@"Bad HMAC on decrypting payload. Their MAC: %@, our MAC: %@", hmac, ourHmacData); + return nil; + } + + // Optionally verify digest of: version? || iv || encrypted data || hmac + if (digest) { + OWSLogDebug(@"verifying their digest"); + [dataToAuth appendData:ourHmacData]; + NSData *_Nullable ourDigest = [Cryptography computeSHA256Digest:dataToAuth]; + if (!ourDigest || ![ourDigest ows_constantTimeIsEqualToData:digest]) { + OWSLogWarn(@"Bad digest on decrypting payload"); + // Don't log digest in prod + DDLogDebug(@"Bad digest on decrypting payload. Their digest: %@, our digest: %@, data: %@", + digest.hexadecimalString, + ourDigest.hexadecimalString, + dataToAuth.hexadecimalString); + return nil; + } + } + + // decrypt + NSMutableData *_Nullable bufferData = [NSMutableData dataWithLength:bufferSize]; + if (!bufferData) { + OWSLogError(@"Failed to allocate buffer."); + return nil; + } + + size_t bytesDecrypted = 0; + CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, + kCCAlgorithmAES128, + kCCOptionPKCS7Padding, + [key bytes], + [key length], + [iv bytes], + [dataToDecrypt bytes], + [dataToDecrypt length], + bufferData.mutableBytes, + bufferSize, + &bytesDecrypted); + if (cryptStatus == kCCSuccess) { + return [bufferData subdataWithRange:NSMakeRange(0, bytesDecrypted)]; + } else { + OWSLogError(@"Failed CBC decryption"); + } + + return nil; +} + +#pragma mark - methods which use AES CBC + ++ (nullable NSData *)decryptAppleMessagePayload:(NSData *)payload withSignalingKey:(NSString *)signalingKeyString +{ + OWSAssertDebug(payload); + OWSAssertDebug(signalingKeyString); + + size_t versionLength = 1; + size_t ivLength = 16; + size_t macLength = 10; + size_t nonCiphertextLength = versionLength + ivLength + macLength; + + size_t ciphertextLength; + ows_sub_overflow(payload.length, nonCiphertextLength, &ciphertextLength); + + if (payload.length < nonCiphertextLength) { + OWSFailDebug(@"Invalid payload"); + return nil; + } + if (payload.length >= MIN(SIZE_MAX, NSUIntegerMax) - nonCiphertextLength) { + OWSFailDebug(@"Invalid payload"); + return nil; + } + + NSUInteger cursor = 0; + NSData *versionData = [payload subdataWithRange:NSMakeRange(cursor, versionLength)]; + cursor += versionLength; + NSData *ivData = [payload subdataWithRange:NSMakeRange(cursor, ivLength)]; + cursor += ivLength; + NSData *ciphertextData = [payload subdataWithRange:NSMakeRange(cursor, ciphertextLength)]; + ows_add_overflow(cursor, ciphertextLength, &cursor); + NSData *macData = [payload subdataWithRange:NSMakeRange(cursor, macLength)]; + + NSData *signalingKey = [NSData dataFromBase64String:signalingKeyString]; + NSData *signalingKeyAESKeyMaterial = [signalingKey subdataWithRange:NSMakeRange(0, 32)]; + NSData *signalingKeyHMACKeyMaterial = [signalingKey subdataWithRange:NSMakeRange(32, kHMAC256_EnvelopeKeyLength)]; + return [Cryptography decryptCBCMode:ciphertextData + key:signalingKeyAESKeyMaterial + IV:ivData + version:versionData + HMACKey:signalingKeyHMACKeyMaterial + HMACType:TSHMACSHA256Truncated10Bytes + matchingHMAC:macData + digest:nil]; +} + +#pragma mark - Attachments & Stickers + ++ (nullable NSData *)decryptAttachment:(NSData *)dataToDecrypt + withKey:(NSData *)key + digest:(nullable NSData *)digest + unpaddedSize:(UInt32)unpaddedSize + error:(NSError **)error +{ + if (digest.length <= 0) { + // This *could* happen with sufficiently outdated clients. + OWSLogError(@"Refusing to decrypt attachment without a digest."); + *error = SCKErrorWithCodeDescription(SCKErrorCodeFailedToDecryptMessage, + NSLocalizedString(@"ERROR_MESSAGE_ATTACHMENT_FROM_OLD_CLIENT", + @"Error message when unable to receive an attachment because the sending client is too old.")); + return nil; + } + + return [self decryptData:dataToDecrypt + withKey:key + digest:digest + unpaddedSize:unpaddedSize + error:error]; +} + ++ (nullable NSData *)decryptStickerData:(NSData *)dataToDecrypt + withKey:(NSData *)key + error:(NSError **)error +{ + return [self decryptData:dataToDecrypt + withKey:key + digest:nil + unpaddedSize:0 + error:error]; +} + ++ (nullable NSData *)decryptData:(NSData *)dataToDecrypt + withKey:(NSData *)key + digest:(nullable NSData *)digest + unpaddedSize:(UInt32)unpaddedSize + error:(NSError **)error +{ + if (([dataToDecrypt length] < AES_CBC_IV_LENGTH + HMAC256_OUTPUT_LENGTH) || + ([key length] < AES_KEY_SIZE + HMAC256_KEY_LENGTH)) { + OWSLogError(@"Message shorter than crypto overhead!"); + *error = SCKErrorWithCodeDescription(SCKErrorCodeFailedToDecryptMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); + return nil; + } + + // key: 32 byte AES key || 32 byte Hmac-SHA256 key. + NSData *encryptionKey = [key subdataWithRange:NSMakeRange(0, AES_KEY_SIZE)]; + NSData *hmacKey = [key subdataWithRange:NSMakeRange(AES_KEY_SIZE, HMAC256_KEY_LENGTH)]; + + // dataToDecrypt: IV || Ciphertext || truncated MAC(IV||Ciphertext) + NSData *iv = [dataToDecrypt subdataWithRange:NSMakeRange(0, AES_CBC_IV_LENGTH)]; + + NSUInteger cipherTextLength; + ows_sub_overflow(dataToDecrypt.length, (AES_CBC_IV_LENGTH + HMAC256_OUTPUT_LENGTH), &cipherTextLength); + NSData *encryptedAttachment = [dataToDecrypt subdataWithRange:NSMakeRange(AES_CBC_IV_LENGTH, cipherTextLength)]; + + NSUInteger hmacOffset; + ows_sub_overflow(dataToDecrypt.length, HMAC256_OUTPUT_LENGTH, &hmacOffset); + NSData *hmac = [dataToDecrypt subdataWithRange:NSMakeRange(hmacOffset, HMAC256_OUTPUT_LENGTH)]; + + NSData *_Nullable paddedPlainText = [Cryptography decryptCBCMode:encryptedAttachment + key:encryptionKey + IV:iv + version:nil + HMACKey:hmacKey + HMACType:TSHMACSHA256AttachementType + matchingHMAC:hmac + digest:digest]; + if (!paddedPlainText) { + OWSFailDebug(@"couldn't decrypt attachment."); + *error = SCKErrorWithCodeDescription(SCKErrorCodeFailedToDecryptMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); + return nil; + } else if (unpaddedSize == 0) { + // Work around for legacy iOS client's which weren't setting padding size. + // Since we know those clients pre-date attachment padding we return the entire data. + OWSLogWarn(@"Decrypted attachment with unspecified size."); + return paddedPlainText; + } else { + if (unpaddedSize > paddedPlainText.length) { + *error = SCKErrorWithCodeDescription(SCKErrorCodeFailedToDecryptMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); + return nil; + } + + if (unpaddedSize == paddedPlainText.length) { + OWSLogInfo(@"decrypted unpadded attachment."); + return [paddedPlainText copy]; + } else { + unsigned long paddingSize; + ows_sub_overflow(paddedPlainText.length, unpaddedSize, &paddingSize); + + OWSLogInfo(@"decrypted padded attachment with unpaddedSize: %lu, paddingSize: %lu", + (unsigned long)unpaddedSize, + paddingSize); + return [paddedPlainText subdataWithRange:NSMakeRange(0, unpaddedSize)]; + } + } +} + ++ (unsigned long)paddedSize:(unsigned long)unpaddedSize +{ + // Don't enable this until clients are sufficiently rolled out. + BOOL shouldPad = NO; + if (shouldPad) { + // Note: This just rounds up to the nearsest power of two, + // but the actual padding scheme is TBD + return pow(2, ceil( log2( unpaddedSize ))); + } else { + return unpaddedSize; + } +} + ++ (nullable NSData *)encryptAttachmentData:(NSData *)attachmentData + outKey:(NSData *_Nonnull *_Nullable)outKey + outDigest:(NSData *_Nonnull *_Nullable)outDigest +{ + // Due to paddedSize, we need to divide by two. + if (attachmentData.length >= SIZE_MAX / 2) { + OWSLogError(@"data is too long."); + return nil; + } + + NSData *iv = [Cryptography generateRandomBytes:AES_CBC_IV_LENGTH]; + NSData *encryptionKey = [Cryptography generateRandomBytes:AES_KEY_SIZE]; + NSData *hmacKey = [Cryptography generateRandomBytes:HMAC256_KEY_LENGTH]; + + // The concatenated key for storage + NSMutableData *attachmentKey = [NSMutableData data]; + [attachmentKey appendData:encryptionKey]; + [attachmentKey appendData:hmacKey]; + *outKey = [attachmentKey copy]; + + // Apply any padding + unsigned long desiredSize = [self paddedSize:attachmentData.length]; + NSMutableData *paddedAttachmentData = [attachmentData mutableCopy]; + paddedAttachmentData.length = desiredSize; + + // Encrypt + size_t bufferSize; + ows_add_overflow(paddedAttachmentData.length, kCCBlockSizeAES128, &bufferSize); + NSMutableData *_Nullable bufferData = [NSMutableData dataWithLength:bufferSize]; + if (!bufferData) { + OWSFail(@"Failed to allocate buffer."); + } + + size_t bytesEncrypted = 0; + CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, + kCCAlgorithmAES128, + kCCOptionPKCS7Padding, + [encryptionKey bytes], + [encryptionKey length], + [iv bytes], + [paddedAttachmentData bytes], + [paddedAttachmentData length], + bufferData.mutableBytes, + bufferSize, + &bytesEncrypted); + + if (cryptStatus != kCCSuccess) { + OWSLogError(@"CCCrypt failed with status: %d", (int32_t)cryptStatus); + return nil; + } + + NSData *cipherText = [bufferData subdataWithRange:NSMakeRange(0, bytesEncrypted)]; + + NSMutableData *encryptedPaddedData = [NSMutableData data]; + [encryptedPaddedData appendData:iv]; + [encryptedPaddedData appendData:cipherText]; + + // compute hmac of: iv || encrypted data + NSData *_Nullable hmac = + [Cryptography truncatedSHA256HMAC:encryptedPaddedData withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH]; + if (!hmac) { + OWSFailDebug(@"could not compute SHA 256 HMAC."); + return nil; + } + + [encryptedPaddedData appendData:hmac]; + + // compute digest of: iv || encrypted data || hmac + NSData *_Nullable digest = [self computeSHA256Digest:encryptedPaddedData]; + if (!digest) { + OWSFailDebug(@"data is too long."); + return nil; + } + *outDigest = digest; + + return [encryptedPaddedData copy]; +} + +#pragma mark - AES-GCM + ++ (nullable AES25GCMEncryptionResult *)encryptAESGCMWithData:(NSData *)plaintext + additionalAuthenticatedData:(nullable NSData *)additionalAuthenticatedData + key:(OWSAES256Key *)key +{ + NSData *initializationVector = [Cryptography generateRandomBytes:kAESGCM256_IVLength]; + + return [self encryptAESGCMWithData:plaintext + initializationVector:initializationVector + additionalAuthenticatedData:additionalAuthenticatedData + key:key]; +} + ++ (nullable AES25GCMEncryptionResult *)encryptAESGCMWithData:(NSData *)plaintext + initializationVector:(NSData *)initializationVector + additionalAuthenticatedData:(nullable NSData *)additionalAuthenticatedData + key:(OWSAES256Key *)key +{ + OWSAssert(initializationVector.length == kAESGCM256_IVLength); + + NSMutableData *ciphertext = [NSMutableData dataWithLength:plaintext.length]; + NSMutableData *authTag = [NSMutableData dataWithLength:kAESGCM256_TagLength]; + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + OWSFailDebug(@"failed to build context while encrypting"); + return nil; + } + + // Initialise the encryption operation. + if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) != kOpenSSLSuccess) { + OWSFailDebug(@"failed to init encryption"); + return nil; + } + + // Set IV length if default 12 bytes (96 bits) is not appropriate + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, (int)initializationVector.length, NULL) != kOpenSSLSuccess) { + OWSFailDebug(@"failed to set IV length"); + return nil; + } + + // Initialise key and IV + if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.keyData.bytes, initializationVector.bytes) != kOpenSSLSuccess) { + OWSFailDebug(@"failed to set key and iv while encrypting"); + return nil; + } + + int bytesEncrypted = 0; + + // Provide any AAD data. This can be called zero or more times as + // required + if (additionalAuthenticatedData != nil) { + if (additionalAuthenticatedData.length >= INT_MAX) { + OWSFailDebug(@"additionalAuthenticatedData too large"); + return nil; + } + if (EVP_EncryptUpdate( + ctx, NULL, &bytesEncrypted, additionalAuthenticatedData.bytes, (int)additionalAuthenticatedData.length) + != kOpenSSLSuccess) { + OWSFailDebug(@"encryptUpdate failed"); + return nil; + } + } + + if (plaintext.length >= INT_MAX) { + OWSFailDebug(@"plaintext too large"); + return nil; + } + + // Provide the message to be encrypted, and obtain the encrypted output. + // + // If we wanted to save memory, we could encrypt piece-wise from a plaintext iostream - + // feeding each chunk to EVP_EncryptUpdate, which can be called multiple times. + // For simplicity, we currently encrypt the entire plaintext in one shot. + if (EVP_EncryptUpdate(ctx, ciphertext.mutableBytes, &bytesEncrypted, plaintext.bytes, (int)plaintext.length) + != kOpenSSLSuccess) { + OWSFailDebug(@"encryptUpdate failed"); + return nil; + } + if (bytesEncrypted != plaintext.length) { + OWSFailDebug(@"bytesEncrypted != plainTextData.length"); + return nil; + } + + int finalizedBytes = 0; + // Finalize the encryption. Normally ciphertext bytes may be written at + // this stage, but this does not occur in GCM mode + if (EVP_EncryptFinal_ex(ctx, ciphertext.mutableBytes + bytesEncrypted, &finalizedBytes) != kOpenSSLSuccess) { + OWSFailDebug(@"failed to finalize encryption"); + return nil; + } + if (finalizedBytes != 0) { + OWSFailDebug(@"Unexpected finalized bytes written"); + return nil; + } + + // Get the tag + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, kAESGCM256_TagLength, authTag.mutableBytes) != kOpenSSLSuccess) { + OWSFailDebug(@"failed to write tag"); + return nil; + } + + // Clean up + EVP_CIPHER_CTX_free(ctx); + + AES25GCMEncryptionResult *_Nullable result = + [[AES25GCMEncryptionResult alloc] initWithCipherText:ciphertext + initializationVector:initializationVector + authTag:authTag]; + + return result; +} + ++ (nullable NSData *)decryptAESGCMWithInitializationVector:(NSData *)initializationVector + ciphertext:(NSData *)ciphertext + additionalAuthenticatedData:(nullable NSData *)additionalAuthenticatedData + authTag:(NSData *)authTagFromEncrypt + key:(OWSAES256Key *)key +{ + OWSAssertDebug(initializationVector.length == kAESGCM256_IVLength); + OWSAssertDebug(ciphertext.length > 0); + OWSAssertDebug(authTagFromEncrypt.length == kAESGCM256_TagLength); + OWSAssertDebug(key); + + NSMutableData *plaintext = [NSMutableData dataWithLength:ciphertext.length]; + + // Create and initialise the context + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + + if (!ctx) { + OWSFailDebug(@"failed to build context while decrypting"); + return nil; + } + + // Initialise the decryption operation. + if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) != kOpenSSLSuccess) { + OWSFailDebug(@"failed to init decryption"); + return nil; + } + + // Set IV length. Not necessary if this is 12 bytes (96 bits) + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, kAESGCM256_IVLength, NULL) != kOpenSSLSuccess) { + OWSFailDebug(@"failed to set key and iv while decrypting"); + return nil; + } + + // Initialise key and IV + if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.keyData.bytes, initializationVector.bytes) != kOpenSSLSuccess) { + OWSFailDebug(@"failed to init decryption"); + return nil; + } + + int decryptedBytes = 0; + + // Provide any AAD data. This can be called zero or more times as + // required + if (additionalAuthenticatedData) { + if (additionalAuthenticatedData.length >= INT_MAX) { + OWSFailDebug(@"additionalAuthenticatedData too large"); + return nil; + } + if (!EVP_DecryptUpdate(ctx, + NULL, + &decryptedBytes, + additionalAuthenticatedData.bytes, + (int)additionalAuthenticatedData.length)) { + OWSFailDebug(@"failed during additionalAuthenticatedData"); + return nil; + } + } + + // Provide the message to be decrypted, and obtain the plaintext output. + // + // If we wanted to save memory, we could decrypt piece-wise from an iostream - + // feeding each chunk to EVP_DecryptUpdate, which can be called multiple times. + // For simplicity, we currently decrypt the entire ciphertext in one shot. + if (ciphertext.length >= INT_MAX) { + OWSFailDebug(@"ciphertext too large"); + return nil; + } + if (EVP_DecryptUpdate(ctx, plaintext.mutableBytes, &decryptedBytes, ciphertext.bytes, (int)ciphertext.length) + != kOpenSSLSuccess) { + OWSFailDebug(@"decryptUpdate failed"); + return nil; + } + + if (decryptedBytes != ciphertext.length) { + OWSFailDebug(@"Failed to decrypt entire ciphertext"); + return nil; + } + + // Set expected tag value. Works in OpenSSL 1.0.1d and later + if (authTagFromEncrypt.length >= INT_MAX) { + OWSFailDebug(@"authTagFromEncrypt too large"); + return nil; + } + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, (int)authTagFromEncrypt.length, (void *)authTagFromEncrypt.bytes) + != kOpenSSLSuccess) { + OWSFailDebug(@"Failed to set auth tag in decrypt."); + return nil; + } + + // Finalise the decryption. A positive return value indicates success, + // anything else is a failure - the plaintext is not trustworthy. + int finalBytes = 0; + int decryptStatus = EVP_DecryptFinal_ex(ctx, (unsigned char *)(plaintext.bytes + decryptedBytes), &finalBytes); + + // AESGCM doesn't write any final bytes + OWSAssertDebug(finalBytes == 0); + + // Clean up + EVP_CIPHER_CTX_free(ctx); + + if (decryptStatus > 0) { + return [plaintext copy]; + } else { + // This should only happen if the user has changed their profile key, which should only + // happen currently if they re-register. + OWSLogError(@"Decrypt verification failed"); + return nil; + } +} + +#pragma mark - Profiles + ++ (nullable NSData *)encryptAESGCMWithProfileData:(NSData *)plaintext key:(OWSAES256Key *)key +{ + AES25GCMEncryptionResult *result = [self encryptAESGCMWithData:plaintext additionalAuthenticatedData:nil key:key]; + return [NSData join:@[ + result.initializationVector, + result.ciphertext, + result.authTag, + ]]; +} + ++ (nullable NSData *)decryptAESGCMWithProfileData:(NSData *)encryptedData key:(OWSAES256Key *)key +{ + NSUInteger cipherTextLength; + BOOL didOverflow + = __builtin_sub_overflow(encryptedData.length, (kAESGCM256_IVLength + kAESGCM256_TagLength), &cipherTextLength); + if (didOverflow) { + OWSFailDebug(@"unexpectedly short encryptedData.length: %lu", (unsigned long)encryptedData.length); + return nil; + } + + // encryptedData layout: initializationVector || ciphertext || authTag + NSData *initializationVector = [encryptedData subdataWithRange:NSMakeRange(0, kAESGCM256_IVLength)]; + NSData *ciphertext = [encryptedData subdataWithRange:NSMakeRange(kAESGCM256_IVLength, cipherTextLength)]; + + NSUInteger tagOffset; + ows_add_overflow(kAESGCM256_IVLength, cipherTextLength, &tagOffset); + + NSData *authTag = [encryptedData subdataWithRange:NSMakeRange(tagOffset, kAESGCM256_TagLength)]; + + return [self decryptAESGCMWithInitializationVector:initializationVector + ciphertext:ciphertext + additionalAuthenticatedData:nil + authTag:authTag + key:key]; +} + +#pragma mark - AES-CTR + ++ (nullable AES256CTREncryptionResult *)encryptAESCTRWithData:(NSData *)plaintext + initializationVector:(NSData *)initializationVector + key:(OWSAES256Key *)key +{ + OWSAssertDebug(plaintext); + OWSAssertDebug(initializationVector.length == kAES256CTR_IVLength); + OWSAssertDebug(key); + + NSMutableData *cipherText = [NSMutableData dataWithLength:plaintext.length]; + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + OWSFailDebug(@"%@ failed to build context while encrypting", self.logTag); + return nil; + } + + // Initialise the encryption operation. + if (EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, NULL, NULL) != kOpenSSLSuccess) { + OWSFailDebug(@"%@ failed to init encryption", self.logTag); + return nil; + } + + // Initialise key and IV + if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.keyData.bytes, initializationVector.bytes) != kOpenSSLSuccess) { + OWSFailDebug(@"%@ failed to set key and iv while encrypting", self.logTag); + return nil; + } + + if (plaintext.length >= INT_MAX) { + OWSFailDebug(@"%@ plaintext too large", self.logTag); + return nil; + } + + // Provide the message to be encrypted, and obtain the encrypted output. + // + // If we wanted to save memory, we could encrypt piece-wise from a plaintext iostream - + // feeding each chunk to EVP_EncryptUpdate, which can be called multiple times. + // For simplicity, we currently encrypt the entire plaintext in one shot. + int bytesEncrypted = 0; + if (EVP_EncryptUpdate(ctx, cipherText.mutableBytes, &bytesEncrypted, plaintext.bytes, (int)plaintext.length) + != kOpenSSLSuccess) { + OWSFailDebug(@"%@ encryptUpdate failed", self.logTag); + return nil; + } + if (bytesEncrypted != plaintext.length) { + OWSFailDebug(@"%@ bytesEncrypted != plaintextData.length", self.logTag); + return nil; + } + + int finalizedBytes = 0; + // Finalize the encryption. Normally cipherText bytes may be written at + // this stage, but this does not occur in CTR mode + if (EVP_EncryptFinal_ex(ctx, cipherText.mutableBytes + bytesEncrypted, &finalizedBytes) != kOpenSSLSuccess) { + OWSFailDebug(@"%@ failed to finalize encryption", self.logTag); + return nil; + } + if (finalizedBytes != 0) { + OWSFailDebug(@"%@ Unexpected finalized bytes written", self.logTag); + return nil; + } + + // Clean up + EVP_CIPHER_CTX_free(ctx); + + AES256CTREncryptionResult *_Nullable result = + [[AES256CTREncryptionResult alloc] initWithCiphertext:cipherText initializationVector:initializationVector]; + + return result; +} + ++ (nullable NSData *)decryptAESCTRWithCipherText:(NSData *)cipherText + initializationVector:(NSData *)initializationVector + key:(OWSAES256Key *)key +{ + OWSAssertDebug(initializationVector.length == kAES256CTR_IVLength); + OWSAssertDebug(cipherText.length > 0); + OWSAssertDebug(key); + + NSMutableData *plaintext = [NSMutableData dataWithLength:cipherText.length]; + + // Create and initialise the context + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + + if (!ctx) { + OWSFailDebug(@"%@ failed to build context while decrypting", self.logTag); + return nil; + } + + // Initialise the decryption operation. + if (EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, NULL, NULL) != kOpenSSLSuccess) { + OWSFailDebug(@"%@ failed to init decryption", self.logTag); + return nil; + } + + // Initialise key and IV + if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.keyData.bytes, initializationVector.bytes) != kOpenSSLSuccess) { + OWSFailDebug(@"%@ failed to init decryption", self.logTag); + return nil; + } + + // Provide the message to be decrypted, and obtain the plaintext output. + // + // If we wanted to save memory, we could decrypt piece-wise from an iostream - + // feeding each chunk to EVP_DecryptUpdate, which can be called multiple times. + // For simplicity, we currently decrypt the entire cipherText in one shot. + if (cipherText.length >= INT_MAX) { + OWSFailDebug(@"%@ cipherText too large", self.logTag); + return nil; + } + int decryptedBytes = 0; + if (EVP_DecryptUpdate(ctx, plaintext.mutableBytes, &decryptedBytes, cipherText.bytes, (int)cipherText.length) + != kOpenSSLSuccess) { + OWSFailDebug(@"%@ decryptUpdate failed", self.logTag); + return nil; + } + + if (decryptedBytes != cipherText.length) { + OWSFailDebug(@"%@ Failed to decrypt entire cipherText", self.logTag); + return nil; + } + + // Finalise the decryption. A positive return value indicates success, + // anything else is a failure - the plaintext is not trustworthy. + int finalBytes = 0; + int decryptStatus = EVP_DecryptFinal_ex(ctx, (unsigned char *)(plaintext.bytes + decryptedBytes), &finalBytes); + + // AES CTR doesn't write any final bytes + OWSAssertDebug(finalBytes == 0); + + // Clean up + EVP_CIPHER_CTX_free(ctx); + + if (decryptStatus > 0) { + return [plaintext copy]; + } else { + DDLogError(@"%@ Decrypt verification failed", self.logTag); + return nil; + } +} + +#pragma mark - + ++ (void)seedRandom +{ + // We should never use rand(), but seed it just in case it's used by 3rd-party code + unsigned seed = [Cryptography randomUnsigned]; + srand(seed); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/Data+OWS.swift b/SessionProtocolKit/Utility/Data+OWS.swift new file mode 100644 index 000000000..0468f3d54 --- /dev/null +++ b/SessionProtocolKit/Utility/Data+OWS.swift @@ -0,0 +1,22 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +extension Data { + public var hexadecimalString: String { + return (self as NSData).hexadecimalString() + } + + public static func data(fromHex hexString: String) -> Data? { + guard let data = NSData(fromHexString : hexString) else { + return nil + } + return data as Data + } + + public func ows_constantTimeIsEqual(to other: Data) -> Bool { + return (self as NSData).ows_constantTimeIsEqual(to: other) + } +} diff --git a/SessionProtocolKit/Utility/Logger.swift b/SessionProtocolKit/Utility/Logger.swift new file mode 100644 index 000000000..be05f3e1c --- /dev/null +++ b/SessionProtocolKit/Utility/Logger.swift @@ -0,0 +1,77 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +// Once we're on Swift4.2 we can mark this as inlineable +// @inlinable +public func owsFormatLogMessage(_ logString: String, + file: String = #file, + function: String = #function, + line: Int = #line) -> String { + let filename = (file as NSString).lastPathComponent + // We format the filename & line number in a format compatible + // with XCode's "Open Quickly..." feature. + return "[\(filename):\(line) \(function)]: \(logString)" +} + +/** + * A minimal DDLog wrapper for swift. + */ +open class Logger: NSObject { + + open class func verbose(_ logString: @autoclosure () -> String, + file: String = #file, + function: String = #function, + line: Int = #line) { + guard ShouldLogVerbose() else { + return + } + OWSLogger.verbose(owsFormatLogMessage(logString(), file: file, function: function, line: line)) + } + + open class func debug(_ logString: @autoclosure () -> String, + file: String = #file, + function: String = #function, + line: Int = #line) { + guard ShouldLogDebug() else { + return + } + OWSLogger.debug(owsFormatLogMessage(logString(), file: file, function: function, line: line)) + } + + open class func info(_ logString: @autoclosure () -> String, + file: String = #file, + function: String = #function, + line: Int = #line) { + guard ShouldLogInfo() else { + return + } + OWSLogger.info(owsFormatLogMessage(logString(), file: file, function: function, line: line)) + } + + open class func warn(_ logString: @autoclosure () -> String, + file: String = #file, + function: String = #function, + line: Int = #line) { + guard ShouldLogWarning() else { + return + } + OWSLogger.warn(owsFormatLogMessage(logString(), file: file, function: function, line: line)) + } + + open class func error(_ logString: @autoclosure () -> String, + file: String = #file, + function: String = #function, + line: Int = #line) { + guard ShouldLogError() else { + return + } + OWSLogger.error(owsFormatLogMessage(logString(), file: file, function: function, line: line)) + } + + open class func flush() { + OWSLogger.flush() + } +} diff --git a/SessionProtocolKit/Utility/NSData+OWS.h b/SessionProtocolKit/Utility/NSData+OWS.h new file mode 100644 index 000000000..80324051d --- /dev/null +++ b/SessionProtocolKit/Utility/NSData+OWS.h @@ -0,0 +1,37 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSData (OWS) + ++ (NSData *)join:(NSArray *)datas; + +- (NSData *)dataByAppendingData:(NSData *)data; + +#pragma mark - Hex + +- (NSString *)hexadecimalString; + ++ (nullable NSData *)dataFromHexString:(NSString *)hexString; + +#pragma mark - Base64 + ++ (nullable NSData *)dataFromBase64StringNoPadding:(NSString *)aString; ++ (nullable NSData *)dataFromBase64String:(NSString *)aString; + +- (NSString *)base64EncodedString; + +#pragma mark - + +/** + * Compares data in constant time so as to help avoid potential timing attacks. + */ +- (BOOL)ows_constantTimeIsEqualToData:(NSData *)other; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/NSData+OWS.m b/SessionProtocolKit/Utility/NSData+OWS.m new file mode 100644 index 000000000..c33c8b782 --- /dev/null +++ b/SessionProtocolKit/Utility/NSData+OWS.m @@ -0,0 +1,147 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "NSData+OWS.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@implementation NSData (OWS) + ++ (NSData *)join:(NSArray *)datas +{ + OWSAssert(datas); + + NSMutableData *result = [NSMutableData new]; + for (NSData *data in datas) { + [result appendData:data]; + } + return [result copy]; +} + +- (NSData *)dataByAppendingData:(NSData *)data +{ + NSMutableData *result = [self mutableCopy]; + [result appendData:data]; + return [result copy]; +} + +#pragma mark - Hex + +- (NSString *)hexadecimalString +{ + /* Returns hexadecimal string of NSData. Empty string if data is empty. */ + const unsigned char *dataBuffer = (const unsigned char *)[self bytes]; + if (!dataBuffer) { + return @""; + } + + NSUInteger dataLength = [self length]; + NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)]; + + for (NSUInteger i = 0; i < dataLength; ++i) { + [hexString appendFormat:@"%02x", dataBuffer[i]]; + } + return [hexString copy]; +} + ++ (nullable NSData *)dataFromHexString:(NSString *)hexString { + NSMutableData *data = [NSMutableData new]; + + if (hexString.length % 2 != 0) { + OWSFailDebug(@"Hexadecimal string has unexpected length: %@ (%lu)", hexString, (unsigned long)hexString.length); + return nil; + } + for (NSUInteger i = 0; i + 2 <= hexString.length; i += 2) { + NSString *_Nullable byteString = [hexString substringWithRange:NSMakeRange(i, 2)]; + if (!byteString) { + OWSFailDebug(@"Couldn't slice hexadecimal string."); + return nil; + } + unsigned byteValue; + if (![[NSScanner scannerWithString:byteString] scanHexInt:&byteValue]) { + OWSFailDebug(@"Couldn't parse hex byte: %@.", byteString); + return nil; + } + if (byteValue > 0xff) { + OWSFailDebug(@"Invalid hex byte: %@ (%d).", byteString, byteValue); + return nil; + } + uint8_t byte = (uint8_t)(0xff & byteValue); + [data appendBytes:&byte length:1]; + } + return [data copy]; +} + +#pragma mark - Base64 + ++ (nullable NSData *)dataFromBase64StringNoPadding:(NSString *)aString +{ + int padding = aString.length % 4; + + NSMutableString *strResult = [aString mutableCopy]; + if (padding != 0) { + int charsToAdd = 4 - padding; + for (int i = 0; i < charsToAdd; i++) { + [strResult appendString:@"="]; + } + } + return [self dataFromBase64String:strResult]; +} + +// +// dataFromBase64String: +// +// Creates an NSData object containing the base64 decoded representation of +// the base64 string 'aString' +// +// Parameters: +// aString - the base64 string to decode +// +// returns the NSData representation of the base64 string +// + ++ (nullable NSData *)dataFromBase64String:(NSString *)aString +{ + return [[NSData alloc] initWithBase64EncodedString:aString options:NSDataBase64DecodingIgnoreUnknownCharacters]; +} + +// +// base64EncodedString +// +// Creates an NSString object that contains the base 64 encoding of the +// receiver's data. Lines are broken at 64 characters long. +// +// returns an NSString being the base 64 representation of the +// receiver. +// +- (NSString *)base64EncodedString +{ + return [self base64EncodedStringWithOptions:0]; +} + +#pragma mark - + +- (BOOL)ows_constantTimeIsEqualToData:(NSData *)other +{ + volatile UInt8 isEqual = 0; + + if (self.length != other.length) { + return NO; + } + + UInt8 *leftBytes = (UInt8 *)self.bytes; + UInt8 *rightBytes = (UInt8 *)other.bytes; + for (int i = 0; i < self.length; i++) { + // rather than returning as soon as we find a discrepency, we compare the rest of + // the byte stream to maintain a constant time comparison + isEqual |= leftBytes[i] ^ rightBytes[i]; + } + + return isEqual == 0; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/NSData+keyVersionByte.h b/SessionProtocolKit/Utility/NSData+keyVersionByte.h new file mode 100644 index 000000000..a4c6f1b50 --- /dev/null +++ b/SessionProtocolKit/Utility/NSData+keyVersionByte.h @@ -0,0 +1,14 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +@interface NSData (keyVersionByte) + +- (instancetype)prependKeyType; + +- (instancetype)throws_removeKeyType NS_SWIFT_UNAVAILABLE("throws objc exceptions"); +- (nullable instancetype)removeKeyTypeAndReturnError:(NSError **)outError; + +@end diff --git a/SessionProtocolKit/Utility/NSData+keyVersionByte.m b/SessionProtocolKit/Utility/NSData+keyVersionByte.m new file mode 100644 index 000000000..04a3a2554 --- /dev/null +++ b/SessionProtocolKit/Utility/NSData+keyVersionByte.m @@ -0,0 +1,49 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "NSData+keyVersionByte.h" +#import "AxolotlExceptions.h" +#import +#import + +@implementation NSData (keyVersionByte) + +const Byte DJB_TYPE = 0x05; + +- (instancetype)prependKeyType { + if (self.length == 32) { + NSMutableData *data = [NSMutableData dataWithBytes:&DJB_TYPE length:1]; + [data appendData:self.copy]; + return data; + } else { + OWSLogDebug(@"key length: %lu", (unsigned long)self.length); + } + return self; +} + +- (nullable instancetype)removeKeyTypeAndReturnError:(NSError **)outError +{ + @try { + return self.throws_removeKeyType; + } @catch (NSException *exception) { + *outError = SCKExceptionWrapperErrorMake(exception); + return nil; + } +} + +- (instancetype)throws_removeKeyType +{ + if (self.length == 33) { + if ([[self subdataWithRange:NSMakeRange(0, 1)] isEqualToData:[NSData dataWithBytes:&DJB_TYPE length:1]]) { + return [self subdataWithRange:NSMakeRange(1, 32)]; + } else{ + @throw [NSException exceptionWithName:InvalidKeyException reason:@"Key type is incorrect" userInfo:@{}]; + } + } else { + OWSLogDebug(@"key length: %lu", (unsigned long)self.length); + return self; + } +} + +@end diff --git a/SessionProtocolKit/Utility/NSDate+OWS.h b/SessionProtocolKit/Utility/NSDate+OWS.h new file mode 100644 index 000000000..e246493a3 --- /dev/null +++ b/SessionProtocolKit/Utility/NSDate+OWS.h @@ -0,0 +1,44 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// These NSTimeInterval constants provide simplified durations for readability. +// +// These approximations should never be used for strict date/time calcuations. +extern const NSTimeInterval kSecondInterval; +extern const NSTimeInterval kMinuteInterval; +extern const NSTimeInterval kHourInterval; +extern const NSTimeInterval kDayInterval; +extern const NSTimeInterval kWeekInterval; +extern const NSTimeInterval kMonthInterval; +extern const NSTimeInterval kYearInterval; + +#define kSecondInMs ((uint64_t)1000) +#define kMinuteInMs (kSecondInMs * 60) +#define kHourInMs (kMinuteInMs * 60) +#define kDayInMs (kHourInMs * 24) +#define kWeekInMs (kDayInMs * 7) +#define kMonthInMs (kDayInMs * 30) + +// kYearsInMs is a double to avoid overflow +#define kYearsInMs (kDayInMs * 365.0) + +@interface NSDate (OWS) + ++ (uint64_t)ows_millisecondTimeStamp; ++ (NSDate *)ows_dateWithMillisecondsSince1970:(uint64_t)milliseconds; ++ (uint64_t)ows_millisecondsSince1970ForDate:(NSDate *)date; + +- (BOOL)isAfterDate:(NSDate *)otherDate; +- (BOOL)isBeforeDate:(NSDate *)otherDate; + +- (BOOL)isAfterNow; +- (BOOL)isBeforeNow; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/NSDate+OWS.mm b/SessionProtocolKit/Utility/NSDate+OWS.mm new file mode 100644 index 000000000..4680bfdfc --- /dev/null +++ b/SessionProtocolKit/Utility/NSDate+OWS.mm @@ -0,0 +1,59 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "NSDate+OWS.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +const NSTimeInterval kSecondInterval = 1; +const NSTimeInterval kMinuteInterval = 60; +const NSTimeInterval kHourInterval = 60 * kMinuteInterval; +const NSTimeInterval kDayInterval = 24 * kHourInterval; +const NSTimeInterval kWeekInterval = 7 * kDayInterval; +const NSTimeInterval kMonthInterval = 30 * kDayInterval; +const NSTimeInterval kYearInterval = 365 * kDayInterval; + +@implementation NSDate (OWS) + ++ (uint64_t)ows_millisecondTimeStamp +{ + uint64_t milliseconds + = (uint64_t)(std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1)); + return milliseconds; +} + ++ (NSDate *)ows_dateWithMillisecondsSince1970:(uint64_t)milliseconds +{ + return [NSDate dateWithTimeIntervalSince1970:(milliseconds / 1000.0)]; +} + ++ (uint64_t)ows_millisecondsSince1970ForDate:(NSDate *)date +{ + return (uint64_t)(date.timeIntervalSince1970 * 1000); +} + +- (BOOL)isAfterDate:(NSDate *)otherDate +{ + return [self compare:otherDate] == NSOrderedDescending; +} + +- (BOOL)isBeforeDate:(NSDate *)otherDate +{ + return [self compare:otherDate] == NSOrderedAscending; +} + +- (BOOL)isAfterNow +{ + return [self isAfterDate:[NSDate new]]; +} + +- (BOOL)isBeforeNow +{ + return [self isBeforeDate:[NSDate new]]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/NSObject+OWS.h b/SessionProtocolKit/Utility/NSObject+OWS.h new file mode 100644 index 000000000..5799ba8d1 --- /dev/null +++ b/SessionProtocolKit/Utility/NSObject+OWS.h @@ -0,0 +1,21 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSObject (OWS) + +#pragma mark - Logging + +@property (nonatomic, readonly) NSString *logTag; + +@property (class, nonatomic, readonly) NSString *logTag; + ++ (BOOL)isNullableObject:(nullable NSObject *)left equalTo:(nullable NSObject *)right; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/NSObject+OWS.m b/SessionProtocolKit/Utility/NSObject+OWS.m new file mode 100644 index 000000000..2368ef19a --- /dev/null +++ b/SessionProtocolKit/Utility/NSObject+OWS.m @@ -0,0 +1,36 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "NSObject+OWS.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation NSObject (OWS) + +#pragma mark - Logging + ++ (NSString *)logTag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)logTag +{ + return self.class.logTag; +} + ++ (BOOL)isNullableObject:(nullable NSObject *)left equalTo:(nullable NSObject *)right +{ + if (!left && !right) { + return YES; + } else if (!left || !right) { + return NO; + } else { + return [left isEqual:right]; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/NSString+OWS.h b/SessionProtocolKit/Utility/NSString+OWS.h new file mode 100644 index 000000000..f76b1b096 --- /dev/null +++ b/SessionProtocolKit/Utility/NSString+OWS.h @@ -0,0 +1,28 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSString (OWS) + +- (NSString *)ows_stripped; + +- (NSString *)digitsOnly; + +@property (nonatomic, readonly) BOOL hasAnyASCII; +@property (nonatomic, readonly) BOOL isOnlyASCII; + +- (NSString *)filterStringForDisplay; + +- (NSString *)filterFilename; + +- (BOOL)isValidE164; + ++ (NSString *)formatDurationSeconds:(uint32_t)durationSeconds useShortFormat:(BOOL)useShortFormat; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/NSString+OWS.m b/SessionProtocolKit/Utility/NSString+OWS.m new file mode 100644 index 000000000..5414f46f0 --- /dev/null +++ b/SessionProtocolKit/Utility/NSString+OWS.m @@ -0,0 +1,453 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "NSString+OWS.h" +#import "iOSVersions.h" +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UnicodeCodeRange : NSObject + +@property (nonatomic) unichar first; +@property (nonatomic) unichar last; + +@end + +#pragma mark - + +@implementation UnicodeCodeRange + ++ (UnicodeCodeRange *)rangeWithStart:(unichar)first last:(unichar)last +{ + OWSAssertDebug(first <= last); + + UnicodeCodeRange *range = [UnicodeCodeRange new]; + range.first = first; + range.last = last; + return range; +} + +- (NSComparisonResult)compare:(UnicodeCodeRange *)other +{ + + return self.first > other.first; +} + +@end + +#pragma mark - + +static void *kNSString_SSK_hasExcessiveDiacriticals = &kNSString_SSK_hasExcessiveDiacriticals; + +@implementation NSString (OWS) + +- (NSString *)ows_stripped +{ + return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; +} + ++ (BOOL)shouldFilterIndic +{ + static BOOL result = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + result = (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 0) && !SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 3)); + }); + return result; +} + ++ (BOOL)isIndicVowel:(unichar)c +{ + static NSArray *ranges; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // From: + // https://unicode.org/charts/PDF/U0C00.pdf + // https://unicode.org/charts/PDF/U0980.pdf + // https://unicode.org/charts/PDF/U0900.pdf + ranges = [@[ + // Telugu: + [UnicodeCodeRange rangeWithStart:0xC05 last:0xC14], + [UnicodeCodeRange rangeWithStart:0xC3E last:0xC4C], + [UnicodeCodeRange rangeWithStart:0xC60 last:0xC63], + // Bengali + [UnicodeCodeRange rangeWithStart:0x985 last:0x994], + [UnicodeCodeRange rangeWithStart:0x9BE last:0x9C8], + [UnicodeCodeRange rangeWithStart:0x9CB last:0x9CC], + [UnicodeCodeRange rangeWithStart:0x9E0 last:0x9E3], + // Devanagari + [UnicodeCodeRange rangeWithStart:0x904 last:0x914], + [UnicodeCodeRange rangeWithStart:0x93A last:0x93B], + [UnicodeCodeRange rangeWithStart:0x93E last:0x94C], + [UnicodeCodeRange rangeWithStart:0x94E last:0x94F], + [UnicodeCodeRange rangeWithStart:0x955 last:0x957], + [UnicodeCodeRange rangeWithStart:0x960 last:0x963], + [UnicodeCodeRange rangeWithStart:0x972 last:0x977], + ] sortedArrayUsingSelector:@selector(compare:)]; + }); + + for (UnicodeCodeRange *range in ranges) { + if (c < range.first) { + // For perf, we can take advantage of the fact that the + // ranges are sorted to exit early if the character lies + // before the current range. + return NO; + } + if (range.first <= c && c <= range.last) { + return YES; + } + } + return NO; +} + ++ (NSCharacterSet *)problematicCharacterSetForIndicScript +{ + static NSCharacterSet *characterSet; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + characterSet = [NSCharacterSet characterSetWithCharactersInString:@"\u200C"]; + }); + + return characterSet; +} + +// See: https://manishearth.github.io/blog/2018/02/15/picking-apart-the-crashing-ios-string/ +- (NSString *)filterForIndicScripts +{ + if (!NSString.shouldFilterIndic) { + return self; + } + + if ([self rangeOfCharacterFromSet:[[self class] problematicCharacterSetForIndicScript]].location == NSNotFound) { + return self; + } + + NSMutableString *filteredForIndic = [NSMutableString new]; + for (NSUInteger index = 0; index < self.length; index++) { + unichar c = [self characterAtIndex:index]; + if (c == 0x200C) { + NSUInteger nextIndex = index + 1; + if (nextIndex < self.length) { + unichar next = [self characterAtIndex:nextIndex]; + if ([NSString isIndicVowel:next]) { + // Discard ZWNJ (zero-width non-joiner) whenever we find a ZWNJ + // followed by an Indic (Telugu, Bengali, Devanagari) vowel + // and replace it with 0xFFFD, the Unicode "replacement character." + [filteredForIndic appendFormat:@"\uFFFD"]; + OWSLogError(@"Filtered unsafe Indic script."); + // Then discard the vowel too. + index++; + continue; + } + } + } + [filteredForIndic appendFormat:@"%C", c]; + } + return [filteredForIndic copy]; +} + ++ (NSCharacterSet *)unsafeFilenameCharacterSet +{ + static NSCharacterSet *characterSet; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // 0x202D and 0x202E are the unicode ordering letters + // and can be used to control the rendering of text. + // They could be used to construct misleading attachment + // filenames that appear to have a different file extension, + // for example. + characterSet = [NSCharacterSet characterSetWithCharactersInString:@"\u202D\u202E"]; + }); + + return characterSet; +} + +- (NSString *)filterUnsafeFilenameCharacters +{ + NSCharacterSet *unsafeCharacterSet = [[self class] unsafeFilenameCharacterSet]; + NSRange range = [self rangeOfCharacterFromSet:unsafeCharacterSet]; + if (range.location == NSNotFound) { + return self; + } + NSMutableString *filtered = [NSMutableString new]; + NSString *remainder = [self copy]; + while (range.location != NSNotFound) { + if (range.location > 0) { + [filtered appendString:[remainder substringToIndex:range.location]]; + } + // The "replacement" code point. + [filtered appendString:@"\uFFFD"]; + remainder = [remainder substringFromIndex:range.location + range.length]; + range = [remainder rangeOfCharacterFromSet:unsafeCharacterSet]; + } + [filtered appendString:remainder]; + return filtered; +} + +- (NSString *)filterStringForDisplay +{ + return self.ows_stripped.filterForIndicScripts.filterForExcessiveDiacriticals; +} + +- (NSString *)filterFilename +{ + return self.ows_stripped.filterForIndicScripts.filterForExcessiveDiacriticals.filterUnsafeFilenameCharacters; +} + +- (NSString *)filterForExcessiveDiacriticals +{ + if (!self.hasExcessiveDiacriticals) { + return self; + } + return [self stringByFoldingWithOptions:NSDiacriticInsensitiveSearch locale:[NSLocale currentLocale]]; +} + +- (BOOL)hasExcessiveDiacriticals +{ + NSNumber *cachedValue = objc_getAssociatedObject(self, kNSString_SSK_hasExcessiveDiacriticals); + if (!cachedValue) { + cachedValue = @([self computeHasExcessiveDiacriticals]); + objc_setAssociatedObject(self, kNSString_SSK_hasExcessiveDiacriticals, cachedValue, OBJC_ASSOCIATION_COPY); + } + + return cachedValue.boolValue; +} + +- (BOOL)computeHasExcessiveDiacriticals +{ + // discard any zalgo style text, by detecting maximum number of glyphs per character + NSUInteger index = 0; + + // store in local var, it's a hot code path. + NSUInteger length = self.length; + while (index < length) { + // Walk the grapheme clusters in the string. + NSRange range = [self rangeOfComposedCharacterSequenceAtIndex:index]; + if (range.length > 8) { + // There are too many characters in this grapheme cluster. + return YES; + } else if (range.location != index || range.length < 1) { + // This should never happen. + OWSFailDebug( + @"unexpected composed character sequence: %lu, %@", (unsigned long)index, NSStringFromRange(range)); + return YES; + } + index = range.location + range.length; + } + return NO; +} + ++ (NSRegularExpression *)anyASCIIRegex +{ + static dispatch_once_t onceToken; + static NSRegularExpression *regex; + dispatch_once(&onceToken, ^{ + NSError *error; + regex = [NSRegularExpression regularExpressionWithPattern:@"[\x00-\x7F]+" + options:0 + error:&error]; + if (error || !regex) { + // crash! it's not clear how to proceed safely, and this regex should never fail. + OWSFail(@"could not compile regex: %@", error); + } + }); + + return regex; +} + ++ (NSRegularExpression *)onlyASCIIRegex +{ + static dispatch_once_t onceToken; + static NSRegularExpression *regex; + dispatch_once(&onceToken, ^{ + NSError *error; + regex = [NSRegularExpression regularExpressionWithPattern:@"^[\x00-\x7F]*$" + options:0 + error:&error]; + if (error || !regex) { + // crash! it's not clear how to proceed safely, and this regex should never fail. + OWSFail(@"could not compile regex: %@", error); + } + }); + + return regex; +} + + +- (BOOL)isOnlyASCII; +{ + return [self.class.onlyASCIIRegex rangeOfFirstMatchInString:self + options:0 + range:NSMakeRange(0, self.length)].location != NSNotFound; +} + +- (BOOL)hasAnyASCII +{ + return [self.class.anyASCIIRegex rangeOfFirstMatchInString:self + options:0 + range:NSMakeRange(0, self.length)].location != NSNotFound; +} + +- (BOOL)isValidE164 +{ + NSError *error = nil; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^\\+\\d+$" + options:NSRegularExpressionCaseInsensitive + error:&error]; + if (error || !regex) { + OWSFailDebug(@"could not compile regex: %@", error); + return NO; + } + return [regex rangeOfFirstMatchInString:self options:0 range:NSMakeRange(0, self.length)].location != NSNotFound; +} + ++ (NSString *)formatDurationSeconds:(uint32_t)durationSeconds useShortFormat:(BOOL)useShortFormat +{ + NSString *amountFormat; + uint32_t duration; + + uint32_t secondsPerMinute = 60; + uint32_t secondsPerHour = secondsPerMinute * 60; + uint32_t secondsPerDay = secondsPerHour * 24; + uint32_t secondsPerWeek = secondsPerDay * 7; + + if (durationSeconds < secondsPerMinute) { // XX Seconds + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SECONDS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of seconds}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally omitted between the text and the embedded duration so that " + @"we get, e.g. '5s' not '5 s'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SECONDS", + @"{{number of seconds}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{5 seconds}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds; + } else if (durationSeconds < secondsPerMinute * 1.5) { // 1 Minute + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_MINUTES_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally omitted between the text and the embedded duration so that " + @"we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_MINUTE", + @"{{1 minute}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{1 minute}}'. See other *_TIME_AMOUNT strings"); + } + duration = durationSeconds / secondsPerMinute; + } else if (durationSeconds < secondsPerHour) { // Multiple Minutes + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_MINUTES_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally omitted between the text and the embedded duration so that " + @"we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_MINUTES", + @"{{number of minutes}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{5 minutes}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerMinute; + } else if (durationSeconds < secondsPerHour * 1.5) { // 1 Hour + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_HOURS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally omitted between the text and the embedded duration so that " + @"we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_HOUR", + @"{{1 hour}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{1 hour}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerHour; + } else if (durationSeconds < secondsPerDay) { // Multiple Hours + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_HOURS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally omitted between the text and the embedded duration so that " + @"we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_HOURS", + @"{{number of hours}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{5 hours}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerHour; + } else if (durationSeconds < secondsPerDay * 1.5) { // 1 Day + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_DAYS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally omitted between the text and the embedded duration so that " + @"we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_DAY", + @"{{1 day}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{1 day}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerDay; + } else if (durationSeconds < secondsPerWeek) { // Multiple Days + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_DAYS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally omitted between the text and the embedded duration so that " + @"we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_DAYS", + @"{{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{5 days}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerDay; + } else if (durationSeconds < secondsPerWeek * 1.5) { // 1 Week + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_WEEKS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally omitted between the text and the embedded duration so that " + @"we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_WEEK", + @"{{1 week}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{1 week}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerWeek; + } else { // Multiple weeks + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_WEEKS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally omitted between the text and the embedded duration so that " + @"we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_WEEKS", + @"{{number of weeks}}, embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{5 weeks}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerWeek; + } + + return [NSString stringWithFormat:amountFormat, + [NSNumberFormatter localizedStringFromNumber:@(duration) numberStyle:NSNumberFormatterNoStyle]]; +} + +- (NSString *)removeAllCharactersIn:(NSCharacterSet *)characterSet +{ + OWSAssertDebug(characterSet); + + return [[self componentsSeparatedByCharactersInSet:characterSet] componentsJoinedByString:@""]; +} + +- (NSString *)digitsOnly +{ + return [self removeAllCharactersIn:[NSCharacterSet.decimalDigitCharacterSet invertedSet]]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/OWSAsserts.h b/SessionProtocolKit/Utility/OWSAsserts.h new file mode 100644 index 000000000..5049740e0 --- /dev/null +++ b/SessionProtocolKit/Utility/OWSAsserts.h @@ -0,0 +1,210 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "OWSLogs.h" + +NS_ASSUME_NONNULL_BEGIN + +#ifndef OWSAssert + +#define CONVERT_TO_STRING(X) #X +#define CONVERT_EXPR_TO_STRING(X) CONVERT_TO_STRING(X) + +#ifdef DEBUG + +#define USE_ASSERTS + +// OWSAssertDebug() and OWSFailDebug() should be used in Obj-C methods. +// OWSCAssertDebug() and OWSCFailDebug() should be used in free functions. + +#define OWSAssertDebug(X) \ + do { \ + if (!(X)) { \ + OWSLogError(@"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ + OWSLogFlush(); \ + NSAssert(0, @"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ + } \ + } while (NO) + +#define OWSCAssertDebug(X) \ + do { \ + if (!(X)) { \ + OWSLogError(@"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ + OWSLogFlush(); \ + NSCAssert(0, @"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ + } \ + } while (NO) + +#define OWSFailWithoutLogging(message, ...) \ + do { \ + NSString *formattedMessage = [NSString stringWithFormat:message, ##__VA_ARGS__]; \ + NSAssert(0, formattedMessage); \ + } while (NO) + +#define OWSCFailWithoutLogging(message, ...) \ + do { \ + NSString *formattedMessage = [NSString stringWithFormat:message, ##__VA_ARGS__]; \ + NSCAssert(0, formattedMessage); \ + } while (NO) + +#define OWSFailNoFormat(message) \ + do { \ + OWSLogError(@"%@", message); \ + OWSLogFlush(); \ + NSAssert(0, message); \ + } while (NO) + +#define OWSCFailNoFormat(message) \ + do { \ + OWSLogError(@"%@", message); \ + OWSLogFlush(); \ + NSCAssert(0, message); \ + } while (NO) + +#else + +#define OWSAssertDebug(X) +#define OWSCAssertDebug(X) +#define OWSFailWithoutLogging(message, ...) +#define OWSCFailWithoutLogging(message, ...) +#define OWSFailNoFormat(X) +#define OWSCFailNoFormat(X) + +#endif + +#endif + +// Like OWSAssertDebug, but will fail in production, terminating the app +#define OWSAssert(X) \ + do { \ + if (!(X)) { \ + OWSFail(@"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ + } \ + } while (NO) + +#define OWSCAssert(X) \ + do { \ + if (!(X)) { \ + OWSCFail(@"Assertion failed: %s", CONVERT_EXPR_TO_STRING(X)); \ + } \ + } while (NO) + +#define OWSAbstractMethod() OWSFail(@"Method needs to be implemented by subclasses.") + +// This macro is intended for use in Objective-C. +#define OWSAssertIsOnMainThread() OWSCAssertDebug([NSThread isMainThread]) + +#define OWSFailDebug(_messageFormat, ...) \ + do { \ + OWSLogError(_messageFormat, ##__VA_ARGS__); \ + OWSLogFlush(); \ + OWSFailWithoutLogging(_messageFormat, ##__VA_ARGS__); \ + } while (0) + +#define OWSCFailDebug(_messageFormat, ...) \ + do { \ + OWSLogError(_messageFormat, ##__VA_ARGS__); \ + OWSLogFlush(); \ + OWSCFailWithoutLogging(_messageFormat, ##__VA_ARGS__); \ + } while (NO) + +void SwiftExit(NSString *message, const char *file, const char *function, int line); + +#define OWSFail(_messageFormat, ...) \ + do { \ + OWSFailDebug(_messageFormat, ##__VA_ARGS__); \ + \ + NSString *_message = [NSString stringWithFormat:_messageFormat, ##__VA_ARGS__]; \ + SwiftExit(_message, __FILE__, __PRETTY_FUNCTION__, __LINE__); \ + } while (0) + +#define OWSCFail(_messageFormat, ...) \ + do { \ + OWSCFailDebug(_messageFormat, ##__VA_ARGS__); \ + \ + NSString *_message = [NSString stringWithFormat:_messageFormat, ##__VA_ARGS__]; \ + SwiftExit(_message, __FILE__, __PRETTY_FUNCTION__, __LINE__); \ + } while (NO) + +// Avoids Clang analyzer warning: +// Value stored to 'x' during it's initialization is never read +#define SUPPRESS_DEADSTORE_WARNING(x) \ + do { \ + (void)x; \ + } while (0) + +__attribute__((annotate("returns_localized_nsstring"))) static inline NSString *LocalizationNotNeeded(NSString *s) +{ + return s; +} + +#define OWSGuardWithException(X, ExceptionName) \ + do { \ + if (!(X)) { \ + OWSRaiseException(ExceptionName, @"Guard failed: %s", CONVERT_EXPR_TO_STRING(X)); \ + } \ + } while (NO) + +#define OWSRaiseException(name, formatParam, ...) \ + do { \ + OWSLogError(@"Exception: %@ %@", name, [NSString stringWithFormat:formatParam, ##__VA_ARGS__]); \ + OWSLogFlush(); \ + @throw [NSException exceptionWithName:name \ + reason:[NSString stringWithFormat:formatParam, ##__VA_ARGS__] \ + userInfo:nil]; \ + } while (NO) + +#define OWSRaiseExceptionWithUserInfo(name, userInfoParam, formatParam, ...) \ + do { \ + OWSLogError( \ + @"Exception: %@ %@ %@", name, userInfoParam, [NSString stringWithFormat:formatParam, ##__VA_ARGS__]); \ + OWSLogFlush(); \ + @throw [NSException exceptionWithName:name \ + reason:[NSString stringWithFormat:formatParam, ##__VA_ARGS__] \ + userInfo:userInfoParam]; \ + } while (NO) + + +// UI JANK +// +// In pursuit of smooth UI, we want to continue moving blocking operations off the main thread. +// Add `OWSJanksUI` in code paths that shouldn't be called on the main thread. +// Because we have pervasively broken this tenant, enabling it by default would be too disruptive +// but it's helpful while unjanking and maybe someday we can have it enabled by default. +//#define DEBUG_UI_JANK 1 + +#ifdef DEBUG +#ifdef DEBUG_UI_JANK +#define OWSJanksUI() \ + do { \ + OWSAssertDebug(![NSThread isMainThread]) \ + } while (NO) +#endif +#endif + +#ifndef OWSJanksUI +#define OWSJanksUI() +#endif + +#pragma mark - Overflow Math + +#define ows_add_overflow(a, b, resultRef) \ + do { \ + BOOL _didOverflow = __builtin_add_overflow(a, b, resultRef); \ + OWSAssert(!_didOverflow); \ + } while (NO) + +#define ows_sub_overflow(a, b, resultRef) \ + do { \ + BOOL _didOverflow = __builtin_sub_overflow(a, b, resultRef); \ + OWSAssert(!_didOverflow); \ + } while (NO) + +#define ows_mul_overflow(a, b, resultRef) \ + do { \ + BOOL _didOverflow = __builtin_mul_overflow(a, b, resultRef); \ + OWSAssert(!_didOverflow); \ + } while (NO) + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/OWSAsserts.m b/SessionProtocolKit/Utility/OWSAsserts.m new file mode 100644 index 000000000..cafc3aa3d --- /dev/null +++ b/SessionProtocolKit/Utility/OWSAsserts.m @@ -0,0 +1,17 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "OWSAsserts.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +void SwiftExit(NSString *message, const char *file, const char *function, int line) +{ + NSString *_file = [NSString stringWithFormat:@"%s", file]; + NSString *_function = [NSString stringWithFormat:@"%s", function]; + [OWSSwiftUtils owsFail:message file:_file function:_function line:line]; +} + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/OWSDataParser.swift b/SessionProtocolKit/Utility/OWSDataParser.swift new file mode 100644 index 000000000..3fc472d68 --- /dev/null +++ b/SessionProtocolKit/Utility/OWSDataParser.swift @@ -0,0 +1,48 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +public enum OWSDataParserError: Error { + case overflow(description : String) +} + +// MARK: - OWSDataParser + +@objc public class OWSDataParser: NSObject { + + private let data: Data + private var cursor: UInt = 0 + + @objc public init(data: Data) { + self.data = data + } + + @objc public func nextData(length: UInt, name: String?=nil) throws -> Data { + guard cursor + length <= data.count else { + guard let name = name else { + throw OWSDataParserError.overflow(description: "\(logTag) invalid data read") + } + throw OWSDataParserError.overflow(description: "\(logTag) invalid data read: \(name)") + } + + let endIndex = cursor + length + let result = data.subdata(in: Int(cursor).. UInt8 { + let subdata = try nextData(length: 1, name: name) + return subdata[0] + } + + @objc public func remainder(name: String?=nil) throws -> Data { + return try nextData(length: UInt(data.count) - cursor, name: name) + } + + @objc public var isEmpty: Bool { + return data.count == cursor + } +} diff --git a/SessionProtocolKit/Utility/OWSLogs.h b/SessionProtocolKit/Utility/OWSLogs.h new file mode 100644 index 000000000..cdf8b8fe3 --- /dev/null +++ b/SessionProtocolKit/Utility/OWSLogs.h @@ -0,0 +1,91 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +#ifdef DEBUG +static const NSUInteger ddLogLevel = DDLogLevelAll; +#else +static const NSUInteger ddLogLevel = DDLogLevelInfo; +#endif + +static inline BOOL ShouldLogVerbose() +{ + return ddLogLevel >= DDLogLevelVerbose; +} + +static inline BOOL ShouldLogDebug() +{ + return ddLogLevel >= DDLogLevelDebug; +} + +static inline BOOL ShouldLogInfo() +{ + return ddLogLevel >= DDLogLevelInfo; +} + +static inline BOOL ShouldLogWarning() +{ + return ddLogLevel >= DDLogLevelWarning; +} + +static inline BOOL ShouldLogError() +{ + return ddLogLevel >= DDLogLevelError; +} + +/** + * A minimal DDLog wrapper for swift. + */ +@interface OWSLogger : NSObject + ++ (void)verbose:(NSString *)logString; ++ (void)debug:(NSString *)logString; ++ (void)info:(NSString *)logString; ++ (void)warn:(NSString *)logString; ++ (void)error:(NSString *)logString; + ++ (void)flush; + +@end + +#define OWSLogPrefix() \ + ([NSString stringWithFormat:@"[%@:%d %s]: ", \ + [[NSString stringWithUTF8String:__FILE__] lastPathComponent], \ + __LINE__, \ + __PRETTY_FUNCTION__]) + +#define OWSLogVerbose(_messageFormat, ...) \ + do { \ + DDLogVerbose(@"%@%@", OWSLogPrefix(), [NSString stringWithFormat:_messageFormat, ##__VA_ARGS__]); \ + } while (0) + +#define OWSLogDebug(_messageFormat, ...) \ + do { \ + DDLogDebug(@"%@%@", OWSLogPrefix(), [NSString stringWithFormat:_messageFormat, ##__VA_ARGS__]); \ + } while (0) + +#define OWSLogInfo(_messageFormat, ...) \ + do { \ + DDLogInfo(@"%@%@", OWSLogPrefix(), [NSString stringWithFormat:_messageFormat, ##__VA_ARGS__]); \ + } while (0) + +#define OWSLogWarn(_messageFormat, ...) \ + do { \ + DDLogWarn(@"%@%@", OWSLogPrefix(), [NSString stringWithFormat:_messageFormat, ##__VA_ARGS__]); \ + } while (0) + +#define OWSLogError(_messageFormat, ...) \ + do { \ + DDLogError(@"%@%@", OWSLogPrefix(), [NSString stringWithFormat:_messageFormat, ##__VA_ARGS__]); \ + } while (0) + +#define OWSLogFlush() \ + do { \ + [DDLog flushLog]; \ + } while (0) + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/OWSLogs.m b/SessionProtocolKit/Utility/OWSLogs.m new file mode 100644 index 000000000..4d38312e7 --- /dev/null +++ b/SessionProtocolKit/Utility/OWSLogs.m @@ -0,0 +1,43 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "OWSLogs.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OWSLogger + ++ (void)verbose:(NSString *)logString +{ + DDLogVerbose(@"%@", logString); +} + ++ (void)debug:(NSString *)logString +{ + DDLogDebug(@"%@", logString); +} + ++ (void)info:(NSString *)logString +{ + DDLogInfo(@"%@", logString); +} + ++ (void)warn:(NSString *)logString +{ + DDLogWarn(@"%@", logString); +} + ++ (void)error:(NSString *)logString +{ + DDLogError(@"%@", logString); +} + ++ (void)flush +{ + OWSLogFlush(); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/OWSSwiftUtils.swift b/SessionProtocolKit/Utility/OWSSwiftUtils.swift new file mode 100644 index 000000000..c5e08bfb3 --- /dev/null +++ b/SessionProtocolKit/Utility/OWSSwiftUtils.swift @@ -0,0 +1,70 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +/** + * We synchronize access to state in this class using this queue. + */ +public func assertOnQueue(_ queue: DispatchQueue) { + if #available(iOS 10.0, *) { + dispatchPrecondition(condition: .onQueue(queue)) + } else { + // Skipping check on Never { + owsFailDebug(logMessage, file: file, function: function, line: line) + let formattedMessage = owsFormatLogMessage(logMessage, file: file, function: function, line: line) + fatalError(formattedMessage) +} + +// Once we're on Swift4.2 we can mark this as inlineable +// @inlinable +public func notImplemented(file: String = #file, + function: String = #function, + line: Int = #line) -> Never { + owsFail("Method not implemented.", file: file, function: function, line: line) +} + +@objc public class OWSSwiftUtils: NSObject { + // This method can be invoked from Obj-C to exit the app. + @objc public class func owsFail(_ logMessage: String, + file: String = #file, + function: String = #function, + line: Int = #line) -> Never { + + owsFailDebug(logMessage, file: file, function: function, line: line) + let formattedMessage = owsFormatLogMessage(logMessage, file: file, function: function, line: line) + fatalError(formattedMessage) + } +} diff --git a/SessionProtocolKit/Utility/Randomness.h b/SessionProtocolKit/Utility/Randomness.h new file mode 100644 index 000000000..f74b525ee --- /dev/null +++ b/SessionProtocolKit/Utility/Randomness.h @@ -0,0 +1,20 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +@interface Randomness : NSObject + +/** + * Generates a given number of cryptographically secure bytes using SecRandomCopyBytes. + * + * @param numberBytes The number of bytes to be generated. + * + * @return Random Bytes. + */ + ++ (NSData *)generateRandomBytes:(int)numberBytes; + + +@end diff --git a/SessionProtocolKit/Utility/Randomness.m b/SessionProtocolKit/Utility/Randomness.m new file mode 100644 index 000000000..27fbdf562 --- /dev/null +++ b/SessionProtocolKit/Utility/Randomness.m @@ -0,0 +1,24 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "Randomness.h" +#import + +@implementation Randomness + ++ (NSData *)generateRandomBytes:(int)numberBytes +{ + NSMutableData *_Nullable randomBytes = [NSMutableData dataWithLength:numberBytes]; + if (!randomBytes) { + OWSFail(@"Could not allocate buffer for random bytes."); + } + int err = 0; + err = SecRandomCopyBytes(kSecRandomDefault, numberBytes, [randomBytes mutableBytes]); + if (err != noErr || randomBytes.length != numberBytes) { + OWSFail(@"Could not generate random bytes."); + } + return [randomBytes copy]; +} + +@end diff --git a/SessionProtocolKit/Utility/SCKExceptionWrapper.h b/SessionProtocolKit/Utility/SCKExceptionWrapper.h new file mode 100644 index 000000000..b2eab60bb --- /dev/null +++ b/SessionProtocolKit/Utility/SCKExceptionWrapper.h @@ -0,0 +1,95 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +extern NSErrorDomain const SCKExceptionWrapperErrorDomain; +typedef NS_ERROR_ENUM(SCKExceptionWrapperErrorDomain, SCKExceptionWrapperError) { + SCKExceptionWrapperErrorThrown = 900 +}; + +extern NSErrorUserInfoKey const SCKExceptionWrapperUnderlyingExceptionKey; + +NSError *SCKExceptionWrapperErrorMake(NSException *exception); + +/// Naming conventions: +/// +/// Every objc method that can throw an exception should be prefixed with `throws_`. +/// e.g. `throws_foo` and `throws_fooWithBar:bar` +/// +/// Every objc method that *calls* an uncaught `throws_` method can throw an exception, +/// so transitively, it should be a `throws_method` +/// +/// WRONG!: +/// +/// -(void)bar +/// { +/// [foo throws_foo]; +/// } +/// +/// RIGHT!: +/// +/// -(void)throws_bar +/// { +/// [foo throws_foo]; +/// } +/// +/// WRONG!: +/// +/// -(void)throws_bar +/// { +/// @try { +/// [foo throws_foo]; +/// } @catch(NSException *exception) { +/// // all exceptions are caught, +/// // so bar doesn't throw. +/// [self doSomethingElse]; +/// } +/// } +/// +/// RIGHT!: +/// +/// -(void)bar +/// { +/// @try { +/// [foo throws_foo]; +/// } @catch(NSException *exception) { +/// // all exceptions are caught, +/// // so bar doesn't throw. +/// [self doSomethingElse]; +/// } +/// } +/// +/// Since initializers must start with the word `init`, an initializer which throws is labeled +/// somewhat awkwardly as: `init_throws_foo` or `init_throws_withFoo:` +/// +/// +/// Any method that can throw an objc exception must not be called from swift, so must be marked +/// as NS_SWIFT_UNAVAILABLE("some helpful comment or alternative"). When appropriate, provide a +/// Swift safe wrapper using SCKExceptionWrapper. +/// +/// +/// -(BOOL)barAndReturnError:(NSError **)outError +/// { +/// return [SCKExceptionWrapper tryBlock:^{ [self throws_bar]; } +/// error:outError]; +/// } +/// +/// -(void)throws_bar +/// { +/// [foo throws_foo]; +/// } + +NS_SWIFT_UNAVAILABLE("throws objc exceptions") +@interface SCKExceptionWrapper: NSObject + ++ (BOOL)tryBlock:(void (^)(void))block error:(NSError **)outError; + +@end + +void SCKRaiseIfExceptionWrapperError(NSError *_Nullable error) NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/SCKExceptionWrapper.m b/SessionProtocolKit/Utility/SCKExceptionWrapper.m new file mode 100644 index 000000000..fcea30a7f --- /dev/null +++ b/SessionProtocolKit/Utility/SCKExceptionWrapper.m @@ -0,0 +1,46 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "SCKExceptionWrapper.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +NSErrorDomain const SCKExceptionWrapperErrorDomain = @"SignalCoreKit.SCKExceptionWrapper"; +NSErrorUserInfoKey const SCKExceptionWrapperUnderlyingExceptionKey = @"SCKExceptionWrapperUnderlyingException"; + +NSError *SCKExceptionWrapperErrorMake(NSException *exception) +{ + return [NSError errorWithDomain:SCKExceptionWrapperErrorDomain + code:SCKExceptionWrapperErrorThrown + userInfo:@{ SCKExceptionWrapperUnderlyingExceptionKey : exception }]; +} + +@implementation SCKExceptionWrapper + ++ (BOOL)tryBlock:(void (^)(void))block error:(NSError **)outError +{ + OWSAssertDebug(outError); + @try { + block(); + return YES; + } @catch (NSException *exception) { + *outError = SCKExceptionWrapperErrorMake(exception); + return NO; + } +} + +@end + +void SCKRaiseIfExceptionWrapperError(NSError *_Nullable error) +{ + if (error && [error.domain isEqualToString:SCKExceptionWrapperErrorDomain] + && error.code == SCKExceptionWrapperErrorThrown) { + NSException *_Nullable exception = error.userInfo[SCKExceptionWrapperUnderlyingExceptionKey]; + OWSCAssert(exception); + @throw exception; + } +} + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/SerializationUtilities.h b/SessionProtocolKit/Utility/SerializationUtilities.h new file mode 100644 index 000000000..a108969cc --- /dev/null +++ b/SessionProtocolKit/Utility/SerializationUtilities.h @@ -0,0 +1,27 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +#define MAC_LENGTH 8 + +@interface SerializationUtilities : NSObject + ++ (int)highBitsToIntFromByte:(Byte)byte; + ++ (int)lowBitsToIntFromByte:(Byte)byte; + ++ (Byte)intsToByteHigh:(int)highValue low:(int)lowValue; + ++ (NSData *)throws_macWithVersion:(int)version + identityKey:(NSData *)senderIdentityKey + receiverIdentityKey:(NSData *)receiverIdentityKey + macKey:(NSData *)macKey + serialized:(NSData *)serialized NS_SWIFT_UNAVAILABLE("throws objc exceptions"); + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/SerializationUtilities.m b/SessionProtocolKit/Utility/SerializationUtilities.m new file mode 100644 index 000000000..a06c1f659 --- /dev/null +++ b/SessionProtocolKit/Utility/SerializationUtilities.m @@ -0,0 +1,81 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "SerializationUtilities.h" +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@implementation SerializationUtilities + ++ (int)highBitsToIntFromByte:(Byte)byte +{ + return (byte & 0xFF) >> 4; +} + ++ (int)lowBitsToIntFromByte:(Byte)byte +{ + return (byte & 0xF); +} + ++ (Byte)intsToByteHigh:(int)highValue low:(int)lowValue +{ + return (Byte)((highValue << 4 | lowValue) & 0xFF); +} + ++ (NSData *)throws_macWithVersion:(int)version + identityKey:(NSData *)senderIdentityKey + receiverIdentityKey:(NSData *)receiverIdentityKey + macKey:(NSData *)macKey + serialized:(NSData *)serialized +{ + if (!macKey) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Missing macKey." userInfo:nil]; + } + if (macKey.length >= SIZE_MAX) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Oversize macKey." userInfo:nil]; + } + if (!senderIdentityKey) { + @throw + [NSException exceptionWithName:NSInvalidArgumentException reason:@"Missing senderIdentityKey" userInfo:nil]; + } + if (senderIdentityKey.length >= SIZE_MAX) { + @throw [NSException exceptionWithName:NSInvalidArgumentException + reason:@"Oversize senderIdentityKey" + userInfo:nil]; + } + if (!receiverIdentityKey) { + @throw [NSException exceptionWithName:NSInvalidArgumentException + reason:@"Missing receiverIdentityKey" + userInfo:nil]; + } + if (receiverIdentityKey.length >= SIZE_MAX) { + @throw [NSException exceptionWithName:NSInvalidArgumentException + reason:@"Oversize receiverIdentityKey" + userInfo:nil]; + } + if (!serialized) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Missing serialized." userInfo:nil]; + } + if (serialized.length >= SIZE_MAX) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Oversize serialized." userInfo:nil]; + } + + NSMutableData *_Nullable bufferData = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; + OWSAssert(bufferData); + + CCHmacContext context; + CCHmacInit(&context, kCCHmacAlgSHA256, [macKey bytes], [macKey length]); + CCHmacUpdate(&context, [senderIdentityKey bytes], [senderIdentityKey length]); + CCHmacUpdate(&context, [receiverIdentityKey bytes], [receiverIdentityKey length]); + CCHmacUpdate(&context, [serialized bytes], [serialized length]); + CCHmacFinal(&context, bufferData.mutableBytes); + + return [bufferData subdataWithRange:NSMakeRange(0, MAC_LENGTH)]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/String+OWS.swift b/SessionProtocolKit/Utility/String+OWS.swift new file mode 100644 index 000000000..518ccebb1 --- /dev/null +++ b/SessionProtocolKit/Utility/String+OWS.swift @@ -0,0 +1,72 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +public extension NSString { + func ows_truncated(toByteCount byteCount: UInt) -> NSString? { + return (self as String).truncated(toByteCount: byteCount) as NSString? + } +} + +public extension String { + var stripped: String { + return (self as NSString).ows_stripped() + } + + var filterForDisplay: String? { + return (self as NSString).filterStringForDisplay() + } + + // Truncates string to be less than or equal to byteCount, while ensuring we never truncate partial characters for multibyte characters. + func truncated(toByteCount byteCount: UInt) -> String? { + var lowerBoundCharCount = 0 + var upperBoundCharCount = self.count + + while (lowerBoundCharCount < upperBoundCharCount) { + guard let upperBoundData = self.prefix(upperBoundCharCount).data(using: .utf8) else { + owsFailDebug("upperBoundData was unexpectedly nil") + return nil + } + + if upperBoundData.count <= byteCount { + break + } + + // converge + if upperBoundCharCount - lowerBoundCharCount == 1 { + upperBoundCharCount = lowerBoundCharCount + break + } + + let midpointCharCount = (lowerBoundCharCount + upperBoundCharCount) / 2 + let midpointString = self.prefix(midpointCharCount) + + guard let midpointData = midpointString.data(using: .utf8) else { + owsFailDebug("midpointData was unexpectedly nil") + return nil + } + let midpointByteCount = midpointData.count + + if midpointByteCount < byteCount { + lowerBoundCharCount = midpointCharCount + } else { + upperBoundCharCount = midpointCharCount + } + } + + return String(self.prefix(upperBoundCharCount)) + } + + func replaceCharacters(characterSet: CharacterSet, replacement: String) -> String { + let components = self.components(separatedBy: characterSet) + return components.joined(separator: replacement) + } + + func removeCharacters(characterSet: CharacterSet) -> String { + let components = self.components(separatedBy: characterSet) + return components.joined() + } +} diff --git a/SessionProtocolKit/Utility/Threading.h b/SessionProtocolKit/Utility/Threading.h new file mode 100644 index 000000000..98c4d724c --- /dev/null +++ b/SessionProtocolKit/Utility/Threading.h @@ -0,0 +1,19 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// The block is executed immediately if called from the +// main thread; otherwise it is dispatched async to the +// main thread. +void DispatchMainThreadSafe(dispatch_block_t block); + +// The block is executed immediately if called from the +// main thread; otherwise it is dispatched sync to the +// main thread. +void DispatchSyncMainThreadSafe(dispatch_block_t block); + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/Threading.m b/SessionProtocolKit/Utility/Threading.m new file mode 100644 index 000000000..10504a585 --- /dev/null +++ b/SessionProtocolKit/Utility/Threading.m @@ -0,0 +1,36 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "Threading.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +void DispatchMainThreadSafe(dispatch_block_t block) +{ + OWSCAssertDebug(block); + + if ([NSThread isMainThread]) { + block(); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + block(); + }); + } +} + +void DispatchSyncMainThreadSafe(dispatch_block_t block) +{ + OWSCAssertDebug(block); + + if ([NSThread isMainThread]) { + block(); + } else { + dispatch_sync(dispatch_get_main_queue(), ^{ + block(); + }); + } +} + +NS_ASSUME_NONNULL_END diff --git a/SessionProtocolKit/Utility/iOSVersions.h b/SessionProtocolKit/Utility/iOSVersions.h new file mode 100644 index 000000000..581401847 --- /dev/null +++ b/SessionProtocolKit/Utility/iOSVersions.h @@ -0,0 +1,8 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(major, minor) \ + ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = major, .minorVersion = minor, .patchVersion = 0}]) diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index c3faa1e76..90929a189 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -482,6 +482,7 @@ A1A018531805C60D00A052A6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A091169C9E5E00537ABF /* CoreGraphics.framework */; }; A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4F17A06537000A904E /* AddressBookUI.framework */; }; A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4D17A0652C000A904E /* AddressBook.framework */; }; + A33A4BA9D050805FE156E3ED /* Pods_SessionProtocolKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2183DCA28E0620BC73FCC554 /* Pods_SessionProtocolKit.framework */; }; A5509ECA1A69AB8B00ABA4BC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A5509EC91A69AB8B00ABA4BC /* Main.storyboard */; }; AD83FF3F1A73426500B5C81A /* audio_pause_button_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = AD83FF381A73426500B5C81A /* audio_pause_button_blue.png */; }; AD83FF401A73426500B5C81A /* audio_pause_button_blue@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AD83FF391A73426500B5C81A /* audio_pause_button_blue@2x.png */; }; @@ -639,6 +640,97 @@ C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A7702553A41E00C340D1 /* ControlMessage.swift */; }; C3C2A7842553AAF300C340D1 /* SNProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A7822553AAF200C340D1 /* SNProto.swift */; }; C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A7832553AAF300C340D1 /* SessionProtos.pb.swift */; }; + C3C2A8662553B41A00C340D1 /* SessionProtocolKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8642553B41A00C340D1 /* SessionProtocolKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A8692553B41A00C340D1 /* SessionProtocolKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */; }; + C3C2A86A2553B41A00C340D1 /* SessionProtocolKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C3C2A8882553B4CC00C340D1 /* AxolotlExceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8872553B4CC00C340D1 /* AxolotlExceptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A89C2553B4F600C340D1 /* PreKeyWhisperMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8932553B4F500C340D1 /* PreKeyWhisperMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A89D2553B4F600C340D1 /* ClosedGroupCiphertextMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8942553B4F500C340D1 /* ClosedGroupCiphertextMessage.m */; }; + C3C2A89E2553B4F600C340D1 /* WhisperMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8952553B4F500C340D1 /* WhisperMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A89F2553B4F600C340D1 /* ClosedGroupCiphertextMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8962553B4F500C340D1 /* ClosedGroupCiphertextMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A8A02553B4F600C340D1 /* PreKeyWhisperMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8972553B4F500C340D1 /* PreKeyWhisperMessage.m */; }; + C3C2A8A12553B4F600C340D1 /* WhisperMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8982553B4F600C340D1 /* WhisperMessage.m */; }; + C3C2A8A22553B4F600C340D1 /* CipherMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8992553B4F600C340D1 /* CipherMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A8A32553B4F600C340D1 /* FallbackMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A89A2553B4F600C340D1 /* FallbackMessage.m */; }; + C3C2A8A42553B4F600C340D1 /* FallbackMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A89B2553B4F600C340D1 /* FallbackMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A8B62553B53800C340D1 /* Constants.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8B52553B53700C340D1 /* Constants.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A8C22553B55600C340D1 /* AES-CBC.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8C02553B55600C340D1 /* AES-CBC.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A8C32553B55600C340D1 /* AES-CBC.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8C12553B55600C340D1 /* AES-CBC.m */; }; + C3C2A8D42553B57C00C340D1 /* SignedPrekeyRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8CE2553B57C00C340D1 /* SignedPrekeyRecord.m */; }; + C3C2A8D52553B57C00C340D1 /* PreKeyBundle.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8CF2553B57C00C340D1 /* PreKeyBundle.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A8D62553B57C00C340D1 /* PreKeyBundle.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8D02553B57C00C340D1 /* PreKeyBundle.m */; }; + C3C2A8D72553B57C00C340D1 /* SignedPrekeyRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8D12553B57C00C340D1 /* SignedPrekeyRecord.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A8D82553B57C00C340D1 /* PreKeyRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8D22553B57C00C340D1 /* PreKeyRecord.m */; }; + C3C2A8D92553B57C00C340D1 /* PreKeyRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8D32553B57C00C340D1 /* PreKeyRecord.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A8E62553B59B00C340D1 /* WhisperTextProtocol.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8E42553B59B00C340D1 /* WhisperTextProtocol.pb.swift */; }; + C3C2A8E72553B59B00C340D1 /* SPKProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8E52553B59B00C340D1 /* SPKProto.swift */; }; + C3C2A9092553B5B200C340D1 /* RatchetingSession.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8F12553B5B000C340D1 /* RatchetingSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A90A2553B5B200C340D1 /* Chain.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8F22553B5B000C340D1 /* Chain.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A90B2553B5B200C340D1 /* MessageKeys.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8F32553B5B000C340D1 /* MessageKeys.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A90C2553B5B200C340D1 /* RKCK.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8F42553B5B000C340D1 /* RKCK.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A90D2553B5B200C340D1 /* RKCK.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8F52553B5B000C340D1 /* RKCK.m */; }; + C3C2A90E2553B5B200C340D1 /* TSDerivedSecrets.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8F62553B5B000C340D1 /* TSDerivedSecrets.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A90F2553B5B200C340D1 /* RatchetingSession.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8F72553B5B000C340D1 /* RatchetingSession.m */; }; + C3C2A9102553B5B200C340D1 /* AliceAxolotlParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8F82553B5B000C340D1 /* AliceAxolotlParameters.m */; }; + C3C2A9112553B5B200C340D1 /* ChainKey.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8F92553B5B000C340D1 /* ChainKey.m */; }; + C3C2A9122553B5B200C340D1 /* ReceivingChain.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8FA2553B5B000C340D1 /* ReceivingChain.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9132553B5B200C340D1 /* RootKey.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8FB2553B5B000C340D1 /* RootKey.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9142553B5B200C340D1 /* ChainKey.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8FC2553B5B100C340D1 /* ChainKey.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9152553B5B200C340D1 /* BobAxolotlParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8FD2553B5B100C340D1 /* BobAxolotlParameters.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9162553B5B200C340D1 /* ChainAndIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8FE2553B5B100C340D1 /* ChainAndIndex.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9172553B5B200C340D1 /* RootKey.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A8FF2553B5B100C340D1 /* RootKey.m */; }; + C3C2A9182553B5B200C340D1 /* SendingChain.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9002553B5B100C340D1 /* SendingChain.m */; }; + C3C2A9192553B5B200C340D1 /* MessageKeys.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9012553B5B100C340D1 /* MessageKeys.m */; }; + C3C2A91A2553B5B200C340D1 /* TSDerivedSecrets.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9022553B5B100C340D1 /* TSDerivedSecrets.m */; }; + C3C2A91B2553B5B200C340D1 /* BobAxolotlParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9032553B5B100C340D1 /* BobAxolotlParameters.m */; }; + C3C2A91C2553B5B200C340D1 /* ReceivingChain.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9042553B5B100C340D1 /* ReceivingChain.m */; }; + C3C2A91D2553B5B200C340D1 /* AliceAxolotlParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9052553B5B200C340D1 /* AliceAxolotlParameters.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A91E2553B5B200C340D1 /* ChainAndIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9062553B5B200C340D1 /* ChainAndIndex.m */; }; + C3C2A91F2553B5B200C340D1 /* AxolotlParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9072553B5B200C340D1 /* AxolotlParameters.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9202553B5B200C340D1 /* SendingChain.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9082553B5B200C340D1 /* SendingChain.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A92B2553B5BE00C340D1 /* SessionCipher.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9292553B5BD00C340D1 /* SessionCipher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A92C2553B5BE00C340D1 /* SessionCipher.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A92A2553B5BE00C340D1 /* SessionCipher.m */; }; + C3C2A93C2553B5D700C340D1 /* SessionBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9362553B5D600C340D1 /* SessionBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A93D2553B5D700C340D1 /* SessionBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9372553B5D600C340D1 /* SessionBuilder.m */; }; + C3C2A93E2553B5D700C340D1 /* SessionRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9382553B5D600C340D1 /* SessionRecord.m */; }; + C3C2A93F2553B5D700C340D1 /* SessionState.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9392553B5D600C340D1 /* SessionState.m */; }; + C3C2A9402553B5D700C340D1 /* SessionRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A93A2553B5D600C340D1 /* SessionRecord.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9412553B5D700C340D1 /* SessionState.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A93B2553B5D700C340D1 /* SessionState.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A95C2553B62400C340D1 /* AxolotlStore.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9572553B62300C340D1 /* AxolotlStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A95D2553B62400C340D1 /* PreKeyStore.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9582553B62400C340D1 /* PreKeyStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A95E2553B62400C340D1 /* IdentityKeyStore.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9592553B62400C340D1 /* IdentityKeyStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A95F2553B62400C340D1 /* SessionStore.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A95A2553B62400C340D1 /* SessionStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9602553B62400C340D1 /* SignedPreKeyStore.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A95B2553B62400C340D1 /* SignedPreKeyStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A96E2553B63C00C340D1 /* NSData+keyVersionByte.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A96A2553B63B00C340D1 /* NSData+keyVersionByte.m */; }; + C3C2A96F2553B63C00C340D1 /* NSData+keyVersionByte.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A96B2553B63C00C340D1 /* NSData+keyVersionByte.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9702553B63C00C340D1 /* SerializationUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A96C2553B63C00C340D1 /* SerializationUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9712553B63C00C340D1 /* SerializationUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A96D2553B63C00C340D1 /* SerializationUtilities.m */; }; + C3C2A9EF2553B9C400C340D1 /* OWSDataParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9D42553B9C100C340D1 /* OWSDataParser.swift */; }; + C3C2A9F02553B9C400C340D1 /* OWSAsserts.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9D52553B9C200C340D1 /* OWSAsserts.m */; }; + C3C2A9F12553B9C400C340D1 /* OWSLogs.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9D62553B9C200C340D1 /* OWSLogs.m */; }; + C3C2A9F22553B9C400C340D1 /* NSObject+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9D72553B9C200C340D1 /* NSObject+OWS.m */; }; + C3C2A9F32553B9C400C340D1 /* String+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9D82553B9C200C340D1 /* String+OWS.swift */; }; + C3C2A9F42553B9C400C340D1 /* Threading.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9D92553B9C200C340D1 /* Threading.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9F52553B9C400C340D1 /* NSDate+OWS.mm in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9DA2553B9C200C340D1 /* NSDate+OWS.mm */; }; + C3C2A9F62553B9C400C340D1 /* Data+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9DB2553B9C200C340D1 /* Data+OWS.swift */; }; + C3C2A9F72553B9C400C340D1 /* NSData+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9DC2553B9C200C340D1 /* NSData+OWS.m */; }; + C3C2A9F82553B9C400C340D1 /* NSString+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9DD2553B9C200C340D1 /* NSString+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9F92553B9C400C340D1 /* NSData+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9DE2553B9C200C340D1 /* NSData+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9FA2553B9C400C340D1 /* OWSSwiftUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9DF2553B9C200C340D1 /* OWSSwiftUtils.swift */; }; + C3C2A9FB2553B9C400C340D1 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9E02553B9C300C340D1 /* Logger.swift */; }; + C3C2A9FC2553B9C400C340D1 /* iOSVersions.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9E12553B9C300C340D1 /* iOSVersions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9FD2553B9C400C340D1 /* SCKExceptionWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9E22553B9C300C340D1 /* SCKExceptionWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A9FE2553B9C400C340D1 /* OWSLogs.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9E32553B9C300C340D1 /* OWSLogs.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2AA002553B9C400C340D1 /* NSDate+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9E52553B9C300C340D1 /* NSDate+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2AA012553B9C400C340D1 /* Threading.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9E62553B9C300C340D1 /* Threading.m */; }; + C3C2AA022553B9C400C340D1 /* Cryptography.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9E72553B9C300C340D1 /* Cryptography.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2AA032553B9C400C340D1 /* Randomness.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9E82553B9C300C340D1 /* Randomness.m */; }; + C3C2AA042553B9C400C340D1 /* OWSAsserts.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9E92553B9C300C340D1 /* OWSAsserts.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2AA052553B9C400C340D1 /* SCKExceptionWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9EA2553B9C300C340D1 /* SCKExceptionWrapper.m */; }; + C3C2AA062553B9C400C340D1 /* Randomness.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9EB2553B9C400C340D1 /* Randomness.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2AA072553B9C400C340D1 /* Cryptography.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9EC2553B9C400C340D1 /* Cryptography.m */; }; + C3C2AA082553B9C400C340D1 /* NSString+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A9ED2553B9C400C340D1 /* NSString+OWS.m */; }; + C3C2AA092553B9C400C340D1 /* NSObject+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A9EE2553B9C400C340D1 /* NSObject+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; @@ -741,6 +833,13 @@ remoteGlobalIDString = C3C2A6EF25539DE700C340D1; remoteInfo = SessionMessagingKit; }; + C3C2A8672553B41A00C340D1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D221A080169C9E5E00537ABF /* Project object */; + proxyType = 1; + remoteGlobalIDString = C3C2A8612553B41A00C340D1; + remoteInfo = SessionProtocolKit; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -762,6 +861,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + C3C2A86A2553B41A00C340D1 /* SessionProtocolKit.framework in Embed Frameworks */, C3C2A681255388CC00C340D1 /* SessionUtilities.framework in Embed Frameworks */, C3C2A5A7255385C100C340D1 /* SessionSnodeKit.framework in Embed Frameworks */, 4535189A1FC63DBF00210559 /* SignalMessaging.framework in Embed Frameworks */, @@ -775,9 +875,11 @@ /* Begin PBXFileReference section */ 04912E453971FB16E5E78EC6 /* Pods_LokiPushNotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LokiPushNotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0F94C85CB0B235DA37F68ED0 /* Pods_SignalShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 174BD0AE74771D02DAC2B7A9 /* Pods-SessionProtocolKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionProtocolKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionProtocolKit/Pods-SessionProtocolKit.app store release.xcconfig"; sourceTree = ""; }; 18D19142FD6E60FD0A5D89F7 /* Pods-LokiPushNotificationService.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LokiPushNotificationService.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-LokiPushNotificationService/Pods-LokiPushNotificationService.app store release.xcconfig"; sourceTree = ""; }; 1C93CF3971B64E8B6C1F9AC1 /* Pods-SignalShareExtension.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.test.xcconfig"; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; @@ -1346,6 +1448,7 @@ AD83FF3D1A73426500B5C81A /* audio_pause_button.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = audio_pause_button.png; sourceTree = ""; }; AD83FF3E1A73426500B5C81A /* audio_pause_button@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "audio_pause_button@2x.png"; sourceTree = ""; }; AD83FF461A73428300B5C81A /* audio_play_button_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = audio_play_button_blue.png; sourceTree = ""; }; + AEA8083C060FF9BAFF6E0C9F /* Pods-SessionProtocolKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionProtocolKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionProtocolKit/Pods-SessionProtocolKit.debug.xcconfig"; sourceTree = ""; }; B10C9B5B1A7049EC00ECA2BF /* pause_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pause_icon.png; sourceTree = ""; }; B10C9B5C1A7049EC00ECA2BF /* pause_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pause_icon@2x.png"; sourceTree = ""; }; B10C9B5D1A7049EC00ECA2BF /* play_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = play_icon.png; sourceTree = ""; }; @@ -1511,6 +1614,97 @@ C3C2A7702553A41E00C340D1 /* ControlMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlMessage.swift; sourceTree = ""; }; C3C2A7822553AAF200C340D1 /* SNProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SNProto.swift; sourceTree = ""; }; C3C2A7832553AAF300C340D1 /* SessionProtos.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionProtos.pb.swift; sourceTree = ""; }; + C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionProtocolKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C3C2A8642553B41A00C340D1 /* SessionProtocolKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionProtocolKit.h; sourceTree = ""; }; + C3C2A8652553B41A00C340D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C3C2A8872553B4CC00C340D1 /* AxolotlExceptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AxolotlExceptions.h; sourceTree = ""; }; + C3C2A8932553B4F500C340D1 /* PreKeyWhisperMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PreKeyWhisperMessage.h; path = SessionProtocolKit/CipherMessage/PreKeyWhisperMessage.h; sourceTree = SOURCE_ROOT; }; + C3C2A8942553B4F500C340D1 /* ClosedGroupCiphertextMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ClosedGroupCiphertextMessage.m; path = SessionProtocolKit/CipherMessage/ClosedGroupCiphertextMessage.m; sourceTree = SOURCE_ROOT; }; + C3C2A8952553B4F500C340D1 /* WhisperMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WhisperMessage.h; path = SessionProtocolKit/CipherMessage/WhisperMessage.h; sourceTree = SOURCE_ROOT; }; + C3C2A8962553B4F500C340D1 /* ClosedGroupCiphertextMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ClosedGroupCiphertextMessage.h; path = SessionProtocolKit/CipherMessage/ClosedGroupCiphertextMessage.h; sourceTree = SOURCE_ROOT; }; + C3C2A8972553B4F500C340D1 /* PreKeyWhisperMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PreKeyWhisperMessage.m; path = SessionProtocolKit/CipherMessage/PreKeyWhisperMessage.m; sourceTree = SOURCE_ROOT; }; + C3C2A8982553B4F600C340D1 /* WhisperMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = WhisperMessage.m; path = SessionProtocolKit/CipherMessage/WhisperMessage.m; sourceTree = SOURCE_ROOT; }; + C3C2A8992553B4F600C340D1 /* CipherMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CipherMessage.h; path = SessionProtocolKit/CipherMessage/CipherMessage.h; sourceTree = SOURCE_ROOT; }; + C3C2A89A2553B4F600C340D1 /* FallbackMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FallbackMessage.m; path = SessionProtocolKit/CipherMessage/FallbackMessage.m; sourceTree = SOURCE_ROOT; }; + C3C2A89B2553B4F600C340D1 /* FallbackMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FallbackMessage.h; path = SessionProtocolKit/CipherMessage/FallbackMessage.h; sourceTree = SOURCE_ROOT; }; + C3C2A8B52553B53700C340D1 /* Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; }; + C3C2A8C02553B55600C340D1 /* AES-CBC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "AES-CBC.h"; path = "Crypto/AES-CBC.h"; sourceTree = ""; }; + C3C2A8C12553B55600C340D1 /* AES-CBC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "AES-CBC.m"; path = "Crypto/AES-CBC.m"; sourceTree = ""; }; + C3C2A8CE2553B57C00C340D1 /* SignedPrekeyRecord.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SignedPrekeyRecord.m; path = Prekeys/SignedPrekeyRecord.m; sourceTree = ""; }; + C3C2A8CF2553B57C00C340D1 /* PreKeyBundle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PreKeyBundle.h; path = Prekeys/PreKeyBundle.h; sourceTree = ""; }; + C3C2A8D02553B57C00C340D1 /* PreKeyBundle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PreKeyBundle.m; path = Prekeys/PreKeyBundle.m; sourceTree = ""; }; + C3C2A8D12553B57C00C340D1 /* SignedPrekeyRecord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SignedPrekeyRecord.h; path = Prekeys/SignedPrekeyRecord.h; sourceTree = ""; }; + C3C2A8D22553B57C00C340D1 /* PreKeyRecord.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PreKeyRecord.m; path = Prekeys/PreKeyRecord.m; sourceTree = ""; }; + C3C2A8D32553B57C00C340D1 /* PreKeyRecord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PreKeyRecord.h; path = Prekeys/PreKeyRecord.h; sourceTree = ""; }; + C3C2A8E42553B59B00C340D1 /* WhisperTextProtocol.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WhisperTextProtocol.pb.swift; path = Protos/WhisperTextProtocol.pb.swift; sourceTree = ""; }; + C3C2A8E52553B59B00C340D1 /* SPKProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SPKProto.swift; path = Protos/SPKProto.swift; sourceTree = ""; }; + C3C2A8F12553B5B000C340D1 /* RatchetingSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RatchetingSession.h; path = Ratchet/RatchetingSession.h; sourceTree = ""; }; + C3C2A8F22553B5B000C340D1 /* Chain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Chain.h; path = Ratchet/Chain.h; sourceTree = ""; }; + C3C2A8F32553B5B000C340D1 /* MessageKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MessageKeys.h; path = Ratchet/MessageKeys.h; sourceTree = ""; }; + C3C2A8F42553B5B000C340D1 /* RKCK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKCK.h; path = Ratchet/RKCK.h; sourceTree = ""; }; + C3C2A8F52553B5B000C340D1 /* RKCK.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKCK.m; path = Ratchet/RKCK.m; sourceTree = ""; }; + C3C2A8F62553B5B000C340D1 /* TSDerivedSecrets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TSDerivedSecrets.h; path = Ratchet/TSDerivedSecrets.h; sourceTree = ""; }; + C3C2A8F72553B5B000C340D1 /* RatchetingSession.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RatchetingSession.m; path = Ratchet/RatchetingSession.m; sourceTree = ""; }; + C3C2A8F82553B5B000C340D1 /* AliceAxolotlParameters.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AliceAxolotlParameters.m; path = Ratchet/AliceAxolotlParameters.m; sourceTree = ""; }; + C3C2A8F92553B5B000C340D1 /* ChainKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ChainKey.m; path = Ratchet/ChainKey.m; sourceTree = ""; }; + C3C2A8FA2553B5B000C340D1 /* ReceivingChain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ReceivingChain.h; path = Ratchet/ReceivingChain.h; sourceTree = ""; }; + C3C2A8FB2553B5B000C340D1 /* RootKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RootKey.h; path = Ratchet/RootKey.h; sourceTree = ""; }; + C3C2A8FC2553B5B100C340D1 /* ChainKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ChainKey.h; path = Ratchet/ChainKey.h; sourceTree = ""; }; + C3C2A8FD2553B5B100C340D1 /* BobAxolotlParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BobAxolotlParameters.h; path = Ratchet/BobAxolotlParameters.h; sourceTree = ""; }; + C3C2A8FE2553B5B100C340D1 /* ChainAndIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ChainAndIndex.h; path = Ratchet/ChainAndIndex.h; sourceTree = ""; }; + C3C2A8FF2553B5B100C340D1 /* RootKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RootKey.m; path = Ratchet/RootKey.m; sourceTree = ""; }; + C3C2A9002553B5B100C340D1 /* SendingChain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SendingChain.m; path = Ratchet/SendingChain.m; sourceTree = ""; }; + C3C2A9012553B5B100C340D1 /* MessageKeys.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MessageKeys.m; path = Ratchet/MessageKeys.m; sourceTree = ""; }; + C3C2A9022553B5B100C340D1 /* TSDerivedSecrets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSDerivedSecrets.m; path = Ratchet/TSDerivedSecrets.m; sourceTree = ""; }; + C3C2A9032553B5B100C340D1 /* BobAxolotlParameters.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BobAxolotlParameters.m; path = Ratchet/BobAxolotlParameters.m; sourceTree = ""; }; + C3C2A9042553B5B100C340D1 /* ReceivingChain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ReceivingChain.m; path = Ratchet/ReceivingChain.m; sourceTree = ""; }; + C3C2A9052553B5B200C340D1 /* AliceAxolotlParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AliceAxolotlParameters.h; path = Ratchet/AliceAxolotlParameters.h; sourceTree = ""; }; + C3C2A9062553B5B200C340D1 /* ChainAndIndex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ChainAndIndex.m; path = Ratchet/ChainAndIndex.m; sourceTree = ""; }; + C3C2A9072553B5B200C340D1 /* AxolotlParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AxolotlParameters.h; path = Ratchet/AxolotlParameters.h; sourceTree = ""; }; + C3C2A9082553B5B200C340D1 /* SendingChain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SendingChain.h; path = Ratchet/SendingChain.h; sourceTree = ""; }; + C3C2A9292553B5BD00C340D1 /* SessionCipher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SessionCipher.h; sourceTree = ""; }; + C3C2A92A2553B5BE00C340D1 /* SessionCipher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SessionCipher.m; sourceTree = ""; }; + C3C2A9362553B5D600C340D1 /* SessionBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SessionBuilder.h; path = Sessions/SessionBuilder.h; sourceTree = ""; }; + C3C2A9372553B5D600C340D1 /* SessionBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SessionBuilder.m; path = Sessions/SessionBuilder.m; sourceTree = ""; }; + C3C2A9382553B5D600C340D1 /* SessionRecord.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SessionRecord.m; path = Sessions/SessionRecord.m; sourceTree = ""; }; + C3C2A9392553B5D600C340D1 /* SessionState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SessionState.m; path = Sessions/SessionState.m; sourceTree = ""; }; + C3C2A93A2553B5D600C340D1 /* SessionRecord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SessionRecord.h; path = Sessions/SessionRecord.h; sourceTree = ""; }; + C3C2A93B2553B5D700C340D1 /* SessionState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SessionState.h; path = Sessions/SessionState.h; sourceTree = ""; }; + C3C2A9572553B62300C340D1 /* AxolotlStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AxolotlStore.h; sourceTree = ""; }; + C3C2A9582553B62400C340D1 /* PreKeyStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreKeyStore.h; sourceTree = ""; }; + C3C2A9592553B62400C340D1 /* IdentityKeyStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IdentityKeyStore.h; sourceTree = ""; }; + C3C2A95A2553B62400C340D1 /* SessionStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SessionStore.h; sourceTree = ""; }; + C3C2A95B2553B62400C340D1 /* SignedPreKeyStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignedPreKeyStore.h; sourceTree = ""; }; + C3C2A96A2553B63B00C340D1 /* NSData+keyVersionByte.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSData+keyVersionByte.m"; path = "Utility/NSData+keyVersionByte.m"; sourceTree = ""; }; + C3C2A96B2553B63C00C340D1 /* NSData+keyVersionByte.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSData+keyVersionByte.h"; path = "Utility/NSData+keyVersionByte.h"; sourceTree = ""; }; + C3C2A96C2553B63C00C340D1 /* SerializationUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SerializationUtilities.h; path = Utility/SerializationUtilities.h; sourceTree = ""; }; + C3C2A96D2553B63C00C340D1 /* SerializationUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SerializationUtilities.m; path = Utility/SerializationUtilities.m; sourceTree = ""; }; + C3C2A9D42553B9C100C340D1 /* OWSDataParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSDataParser.swift; path = Utility/OWSDataParser.swift; sourceTree = ""; }; + C3C2A9D52553B9C200C340D1 /* OWSAsserts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSAsserts.m; path = Utility/OWSAsserts.m; sourceTree = ""; }; + C3C2A9D62553B9C200C340D1 /* OWSLogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSLogs.m; path = Utility/OWSLogs.m; sourceTree = ""; }; + C3C2A9D72553B9C200C340D1 /* NSObject+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+OWS.m"; path = "Utility/NSObject+OWS.m"; sourceTree = ""; }; + C3C2A9D82553B9C200C340D1 /* String+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "String+OWS.swift"; path = "Utility/String+OWS.swift"; sourceTree = ""; }; + C3C2A9D92553B9C200C340D1 /* Threading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Threading.h; path = Utility/Threading.h; sourceTree = ""; }; + C3C2A9DA2553B9C200C340D1 /* NSDate+OWS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "NSDate+OWS.mm"; path = "Utility/NSDate+OWS.mm"; sourceTree = ""; }; + C3C2A9DB2553B9C200C340D1 /* Data+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Data+OWS.swift"; path = "Utility/Data+OWS.swift"; sourceTree = ""; }; + C3C2A9DC2553B9C200C340D1 /* NSData+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSData+OWS.m"; path = "Utility/NSData+OWS.m"; sourceTree = ""; }; + C3C2A9DD2553B9C200C340D1 /* NSString+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSString+OWS.h"; path = "Utility/NSString+OWS.h"; sourceTree = ""; }; + C3C2A9DE2553B9C200C340D1 /* NSData+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSData+OWS.h"; path = "Utility/NSData+OWS.h"; sourceTree = ""; }; + C3C2A9DF2553B9C200C340D1 /* OWSSwiftUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSSwiftUtils.swift; path = Utility/OWSSwiftUtils.swift; sourceTree = ""; }; + C3C2A9E02553B9C300C340D1 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Logger.swift; path = Utility/Logger.swift; sourceTree = ""; }; + C3C2A9E12553B9C300C340D1 /* iOSVersions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iOSVersions.h; path = Utility/iOSVersions.h; sourceTree = ""; }; + C3C2A9E22553B9C300C340D1 /* SCKExceptionWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SCKExceptionWrapper.h; path = Utility/SCKExceptionWrapper.h; sourceTree = ""; }; + C3C2A9E32553B9C300C340D1 /* OWSLogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSLogs.h; path = Utility/OWSLogs.h; sourceTree = ""; }; + C3C2A9E52553B9C300C340D1 /* NSDate+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDate+OWS.h"; path = "Utility/NSDate+OWS.h"; sourceTree = ""; }; + C3C2A9E62553B9C300C340D1 /* Threading.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Threading.m; path = Utility/Threading.m; sourceTree = ""; }; + C3C2A9E72553B9C300C340D1 /* Cryptography.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Cryptography.h; path = Utility/Cryptography.h; sourceTree = ""; }; + C3C2A9E82553B9C300C340D1 /* Randomness.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Randomness.m; path = Utility/Randomness.m; sourceTree = ""; }; + C3C2A9E92553B9C300C340D1 /* OWSAsserts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSAsserts.h; path = Utility/OWSAsserts.h; sourceTree = ""; }; + C3C2A9EA2553B9C300C340D1 /* SCKExceptionWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SCKExceptionWrapper.m; path = Utility/SCKExceptionWrapper.m; sourceTree = ""; }; + C3C2A9EB2553B9C400C340D1 /* Randomness.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Randomness.h; path = Utility/Randomness.h; sourceTree = ""; }; + C3C2A9EC2553B9C400C340D1 /* Cryptography.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Cryptography.m; path = Utility/Cryptography.m; sourceTree = ""; }; + C3C2A9ED2553B9C400C340D1 /* NSString+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSString+OWS.m"; path = "Utility/NSString+OWS.m"; sourceTree = ""; }; + C3C2A9EE2553B9C400C340D1 /* NSObject+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSObject+OWS.h"; path = "Utility/NSObject+OWS.h"; sourceTree = ""; }; C3C3CF8824D8EED300E1CCE7 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; }; C3D0972A2510499C00F6E3E4 /* BackgroundPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundPoller.swift; sourceTree = ""; }; C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCopyableLabel.swift; sourceTree = ""; }; @@ -1602,6 +1796,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C3C2A85F2553B41A00C340D1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A33A4BA9D050805FE156E3ED /* Pods_SessionProtocolKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D221A086169C9E5E00537ABF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1612,6 +1814,7 @@ 45847E871E4283C30080EAB3 /* Intents.framework in Frameworks */, 4509E79A1DD653700025A59F /* WebRTC.framework in Frameworks */, 4520D8D51D417D8E00123472 /* Photos.framework in Frameworks */, + C3C2A8692553B41A00C340D1 /* SessionProtocolKit.framework in Frameworks */, 4C9CA25D217E676900607C63 /* ZXingObjC.framework in Frameworks */, B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */, 45BD60821DE9547E00A8F436 /* Contacts.framework in Frameworks */, @@ -2646,6 +2849,8 @@ 3303495F6651CE2F3CC9693B /* Pods-SessionUtilities.app store release.xcconfig */, 6A26D6558DE69AF455E571C1 /* Pods-SessionMessagingKit.debug.xcconfig */, FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */, + AEA8083C060FF9BAFF6E0C9F /* Pods-SessionProtocolKit.debug.xcconfig */, + 174BD0AE74771D02DAC2B7A9 /* Pods-SessionProtocolKit.app store release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -3056,6 +3261,175 @@ path = Generated; sourceTree = ""; }; + C3C2A8632553B41A00C340D1 /* SessionProtocolKit */ = { + isa = PBXGroup; + children = ( + C3C2A8872553B4CC00C340D1 /* AxolotlExceptions.h */, + C3C2A8922553B4E400C340D1 /* CipherMessage */, + C3C2A8B52553B53700C340D1 /* Constants.h */, + C3C2A8BF2553B54600C340D1 /* Crypto */, + C3C2A8762553B42C00C340D1 /* Meta */, + C3C2A8CD2553B56400C340D1 /* Prekeys */, + C3C2A8E32553B58B00C340D1 /* Protos */, + C3C2A8F02553B5A100C340D1 /* Ratchet */, + C3C2A9292553B5BD00C340D1 /* SessionCipher.h */, + C3C2A92A2553B5BE00C340D1 /* SessionCipher.m */, + C3C2A9352553B5C300C340D1 /* Sessions */, + C3C2A9562553B5F600C340D1 /* State */, + C3C2A9692553B62B00C340D1 /* Utility */, + ); + path = SessionProtocolKit; + sourceTree = ""; + }; + C3C2A8762553B42C00C340D1 /* Meta */ = { + isa = PBXGroup; + children = ( + C3C2A8642553B41A00C340D1 /* SessionProtocolKit.h */, + C3C2A8652553B41A00C340D1 /* Info.plist */, + ); + path = Meta; + sourceTree = ""; + }; + C3C2A8922553B4E400C340D1 /* CipherMessage */ = { + isa = PBXGroup; + children = ( + C3C2A8992553B4F600C340D1 /* CipherMessage.h */, + C3C2A8962553B4F500C340D1 /* ClosedGroupCiphertextMessage.h */, + C3C2A8942553B4F500C340D1 /* ClosedGroupCiphertextMessage.m */, + C3C2A89B2553B4F600C340D1 /* FallbackMessage.h */, + C3C2A89A2553B4F600C340D1 /* FallbackMessage.m */, + C3C2A8932553B4F500C340D1 /* PreKeyWhisperMessage.h */, + C3C2A8972553B4F500C340D1 /* PreKeyWhisperMessage.m */, + C3C2A8952553B4F500C340D1 /* WhisperMessage.h */, + C3C2A8982553B4F600C340D1 /* WhisperMessage.m */, + ); + path = CipherMessage; + sourceTree = ""; + }; + C3C2A8BF2553B54600C340D1 /* Crypto */ = { + isa = PBXGroup; + children = ( + C3C2A8C02553B55600C340D1 /* AES-CBC.h */, + C3C2A8C12553B55600C340D1 /* AES-CBC.m */, + ); + name = Crypto; + sourceTree = ""; + }; + C3C2A8CD2553B56400C340D1 /* Prekeys */ = { + isa = PBXGroup; + children = ( + C3C2A8CF2553B57C00C340D1 /* PreKeyBundle.h */, + C3C2A8D02553B57C00C340D1 /* PreKeyBundle.m */, + C3C2A8D32553B57C00C340D1 /* PreKeyRecord.h */, + C3C2A8D22553B57C00C340D1 /* PreKeyRecord.m */, + C3C2A8D12553B57C00C340D1 /* SignedPrekeyRecord.h */, + C3C2A8CE2553B57C00C340D1 /* SignedPrekeyRecord.m */, + ); + name = Prekeys; + sourceTree = ""; + }; + C3C2A8E32553B58B00C340D1 /* Protos */ = { + isa = PBXGroup; + children = ( + C3C2A8E52553B59B00C340D1 /* SPKProto.swift */, + C3C2A8E42553B59B00C340D1 /* WhisperTextProtocol.pb.swift */, + ); + name = Protos; + sourceTree = ""; + }; + C3C2A8F02553B5A100C340D1 /* Ratchet */ = { + isa = PBXGroup; + children = ( + C3C2A9052553B5B200C340D1 /* AliceAxolotlParameters.h */, + C3C2A8F82553B5B000C340D1 /* AliceAxolotlParameters.m */, + C3C2A9072553B5B200C340D1 /* AxolotlParameters.h */, + C3C2A8FD2553B5B100C340D1 /* BobAxolotlParameters.h */, + C3C2A9032553B5B100C340D1 /* BobAxolotlParameters.m */, + C3C2A8F22553B5B000C340D1 /* Chain.h */, + C3C2A8FE2553B5B100C340D1 /* ChainAndIndex.h */, + C3C2A9062553B5B200C340D1 /* ChainAndIndex.m */, + C3C2A8FC2553B5B100C340D1 /* ChainKey.h */, + C3C2A8F92553B5B000C340D1 /* ChainKey.m */, + C3C2A8F32553B5B000C340D1 /* MessageKeys.h */, + C3C2A9012553B5B100C340D1 /* MessageKeys.m */, + C3C2A8F12553B5B000C340D1 /* RatchetingSession.h */, + C3C2A8F72553B5B000C340D1 /* RatchetingSession.m */, + C3C2A8FA2553B5B000C340D1 /* ReceivingChain.h */, + C3C2A9042553B5B100C340D1 /* ReceivingChain.m */, + C3C2A8F42553B5B000C340D1 /* RKCK.h */, + C3C2A8F52553B5B000C340D1 /* RKCK.m */, + C3C2A8FB2553B5B000C340D1 /* RootKey.h */, + C3C2A8FF2553B5B100C340D1 /* RootKey.m */, + C3C2A9082553B5B200C340D1 /* SendingChain.h */, + C3C2A9002553B5B100C340D1 /* SendingChain.m */, + C3C2A8F62553B5B000C340D1 /* TSDerivedSecrets.h */, + C3C2A9022553B5B100C340D1 /* TSDerivedSecrets.m */, + ); + name = Ratchet; + sourceTree = ""; + }; + C3C2A9352553B5C300C340D1 /* Sessions */ = { + isa = PBXGroup; + children = ( + C3C2A9362553B5D600C340D1 /* SessionBuilder.h */, + C3C2A9372553B5D600C340D1 /* SessionBuilder.m */, + C3C2A93A2553B5D600C340D1 /* SessionRecord.h */, + C3C2A9382553B5D600C340D1 /* SessionRecord.m */, + C3C2A93B2553B5D700C340D1 /* SessionState.h */, + C3C2A9392553B5D600C340D1 /* SessionState.m */, + ); + name = Sessions; + sourceTree = ""; + }; + C3C2A9562553B5F600C340D1 /* State */ = { + isa = PBXGroup; + children = ( + C3C2A9572553B62300C340D1 /* AxolotlStore.h */, + C3C2A9592553B62400C340D1 /* IdentityKeyStore.h */, + C3C2A9582553B62400C340D1 /* PreKeyStore.h */, + C3C2A95A2553B62400C340D1 /* SessionStore.h */, + C3C2A95B2553B62400C340D1 /* SignedPreKeyStore.h */, + ); + path = State; + sourceTree = ""; + }; + C3C2A9692553B62B00C340D1 /* Utility */ = { + isa = PBXGroup; + children = ( + C3C2A9E72553B9C300C340D1 /* Cryptography.h */, + C3C2A9EC2553B9C400C340D1 /* Cryptography.m */, + C3C2A9DB2553B9C200C340D1 /* Data+OWS.swift */, + C3C2A9E12553B9C300C340D1 /* iOSVersions.h */, + C3C2A9E02553B9C300C340D1 /* Logger.swift */, + C3C2A96B2553B63C00C340D1 /* NSData+keyVersionByte.h */, + C3C2A96A2553B63B00C340D1 /* NSData+keyVersionByte.m */, + C3C2A9DE2553B9C200C340D1 /* NSData+OWS.h */, + C3C2A9DC2553B9C200C340D1 /* NSData+OWS.m */, + C3C2A9E52553B9C300C340D1 /* NSDate+OWS.h */, + C3C2A9DA2553B9C200C340D1 /* NSDate+OWS.mm */, + C3C2A9EE2553B9C400C340D1 /* NSObject+OWS.h */, + C3C2A9D72553B9C200C340D1 /* NSObject+OWS.m */, + C3C2A9DD2553B9C200C340D1 /* NSString+OWS.h */, + C3C2A9ED2553B9C400C340D1 /* NSString+OWS.m */, + C3C2A9E92553B9C300C340D1 /* OWSAsserts.h */, + C3C2A9D52553B9C200C340D1 /* OWSAsserts.m */, + C3C2A9D42553B9C100C340D1 /* OWSDataParser.swift */, + C3C2A9E32553B9C300C340D1 /* OWSLogs.h */, + C3C2A9D62553B9C200C340D1 /* OWSLogs.m */, + C3C2A9DF2553B9C200C340D1 /* OWSSwiftUtils.swift */, + C3C2A9EB2553B9C400C340D1 /* Randomness.h */, + C3C2A9E82553B9C300C340D1 /* Randomness.m */, + C3C2A9E22553B9C300C340D1 /* SCKExceptionWrapper.h */, + C3C2A9EA2553B9C300C340D1 /* SCKExceptionWrapper.m */, + C3C2A96C2553B63C00C340D1 /* SerializationUtilities.h */, + C3C2A96D2553B63C00C340D1 /* SerializationUtilities.m */, + C3C2A9D82553B9C200C340D1 /* String+OWS.swift */, + C3C2A9D92553B9C200C340D1 /* Threading.h */, + C3C2A9E62553B9C300C340D1 /* Threading.m */, + ); + name = Utility; + sourceTree = ""; + }; D221A07E169C9E5E00537ABF = { isa = PBXGroup; children = ( @@ -3064,6 +3438,7 @@ 453518931FC63DBF00210559 /* SignalMessaging */, 7BC01A3C241F40AB00BC7C55 /* LokiPushNotificationService */, C3C2A6F125539DE700C340D1 /* SessionMessagingKit */, + C3C2A8632553B41A00C340D1 /* SessionProtocolKit */, C3C2A5A0255385C100C340D1 /* SessionSnodeKit */, C3C2A67A255388CC00C340D1 /* SessionUtilities */, D221A08C169C9E5E00537ABF /* Frameworks */, @@ -3083,6 +3458,7 @@ C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */, C3C2A679255388CC00C340D1 /* SessionUtilities.framework */, C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */, + C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */, ); name = Products; sourceTree = ""; @@ -3133,6 +3509,7 @@ 9559C3068280BA2383F547F7 /* Pods_SessionSnodeKit.framework */, 686875887229AB29C07145BA /* Pods_SessionUtilities.framework */, FB523C549815DE935E98151E /* Pods_SessionMessagingKit.framework */, + 2183DCA28E0620BC73FCC554 /* Pods_SessionProtocolKit.framework */, ); name = Frameworks; sourceTree = ""; @@ -3274,6 +3651,60 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C3C2A85D2553B41A00C340D1 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C3C2AA092553B9C400C340D1 /* NSObject+OWS.h in Headers */, + C3C2A95E2553B62400C340D1 /* IdentityKeyStore.h in Headers */, + C3C2A95D2553B62400C340D1 /* PreKeyStore.h in Headers */, + C3C2A91F2553B5B200C340D1 /* AxolotlParameters.h in Headers */, + C3C2A95F2553B62400C340D1 /* SessionStore.h in Headers */, + C3C2AA062553B9C400C340D1 /* Randomness.h in Headers */, + C3C2A8D52553B57C00C340D1 /* PreKeyBundle.h in Headers */, + C3C2A9092553B5B200C340D1 /* RatchetingSession.h in Headers */, + C3C2A96F2553B63C00C340D1 /* NSData+keyVersionByte.h in Headers */, + C3C2A9FC2553B9C400C340D1 /* iOSVersions.h in Headers */, + C3C2A91D2553B5B200C340D1 /* AliceAxolotlParameters.h in Headers */, + C3C2A9402553B5D700C340D1 /* SessionRecord.h in Headers */, + C3C2A9F42553B9C400C340D1 /* Threading.h in Headers */, + C3C2A90A2553B5B200C340D1 /* Chain.h in Headers */, + C3C2A8882553B4CC00C340D1 /* AxolotlExceptions.h in Headers */, + C3C2A89E2553B4F600C340D1 /* WhisperMessage.h in Headers */, + C3C2A8D92553B57C00C340D1 /* PreKeyRecord.h in Headers */, + C3C2A90B2553B5B200C340D1 /* MessageKeys.h in Headers */, + C3C2AA022553B9C400C340D1 /* Cryptography.h in Headers */, + C3C2A90C2553B5B200C340D1 /* RKCK.h in Headers */, + C3C2AA042553B9C400C340D1 /* OWSAsserts.h in Headers */, + C3C2A92B2553B5BE00C340D1 /* SessionCipher.h in Headers */, + C3C2A9152553B5B200C340D1 /* BobAxolotlParameters.h in Headers */, + C3C2A9412553B5D700C340D1 /* SessionState.h in Headers */, + C3C2A9142553B5B200C340D1 /* ChainKey.h in Headers */, + C3C2A8662553B41A00C340D1 /* SessionProtocolKit.h in Headers */, + C3C2A89F2553B4F600C340D1 /* ClosedGroupCiphertextMessage.h in Headers */, + C3C2A95C2553B62400C340D1 /* AxolotlStore.h in Headers */, + C3C2A9602553B62400C340D1 /* SignedPreKeyStore.h in Headers */, + C3C2A9F82553B9C400C340D1 /* NSString+OWS.h in Headers */, + C3C2A9702553B63C00C340D1 /* SerializationUtilities.h in Headers */, + C3C2A8A42553B4F600C340D1 /* FallbackMessage.h in Headers */, + C3C2A9122553B5B200C340D1 /* ReceivingChain.h in Headers */, + C3C2AA002553B9C400C340D1 /* NSDate+OWS.h in Headers */, + C3C2A9202553B5B200C340D1 /* SendingChain.h in Headers */, + C3C2A89C2553B4F600C340D1 /* PreKeyWhisperMessage.h in Headers */, + C3C2A9FD2553B9C400C340D1 /* SCKExceptionWrapper.h in Headers */, + C3C2A8B62553B53800C340D1 /* Constants.h in Headers */, + C3C2A9FE2553B9C400C340D1 /* OWSLogs.h in Headers */, + C3C2A9F92553B9C400C340D1 /* NSData+OWS.h in Headers */, + C3C2A8C22553B55600C340D1 /* AES-CBC.h in Headers */, + C3C2A93C2553B5D700C340D1 /* SessionBuilder.h in Headers */, + C3C2A9162553B5B200C340D1 /* ChainAndIndex.h in Headers */, + C3C2A8A22553B4F600C340D1 /* CipherMessage.h in Headers */, + C3C2A8D72553B57C00C340D1 /* SignedPrekeyRecord.h in Headers */, + C3C2A9132553B5B200C340D1 /* RootKey.h in Headers */, + C3C2A90E2553B5B200C340D1 /* TSDerivedSecrets.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -3391,6 +3822,25 @@ productReference = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; productType = "com.apple.product-type.framework"; }; + C3C2A8612553B41A00C340D1 /* SessionProtocolKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = C3C2A86B2553B41A00C340D1 /* Build configuration list for PBXNativeTarget "SessionProtocolKit" */; + buildPhases = ( + 099772F07D67DC2A83009D2F /* [CP] Check Pods Manifest.lock */, + C3C2A85D2553B41A00C340D1 /* Headers */, + C3C2A85E2553B41A00C340D1 /* Sources */, + C3C2A85F2553B41A00C340D1 /* Frameworks */, + C3C2A8602553B41A00C340D1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SessionProtocolKit; + productName = SessionProtocolKit; + productReference = C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */; + productType = "com.apple.product-type.framework"; + }; D221A088169C9E5E00537ABF /* Signal */ = { isa = PBXNativeTarget; buildConfigurationList = D221A0BC169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "Signal" */; @@ -3414,6 +3864,7 @@ C3C2A5A5255385C100C340D1 /* PBXTargetDependency */, C3C2A67F255388CC00C340D1 /* PBXTargetDependency */, C3C2A6F625539DE700C340D1 /* PBXTargetDependency */, + C3C2A8682553B41A00C340D1 /* PBXTargetDependency */, ); name = Signal; productName = RedPhone; @@ -3502,6 +3953,12 @@ LastSwiftMigration = 1210; ProvisioningStyle = Automatic; }; + C3C2A8612553B41A00C340D1 = { + CreatedOnToolsVersion = 12.1; + DevelopmentTeam = SUQ8J2PCT7; + LastSwiftMigration = 1210; + ProvisioningStyle = Automatic; + }; D221A088169C9E5E00537ABF = { DevelopmentTeam = SUQ8J2PCT7; LastSwiftMigration = 1020; @@ -3572,6 +4029,7 @@ 453518911FC63DBF00210559 /* SignalMessaging */, 7BC01A3A241F40AB00BC7C55 /* LokiPushNotificationService */, C3C2A6EF25539DE700C340D1 /* SessionMessagingKit */, + C3C2A8612553B41A00C340D1 /* SessionProtocolKit */, C3C2A59E255385C100C340D1 /* SessionSnodeKit */, C3C2A678255388CC00C340D1 /* SessionUtilities */, ); @@ -3630,6 +4088,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C3C2A8602553B41A00C340D1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; D221A087169C9E5E00537ABF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -3724,6 +4189,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 099772F07D67DC2A83009D2F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SessionProtocolKit-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 1460156AE01E0DB0949D61FE /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3860,6 +4347,7 @@ "${BUILT_PRODUCTS_DIR}/ZXingObjC/ZXingObjC.framework", "${BUILT_PRODUCTS_DIR}/libPhoneNumber-iOS/libPhoneNumber_iOS.framework", "${BUILT_PRODUCTS_DIR}/Curve25519Kit/Curve25519Kit.framework", + "${BUILT_PRODUCTS_DIR}/HKDFKit/HKDFKit.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -3890,6 +4378,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZXingObjC.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libPhoneNumber_iOS.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Curve25519Kit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HKDFKit.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -4281,6 +4770,55 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C3C2A85E2553B41A00C340D1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C3C2A91B2553B5B200C340D1 /* BobAxolotlParameters.m in Sources */, + C3C2A8D82553B57C00C340D1 /* PreKeyRecord.m in Sources */, + C3C2A9712553B63C00C340D1 /* SerializationUtilities.m in Sources */, + C3C2A9172553B5B200C340D1 /* RootKey.m in Sources */, + C3C2A9112553B5B200C340D1 /* ChainKey.m in Sources */, + C3C2A8C32553B55600C340D1 /* AES-CBC.m in Sources */, + C3C2AA012553B9C400C340D1 /* Threading.m in Sources */, + C3C2A9F32553B9C400C340D1 /* String+OWS.swift in Sources */, + C3C2A9F12553B9C400C340D1 /* OWSLogs.m in Sources */, + C3C2A9F22553B9C400C340D1 /* NSObject+OWS.m in Sources */, + C3C2A9182553B5B200C340D1 /* SendingChain.m in Sources */, + C3C2A8A02553B4F600C340D1 /* PreKeyWhisperMessage.m in Sources */, + C3C2A93F2553B5D700C340D1 /* SessionState.m in Sources */, + C3C2A9F02553B9C400C340D1 /* OWSAsserts.m in Sources */, + C3C2A9F72553B9C400C340D1 /* NSData+OWS.m in Sources */, + C3C2A90D2553B5B200C340D1 /* RKCK.m in Sources */, + C3C2A8E72553B59B00C340D1 /* SPKProto.swift in Sources */, + C3C2A91E2553B5B200C340D1 /* ChainAndIndex.m in Sources */, + C3C2A91A2553B5B200C340D1 /* TSDerivedSecrets.m in Sources */, + C3C2A91C2553B5B200C340D1 /* ReceivingChain.m in Sources */, + C3C2AA032553B9C400C340D1 /* Randomness.m in Sources */, + C3C2A9EF2553B9C400C340D1 /* OWSDataParser.swift in Sources */, + C3C2A8A32553B4F600C340D1 /* FallbackMessage.m in Sources */, + C3C2AA072553B9C400C340D1 /* Cryptography.m in Sources */, + C3C2A9F52553B9C400C340D1 /* NSDate+OWS.mm in Sources */, + C3C2A93E2553B5D700C340D1 /* SessionRecord.m in Sources */, + C3C2AA052553B9C400C340D1 /* SCKExceptionWrapper.m in Sources */, + C3C2A8D62553B57C00C340D1 /* PreKeyBundle.m in Sources */, + C3C2A9F62553B9C400C340D1 /* Data+OWS.swift in Sources */, + C3C2A89D2553B4F600C340D1 /* ClosedGroupCiphertextMessage.m in Sources */, + C3C2A9FB2553B9C400C340D1 /* Logger.swift in Sources */, + C3C2A96E2553B63C00C340D1 /* NSData+keyVersionByte.m in Sources */, + C3C2A8E62553B59B00C340D1 /* WhisperTextProtocol.pb.swift in Sources */, + C3C2A9102553B5B200C340D1 /* AliceAxolotlParameters.m in Sources */, + C3C2A9FA2553B9C400C340D1 /* OWSSwiftUtils.swift in Sources */, + C3C2A92C2553B5BE00C340D1 /* SessionCipher.m in Sources */, + C3C2A8D42553B57C00C340D1 /* SignedPrekeyRecord.m in Sources */, + C3C2A8A12553B4F600C340D1 /* WhisperMessage.m in Sources */, + C3C2A9192553B5B200C340D1 /* MessageKeys.m in Sources */, + C3C2A90F2553B5B200C340D1 /* RatchetingSession.m in Sources */, + C3C2A93D2553B5D700C340D1 /* SessionBuilder.m in Sources */, + C3C2AA082553B9C400C340D1 /* NSString+OWS.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D221A085169C9E5E00537ABF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4622,6 +5160,11 @@ target = C3C2A6EF25539DE700C340D1 /* SessionMessagingKit */; targetProxy = C3C2A6F525539DE700C340D1 /* PBXContainerItemProxy */; }; + C3C2A8682553B41A00C340D1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C3C2A8612553B41A00C340D1 /* SessionProtocolKit */; + targetProxy = C3C2A8672553B41A00C340D1 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -5396,6 +5939,134 @@ }; name = "App Store Release"; }; + C3C2A86C2553B41A00C340D1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AEA8083C060FF9BAFF6E0C9F /* Pods-SessionProtocolKit.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = SUQ8J2PCT7; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = SessionProtocolKit/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"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionProtocolKit"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C3C2A86D2553B41A00C340D1 /* App Store Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 174BD0AE74771D02DAC2B7A9 /* Pods-SessionProtocolKit.app store release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = SUQ8J2PCT7; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = SessionProtocolKit/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"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionProtocolKit"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "App Store Release"; + }; D221A0BA169C9E5F00537ABF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5857,6 +6528,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = "App Store Release"; }; + C3C2A86B2553B41A00C340D1 /* Build configuration list for PBXNativeTarget "SessionProtocolKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C3C2A86C2553B41A00C340D1 /* Debug */, + C3C2A86D2553B41A00C340D1 /* App Store Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "App Store Release"; + }; D221A083169C9E5E00537ABF /* Build configuration list for PBXProject "Signal" */ = { isa = XCConfigurationList; buildConfigurations = (