Serialize RefreshKeyOperation

TODO

-[] rotate signed prekey job
-[] verify current prekey w/ server
-[] create keys
pull/1/head
Michael Kirk 6 years ago
parent 01811a4891
commit 286d3c8ce9

@ -1 +1 @@
Subproject commit e9fb539a0061d7e9f87ba48a724f99092232b41b
Subproject commit af45234a3daa09e47012af11feec181dfcec1d55

@ -0,0 +1,29 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
// TODO define actual type, and validate length
public typealias IdentityKey = Data
@objc(SSKAccountManager)
public class AccountManager: NSObject {
static var shared = AccountManager()
private let serviceSocket: ServiceSocket
override init() {
self.serviceSocket = ServiceRestSocket()
}
public func getPreKeysCount() -> Promise<Int> {
return serviceSocket.getAvailablePreKeys()
}
public func setPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise<Void> {
return serviceSocket.registerPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords)
}
}

@ -0,0 +1,66 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
// We generate 100 one-time prekeys at a time. We should replenish
// whenever ~2/3 of them have been consumed.
let kEphemeralPreKeysMinimumCount: UInt = 35
@objc(SSKRefreshPreKeysOperation)
public class RefreshPreKeysOperation: OWSOperation {
private var tsAccountManager: TSAccountManager {
return TSAccountManager.sharedInstance()
}
private var accountManager: AccountManager {
return AccountManager.shared
}
private var primaryStorage: OWSPrimaryStorage {
return OWSPrimaryStorage.shared()
}
private var identityKeyManager: OWSIdentityManager {
return OWSIdentityManager.shared()
}
public override func run() {
Logger.debug("")
guard tsAccountManager.isRegistered() else {
Logger.debug("skipping - not registered")
return
}
firstly {
self.accountManager.getPreKeysCount()
}.then(on: DispatchQueue.global()) { preKeysCount -> Promise<Void> in
Logger.debug("preKeysCount: \(preKeysCount)")
guard preKeysCount < kEphemeralPreKeysMinimumCount || self.primaryStorage.currentSignedPrekeyId() == nil else {
Logger.debug("Available keys sufficient: \(preKeysCount)")
return Promise(value: ())
}
let identityKey: Data = self.identityKeyManager.identityKeyPair()!.publicKey
let signedPreKeyRecord: SignedPreKeyRecord = self.primaryStorage.generateRandomSignedRecord()
let preKeyRecords: [PreKeyRecord] = self.primaryStorage.generatePreKeyRecords()
return self.accountManager.setPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords).then { () -> Void in
signedPreKeyRecord.markAsAcceptedByService()
self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id)
self.primaryStorage.storePreKeyRecords(preKeyRecords)
TSPreKeyManager.clearSignedPreKeyRecords()
}
}.then { () -> Void in
Logger.debug("done")
self.reportSuccess()
}.catch { error in
self.reportError(error)
}.retainUntilComplete()
}
}

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "TSAccountManager.h"
@ -28,5 +28,6 @@ typedef NS_ENUM(NSInteger, RefreshPreKeysMode) {
+ (void)checkPreKeys;
+ (void)checkPreKeysIfNecessary;
+ (void)clearSignedPreKeyRecordsWithKeyId:(NSNumber *)keyId;
@end

@ -11,6 +11,7 @@
#import "OWSRequestFactory.h"
#import "TSNetworkManager.h"
#import "TSStorageHeaders.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
// Time before deletion of signed prekeys (measured in seconds)
#define kSignedPreKeysDeletionTime (7 * kDayInterval)
@ -21,10 +22,6 @@
// How often we check prekey state on app activation.
#define kPreKeyCheckFrequencySeconds (12 * kHourInterval)
// We generate 100 one-time prekeys at a time. We should replenish
// whenever ~2/3 of them have been consumed.
static const NSUInteger kEphemeralPreKeysMinimumCount = 35;
// This global should only be accessed on prekeyQueue.
static NSDate *lastPreKeyCheckTimestamp = nil;
@ -81,6 +78,17 @@ static const NSUInteger kMaxPrekeyUpdateFailureCount = 5;
return queue;
}
+ (NSOperationQueue *)operationQueue
{
static dispatch_once_t onceToken;
static NSOperationQueue *operationQueue;
dispatch_once(&onceToken, ^{
operationQueue = [NSOperationQueue new];
operationQueue.maxConcurrentOperationCount = 1;
});
return operationQueue;
}
+ (void)checkPreKeysIfNecessary
{
if (!CurrentAppContext().isMainApp) {
@ -183,9 +191,6 @@ static const NSUInteger kMaxPrekeyUpdateFailureCount = 5;
}
}
// Mark the prekeys as _NOT_ checked on failure.
[self markPreKeysAsNotChecked];
failureHandler(error);
NSInteger statusCode = 0;
@ -208,207 +213,83 @@ static const NSUInteger kMaxPrekeyUpdateFailureCount = 5;
return;
}
// Optimistically mark the prekeys as checked. This
// de-bounces prekey checks.
//
// If the check or key registration fails, the prekeys
// will be marked as _NOT_ checked.
dispatch_async(TSPreKeyManager.prekeyQueue, ^{
lastPreKeyCheckTimestamp = [NSDate date];
});
// We want to update prekeys if either the one-time or signed prekeys need an update, so
// we check the status of both.
//
// Most users will refresh their signed prekeys much more often than their
// one-time PreKeys, so we use a "signed only" mode to avoid updating the
// one-time keys in this case.
//
// We do not need a "one-time only" mode.
TSRequest *preKeyCountRequest = [OWSRequestFactory availablePreKeysCountRequest];
[[TSNetworkManager sharedManager] makeRequest:preKeyCountRequest
success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) {
NSString *preKeyCountKey = @"count";
NSNumber *count = [responseObject objectForKey:preKeyCountKey];
BOOL didUpdatePreKeys = NO;
void (^updatePreKeys)(RefreshPreKeysMode) = ^(RefreshPreKeysMode mode) {
[self registerPreKeysWithMode:mode
success:^{
OWSLogInfo(@"New prekeys registered with server.");
[self clearSignedPreKeyRecords];
}
failure:^(NSError *error) {
OWSLogWarn(@"Failed to update prekeys with the server: %@", error);
}];
};
BOOL shouldUpdateOneTimePreKeys = count.integerValue <= kEphemeralPreKeysMinimumCount;
if (shouldUpdateOneTimePreKeys) {
OWSLogInfo(@"Updating one-time and signed prekeys due to shortage of one-time prekeys.");
updatePreKeys(RefreshPreKeysMode_SignedAndOneTime);
didUpdatePreKeys = YES;
} else {
OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager];
NSNumber *currentSignedPrekeyId = [primaryStorage currentSignedPrekeyId];
BOOL shouldUpdateSignedPrekey = NO;
if (!currentSignedPrekeyId) {
OWSLogError(@"Couldn't find current signed prekey id");
shouldUpdateSignedPrekey = YES;
} else {
SignedPreKeyRecord *currentRecord =
[primaryStorage loadSignedPrekeyOrNil:currentSignedPrekeyId.intValue];
if (!currentRecord) {
OWSFailDebug(@"Couldn't find signed prekey for id: %@", currentSignedPrekeyId);
shouldUpdateSignedPrekey = YES;
} else {
shouldUpdateSignedPrekey
= fabs([currentRecord.generatedAt timeIntervalSinceNow]) >= kSignedPreKeyRotationTime;
}
}
if (shouldUpdateSignedPrekey) {
OWSLogInfo(@"Updating signed prekey due to rotation period.");
updatePreKeys(RefreshPreKeysMode_SignedOnly);
didUpdatePreKeys = YES;
} else {
OWSLogDebug(@"Not updating prekeys.");
}
}
if (!didUpdatePreKeys) {
// If we didn't update the prekeys, our local "current signed key" state should
// agree with the service's "current signed key" state. Let's verify that,
// since it's closely related to the issues we saw with the 2.7.0.10 release.
TSRequest *currentSignedPreKey = [OWSRequestFactory currentSignedPreKeyRequest];
[[TSNetworkManager sharedManager] makeRequest:currentSignedPreKey
success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) {
NSString *keyIdDictKey = @"keyId";
NSNumber *keyId = [responseObject objectForKey:keyIdDictKey];
OWSAssertDebug(keyId);
OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager];
NSNumber *currentSignedPrekeyId = [primaryStorage currentSignedPrekeyId];
if (!keyId || !currentSignedPrekeyId || ![currentSignedPrekeyId isEqualToNumber:keyId]) {
OWSLogError(@"Local and service 'current signed prekey ids' did not match. %@ == %@ == %d.",
keyId,
currentSignedPrekeyId,
[currentSignedPrekeyId isEqualToNumber:keyId]);
}
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (!IsNSErrorNetworkFailure(error)) {
OWSProdError([OWSAnalyticsEvents errorPrekeysCurrentSignedPrekeyRequestFailed]);
}
OWSLogWarn(@"Could not retrieve current signed key from the service.");
// Mark the prekeys as _NOT_ checked on failure.
[self markPreKeysAsNotChecked];
}];
}
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (!IsNSErrorNetworkFailure(error)) {
OWSProdError([OWSAnalyticsEvents errorPrekeysAvailablePrekeysRequestFailed]);
}
OWSLogError(@"Failed to retrieve the number of available prekeys.");
// Mark the prekeys as _NOT_ checked on failure.
[self markPreKeysAsNotChecked];
}];
}
+ (void)markPreKeysAsNotChecked
{
dispatch_async(TSPreKeyManager.prekeyQueue, ^{
lastPreKeyCheckTimestamp = nil;
});
SSKRefreshPreKeysOperation *operation = [SSKRefreshPreKeysOperation new];
[self.operationQueue addOperation:operation];
}
+ (void)clearSignedPreKeyRecords {
OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager];
NSNumber *currentSignedPrekeyId = [primaryStorage currentSignedPrekeyId];
[self clearSignedPreKeyRecordsWithKeyId:currentSignedPrekeyId success:nil];
[self clearSignedPreKeyRecordsWithKeyId:currentSignedPrekeyId];
}
+ (void)clearSignedPreKeyRecordsWithKeyId:(NSNumber *)keyId success:(void (^_Nullable)(void))successHandler
+ (void)clearSignedPreKeyRecordsWithKeyId:(NSNumber *)keyId
{
if (!keyId) {
OWSFailDebug(@"Ignoring request to clear signed preKeys since no keyId was specified");
return;
}
// We use prekeyQueue to serialize this logic and ensure that only
// one thread is "registering" or "clearing" prekeys at a time.
dispatch_async(TSPreKeyManager.prekeyQueue, ^{
OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager];
SignedPreKeyRecord *currentRecord = [primaryStorage loadSignedPrekeyOrNil:keyId.intValue];
if (!currentRecord) {
OWSFailDebug(@"Couldn't find signed prekey for id: %@", keyId);
OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager];
SignedPreKeyRecord *currentRecord = [primaryStorage loadSignedPrekeyOrNil:keyId.intValue];
if (!currentRecord) {
OWSFailDebug(@"Couldn't find signed prekey for id: %@", keyId);
}
NSArray *allSignedPrekeys = [primaryStorage loadSignedPreKeys];
NSArray *oldSignedPrekeys
= (currentRecord != nil ? [self removeCurrentRecord:currentRecord fromRecords:allSignedPrekeys]
: allSignedPrekeys);
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateStyle = NSDateFormatterMediumStyle;
dateFormatter.timeStyle = NSDateFormatterMediumStyle;
dateFormatter.locale = [NSLocale systemLocale];
// Sort the signed prekeys in ascending order of generation time.
oldSignedPrekeys = [oldSignedPrekeys sortedArrayUsingComparator:^NSComparisonResult(
SignedPreKeyRecord *_Nonnull left, SignedPreKeyRecord *_Nonnull right) {
return [left.generatedAt compare:right.generatedAt];
}];
NSUInteger oldSignedPreKeyCount = oldSignedPrekeys.count;
int oldAcceptedSignedPreKeyCount = 0;
for (SignedPreKeyRecord *signedPrekey in oldSignedPrekeys) {
if (signedPrekey.wasAcceptedByService) {
oldAcceptedSignedPreKeyCount++;
}
NSArray *allSignedPrekeys = [primaryStorage loadSignedPreKeys];
NSArray *oldSignedPrekeys
= (currentRecord != nil ? [self removeCurrentRecord:currentRecord fromRecords:allSignedPrekeys]
: allSignedPrekeys);
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateStyle = NSDateFormatterMediumStyle;
dateFormatter.timeStyle = NSDateFormatterMediumStyle;
dateFormatter.locale = [NSLocale systemLocale];
// Sort the signed prekeys in ascending order of generation time.
oldSignedPrekeys = [oldSignedPrekeys sortedArrayUsingComparator:^NSComparisonResult(
SignedPreKeyRecord *_Nonnull left, SignedPreKeyRecord *_Nonnull right) {
return [left.generatedAt compare:right.generatedAt];
}];
NSUInteger oldSignedPreKeyCount = oldSignedPrekeys.count;
int oldAcceptedSignedPreKeyCount = 0;
for (SignedPreKeyRecord *signedPrekey in oldSignedPrekeys) {
if (signedPrekey.wasAcceptedByService) {
oldAcceptedSignedPreKeyCount++;
}
}
// Iterate the signed prekeys in ascending order so that we try to delete older keys first.
for (SignedPreKeyRecord *signedPrekey in oldSignedPrekeys) {
// Always keep at least 3 keys, accepted or otherwise.
if (oldSignedPreKeyCount <= 3) {
continue;
}
// Iterate the signed prekeys in ascending order so that we try to delete older keys first.
for (SignedPreKeyRecord *signedPrekey in oldSignedPrekeys) {
// Always keep at least 3 keys, accepted or otherwise.
if (oldSignedPreKeyCount <= 3) {
continue;
}
// Never delete signed prekeys until they are N days old.
if (fabs([signedPrekey.generatedAt timeIntervalSinceNow]) < kSignedPreKeysDeletionTime) {
continue;
}
// Never delete signed prekeys until they are N days old.
if (fabs([signedPrekey.generatedAt timeIntervalSinceNow]) < kSignedPreKeysDeletionTime) {
// We try to keep a minimum of 3 "old, accepted" signed prekeys.
if (signedPrekey.wasAcceptedByService) {
if (oldAcceptedSignedPreKeyCount <= 3) {
continue;
}
// We try to keep a minimum of 3 "old, accepted" signed prekeys.
if (signedPrekey.wasAcceptedByService) {
if (oldAcceptedSignedPreKeyCount <= 3) {
continue;
} else {
oldAcceptedSignedPreKeyCount--;
}
}
if (signedPrekey.wasAcceptedByService) {
OWSProdInfo([OWSAnalyticsEvents prekeysDeletedOldAcceptedSignedPrekey]);
} else {
OWSProdInfo([OWSAnalyticsEvents prekeysDeletedOldUnacceptedSignedPrekey]);
oldAcceptedSignedPreKeyCount--;
}
oldSignedPreKeyCount--;
[primaryStorage removeSignedPreKey:signedPrekey.Id];
}
if (successHandler) {
successHandler();
if (signedPrekey.wasAcceptedByService) {
OWSProdInfo([OWSAnalyticsEvents prekeysDeletedOldAcceptedSignedPrekey]);
} else {
OWSProdInfo([OWSAnalyticsEvents prekeysDeletedOldUnacceptedSignedPrekey]);
}
});
oldSignedPreKeyCount--;
[primaryStorage removeSignedPreKey:signedPrekey.Id];
}
}
+ (NSArray *)removeCurrentRecord:(SignedPreKeyRecord *)currentRecord fromRecords:(NSArray *)allRecords {

@ -0,0 +1,72 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
protocol ServiceSocket {
func getAvailablePreKeys() -> Promise<Int>
func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise<Void>
}
class ServiceRestSocket: ServiceSocket {
var networkManager: TSNetworkManager {
return TSNetworkManager.shared()
}
func unexpectedServerResponseError() -> Error {
return OWSErrorMakeUnableToProcessServerResponseError()
}
func getAvailablePreKeys() -> Promise<Int> {
Logger.debug("")
let (promise, fulfill, reject) = Promise<Int>.pending()
let request = OWSRequestFactory.availablePreKeysCountRequest()
networkManager.makeRequest(request,
success: { (_, responseObject) in
Logger.debug("got response")
guard let params = ParamParser(responseObject: responseObject) else {
reject(self.unexpectedServerResponseError())
return
}
let count: Int
do {
count = try params.required(key: "count")
} catch {
reject(error)
return
}
fulfill(count)
},
failure: { (_, error) in
Logger.debug("error: \(error)")
reject(error)
})
return promise
}
func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise<Void> {
Logger.debug("")
let (promise, fulfill, reject) = Promise<Void>.pending()
let request = OWSRequestFactory.registerPrekeysRequest(withPrekeyArray: preKeyRecords, identityKey: identityKey, signedPreKey: signedPreKeyRecord)
networkManager.makeRequest(request,
success: { (_, _) in
Logger.debug("success")
fulfill(())
},
failure: { (_, error) in
Logger.debug("error: \(error)")
reject(error)
})
return promise
}
}

@ -8,6 +8,6 @@
@interface OWSPrimaryStorage (PreKeyStore) <PreKeyStore>
- (NSArray<PreKeyRecord *> *)generatePreKeyRecords;
- (void)storePreKeyRecords:(NSArray *)preKeyRecords;
- (void)storePreKeyRecords:(NSArray<PreKeyRecord *> *)preKeyRecords NS_SWIFT_NAME(storePreKeyRecords(_:));
@end

@ -40,7 +40,7 @@
return preKeyRecords;
}
- (void)storePreKeyRecords:(NSArray *)preKeyRecords
- (void)storePreKeyRecords:(NSArray<PreKeyRecord *> *)preKeyRecords
{
for (PreKeyRecord *record in preKeyRecords) {
[self.dbReadWriteConnection setObject:record

Loading…
Cancel
Save