From f3ba6d4c275b45d041981025fee5c63e8766ac36 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 17 Jul 2018 12:24:03 -0400 Subject: [PATCH] Remote attestation. --- Signal/src/AppDelegate.m | 2 + .../src/Contacts/ContactDiscoveryService.h | 46 +++ .../src/Contacts/ContactDiscoveryService.m | 312 ++++++++++++++++++ .../Network/API/Requests/OWSRequestFactory.h | 4 + .../Network/API/Requests/OWSRequestFactory.m | 15 + SignalServiceKit/src/TSConstants.h | 19 +- 6 files changed, 392 insertions(+), 6 deletions(-) create mode 100644 SignalServiceKit/src/Contacts/ContactDiscoveryService.h create mode 100644 SignalServiceKit/src/Contacts/ContactDiscoveryService.m diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 1b4f31e50..972f2d0ce 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -1108,6 +1108,8 @@ static NSTimeInterval launchStartedAt; // Resume lazy restore. [OWSBackupLazyRestoreJob runAsync]; #endif + + [[ContactDiscoveryService sharedService] testService]; } - (void)registrationStateDidChange diff --git a/SignalServiceKit/src/Contacts/ContactDiscoveryService.h b/SignalServiceKit/src/Contacts/ContactDiscoveryService.h new file mode 100644 index 000000000..825684f86 --- /dev/null +++ b/SignalServiceKit/src/Contacts/ContactDiscoveryService.h @@ -0,0 +1,46 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +//#import "SignalRecipient.h" + +NS_ASSUME_NONNULL_BEGIN + +//@class Contact; + +@interface ContactDiscoveryService : NSObject + +- (instancetype)init NS_UNAVAILABLE; + ++ (instancetype)sharedService; + +- (void)testService; + +//- (nullable SignalRecipient *)synchronousLookup:(NSString *)identifier error:(NSError **)error; +// +//// This asynchronously tries to verify whether or not a contact id +//// corresponds to a service account. +//// +//// The failure callback is invoked if the lookup fails _or_ if the +//// contact id doesn't correspond to an account. +//- (void)lookupIdentifier:(NSString *)identifier +// success:(void (^)(SignalRecipient *recipient))success +// failure:(void (^)(NSError *error))failure; +// +//// This asynchronously tries to verify whether or not a group of possible +//// contact ids correspond to service accounts. +//// +//// The failure callback is only invoked if the lookup fails. Otherwise, +//// the success callback is invoked with the (possibly empty) set of contacts +//// that were found. +//- (void)lookupIdentifiers:(NSArray *)identifiers +// success:(void (^)(NSArray *recipients))success +// failure:(void (^)(NSError *error))failure; +// +//- (void)updateSignalContactIntersectionWithABContacts:(NSArray *)abContacts +// success:(void (^)(void))success +// failure:(void (^)(NSError *error))failure; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/ContactDiscoveryService.m b/SignalServiceKit/src/Contacts/ContactDiscoveryService.m new file mode 100644 index 000000000..421fba67e --- /dev/null +++ b/SignalServiceKit/src/Contacts/ContactDiscoveryService.m @@ -0,0 +1,312 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "ContactDiscoveryService.h" +//#import "Contact.h" +//#import "Cryptography.h" +//#import "OWSError.h" +//#import "OWSPrimaryStorage.h" +#import "OWSRequestFactory.h" +//#import "PhoneNumber.h" +#import "TSNetworkManager.h" +//#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@implementation ContactDiscoveryService + ++ (instancetype)sharedService { + static dispatch_once_t onceToken; + static id sharedInstance = nil; + dispatch_once(&onceToken, ^{ + sharedInstance = [[ContactDiscoveryService alloc] initDefault]; + }); + return sharedInstance; +} + + +- (instancetype)initDefault +{ + self = [super init]; + if (!self) { + return self; + } + + OWSSingletonAssert(); + + return self; +} + +- (void)testService +{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self performRemoteAttestation]; + }); +} + +- (void)performRemoteAttestation +{ + ECKeyPair *keyPair = [Curve25519 generateKeyPair]; + + // TODO: + NSString *enclaveId = @"1"; + + TSRequest *request = [OWSRequestFactory remoteAttestationRequest:keyPair + enclaveId:enclaveId]; + [[TSNetworkManager sharedManager] makeRequest:request + success:^(NSURLSessionDataTask *task, id responseDict) { + DDLogVerbose(@"%@ remote attestation success: %@", self.logTag, responseDict); + +// NSMutableDictionary *attributesForIdentifier = [NSMutableDictionary dictionary]; +// NSArray *contactsArray = [(NSDictionary *)responseDict objectForKey:@"contacts"]; +// +// // Map attributes to phone numbers +// if (contactsArray) { +// for (NSDictionary *dict in contactsArray) { +// NSString *hash = [dict objectForKey:@"token"]; +// NSString *identifier = [phoneNumbersByHashes objectForKey:hash]; +// +// if (!identifier) { +// DDLogWarn(@"%@ An interesecting hash wasn't found in the mapping.", self.logTag); +// break; +// } +// +// [attributesForIdentifier setObject:dict forKey:identifier]; +// } +// } +// +// // Insert or update contact attributes +// [OWSPrimaryStorage.dbReadWriteConnection +// readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { +// for (NSString *identifier in attributesForIdentifier) { +// SignalRecipient *recipient = [SignalRecipient recipientWithTextSecureIdentifier:identifier +// withTransaction:transaction]; +// if (!recipient) { +// recipient = [[SignalRecipient alloc] initWithTextSecureIdentifier:identifier relay:nil]; +// } +// +// NSDictionary *attributes = [attributesForIdentifier objectForKey:identifier]; +// +// recipient.relay = attributes[@"relay"]; +// +// [recipient saveWithTransaction:transaction]; +// } +// }]; +// +// success([NSSet setWithArray:attributesForIdentifier.allKeys]); + } + failure:^(NSURLSessionDataTask *task, NSError *error) { +// if (!IsNSErrorNetworkFailure(error)) { +// OWSProdError([OWSAnalyticsEvents contactsErrorContactsIntersectionFailed]); +// } + + NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; + DDLogVerbose(@"%@ remote attestation failure: %zd", self.logTag, response.statusCode); +// if (response.statusCode == 413) { +// failure(OWSErrorWithCodeDescription( +// OWSErrorCodeContactDiscoveryServiceRateLimit, @"Contacts Intersection Rate Limit")); +// } else { +// failure(error); +// } + }]; + + +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ +// [self performRemoteAttestation]; +// }); +} + +//- (nullable SignalRecipient *)synchronousLookup:(NSString *)identifier error:(NSError **)error +//{ +// OWSAssert(error); +// +// DDLogInfo(@"%@ %s %@", self.logTag, __PRETTY_FUNCTION__, identifier); +// +// dispatch_semaphore_t sema = dispatch_semaphore_create(0); +// +// __block SignalRecipient *recipient; +// +// // Assigning to a pointer parameter within the block is not preventing the referenced error from being dealloc +// // Instead, we avoid ambiguity in ownership by assigning to a local __block variable ensuring the error will be +// // retained until our error parameter can take ownership. +// __block NSError *retainedError; +// [self lookupIdentifier:identifier +// success:^(SignalRecipient *fetchedRecipient) { +// recipient = fetchedRecipient; +// dispatch_semaphore_signal(sema); +// } +// failure:^(NSError *lookupError) { +// DDLogError( +// @"%@ Could not find recipient for recipientId: %@, error: %@.", self.logTag, identifier, lookupError); +// +// retainedError = lookupError; +// dispatch_semaphore_signal(sema); +// }]; +// +// dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); +// *error = retainedError; +// return recipient; +//} +// +//- (void)lookupIdentifier:(NSString *)identifier +// success:(void (^)(SignalRecipient *recipient))success +// failure:(void (^)(NSError *error))failure +//{ +// // This should never happen according to nullability annotations... but IIRC it does. =/ +// if (!identifier) { +// OWSFail(@"%@ Cannot lookup nil identifier", self.logTag); +// failure(OWSErrorWithCodeDescription(OWSErrorCodeInvalidMethodParameters, @"Cannot lookup nil identifier")); +// return; +// } +// +// [self contactIntersectionWithSet:[NSSet setWithObject:identifier] +// success:^(NSSet *_Nonnull matchedIds) { +// if (matchedIds.count == 1) { +// success([SignalRecipient recipientWithTextSecureIdentifier:identifier]); +// } else { +// failure(OWSErrorMakeNoSuchSignalRecipientError()); +// } +// } +// failure:failure]; +//} +// +//- (void)lookupIdentifiers:(NSArray *)identifiers +// success:(void (^)(NSArray *recipients))success +// failure:(void (^)(NSError *error))failure +//{ +// if (identifiers.count < 1) { +// OWSFail(@"%@ Cannot lookup zero identifiers", self.logTag); +// failure(OWSErrorWithCodeDescription(OWSErrorCodeInvalidMethodParameters, @"Cannot lookup zero identifiers")); +// return; +// } +// +// [self contactIntersectionWithSet:[NSSet setWithArray:identifiers] +// success:^(NSSet *_Nonnull matchedIds) { +// if (matchedIds.count > 0) { +// NSMutableArray *recipients = [NSMutableArray new]; +// for (NSString *identifier in matchedIds) { +// [recipients addObject:[SignalRecipient recipientWithTextSecureIdentifier:identifier]]; +// } +// success([recipients copy]); +// } else { +// failure(OWSErrorMakeNoSuchSignalRecipientError()); +// } +// } +// failure:failure]; +//} +// +//- (void)updateSignalContactIntersectionWithABContacts:(NSArray *)abContacts +// success:(void (^)(void))success +// failure:(void (^)(NSError *error))failure +//{ +// NSMutableSet *abPhoneNumbers = [NSMutableSet set]; +// +// for (Contact *contact in abContacts) { +// for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) { +// [abPhoneNumbers addObject:phoneNumber.toE164]; +// } +// } +// +// NSMutableSet *recipientIds = [NSMutableSet set]; +// [OWSPrimaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { +// NSArray *allRecipientKeys = [transaction allKeysInCollection:[SignalRecipient collection]]; +// [recipientIds addObjectsFromArray:allRecipientKeys]; +// }]; +// +// NSMutableSet *allContacts = [[abPhoneNumbers setByAddingObjectsFromSet:recipientIds] mutableCopy]; +// +// [self contactIntersectionWithSet:allContacts +// success:^(NSSet *matchedIds) { +// [recipientIds minusSet:matchedIds]; +// +// // Cleaning up unregistered identifiers +// [OWSPrimaryStorage.dbReadWriteConnection +// readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { +// for (NSString *identifier in recipientIds) { +// SignalRecipient *recipient = +// [SignalRecipient fetchObjectWithUniqueID:identifier +// transaction:transaction]; +// +// [recipient removeWithTransaction:transaction]; +// } +// }]; +// +// DDLogInfo(@"%@ successfully intersected contacts.", self.logTag); +// success(); +// } +// failure:failure]; +//} +// +//- (void)contactIntersectionWithSet:(NSSet *)idSet +// success:(void (^)(NSSet *matchedIds))success +// failure:(void (^)(NSError *error))failure { +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ +// NSMutableDictionary *phoneNumbersByHashes = [NSMutableDictionary dictionary]; +// for (NSString *identifier in idSet) { +// [phoneNumbersByHashes setObject:identifier +// forKey:[Cryptography truncatedSHA1Base64EncodedWithoutPadding:identifier]]; +// } +// NSArray *hashes = [phoneNumbersByHashes allKeys]; +// +// TSRequest *request = [OWSRequestFactory contactsIntersectionRequestWithHashesArray:hashes]; +// [[TSNetworkManager sharedManager] makeRequest:request +// success:^(NSURLSessionDataTask *tsTask, id responseDict) { +// NSMutableDictionary *attributesForIdentifier = [NSMutableDictionary dictionary]; +// NSArray *contactsArray = [(NSDictionary *)responseDict objectForKey:@"contacts"]; +// +// // Map attributes to phone numbers +// if (contactsArray) { +// for (NSDictionary *dict in contactsArray) { +// NSString *hash = [dict objectForKey:@"token"]; +// NSString *identifier = [phoneNumbersByHashes objectForKey:hash]; +// +// if (!identifier) { +// DDLogWarn(@"%@ An interesecting hash wasn't found in the mapping.", self.logTag); +// break; +// } +// +// [attributesForIdentifier setObject:dict forKey:identifier]; +// } +// } +// +// // Insert or update contact attributes +// [OWSPrimaryStorage.dbReadWriteConnection +// readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { +// for (NSString *identifier in attributesForIdentifier) { +// SignalRecipient *recipient = [SignalRecipient recipientWithTextSecureIdentifier:identifier +// withTransaction:transaction]; +// if (!recipient) { +// recipient = [[SignalRecipient alloc] initWithTextSecureIdentifier:identifier relay:nil]; +// } +// +// NSDictionary *attributes = [attributesForIdentifier objectForKey:identifier]; +// +// recipient.relay = attributes[@"relay"]; +// +// [recipient saveWithTransaction:transaction]; +// } +// }]; +// +// success([NSSet setWithArray:attributesForIdentifier.allKeys]); +// } +// failure:^(NSURLSessionDataTask *task, NSError *error) { +// if (!IsNSErrorNetworkFailure(error)) { +// OWSProdError([OWSAnalyticsEvents contactsErrorContactsIntersectionFailed]); +// } +// +// NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; +// if (response.statusCode == 413) { +// failure(OWSErrorWithCodeDescription( +// OWSErrorCodeContactDiscoveryServiceRateLimit, @"Contacts Intersection Rate Limit")); +// } else { +// failure(error); +// } +// }]; +// }); +//} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h index d6597ed89..037321d18 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h @@ -8,6 +8,7 @@ NS_ASSUME_NONNULL_BEGIN @class PreKeyRecord; @class SignedPreKeyRecord; @class TSRequest; +@class ECKeyPair; typedef NS_ENUM(NSUInteger, TSVerificationTransport) { TSVerificationTransportVoice = 1, TSVerificationTransportSMS }; @@ -69,6 +70,9 @@ typedef NS_ENUM(NSUInteger, TSVerificationTransport) { TSVerificationTransportVo signedPreKey:(SignedPreKeyRecord *)signedPreKey preKeyLastResort:(PreKeyRecord *)preKeyLastResort; ++ (TSRequest *)remoteAttestationRequest:(ECKeyPair *)keyPair + enclaveId:(NSString *)enclaveId; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m index e00460a0a..cb9bb6696 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m @@ -11,6 +11,7 @@ #import "TSRequest.h" #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -274,6 +275,20 @@ NS_ASSUME_NONNULL_BEGIN }; } ++ (TSRequest *)remoteAttestationRequest:(ECKeyPair *)keyPair + enclaveId:(NSString *)enclaveId +{ + OWSAssert(keyPair); + OWSAssert(enclaveId.length > 0); + + NSString *path = [NSString stringWithFormat:@"/v1/attestation/%@", enclaveId]; + return [TSRequest requestWithUrl:[NSURL URLWithString:path] + method:@"PUT" + parameters:@{ + @"clientPublic" : [[keyPair.publicKey prependKeyType] base64EncodedStringWithOptions:0], + }]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TSConstants.h b/SignalServiceKit/src/TSConstants.h index 375f09609..acf570275 100644 --- a/SignalServiceKit/src/TSConstants.h +++ b/SignalServiceKit/src/TSConstants.h @@ -29,12 +29,12 @@ typedef NS_ENUM(NSInteger, TSWhisperMessageType) { //#ifndef DEBUG // Production -#define textSecureWebSocketAPI @"wss://textsecure-service.whispersystems.org/v1/websocket/" -#define textSecureServerURL @"https://textsecure-service.whispersystems.org/" -#define textSecureCDNServerURL @"https://cdn.signal.org" -// Use same reflector for service and CDN -#define textSecureServiceReflectorHost @"textsecure-service-reflected.whispersystems.org" -#define textSecureCDNReflectorHost @"textsecure-service-reflected.whispersystems.org" +//#define textSecureWebSocketAPI @"wss://textsecure-service.whispersystems.org/v1/websocket/" +//#define textSecureServerURL @"https://textsecure-service.whispersystems.org/" +//#define textSecureCDNServerURL @"https://cdn.signal.org" +//// Use same reflector for service and CDN +//#define textSecureServiceReflectorHost @"textsecure-service-reflected.whispersystems.org" +//#define textSecureCDNReflectorHost @"textsecure-service-reflected.whispersystems.org" //#else // @@ -47,6 +47,13 @@ typedef NS_ENUM(NSInteger, TSWhisperMessageType) { // //#endif +// Testing +#define textSecureWebSocketAPI @"wss://api.contact-discovery.acton-signal.org/v1/websocket/" +#define textSecureServerURL @"https://api.contact-discovery.acton-signal.org/" +#define textSecureCDNServerURL @"https://cdn-staging.signal.org" +#define textSecureServiceReflectorHost @"meek-signal-service-staging.appspot.com"; +#define textSecureCDNReflectorHost @"meek-signal-cdn-staging.appspot.com"; + #define textSecureAccountsAPI @"v1/accounts" #define textSecureAttributesAPI @"/attributes/"