session-ios/SignalServiceKit/src/Contacts/ContactDiscoveryService.m

694 lines
24 KiB
Mathematica
Raw Normal View History

2018-07-17 18:24:03 +02:00
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "ContactDiscoveryService.h"
2018-07-18 19:42:33 +02:00
#import "CDSQuote.h"
2018-07-19 17:10:22 +02:00
#import "CDSSigningCertificate.h"
#import "OWSError.h"
2018-07-17 18:24:03 +02:00
#import "OWSRequestFactory.h"
#import "TSNetworkManager.h"
#import <Curve25519Kit/Curve25519.h>
2018-07-18 19:42:33 +02:00
#import <HKDFKit/HKDFKit.h>
2018-09-25 19:09:55 +02:00
#import <SignalCoreKit/Cryptography.h>
2018-09-21 21:41:10 +02:00
#import <SignalCoreKit/NSData+OWS.h>
#import <SignalCoreKit/NSDate+OWS.h>
2018-07-17 18:24:03 +02:00
NS_ASSUME_NONNULL_BEGIN
2018-09-26 15:30:51 +02:00
@interface RemoteAttestationAuth ()
2018-07-20 16:32:15 +02:00
@property (nonatomic) NSString *username;
2018-07-23 21:33:16 +02:00
@property (nonatomic) NSString *password;
2018-07-20 16:32:15 +02:00
@end
#pragma mark -
@implementation RemoteAttestationAuth
@end
#pragma mark -
@interface RemoteAttestationKeys ()
2018-07-18 16:20:02 +02:00
@property (nonatomic) ECKeyPair *keyPair;
@property (nonatomic) NSData *serverEphemeralPublic;
@property (nonatomic) NSData *serverStaticPublic;
@property (nonatomic) OWSAES256Key *clientKey;
@property (nonatomic) OWSAES256Key *serverKey;
2018-07-18 19:42:33 +02:00
2018-07-18 16:20:02 +02:00
@end
#pragma mark -
@implementation RemoteAttestationKeys
2018-07-18 19:42:33 +02:00
+ (nullable RemoteAttestationKeys *)keysForKeyPair:(ECKeyPair *)keyPair
serverEphemeralPublic:(NSData *)serverEphemeralPublic
serverStaticPublic:(NSData *)serverStaticPublic
{
2018-08-30 16:31:01 +02:00
if (!keyPair) {
OWSFailDebug(@"Missing keyPair");
return nil;
}
2018-09-11 00:56:22 +02:00
if (serverEphemeralPublic.length < 1) {
2018-08-30 16:31:01 +02:00
OWSFailDebug(@"Invalid serverEphemeralPublic");
return nil;
}
if (serverStaticPublic.length < 1) {
OWSFailDebug(@"Invalid serverStaticPublic");
return nil;
}
2018-07-18 19:42:33 +02:00
RemoteAttestationKeys *keys = [RemoteAttestationKeys new];
keys.keyPair = keyPair;
keys.serverEphemeralPublic = serverEphemeralPublic;
keys.serverStaticPublic = serverStaticPublic;
if (![keys deriveKeys]) {
return nil;
}
return keys;
}
// Returns YES on success.
- (BOOL)deriveKeys
{
2018-08-30 16:31:01 +02:00
NSData *ephemeralToEphemeral;
NSData *ephemeralToStatic;
@try {
ephemeralToEphemeral =
[Curve25519 generateSharedSecretFromPublicKey:self.serverEphemeralPublic andKeyPair:self.keyPair];
ephemeralToStatic =
[Curve25519 generateSharedSecretFromPublicKey:self.serverStaticPublic andKeyPair:self.keyPair];
} @catch (NSException *exception) {
OWSFailDebug(@"could not generate shared secrets: %@", exception);
return NO;
}
2018-07-18 19:42:33 +02:00
NSData *masterSecret = [ephemeralToEphemeral dataByAppendingData:ephemeralToStatic];
2018-08-30 16:31:01 +02:00
NSData *publicKeys = [NSData join:@[
self.keyPair.publicKey,
self.serverEphemeralPublic,
self.serverStaticPublic,
]];
2018-07-18 19:42:33 +02:00
NSData *_Nullable derivedMaterial;
@try {
derivedMaterial =
[HKDFKit deriveKey:masterSecret info:nil salt:publicKeys outputSize:(int)kAES256_KeyByteLength * 2];
2018-07-18 19:42:33 +02:00
} @catch (NSException *exception) {
2018-08-30 16:31:01 +02:00
OWSFailDebug(@"could not derive service key: %@", exception);
2018-07-18 19:42:33 +02:00
return NO;
}
if (!derivedMaterial) {
OWSFailDebug(@"missing derived service key.");
2018-07-18 19:42:33 +02:00
return NO;
}
if (derivedMaterial.length != kAES256_KeyByteLength * 2) {
OWSFailDebug(@"derived service key has unexpected length.");
2018-07-18 19:42:33 +02:00
return NO;
}
NSData *_Nullable clientKeyData =
[derivedMaterial subdataWithRange:NSMakeRange(kAES256_KeyByteLength * 0, kAES256_KeyByteLength)];
OWSAES256Key *_Nullable clientKey = [OWSAES256Key keyWithData:clientKeyData];
if (!clientKey) {
OWSFailDebug(@"clientKey has unexpected length.");
2018-07-18 19:42:33 +02:00
return NO;
}
NSData *_Nullable serverKeyData =
[derivedMaterial subdataWithRange:NSMakeRange(kAES256_KeyByteLength * 1, kAES256_KeyByteLength)];
OWSAES256Key *_Nullable serverKey = [OWSAES256Key keyWithData:serverKeyData];
if (!serverKey) {
OWSFailDebug(@"serverKey has unexpected length.");
2018-07-18 19:42:33 +02:00
return NO;
}
self.clientKey = clientKey;
self.serverKey = serverKey;
return YES;
}
2018-07-19 17:10:22 +02:00
@end
#pragma mark -
@interface RemoteAttestation ()
2018-07-19 17:10:22 +02:00
@property (nonatomic) RemoteAttestationKeys *keys;
@property (nonatomic) NSArray<NSHTTPCookie *> *cookies;
2018-07-19 17:10:22 +02:00
@property (nonatomic) NSData *requestId;
@property (nonatomic) NSString *enclaveId;
@property (nonatomic) RemoteAttestationAuth *auth;
2018-07-18 19:42:33 +02:00
2018-07-18 16:20:02 +02:00
@end
#pragma mark -
2018-07-19 17:10:22 +02:00
@implementation RemoteAttestation
2018-07-18 16:20:02 +02:00
@end
#pragma mark -
2018-07-19 19:31:10 +02:00
@interface SignatureBodyEntity : NSObject
@property (nonatomic) NSData *isvEnclaveQuoteBody;
@property (nonatomic) NSString *isvEnclaveQuoteStatus;
@property (nonatomic) NSString *timestamp;
2018-09-20 21:41:39 +02:00
@property (nonatomic) NSNumber *version;
2018-07-19 19:31:10 +02:00
@end
#pragma mark -
@implementation SignatureBodyEntity
@end
#pragma mark -
2018-07-19 17:10:22 +02:00
@interface NSDictionary (CDS)
@end
#pragma mark -
@implementation NSDictionary (CDS)
2018-07-18 16:20:02 +02:00
2018-07-19 19:31:10 +02:00
- (nullable NSString *)stringForKey:(NSString *)key
{
NSString *_Nullable valueString = self[key];
if (![valueString isKindOfClass:[NSString class]]) {
OWSFailDebug(@"couldn't parse string for key: %@", key);
2018-07-19 19:31:10 +02:00
return nil;
}
return valueString;
}
2018-09-20 21:41:39 +02:00
- (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;
}
2018-07-18 16:20:02 +02:00
- (nullable NSData *)base64DataForKey:(NSString *)key
{
NSString *_Nullable valueString = self[key];
if (![valueString isKindOfClass:[NSString class]]) {
OWSFailDebug(@"couldn't parse base 64 value for key: %@", key);
2018-07-18 16:20:02 +02:00
return nil;
}
NSData *_Nullable valueData = [[NSData alloc] initWithBase64EncodedString:valueString options:0];
if (!valueData) {
OWSFailDebug(@"couldn't decode base 64 value for key: %@", key);
2018-07-18 16:20:02 +02:00
return nil;
}
return valueData;
}
- (nullable NSData *)base64DataForKey:(NSString *)key expectedLength:(NSUInteger)expectedLength
{
NSData *_Nullable valueData = [self base64DataForKey:key];
if (valueData && valueData.length != expectedLength) {
2018-08-30 16:31:01 +02:00
OWSLogDebug(@"decoded base 64 value for key: %@, has unexpected length: %lu != %lu",
2018-07-18 16:20:02 +02:00
key,
2018-08-30 16:31:01 +02:00
(unsigned long)valueData.length,
(unsigned long)expectedLength);
OWSFailDebug(@"decoded base 64 value for key has unexpected length: %lu != %lu",
(unsigned long)valueData.length,
(unsigned long)expectedLength);
2018-07-18 16:20:02 +02:00
return nil;
}
return valueData;
}
@end
#pragma mark -
2018-07-17 18:24:03 +02:00
@implementation ContactDiscoveryService
2018-09-20 21:26:03 +02:00
+ (instancetype)shared
{
2018-07-17 18:24:03 +02:00
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
2018-08-30 16:31:01 +02:00
performRemoteAttestationWithSuccess:^(RemoteAttestation *remoteAttestation) {
OWSLogDebug(@"succeeded");
}
2018-08-30 16:31:01 +02:00
failure:^(NSError *error) {
OWSLogDebug(@"failed with error: %@", error);
}];
2018-07-17 18:24:03 +02:00
});
}
2018-08-30 16:31:01 +02:00
- (void)performRemoteAttestationWithSuccess:(void (^)(RemoteAttestation *remoteAttestation))successHandler
failure:(void (^)(NSError *error))failureHandler
2018-07-18 16:20:02 +02:00
{
[self
2018-08-30 16:31:01 +02:00
getRemoteAttestationAuthWithSuccess:^(RemoteAttestationAuth *auth) {
[self performRemoteAttestationWithAuth:auth success:successHandler failure:failureHandler];
}
failure:failureHandler];
2018-07-18 16:20:02 +02:00
}
- (void)getRemoteAttestationAuthWithSuccess:(void (^)(RemoteAttestationAuth *))successHandler
2018-08-30 16:31:01 +02:00
failure:(void (^)(NSError *error))failureHandler
2018-07-18 16:20:02 +02:00
{
TSRequest *request = [OWSRequestFactory remoteAttestationAuthRequest];
[[TSNetworkManager sharedManager] makeRequest:request
success:^(NSURLSessionDataTask *task, id responseDict) {
2018-07-19 17:10:22 +02:00
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2018-07-23 21:33:16 +02:00
RemoteAttestationAuth *_Nullable auth = [self parseAuthParams:responseDict];
2018-07-20 16:32:15 +02:00
if (!auth) {
OWSLogError(@"remote attestation auth could not be parsed: %@", responseDict);
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
failureHandler(error);
2018-07-19 17:10:22 +02:00
return;
}
successHandler(auth);
2018-07-19 17:10:22 +02:00
});
2018-07-18 16:20:02 +02:00
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
2018-08-30 16:31:01 +02:00
OWSLogVerbose(@"remote attestation auth failure: %lu", (unsigned long)response.statusCode);
failureHandler(error);
2018-07-18 16:20:02 +02:00
}];
}
2018-07-23 21:33:16 +02:00
- (nullable RemoteAttestationAuth *)parseAuthParams:(id)response
2018-07-18 16:20:02 +02:00
{
if (![response isKindOfClass:[NSDictionary class]]) {
return nil;
}
2018-07-19 17:10:22 +02:00
2018-07-18 16:20:02 +02:00
NSDictionary *responseDict = response;
2018-07-23 21:33:16 +02:00
NSString *_Nullable password = [responseDict stringForKey:@"password"];
if (password.length < 1) {
OWSFailDebug(@"missing or empty password.");
2018-07-18 16:20:02 +02:00
return nil;
}
2018-07-19 17:10:22 +02:00
2018-07-19 22:54:00 +02:00
NSString *_Nullable username = [responseDict stringForKey:@"username"];
2018-07-19 17:10:22 +02:00
if (username.length < 1) {
OWSFailDebug(@"missing or empty username.");
2018-07-18 16:20:02 +02:00
return nil;
}
2018-07-19 17:10:22 +02:00
2018-07-20 16:32:15 +02:00
RemoteAttestationAuth *result = [RemoteAttestationAuth new];
result.username = username;
2018-07-23 21:33:16 +02:00
result.password = password;
2018-07-20 16:32:15 +02:00
return result;
2018-07-18 16:20:02 +02:00
}
2018-07-20 16:32:15 +02:00
- (void)performRemoteAttestationWithAuth:(RemoteAttestationAuth *)auth
2018-08-30 16:31:01 +02:00
success:(void (^)(RemoteAttestation *remoteAttestation))successHandler
failure:(void (^)(NSError *error))failureHandler
2018-07-17 18:24:03 +02:00
{
ECKeyPair *keyPair = [Curve25519 generateKeyPair];
2018-07-18 16:20:02 +02:00
NSString *enclaveId = @"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9";
2018-07-17 18:24:03 +02:00
2018-07-20 16:32:15 +02:00
TSRequest *request = [OWSRequestFactory remoteAttestationRequest:keyPair
enclaveId:enclaveId
authUsername:auth.username
2018-07-23 21:33:16 +02:00
authPassword:auth.password];
2018-07-17 18:24:03 +02:00
[[TSNetworkManager sharedManager] makeRequest:request
2018-07-18 16:20:02 +02:00
success:^(NSURLSessionDataTask *task, id responseJson) {
2018-07-19 17:10:22 +02:00
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
RemoteAttestation *_Nullable attestation = [self parseAttestationResponseJson:responseJson
response:task.response
keyPair:keyPair
enclaveId:enclaveId
auth:auth];
if (!attestation) {
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
failureHandler(error);
return;
}
successHandler(attestation);
2018-07-19 17:10:22 +02:00
});
2018-07-18 16:20:02 +02:00
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
2018-08-30 16:31:01 +02:00
OWSLogVerbose(@"remote attestation failure: %lu", (unsigned long)response.statusCode);
failureHandler(error);
2018-07-18 16:20:02 +02:00
}];
2018-07-17 18:24:03 +02:00
}
2018-07-19 17:10:22 +02:00
- (nullable RemoteAttestation *)parseAttestationResponseJson:(id)responseJson
response:(NSURLResponse *)response
keyPair:(ECKeyPair *)keyPair
enclaveId:(NSString *)enclaveId
auth:(RemoteAttestationAuth *)auth
2018-07-18 16:20:02 +02:00
{
OWSAssertDebug(responseJson);
OWSAssertDebug(response);
OWSAssertDebug(keyPair);
OWSAssertDebug(enclaveId.length > 0);
2018-07-18 16:20:02 +02:00
if (![response isKindOfClass:[NSHTTPURLResponse class]]) {
OWSFailDebug(@"unexpected response type.");
2018-07-18 16:20:02 +02:00
return nil;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSArray<NSHTTPCookie *> *cookies =
[NSHTTPCookie cookiesWithResponseHeaderFields:httpResponse.allHeaderFields forURL:[NSURL new]];
if (cookies.count < 1) {
OWSFailDebug(@"couldn't parse cookie.");
2018-07-18 16:20:02 +02:00
return nil;
}
2018-07-19 17:10:22 +02:00
2018-07-18 16:20:02 +02:00
if (![responseJson isKindOfClass:[NSDictionary class]]) {
return nil;
}
NSDictionary *responseDict = responseJson;
NSData *_Nullable serverEphemeralPublic =
[responseDict base64DataForKey:@"serverEphemeralPublic" expectedLength:32];
if (!serverEphemeralPublic) {
OWSFailDebug(@"couldn't parse serverEphemeralPublic.");
2018-07-18 16:20:02 +02:00
return nil;
}
NSData *_Nullable serverStaticPublic = [responseDict base64DataForKey:@"serverStaticPublic" expectedLength:32];
if (!serverStaticPublic) {
OWSFailDebug(@"couldn't parse serverStaticPublic.");
2018-07-18 16:20:02 +02:00
return nil;
}
NSData *_Nullable encryptedRequestId = [responseDict base64DataForKey:@"ciphertext"];
if (!encryptedRequestId) {
OWSFailDebug(@"couldn't parse encryptedRequestId.");
2018-07-18 16:20:02 +02:00
return nil;
}
NSData *_Nullable encryptedRequestIv = [responseDict base64DataForKey:@"iv" expectedLength:12];
if (!encryptedRequestIv) {
OWSFailDebug(@"couldn't parse encryptedRequestIv.");
2018-07-18 16:20:02 +02:00
return nil;
}
NSData *_Nullable encryptedRequestTag = [responseDict base64DataForKey:@"tag" expectedLength:16];
if (!encryptedRequestTag) {
OWSFailDebug(@"couldn't parse encryptedRequestTag.");
2018-07-18 16:20:02 +02:00
return nil;
}
2018-07-18 19:42:33 +02:00
NSData *_Nullable quoteData = [responseDict base64DataForKey:@"quote"];
if (!quoteData) {
OWSFailDebug(@"couldn't parse quote data.");
2018-07-18 16:20:02 +02:00
return nil;
}
2018-07-19 22:54:00 +02:00
NSString *_Nullable signatureBody = [responseDict stringForKey:@"signatureBody"];
2018-07-19 17:10:22 +02:00
if (![signatureBody isKindOfClass:[NSString class]]) {
OWSFailDebug(@"couldn't parse signatureBody.");
2018-07-18 16:20:02 +02:00
return nil;
}
NSData *_Nullable signature = [responseDict base64DataForKey:@"signature"];
if (!signature) {
OWSFailDebug(@"couldn't parse signature.");
2018-07-18 16:20:02 +02:00
return nil;
}
2018-07-19 22:54:00 +02:00
NSString *_Nullable encodedCertificates = [responseDict stringForKey:@"certificates"];
2018-07-18 16:20:02 +02:00
if (![encodedCertificates isKindOfClass:[NSString class]]) {
OWSFailDebug(@"couldn't parse encodedCertificates.");
2018-07-18 16:20:02 +02:00
return nil;
}
NSString *_Nullable certificates = [encodedCertificates stringByRemovingPercentEncoding];
if (!certificates) {
OWSFailDebug(@"couldn't parse certificates.");
2018-07-18 16:20:02 +02:00
return nil;
}
2018-07-18 19:42:33 +02:00
RemoteAttestationKeys *_Nullable keys = [RemoteAttestationKeys keysForKeyPair:keyPair
serverEphemeralPublic:serverEphemeralPublic
serverStaticPublic:serverStaticPublic];
if (!keys) {
OWSFailDebug(@"couldn't derive keys.");
2018-07-18 19:42:33 +02:00
return nil;
}
CDSQuote *_Nullable quote = [CDSQuote parseQuoteFromData:quoteData];
if (!quote) {
OWSFailDebug(@"couldn't parse quote.");
2018-07-18 19:42:33 +02:00
return nil;
}
NSData *_Nullable requestId = [self decryptRequestId:encryptedRequestId
encryptedRequestIv:encryptedRequestIv
encryptedRequestTag:encryptedRequestTag
keys:keys];
if (!requestId) {
OWSFailDebug(@"couldn't decrypt request id.");
2018-07-18 19:42:33 +02:00
return nil;
}
2018-07-18 16:20:02 +02:00
2018-07-19 17:10:22 +02:00
if (![self verifyServerQuote:quote keys:keys enclaveId:enclaveId]) {
OWSFailDebug(@"couldn't verify quote.");
2018-07-19 17:10:22 +02:00
return nil;
}
2018-07-19 17:59:39 +02:00
if (![self verifyIasSignatureWithCertificates:certificates
signatureBody:signatureBody
signature:signature
2018-07-19 19:31:10 +02:00
quoteData:quoteData]) {
OWSFailDebug(@"couldn't verify ias signature.");
2018-07-19 17:10:22 +02:00
return nil;
}
2018-07-19 22:54:00 +02:00
RemoteAttestation *result = [RemoteAttestation new];
result.cookies = cookies;
2018-07-19 22:54:00 +02:00
result.keys = keys;
result.requestId = requestId;
result.enclaveId = enclaveId;
result.auth = auth;
2018-07-19 22:54:00 +02:00
OWSLogVerbose(@"remote attestation complete.");
2018-07-19 22:54:00 +02:00
2018-07-19 17:10:22 +02:00
return result;
}
2018-07-19 17:59:39 +02:00
- (BOOL)verifyIasSignatureWithCertificates:(NSString *)certificates
signatureBody:(NSString *)signatureBody
signature:(NSData *)signature
2018-07-19 19:31:10 +02:00
quoteData:(NSData *)quoteData
2018-07-19 17:10:22 +02:00
{
OWSAssertDebug(certificates.length > 0);
OWSAssertDebug(signatureBody.length > 0);
OWSAssertDebug(signature.length > 0);
OWSAssertDebug(quoteData);
2018-07-19 17:10:22 +02:00
CDSSigningCertificate *_Nullable certificate = [CDSSigningCertificate parseCertificateFromPem:certificates];
if (!certificate) {
OWSFailDebug(@"could not parse signing certificate.");
2018-07-19 17:10:22 +02:00
return NO;
}
if (![certificate verifySignatureOfBody:signatureBody signature:signature]) {
OWSFailDebug(@"could not verify signature.");
2018-07-19 22:45:01 +02:00
return NO;
2018-07-19 19:31:10 +02:00
}
SignatureBodyEntity *_Nullable signatureBodyEntity = [self parseSignatureBodyEntity:signatureBody];
if (!signatureBodyEntity) {
OWSFailDebug(@"could not parse signature body.");
2018-07-19 19:31:10 +02:00
return NO;
}
// Compare the first N bytes of the quote data with the signed quote body.
const NSUInteger kQuoteBodyComparisonLength = 432;
if (signatureBodyEntity.isvEnclaveQuoteBody.length < kQuoteBodyComparisonLength) {
OWSFailDebug(@"isvEnclaveQuoteBody has unexpected length.");
2018-07-19 19:31:10 +02:00
return NO;
}
2018-09-20 21:41:39 +02:00
// 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;
}
2018-07-19 19:31:10 +02:00
if (quoteData.length < kQuoteBodyComparisonLength) {
OWSFailDebug(@"quoteData has unexpected length.");
2018-07-19 19:31:10 +02:00
return NO;
}
NSData *isvEnclaveQuoteBodyForComparison =
[signatureBodyEntity.isvEnclaveQuoteBody subdataWithRange:NSMakeRange(0, kQuoteBodyComparisonLength)];
NSData *quoteDataForComparison = [quoteData subdataWithRange:NSMakeRange(0, kQuoteBodyComparisonLength)];
if (![isvEnclaveQuoteBodyForComparison ows_constantTimeIsEqualToData:quoteDataForComparison]) {
OWSFailDebug(@"isvEnclaveQuoteBody and quoteData do not match.");
2018-07-19 19:31:10 +02:00
return NO;
}
2018-09-20 21:41:39 +02:00
if (![@"OK" isEqualToString:signatureBodyEntity.isvEnclaveQuoteStatus]) {
OWSFailDebug(@"invalid isvEnclaveQuoteStatus: %@.", signatureBodyEntity.isvEnclaveQuoteStatus);
2018-07-19 19:31:10 +02:00
return NO;
}
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
[dateFormatter setTimeZone:timeZone];
[dateFormatter setDateFormat:@"yyy-MM-dd'T'HH:mm:ss.SSSSSS"];
NSDate *timestampDate = [dateFormatter dateFromString:signatureBodyEntity.timestamp];
if (!timestampDate) {
OWSFailDebug(@"could not parse signature body timestamp.");
2018-07-19 19:31:10 +02:00
return NO;
}
// Only accept signatures from the last 24 hours.
NSDateComponents *dayComponent = [[NSDateComponents alloc] init];
dayComponent.day = 1;
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *timestampDatePlus1Day = [calendar dateByAddingComponents:dayComponent toDate:timestampDate options:0];
NSDate *now = [NSDate new];
BOOL isExpired = [now isAfterDate:timestampDatePlus1Day];
if (isExpired) {
OWSFailDebug(@"Signature is expired.");
2018-07-19 17:10:22 +02:00
return NO;
}
return YES;
}
2018-07-19 19:31:10 +02:00
- (nullable SignatureBodyEntity *)parseSignatureBodyEntity:(NSString *)signatureBody
{
OWSAssertDebug(signatureBody.length > 0);
2018-07-19 19:31:10 +02:00
NSError *error = nil;
NSDictionary *_Nullable jsonDict =
[NSJSONSerialization JSONObjectWithData:[signatureBody dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&error];
if (error || ![jsonDict isKindOfClass:[NSDictionary class]]) {
OWSFailDebug(@"could not parse signature body JSON: %@.", error);
2018-07-19 19:31:10 +02:00
return nil;
}
NSString *_Nullable timestamp = [jsonDict stringForKey:@"timestamp"];
if (timestamp.length < 1) {
OWSFailDebug(@"could not parse signature timestamp.");
2018-07-19 19:31:10 +02:00
return nil;
}
NSData *_Nullable isvEnclaveQuoteBody = [jsonDict base64DataForKey:@"isvEnclaveQuoteBody"];
if (isvEnclaveQuoteBody.length < 1) {
OWSFailDebug(@"could not parse signature isvEnclaveQuoteBody.");
2018-07-19 19:31:10 +02:00
return nil;
}
NSString *_Nullable isvEnclaveQuoteStatus = [jsonDict stringForKey:@"isvEnclaveQuoteStatus"];
if (isvEnclaveQuoteStatus.length < 1) {
OWSFailDebug(@"could not parse signature isvEnclaveQuoteStatus.");
2018-07-19 19:31:10 +02:00
return nil;
}
2018-09-20 21:41:39 +02:00
NSNumber *_Nullable version = [jsonDict numberForKey:@"version"];
if (!version) {
OWSFailDebug(@"could not parse signature version.");
return nil;
}
2018-07-19 19:31:10 +02:00
SignatureBodyEntity *result = [SignatureBodyEntity new];
result.isvEnclaveQuoteBody = isvEnclaveQuoteBody;
result.isvEnclaveQuoteStatus = isvEnclaveQuoteStatus;
result.timestamp = timestamp;
2018-09-20 21:41:39 +02:00
result.version = version;
2018-07-19 19:31:10 +02:00
return result;
}
2018-07-19 17:10:22 +02:00
- (BOOL)verifyServerQuote:(CDSQuote *)quote keys:(RemoteAttestationKeys *)keys enclaveId:(NSString *)enclaveId
{
OWSAssertDebug(quote);
OWSAssertDebug(keys);
OWSAssertDebug(enclaveId.length > 0);
2018-07-19 17:10:22 +02:00
if (quote.reportData.length < keys.serverStaticPublic.length) {
2018-08-30 16:31:01 +02:00
OWSFailDebug(@"reportData has unexpected length: %lu != %lu.",
(unsigned long)quote.reportData.length,
(unsigned long)keys.serverStaticPublic.length);
2018-07-19 17:10:22 +02:00
return NO;
}
NSData *_Nullable theirServerPublicStatic =
[quote.reportData subdataWithRange:NSMakeRange(0, keys.serverStaticPublic.length)];
if (theirServerPublicStatic.length != keys.serverStaticPublic.length) {
OWSFailDebug(@"could not extract server public static.");
2018-07-19 17:10:22 +02:00
return NO;
}
if (![keys.serverStaticPublic ows_constantTimeIsEqualToData:theirServerPublicStatic]) {
OWSFailDebug(@"server public statics do not match.");
2018-07-19 17:10:22 +02:00
return NO;
}
// It's easier to compare as hex data than parsing hexadecimal.
NSData *_Nullable ourEnclaveIdHexData = [enclaveId dataUsingEncoding:NSUTF8StringEncoding];
NSData *_Nullable theirEnclaveIdHexData =
[quote.mrenclave.hexadecimalString dataUsingEncoding:NSUTF8StringEncoding];
if (!ourEnclaveIdHexData || !theirEnclaveIdHexData
|| ![ourEnclaveIdHexData ows_constantTimeIsEqualToData:theirEnclaveIdHexData]) {
OWSFailDebug(@"enclave ids do not match.");
2018-07-19 17:10:22 +02:00
return NO;
}
2018-09-20 21:41:39 +02:00
if (quote.isDebugQuote) {
OWSFailDebug(@"quote has invalid isDebugQuote value.");
2018-07-19 17:10:22 +02:00
return NO;
}
return YES;
2018-07-18 16:20:02 +02:00
}
2018-07-18 19:42:33 +02:00
- (nullable NSData *)decryptRequestId:(NSData *)encryptedRequestId
encryptedRequestIv:(NSData *)encryptedRequestIv
encryptedRequestTag:(NSData *)encryptedRequestTag
keys:(RemoteAttestationKeys *)keys
{
OWSAssertDebug(encryptedRequestId.length > 0);
OWSAssertDebug(encryptedRequestIv.length > 0);
OWSAssertDebug(encryptedRequestTag.length > 0);
OWSAssertDebug(keys);
2018-07-18 19:42:33 +02:00
OWSAES256Key *_Nullable key = keys.serverKey;
2018-07-18 19:42:33 +02:00
if (!key) {
OWSFailDebug(@"invalid server key.");
2018-07-18 19:42:33 +02:00
return nil;
}
NSData *_Nullable decryptedData = [Cryptography decryptAESGCMWithInitializationVector:encryptedRequestIv
ciphertext:encryptedRequestId
additionalAuthenticatedData:nil
2018-07-18 19:42:33 +02:00
authTag:encryptedRequestTag
key:key];
if (!decryptedData) {
OWSFailDebug(@"couldn't decrypt request id.");
2018-07-18 19:42:33 +02:00
return nil;
}
return decryptedData;
}
2018-07-17 18:24:03 +02:00
@end
NS_ASSUME_NONNULL_END