// // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "SessionBuilder.h" #import "AliceAxolotlParameters.h" #import "AxolotlExceptions.h" #import "AxolotlParameters.h" #import "AxolotlStore.h" #import "BobAxolotlParameters.h" #import "NSData+keyVersionByte.h" #import "PreKeyWhisperMessage.h" #import "PrekeyBundle.h" #import "RatchetingSession.h" #import "SessionState.h" #import #import #import #import #import NS_ASSUME_NONNULL_BEGIN #define CURRENT_VERSION 3 #define MINUMUM_VERSION 3 const int kPreKeyOfLastResortId = 0xFFFFFF; @interface SessionBuilder () @property (nonatomic, readonly)NSString* recipientId; @property (nonatomic, readonly)int deviceId; @property(nonatomic, readonly)id sessionStore; @property(nonatomic, readonly)id prekeyStore ; @property(nonatomic, readonly)id signedPreKeyStore; @property(nonatomic, readonly)id identityStore; @end @implementation SessionBuilder - (instancetype)initWithAxolotlStore:(id)sessionStore recipientId:(NSString*)recipientId deviceId:(int)deviceId{ OWSAssert(sessionStore); OWSAssert(recipientId); return [self initWithSessionStore:sessionStore preKeyStore:sessionStore signedPreKeyStore:sessionStore identityKeyStore:sessionStore recipientId:recipientId deviceId:deviceId]; } - (instancetype)initWithSessionStore:(id)sessionStore preKeyStore:(id)preKeyStore signedPreKeyStore:(id)signedPreKeyStore identityKeyStore:(id)identityKeyStore recipientId:(NSString*)recipientId deviceId:(int)deviceId{ OWSAssert(sessionStore); OWSAssert(preKeyStore); OWSAssert(signedPreKeyStore); OWSAssert(identityKeyStore); OWSAssert(recipientId); self = [super init]; if (self) { _sessionStore = sessionStore; _prekeyStore = preKeyStore; _signedPreKeyStore = signedPreKeyStore; _identityStore = identityKeyStore; _recipientId = recipientId; _deviceId = deviceId; } return self; } - (BOOL)processPrekeyBundle:(PreKeyBundle *)preKeyBundle protocolContext:(nullable id)protocolContext error:(NSError **)outError { return [SCKExceptionWrapper tryBlock:^{ [self throws_processPrekeyBundle:preKeyBundle protocolContext:protocolContext]; } error:outError]; } - (void)throws_processPrekeyBundle:(PreKeyBundle *)preKeyBundle protocolContext:(nullable id)protocolContext { OWSAssert(preKeyBundle); NSData *theirIdentityKey = preKeyBundle.identityKey.throws_removeKeyType; NSData *theirSignedPreKey = preKeyBundle.signedPreKeyPublic.throws_removeKeyType; if (![self.identityStore isTrustedIdentityKey:theirIdentityKey recipientId:self.recipientId direction:TSMessageDirectionOutgoing protocolContext:protocolContext]) { @throw [NSException exceptionWithName:UntrustedIdentityKeyException reason:@"Identity key is not valid" userInfo:@{}]; } // NOTE: we use preKeyBundle.signedPreKeyPublic which has the key type byte. if (![Ed25519 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