Create SessionProtocolKit framework
This commit is contained in:
parent
96bfab5d93
commit
dcb218c864
15
Podfile
15
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
|
||||
|
|
30
Podfile.lock
30
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
|
||||
|
|
2
Pods
2
Pods
|
@ -1 +1 @@
|
|||
Subproject commit ceaefa107175deb5bddd8c8556491f4ebd548046
|
||||
Subproject commit 24da46b65b9581625f18cd9dac30b310401e84f2
|
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// CipherMessage.h
|
||||
// AxolotlKit
|
||||
//
|
||||
// Created by Frederic Jacobs on 26/10/14.
|
||||
// Copyright (c) 2014 Frederic Jacobs. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSUInteger, CipherMessageType) {
|
||||
CipherMessageType_Prekey = 0,
|
||||
CipherMessageType_Whisper,
|
||||
CipherMessageType_Fallback = 3,
|
||||
CipherMessageType_ClosedGroupCiphertext = 4
|
||||
};
|
||||
|
||||
@protocol CipherMessage <NSObject>
|
||||
|
||||
- (NSData *)serialized;
|
||||
|
||||
@property (nonatomic, readonly) CipherMessageType cipherMessageType;
|
||||
|
||||
@end
|
|
@ -0,0 +1,18 @@
|
|||
#import "CipherMessage.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ClosedGroupCiphertextMessage : NSObject<CipherMessage>
|
||||
|
||||
@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
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
#import "ClosedGroupCiphertextMessage.h"
|
||||
#import "AxolotlExceptions.h"
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
#import <SessionProtocolKit/SessionProtocolKit-Swift.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,13 @@
|
|||
#import "CipherMessage.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FallbackMessage : NSObject<CipherMessage>
|
||||
|
||||
@property (nonatomic, readonly) NSData *serialized;
|
||||
|
||||
- (instancetype)init_throws_withData:(NSData *)serialized;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -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
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "WhisperMessage.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface PreKeyWhisperMessage : NSObject <CipherMessage>
|
||||
|
||||
- (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
|
|
@ -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 <SessionProtocolKit/SessionProtocolKit-Swift.h>
|
||||
#import <SessionProtocolKit/SCKExceptionWrapper.h>
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CipherMessage.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ECKeyPair;
|
||||
|
||||
@interface WhisperMessage : NSObject <CipherMessage>
|
||||
|
||||
@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
|
|
@ -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 <SessionProtocolKit/OWSAsserts.h>
|
||||
#import <SessionProtocolKit/SessionProtocolKit-Swift.h>
|
||||
#import <SessionProtocolKit/NSData+OWS.h>
|
||||
#import <SessionProtocolKit/SCKExceptionWrapper.h>
|
||||
#import <SessionProtocolKit/SessionProtocolKit-Swift.h>
|
||||
|
||||
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
|
|
@ -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
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AES-CBC.h"
|
||||
#import "AxolotlExceptions.h"
|
||||
#import "MessageKeys.h"
|
||||
#import <CommonCrypto/CommonCryptor.h>
|
||||
#import <CommonCrypto/CommonHMAC.h>
|
||||
#import <Security/Security.h>
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,12 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
FOUNDATION_EXPORT double SessionProtocolKitVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char SessionProtocolKitVersionString[];
|
||||
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
#import <SessionProtocolKit/AxolotlStore.h>
|
||||
#import <Curve25519Kit/Curve25519.h>
|
||||
#import <SessionProtocolKit/NSData+OWS.h>
|
||||
#import <SessionProtocolKit/NSObject+OWS.h>
|
||||
#import <SessionProtocolKit/NSString+OWS.h>
|
||||
#import <SessionProtocolKit/Threading.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 <Foundation/Foundation.h>
|
||||
|
||||
@interface PreKeyBundle : NSObject <NSSecureCoding>
|
||||
|
||||
@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
|
|
@ -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 <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
|
||||
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
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Curve25519Kit/Curve25519.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface PreKeyRecord : NSObject <NSSecureCoding>
|
||||
|
||||
@property (nonatomic, readonly) int Id;
|
||||
@property (nonatomic, readonly) ECKeyPair *keyPair;
|
||||
|
||||
- (instancetype)initWithId:(int)identifier keyPair:(ECKeyPair*)keyPair;
|
||||
|
||||
@end
|
|
@ -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 <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "PreKeyRecord.h"
|
||||
#import <Curve25519Kit/Curve25519.h>
|
||||
|
||||
@interface SignedPreKeyRecord : PreKeyRecord <NSSecureCoding>
|
||||
|
||||
@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
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SignedPrekeyRecord.h"
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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
|
|
@ -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
|
|
@ -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
|
|
@ -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<D: SwiftProtobuf.Decoder>(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<V: SwiftProtobuf.Visitor>(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<D: SwiftProtobuf.Decoder>(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<V: SwiftProtobuf.Visitor>(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<D: SwiftProtobuf.Decoder>(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<V: SwiftProtobuf.Visitor>(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<D: SwiftProtobuf.Decoder>(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<V: SwiftProtobuf.Visitor>(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<D: SwiftProtobuf.Decoder>(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<V: SwiftProtobuf.Visitor>(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<D: SwiftProtobuf.Decoder>(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<V: SwiftProtobuf.Visitor>(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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// AliceAxolotlParameters.h
|
||||
// AxolotlKit
|
||||
//
|
||||
// Created by Frederic Jacobs on 26/07/14.
|
||||
// Copyright (c) 2014 Frederic Jacobs. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "AxolotlParameters.h"
|
||||
|
||||
@interface AliceAxolotlParameters : NSObject<AxolotlParameters>
|
||||
|
||||
@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
|
|
@ -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 <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
@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
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Curve25519Kit/Curve25519.h>
|
||||
|
||||
@protocol AxolotlParameters <NSObject>
|
||||
|
||||
@property (nonatomic) ECKeyPair *ourIdentityKeyPair;
|
||||
@property (nonatomic) NSData *theirIdentityKey;
|
||||
|
||||
@end
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// BobAxolotlParameters.h
|
||||
// AxolotlKit
|
||||
//
|
||||
// Created by Frederic Jacobs on 26/07/14.
|
||||
// Copyright (c) 2014 Frederic Jacobs. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AxolotlParameters.h"
|
||||
|
||||
@interface BobAxolotlParameters : NSObject<AxolotlParameters>
|
||||
|
||||
@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
|
|
@ -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 <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
@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
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// Chain.h
|
||||
// AxolotlKit
|
||||
//
|
||||
// Created by Frederic Jacobs on 02/09/14.
|
||||
// Copyright (c) 2014 Frederic Jacobs. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@class ChainKey;
|
||||
|
||||
@protocol Chain <NSObject, NSSecureCoding>
|
||||
|
||||
-(ChainKey*)chainKey;
|
||||
-(void)setChainKey:(ChainKey*)chainKey;
|
||||
|
||||
@end
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// ChainAndIndex.h
|
||||
// AxolotlKit
|
||||
//
|
||||
// Created by Frederic Jacobs on 21/09/14.
|
||||
// Copyright (c) 2014 Frederic Jacobs. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ChainKey.h"
|
||||
|
||||
@interface ChainAndIndex : NSObject
|
||||
|
||||
@property id<Chain> chain;
|
||||
@property int index;
|
||||
|
||||
@end
|
|
@ -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
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Chain.h"
|
||||
#import "MessageKeys.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ChainKey : NSObject <NSSecureCoding>
|
||||
|
||||
@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
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ChainKey.h"
|
||||
#import "TSDerivedSecrets.h"
|
||||
#import <CommonCrypto/CommonCrypto.h>
|
||||
#import <Curve25519Kit/Curve25519.h>
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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
|
|
@ -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 <Foundation/Foundation.h>
|
||||
|
||||
@interface MessageKeys : NSObject <NSSecureCoding>
|
||||
|
||||
- (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
|
|
@ -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 <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Chain.h"
|
||||
#import "RootKey.h"
|
||||
#import "SessionState.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class ECKeyPair;
|
||||
|
||||
@interface RKCK : NSObject
|
||||
|
||||
@property (nonatomic,strong) RootKey *rootKey;
|
||||
@property (nonatomic,strong) ChainKey *chainKey;
|
||||
|
||||
-(instancetype) initWithRK:(RootKey*)rootKey CK:(ChainKey*)chainKey;
|
||||
|
||||
@end
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKCK.h"
|
||||
#import <Curve25519Kit/Curve25519.h>
|
||||
#import "TSDerivedSecrets.h"
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
@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
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@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
|
|
@ -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 <Curve25519Kit/Curve25519.h>
|
||||
#import <HKDFKit/HKDFKit.h>
|
||||
#import <SessionProtocolKit/SCKExceptionWrapper.h>
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
@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<AxolotlParameters>)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
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Chain.h"
|
||||
#import <Curve25519Kit/Curve25519.h>
|
||||
|
||||
@interface ReceivingChain : NSObject <Chain, NSSecureCoding>
|
||||
|
||||
- (instancetype)initWithChainKey:(ChainKey*)chainKey senderRatchetKey:(NSData*)senderRatchet;
|
||||
|
||||
@property NSMutableArray *messageKeysList;
|
||||
@property NSData *senderRatchetKey;
|
||||
|
||||
@end
|
|
@ -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 <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
@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
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class ECKeyPair;
|
||||
@class RKCK;
|
||||
|
||||
@interface RootKey : NSObject <NSSecureCoding>
|
||||
|
||||
- (instancetype)initWithData:(NSData *)data;
|
||||
- (RKCK *)throws_createChainWithTheirEphemeral:(NSData *)theirEphemeral
|
||||
ourEphemeral:(ECKeyPair *)ourEphemeral NS_SWIFT_UNAVAILABLE("throws objc exceptions");
|
||||
|
||||
@property (nonatomic, readonly) NSData *keyData;
|
||||
|
||||
@end
|
|
@ -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 <Curve25519Kit/Curve25519.h>
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Chain.h"
|
||||
|
||||
#import <Curve25519Kit/Curve25519.h>
|
||||
|
||||
@interface SendingChain : NSObject <Chain, NSSecureCoding>
|
||||
|
||||
-(instancetype)initWithChainKey:(ChainKey*)chainKey senderRatchetKeyPair:(ECKeyPair*)keyPair;
|
||||
|
||||
@property ECKeyPair *senderRatchetKeyPair;
|
||||
|
||||
@end
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SendingChain.h"
|
||||
#import "ChainKey.h"
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
@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
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@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
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TSDerivedSecrets.h"
|
||||
#import <Curve25519Kit/Curve25519.h>
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
#import <HKDFKit/HKDFKit.h>
|
||||
|
||||
@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
|
|
@ -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 <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SessionCipher : NSObject
|
||||
|
||||
- (instancetype)initWithAxolotlStore:(id<AxolotlStore>)sessionStore recipientId:(NSString*)recipientId deviceId:(int)deviceId;
|
||||
|
||||
- (instancetype)initWithSessionStore:(id<SessionStore>)sessionStore preKeyStore:(id<PreKeyStore>)preKeyStore signedPreKeyStore:(id<SignedPreKeyStore>)signedPreKeyStore identityKeyStore:(id<IdentityKeyStore>)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<CipherMessage>)throws_encryptMessage:(NSData *)paddedMessage
|
||||
protocolContext:(nullable id)protocolContext NS_SWIFT_UNAVAILABLE("throws objc exceptions");
|
||||
- (nullable id<CipherMessage>)encryptMessage:(NSData *)paddedMessage
|
||||
protocolContext:(nullable id)protocolContext
|
||||
error:(NSError **)outError;
|
||||
|
||||
- (NSData *)throws_decrypt:(id<CipherMessage>)whisperMessage
|
||||
protocolContext:(nullable id)protocolContext NS_SWIFT_UNAVAILABLE("throws objc exceptions");
|
||||
- (nullable NSData *)decrypt:(id<CipherMessage>)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
|
|
@ -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 <Curve25519Kit/Curve25519.h>
|
||||
#import <Curve25519Kit/Ed25519.h>
|
||||
#import <HKDFKit/HKDFKit.h>
|
||||
#import <SessionProtocolKit/SCKExceptionWrapper.h>
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SessionCipher ()
|
||||
|
||||
@property (nonatomic, readonly) NSString *recipientId;
|
||||
@property (nonatomic, readonly) int deviceId;
|
||||
|
||||
@property (nonatomic, readonly) id<IdentityKeyStore> identityKeyStore;
|
||||
@property (nonatomic, readonly) id<SessionStore> sessionStore;
|
||||
@property (nonatomic, readonly) SessionBuilder *sessionBuilder;
|
||||
@property (nonatomic, readonly) id<PreKeyStore> prekeyStore;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation SessionCipher
|
||||
|
||||
- (instancetype)initWithAxolotlStore:(id<AxolotlStore>)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>)sessionStore
|
||||
preKeyStore:(id<PreKeyStore>)preKeyStore
|
||||
signedPreKeyStore:(id<SignedPreKeyStore>)signedPreKeyStore
|
||||
identityKeyStore:(id<IdentityKeyStore>)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<CipherMessage>)encryptMessage:(NSData *)paddedMessage
|
||||
protocolContext:(nullable id)protocolContext
|
||||
error:(NSError **)outError
|
||||
{
|
||||
__block id<CipherMessage> result;
|
||||
[SCKExceptionWrapper
|
||||
tryBlock:^{
|
||||
result = [self throws_encryptMessage:paddedMessage protocolContext:protocolContext];
|
||||
}
|
||||
error:outError];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (id<CipherMessage>)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> 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<CipherMessage>)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<CipherMessage>)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
|
|
@ -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 <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class PreKeyWhisperMessage;
|
||||
|
||||
extern const int kPreKeyOfLastResortId;
|
||||
|
||||
@interface SessionBuilder : NSObject
|
||||
|
||||
- (instancetype)initWithAxolotlStore:(id<AxolotlStore>)sessionStore
|
||||
recipientId:(NSString *)recipientId
|
||||
deviceId:(int)deviceId;
|
||||
|
||||
- (instancetype)initWithSessionStore:(id<SessionStore>)sessionStore
|
||||
preKeyStore:(id<PreKeyStore>)preKeyStore
|
||||
signedPreKeyStore:(id<SignedPreKeyStore>)signedPreKeyStore
|
||||
identityKeyStore:(id<IdentityKeyStore>)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
|
|
@ -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 <Curve25519Kit/Curve25519.h>
|
||||
#import <Curve25519Kit/Ed25519.h>
|
||||
#import <SessionProtocolKit/NSData+OWS.h>
|
||||
#import <SessionProtocolKit/SCKExceptionWrapper.h>
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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> sessionStore;
|
||||
@property(nonatomic, readonly)id<PreKeyStore> prekeyStore ;
|
||||
@property(nonatomic, readonly)id<SignedPreKeyStore> signedPreKeyStore;
|
||||
@property(nonatomic, readonly)id<IdentityKeyStore> identityStore;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation SessionBuilder
|
||||
|
||||
- (instancetype)initWithAxolotlStore:(id<AxolotlStore>)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>)sessionStore
|
||||
preKeyStore:(id<PreKeyStore>)preKeyStore
|
||||
signedPreKeyStore:(id<SignedPreKeyStore>)signedPreKeyStore
|
||||
identityKeyStore:(id<IdentityKeyStore>)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
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SessionState.h"
|
||||
|
||||
@interface SessionRecord : NSObject <NSSecureCoding>
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
- (BOOL)hasSessionState:(int)version baseKey:(NSData*)aliceBaseKey;
|
||||
- (SessionState*)sessionState;
|
||||
- (NSMutableArray<SessionState *> *)previousSessionStates;
|
||||
|
||||
- (void)removePreviousSessionStates;
|
||||
- (BOOL)isFresh;
|
||||
- (void)markAsUnFresh;
|
||||
- (void)archiveCurrentState;
|
||||
- (void)promoteState:(SessionState*)promotedState;
|
||||
- (void)setState:(SessionState*)sessionState;
|
||||
|
||||
@end
|
|
@ -0,0 +1,115 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SessionRecord.h"
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
#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<SessionState *> *)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
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@class ECKeyPair;
|
||||
#import "RKCK.h"
|
||||
#import "MessageKeys.h"
|
||||
#import "Chain.h"
|
||||
#import "RootKey.h"
|
||||
|
||||
/**
|
||||
* Pending PreKeys
|
||||
*/
|
||||
|
||||
@interface PendingPreKey : NSObject <NSSecureCoding>
|
||||
|
||||
@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 <NSSecureCoding>
|
||||
|
||||
/**
|
||||
* 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
|
|
@ -0,0 +1,313 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Curve25519Kit/Curve25519.h>
|
||||
#import "SessionState.h"
|
||||
#import "ReceivingChain.h"
|
||||
#import "SendingChain.h"
|
||||
#import "ChainAndIndex.h"
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
@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
|
|
@ -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 <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* The Session Store defines the interface of the storage of sesssions.
|
||||
*/
|
||||
|
||||
@protocol AxolotlStore <SessionStore, IdentityKeyStore, PreKeyStore, SessionStore, SignedPreKeyStore>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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 <NSObject>
|
||||
|
||||
- (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
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PreKeyRecord.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol PreKeyStore <NSObject>
|
||||
|
||||
- (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
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SessionRecord.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// See a discussion of the protocolContext in SessionCipher.h.
|
||||
@protocol SessionStore <NSObject>
|
||||
|
||||
/**
|
||||
* 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
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SignedPrekeyRecord.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol SignedPreKeyStore <NSObject>
|
||||
|
||||
- (SignedPreKeyRecord *)throws_loadSignedPrekey:(int)signedPreKeyId NS_SWIFT_UNAVAILABLE("throws objc exceptions");
|
||||
|
||||
- (nullable SignedPreKeyRecord *)loadSignedPrekeyOrNil:(int)signedPreKeyId;
|
||||
|
||||
- (NSArray<SignedPreKeyRecord *> *)loadSignedPreKeys;
|
||||
|
||||
- (void)storeSignedPreKey:(int)signedPreKeyId signedPreKeyRecord:(SignedPreKeyRecord *)signedPreKeyRecord;
|
||||
|
||||
- (BOOL)containsSignedPreKey:(int)signedPreKeyId;
|
||||
|
||||
- (void)removeSignedPreKey:(int)signedPrekeyId;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,161 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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 <NSSecureCoding>
|
||||
|
||||
/// 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
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSData (OWS)
|
||||
|
||||
+ (NSData *)join:(NSArray<NSData *> *)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
|
|
@ -0,0 +1,147 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSData+OWS.h"
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation NSData (OWS)
|
||||
|
||||
+ (NSData *)join:(NSArray<NSData *> *)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
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSData (keyVersionByte)
|
||||
|
||||
- (instancetype)prependKeyType;
|
||||
|
||||
- (instancetype)throws_removeKeyType NS_SWIFT_UNAVAILABLE("throws objc exceptions");
|
||||
- (nullable instancetype)removeKeyTypeAndReturnError:(NSError **)outError;
|
||||
|
||||
@end
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSData+keyVersionByte.h"
|
||||
#import "AxolotlExceptions.h"
|
||||
#import <SessionProtocolKit/SCKExceptionWrapper.h>
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
@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
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSDate+OWS.h"
|
||||
#import <chrono>
|
||||
|
||||
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
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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
|
|
@ -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
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,453 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSString+OWS.h"
|
||||
#import "iOSVersions.h"
|
||||
#import <objc/runtime.h>
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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<UnicodeCodeRange *> *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
|
|
@ -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
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSAsserts.h"
|
||||
#import <SessionProtocolKit/SessionProtocolKit-Swift.h>
|
||||
|
||||
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
|
|
@ -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)..<Int(endIndex))
|
||||
cursor += length
|
||||
return result
|
||||
}
|
||||
|
||||
public func nextByte(name: String?=nil) throws -> 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <CocoaLumberjack/CocoaLumberjack.h>
|
||||
|
||||
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
|
|
@ -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
|
|
@ -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 <iOS10, since syntax is different and it's just a development convenience.
|
||||
}
|
||||
}
|
||||
|
||||
// Once we're on Swift4.2 we can mark this as inlineable
|
||||
// @inlinable
|
||||
public func AssertIsOnMainThread(file: String = #file,
|
||||
function: String = #function,
|
||||
line: Int = #line) {
|
||||
if !Thread.isMainThread {
|
||||
owsFailDebug("Must be on main thread.", file: file, function: function, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
// Once we're on Swift4.2 we can mark this as inlineable
|
||||
// @inlinable
|
||||
public func owsFailDebug(_ logMessage: String,
|
||||
file: String = #file,
|
||||
function: String = #function,
|
||||
line: Int = #line) {
|
||||
Logger.error(logMessage, file: file, function: function, line: line)
|
||||
Logger.flush()
|
||||
let formattedMessage = owsFormatLogMessage(logMessage, file: file, function: function, line: line)
|
||||
assertionFailure(formattedMessage)
|
||||
}
|
||||
|
||||
// Once we're on Swift4.2 we can mark this as inlineable
|
||||
// @inlinable
|
||||
public 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@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
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Randomness.h"
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
@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
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SCKExceptionWrapper.h"
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,81 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SerializationUtilities.h"
|
||||
#import <CommonCrypto/CommonCrypto.h>
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Threading.h"
|
||||
#import <SessionProtocolKit/OWSAsserts.h>
|
||||
|
||||
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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue