mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			264 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Objective-C
		
	
			
		
		
	
	
			264 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Objective-C
		
	
| //
 | |
| //  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 <SignalCoreKit/NSData+OWS.h>
 | |
| #import <SignalCoreKit/SCKExceptionWrapper.h>
 | |
| #import <SignalCoreKit/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 throws_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
 |