Merge branch 'charlesmchen/productionizeCDS2'

This commit is contained in:
Matthew Chen 2018-09-27 13:57:45 -04:00
commit 0298c35841
7 changed files with 87 additions and 41 deletions

View file

@ -48,6 +48,7 @@ static const long SGX_XFRM_RESERVED = 0xFFFFFFFFFFFFFFF8L;
{
ByteParser *_Nullable parser = [[ByteParser alloc] initWithData:quoteData littleEndian:YES];
// NOTE: This version is separate from and does _NOT_ match the signature body entity version.
uint16_t version = parser.nextShort;
if (version < 1 || version > 2) {
OWSFailDebug(@"unexpected quote version: %d", (int)version);

View file

@ -7,6 +7,15 @@ NS_ASSUME_NONNULL_BEGIN
@class ECKeyPair;
@class OWSAES256Key;
@interface RemoteAttestationAuth : NSObject
@property (nonatomic, readonly) NSString *username;
@property (nonatomic, readonly) NSString *password;
@end
#pragma mark -
@interface RemoteAttestationKeys : NSObject
@property (nonatomic, readonly) ECKeyPair *keyPair;
@ -18,22 +27,25 @@ NS_ASSUME_NONNULL_BEGIN
@end
#pragma mark -
@interface RemoteAttestation : NSObject
@property (nonatomic, readonly) RemoteAttestationKeys *keys;
@property (nonatomic, readonly) NSArray<NSHTTPCookie *> *cookies;
@property (nonatomic, readonly) NSData *requestId;
@property (nonatomic, readonly) NSString *enclaveId;
@property (nonatomic, readonly) NSString *authUsername;
@property (nonatomic, readonly) NSString *authToken;
@property (nonatomic, readonly) RemoteAttestationAuth *auth;
@end
#pragma mark -
@interface ContactDiscoveryService : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)sharedService;
+ (instancetype)shared;
- (void)testService;
- (void)performRemoteAttestationWithSuccess:(void (^)(RemoteAttestation *_Nonnull remoteAttestation))successHandler

View file

@ -16,7 +16,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface RemoteAttestationAuth : NSObject
@interface RemoteAttestationAuth ()
@property (nonatomic) NSString *username;
@property (nonatomic) NSString *password;
@ -152,16 +152,6 @@ NS_ASSUME_NONNULL_BEGIN
@implementation RemoteAttestation
- (NSString *)authUsername
{
return self.auth.username;
}
- (NSString *)password
{
return self.auth.password;
}
@end
#pragma mark -
@ -171,6 +161,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) NSData *isvEnclaveQuoteBody;
@property (nonatomic) NSString *isvEnclaveQuoteStatus;
@property (nonatomic) NSString *timestamp;
@property (nonatomic) NSNumber *version;
@end
@ -200,6 +191,16 @@ NS_ASSUME_NONNULL_BEGIN
return valueString;
}
- (nullable NSNumber *)numberForKey:(NSString *)key
{
NSNumber *_Nullable value = self[key];
if (![value isKindOfClass:[NSNumber class]]) {
OWSFailDebug(@"couldn't parse number for key: %@", key);
return nil;
}
return value;
}
- (nullable NSData *)base64DataForKey:(NSString *)key
{
NSString *_Nullable valueString = self[key];
@ -237,7 +238,8 @@ NS_ASSUME_NONNULL_BEGIN
@implementation ContactDiscoveryService
+ (instancetype)sharedService {
+ (instancetype)shared
{
static dispatch_once_t onceToken;
static id sharedInstance = nil;
dispatch_once(&onceToken, ^{
@ -338,7 +340,6 @@ NS_ASSUME_NONNULL_BEGIN
{
ECKeyPair *keyPair = [Curve25519 generateKeyPair];
// TODO:
NSString *enclaveId = @"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9";
TSRequest *request = [OWSRequestFactory remoteAttestationRequest:keyPair
@ -529,6 +530,12 @@ NS_ASSUME_NONNULL_BEGIN
OWSFailDebug(@"isvEnclaveQuoteBody has unexpected length.");
return NO;
}
// NOTE: This version is separate from and does _NOT_ match the CDS quote version.
const NSUInteger kSignatureBodyVersion = 3;
if (![signatureBodyEntity.version isEqual:@(kSignatureBodyVersion)]) {
OWSFailDebug(@"signatureBodyEntity has unexpected version.");
return NO;
}
if (quoteData.length < kQuoteBodyComparisonLength) {
OWSFailDebug(@"quoteData has unexpected length.");
return NO;
@ -541,9 +548,7 @@ NS_ASSUME_NONNULL_BEGIN
return NO;
}
// TODO: Before going to production, remove GROUP_OUT_OF_DATE.
if (![@"OK" isEqualToString:signatureBodyEntity.isvEnclaveQuoteStatus]
&& ![@"GROUP_OUT_OF_DATE" isEqualToString:signatureBodyEntity.isvEnclaveQuoteStatus]) {
if (![@"OK" isEqualToString:signatureBodyEntity.isvEnclaveQuoteStatus]) {
OWSFailDebug(@"invalid isvEnclaveQuoteStatus: %@.", signatureBodyEntity.isvEnclaveQuoteStatus);
return NO;
}
@ -603,11 +608,17 @@ NS_ASSUME_NONNULL_BEGIN
OWSFailDebug(@"could not parse signature isvEnclaveQuoteStatus.");
return nil;
}
NSNumber *_Nullable version = [jsonDict numberForKey:@"version"];
if (!version) {
OWSFailDebug(@"could not parse signature version.");
return nil;
}
SignatureBodyEntity *result = [SignatureBodyEntity new];
result.isvEnclaveQuoteBody = isvEnclaveQuoteBody;
result.isvEnclaveQuoteStatus = isvEnclaveQuoteStatus;
result.timestamp = timestamp;
result.version = version;
return result;
}
@ -643,8 +654,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSFailDebug(@"enclave ids do not match.");
return NO;
}
// TODO: Reverse this condition in production.
if (!quote.isDebugQuote) {
if (quote.isDebugQuote) {
OWSFailDebug(@"quote has invalid isDebugQuote value.");
return NO;
}

View file

@ -7,8 +7,6 @@ import Foundation
@objc(OWSLegacyContactDiscoveryOperation)
class LegacyContactDiscoveryBatchOperation: OWSOperation {
private let isCDSEnabled = false
@objc
var registeredRecipientIds: Set<String>
@ -85,9 +83,6 @@ class LegacyContactDiscoveryBatchOperation: OWSOperation {
// Called at most one time.
override func didSucceed() {
guard isCDSEnabled else {
return
}
// Compare against new CDS service
let modernCDSOperation = CDSOperation(recipientIdsToLookup: self.recipientIdsToLookup)
let cdsFeedbackOperation = CDSFeedbackOperation(legacyRegisteredRecipientIds: self.registeredRecipientIds)
@ -270,8 +265,8 @@ class CDSBatchOperation: OWSOperation {
cryptIv: encryptionResult.initializationVector,
cryptMac: encryptionResult.authTag,
enclaveId: remoteAttestation.enclaveId,
authUsername: remoteAttestation.authUsername,
authPassword: remoteAttestation.authToken,
authUsername: remoteAttestation.auth.username,
authPassword: remoteAttestation.auth.password,
cookies: remoteAttestation.cookies)
self.networkManager.makeRequest(request,

View file

@ -282,8 +282,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertDebug(authUsername.length > 0);
OWSAssertDebug(authPassword.length > 0);
NSString *path =
[NSString stringWithFormat:@"https://api.contact-discovery.acton-signal.org/v1/attestation/%@", enclaveId];
NSString *path = [NSString stringWithFormat:@"%@/v1/attestation/%@", contactDiscoveryURL, enclaveId];
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path]
method:@"PUT"
parameters:@{
@ -293,6 +292,10 @@ NS_ASSUME_NONNULL_BEGIN
request.authUsername = authUsername;
request.authPassword = authPassword;
// Don't bother with the default cookie store;
// these cookies are ephemeral.
[request setHTTPShouldHandleCookies:NO];
return request;
}
@ -306,8 +309,7 @@ NS_ASSUME_NONNULL_BEGIN
authPassword:(NSString *)authPassword
cookies:(NSArray<NSHTTPCookie *> *)cookies
{
NSString *path =
[NSString stringWithFormat:@"https://api.contact-discovery.acton-signal.org/v1/discovery/%@", enclaveId];
NSString *path = [NSString stringWithFormat:@"%@/v1/discovery/%@", contactDiscoveryURL, enclaveId];
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path]
method:@"PUT"
@ -322,11 +324,12 @@ NS_ASSUME_NONNULL_BEGIN
request.authUsername = authUsername;
request.authPassword = authPassword;
NSDictionary<NSString *, NSString *> *cookieHeaders = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
for (NSString *cookieHeader in cookieHeaders) {
NSString *cookieValue = cookieHeaders[cookieHeader];
[request setValue:cookieValue forHTTPHeaderField:cookieHeader];
}
// Don't bother with the default cookie store;
// these cookies are ephemeral.
[request setHTTPShouldHandleCookies:NO];
// Set the cookie header.
OWSAssertDebug(request.allHTTPHeaderFields.count == 0);
[request setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:cookies]];
return request;
}

View file

@ -117,6 +117,17 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
password:request.authPassword];
}
// Honor the request's preferences about default cookie handling.
//
// Default is YES.
sessionManager.requestSerializer.HTTPShouldHandleCookies = request.HTTPShouldHandleCookies;
// Honor the request's headers.
for (NSString *headerField in request.allHTTPHeaderFields) {
NSString *headerValue = request.allHTTPHeaderFields[headerField];
[sessionManager.requestSerializer setValue:headerValue forHTTPHeaderField:headerField];
}
if ([request.HTTPMethod isEqualToString:@"GET"]) {
[sessionManager GET:request.URL.absoluteString
parameters:request.parameters
@ -178,6 +189,7 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
[curlComponents addObject:@"--data-ascii"];
[curlComponents addObject:[NSString stringWithFormat:@"'%@'", jsonBody]];
}
// TODO: Add support for cookies.
[curlComponents addObject:task.originalRequest.URL.absoluteString];
NSString *curlCommand = [curlComponents componentsJoinedByString:@" "];
OWSLogVerbose(@"curl for failed request: %@", curlCommand);
@ -224,10 +236,6 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
error.isRetryable = NO;
// TODO distinguish CDS requests. we don't want a bad CDS request to trigger "Signal deauth" logic.
// also, shouldn't this be under 403, not 400?
[TSAccountManager.sharedInstance setIsDeregistered:YES];
failureBlock(task, error);
break;
}
@ -236,6 +244,7 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
networkError.debugDescription,
request);
error.isRetryable = NO;
[self deregisterAfterAuthErrorIfNecessary:task statusCode:statusCode];
failureBlock(task, error);
break;
}
@ -243,6 +252,7 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
OWSLogError(
@"The server returned an authentication failure: %@, %@", networkError.debugDescription, request);
error.isRetryable = NO;
[self deregisterAfterAuthErrorIfNecessary:task statusCode:statusCode];
failureBlock(task, error);
break;
}
@ -300,6 +310,19 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
};
}
+ (void)deregisterAfterAuthErrorIfNecessary:(NSURLSessionDataTask *)task statusCode:(NSInteger)statusCode
{
OWSLogVerbose(@"Invalid auth: %@", task.originalRequest.allHTTPHeaderFields);
// Distinguish CDS requests.
// We don't want a bad CDS request to trigger "Signal deauth" logic.
if ([task.originalRequest.URL.absoluteString hasPrefix:textSecureServerURL]) {
[TSAccountManager.sharedInstance setIsDeregistered:YES];
} else {
OWSLogWarn(@"Ignoring %d for URL: %@", (int)statusCode, task.originalRequest.URL.absoluteString);
}
}
+ (NSError *)errorWithHTTPCode:(NSInteger)code
description:(NSString *)description
failureReason:(NSString *)failureReason

View file

@ -33,6 +33,7 @@ typedef NS_ENUM(NSInteger, TSWhisperMessageType) {
// Use same reflector for service and CDN
#define textSecureServiceReflectorHost @"textsecure-service-reflected.whispersystems.org"
#define textSecureCDNReflectorHost @"textsecure-service-reflected.whispersystems.org"
#define contactDiscoveryURL @"https://api.directory.signal.org"
//#else
//
@ -42,6 +43,7 @@ typedef NS_ENUM(NSInteger, TSWhisperMessageType) {
//#define textSecureCDNServerURL @"https://cdn-staging.signal.org"
//#define textSecureServiceReflectorHost @"meek-signal-service-staging.appspot.com";
//#define textSecureCDNReflectorHost @"meek-signal-cdn-staging.appspot.com";
//#define contactDiscoveryURL @"https://api-staging.directory.signal.org"
//
//#endif