Remote attestation.

pull/1/head
Matthew Chen 7 years ago
parent 6686ecb125
commit 460f7344ad

@ -1108,8 +1108,10 @@ static NSTimeInterval launchStartedAt;
// Resume lazy restore.
[OWSBackupLazyRestoreJob runAsync];
#endif
[[ContactDiscoveryService sharedService] testService];
if ([TSAccountManager isRegistered]) {
[[ContactDiscoveryService sharedService] testService];
}
}
- (void)registrationStateDidChange

@ -4,7 +4,7 @@
#import "OWSDatabaseConverterTest.h"
#import <Curve25519Kit/Randomness.h>
#import <SignalServiceKit/NSData+hexString.h>
#import <SignalServiceKit/NSData+OWS.h>
#import <SignalServiceKit/OWSFileSystem.h>
#import <SignalServiceKit/OWSStorage.h>
#import <SignalServiceKit/YapDatabaseConnection+OWS.h>

@ -11,7 +11,7 @@
#import <SignalServiceKit/Cryptography.h>
#import <SignalServiceKit/MIMETypeUtil.h>
#import <SignalServiceKit/NSData+Image.h>
#import <SignalServiceKit/NSData+hexString.h>
#import <SignalServiceKit/NSData+OWS.h>
#import <SignalServiceKit/NSDate+OWS.h>
#import <SignalServiceKit/NSNotificationCenter+OWS.h>
#import <SignalServiceKit/OWSFileSystem.h>

@ -6,7 +6,7 @@
#import "NSString+OWS.h"
#import <SignalServiceKit/AppContext.h>
#import <SignalServiceKit/Cryptography.h>
#import <SignalServiceKit/NSData+hexString.h>
#import <SignalServiceKit/NSData+OWS.h>
#import <SignalServiceKit/NSNotificationCenter+OWS.h>
#import <SignalServiceKit/TSAccountManager.h>
#import <YapDatabase/YapDatabaseConnection.h>

@ -5,7 +5,7 @@
#import "TSAccountManager.h"
#import "AppContext.h"
#import "NSData+Base64.h"
#import "NSData+hexString.h"
#import "NSData+OWS.h"
#import "NSNotificationCenter+OWS.h"
#import "NSURLSessionDataTask+StatusCode.h"
#import "OWSError.h"

@ -25,6 +25,8 @@ NS_ASSUME_NONNULL_BEGIN
+ (nullable CDSQuote *)parseQuoteFromData:(NSData *)quoteData;
- (BOOL)isDebugQuote;
@end
NS_ASSUME_NONNULL_END

@ -320,6 +320,11 @@ static const long SGX_XFRM_RESERVED = 0xFFFFFFFFFFFFFFF8L;
return quote;
}
- (BOOL)isDebugQuote
{
return (self.flags & SGX_FLAGS_DEBUG) != 0;
}
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,17 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface CDSSigningCertificate : NSObject
+ (nullable CDSSigningCertificate *)parseCertificateFromPem:(NSString *)certificatePem;
//- (BOOL)isDebugQuote;
- (BOOL)verifySignatureOfBody:(NSString *)body signature:(NSData *)theirSignature;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,290 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "CDSSigningCertificate.h"
#import "NSData+Base64.h"
#import "NSData+OWS.h"
NS_ASSUME_NONNULL_BEGIN
@interface CDSSigningCertificate ()
@property (nonatomic) SecPolicyRef policy;
@property (nonatomic) SecTrustRef trust;
@property (nonatomic) SecKeyRef publicKey;
@end
#pragma mark -
@implementation CDSSigningCertificate
- (instancetype)init
{
if (self = [super init]) {
_policy = NULL;
_trust = NULL;
_publicKey = NULL;
}
return self;
}
- (void)dealloc
{
if (_policy) {
CFRelease(_policy);
_policy = NULL;
}
if (_trust) {
CFRelease(_trust);
_trust = NULL;
}
if (_publicKey) {
CFRelease(_publicKey);
_publicKey = NULL;
}
}
+ (nullable CDSSigningCertificate *)parseCertificateFromPem:(NSString *)certificatePem
{
OWSAssert(certificatePem);
CDSSigningCertificate *signingCertificate = [CDSSigningCertificate new];
NSArray<NSData *> *_Nullable anchorCertificates = [self anchorCertificates];
if (anchorCertificates.count < 1) {
OWSProdLogAndFail(@"%@ Could not load anchor certificates.", self.logTag);
return nil;
}
NSArray<NSData *> *_Nullable certificateDerDatas = [self convertPemToDer:certificatePem];
if (certificateDerDatas.count < 1) {
OWSProdLogAndFail(@"%@ Could not parse PEM.", self.logTag);
return nil;
}
NSMutableArray *certificates = [NSMutableArray new];
for (NSData *certificateDerData in certificateDerDatas) {
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(certificateDerData));
if (!certificate) {
OWSProdLogAndFail(@"%@ Could not load DER.", self.logTag);
return nil;
}
[certificates addObject:(__bridge_transfer id)certificate];
}
SecPolicyRef policy = SecPolicyCreateBasicX509();
signingCertificate.policy = policy;
if (!policy) {
DDLogError(@"%@ Could not create policy.", self.logTag);
return nil;
}
SecTrustRef trust;
OSStatus status = SecTrustCreateWithCertificates((__bridge CFTypeRef)certificates, policy, &trust);
signingCertificate.trust = trust;
if (status != errSecSuccess) {
DDLogError(@"%@ trust could not be created.", self.logTag);
return nil;
}
if (!trust) {
DDLogError(@"%@ Could not create trust.", self.logTag);
return nil;
}
// TODO:
status = SecTrustSetNetworkFetchAllowed(trust, NO);
if (status != errSecSuccess) {
DDLogError(@"%@ trust fetch could not be configured.", self.logTag);
return nil;
}
// TODO:
status = SecTrustSetAnchorCertificatesOnly(trust, YES);
if (status != errSecSuccess) {
DDLogError(@"%@ trust anchor certs could not be configured.", self.logTag);
return nil;
}
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in anchorCertificates) {
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(certificateData));
if (!certificate) {
OWSProdLogAndFail(@"%@ Could not load DER.", self.logTag);
return nil;
}
if (![self verifyDistinguishedName:certificate]) {
OWSProdLogAndFail(@"%@ Certificate has invalid name.", self.logTag);
return nil;
}
[pinnedCertificates addObject:(__bridge_transfer id)certificate];
}
status = SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)pinnedCertificates);
if (status != errSecSuccess) {
DDLogError(@"%@ The anchor certificates couldn't be set.", self.logTag);
return nil;
}
SecTrustResultType result;
status = SecTrustEvaluate(trust, &result);
if (status != errSecSuccess) {
DDLogError(@"%@ Could not evaluate certificates.", self.logTag);
return nil;
}
// TODO:
BOOL isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
if (!isValid) {
DDLogError(@"%@ Certificate evaluation failed.", self.logTag);
return nil;
}
SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
signingCertificate.publicKey = publicKey;
if (!publicKey) {
DDLogError(@"%@ Could not extract public key.", self.logTag);
return nil;
}
return signingCertificate;
}
// PEM is just a series of blocks of base-64 encoded DER data.
//
// https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail
+ (nullable NSArray<NSData *> *)convertPemToDer:(NSString *)pemString
{
NSMutableArray<NSData *> *certificateDatas = [NSMutableArray new];
NSError *error;
// We use ? for non-greedy matching.
NSRegularExpression *_Nullable regex = [NSRegularExpression
regularExpressionWithPattern:@"-----BEGIN.*?-----(.+?)-----END.*?-----"
options:NSRegularExpressionCaseInsensitive | NSRegularExpressionDotMatchesLineSeparators
error:&error];
if (!regex || error) {
OWSProdLogAndFail(@"%@ could parse regex: %@.", self.logTag, error);
return nil;
}
[regex enumerateMatchesInString:pemString
options:0
range:NSMakeRange(0, pemString.length)
usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *stop) {
if (result.numberOfRanges != 2) {
OWSProdLogAndFail(@"%@ invalid PEM regex match.", self.logTag);
return;
}
NSString *_Nullable derString = [pemString substringWithRange:[result rangeAtIndex:1]];
if (derString.length < 1) {
OWSProdLogAndFail(@"%@ empty PEM match.", self.logTag);
return;
}
// dataFromBase64String will ignore whitespace, which is
// necessary.
NSData *_Nullable derData = [NSData dataFromBase64String:derString];
if (derData.length < 1) {
OWSProdLogAndFail(@"%@ could not parse PEM match.", self.logTag);
return;
}
[certificateDatas addObject:derData];
}];
return certificateDatas;
}
+ (nullable NSArray<NSData *> *)anchorCertificates
{
// We need to use an Intel certificate as the anchor for IAS verification.
NSData *_Nullable anchorCertificate = [self certificateDataForService:@"ias-root"];
if (!anchorCertificate) {
OWSProdLogAndFail(@"%@ could not load anchor certificate.", self.logTag);
return nil;
}
return @[ anchorCertificate ];
}
+ (nullable NSData *)certificateDataForService:(NSString *)service
{
NSBundle *bundle = [NSBundle bundleForClass:self.class];
NSString *path = [bundle pathForResource:service ofType:@"cer"];
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
OWSProdLogAndFail(@"%@ could not locate certificate file.", self.logTag);
return nil;
}
NSData *_Nullable certificateData = [NSData dataWithContentsOfFile:path];
return certificateData;
}
- (BOOL)verifySignatureOfBody:(NSString *)body signature:(NSData *)theirSignature
{
BOOL result = NO;
// TODO: Which algorithm should we be using?
DDLogVerbose(@"%@ kSecKeyAlgorithmRSASignatureDigestPSSSHA256.", self.logTag);
result = result ||
[self verifySignatureOfBody:body
signature:theirSignature
algorithm:kSecKeyAlgorithmRSASignatureDigestPSSSHA256];
DDLogVerbose(@"%@ kSecKeyAlgorithmRSASignatureMessagePSSSHA256.", self.logTag);
result = result ||
[self verifySignatureOfBody:body
signature:theirSignature
algorithm:kSecKeyAlgorithmRSASignatureMessagePSSSHA256];
return result;
}
// TODO: This method requires iOS 10.
- (BOOL)verifySignatureOfBody:(NSString *)body signature:(NSData *)signature algorithm:(SecKeyAlgorithm)algorithm
{
OWSAssert(body.length > 0);
OWSAssert(signature.length > 0);
OWSAssert(self.publicKey);
NSData *bodyData = [body dataUsingEncoding:NSUTF8StringEncoding];
BOOL canSign = SecKeyIsAlgorithmSupported(self.publicKey, kSecKeyOperationTypeVerify, algorithm);
if (!canSign) {
OWSProdLogAndFail(@"%@ signature algorithm is not supported.", self.logTag);
return NO;
}
CFErrorRef error = NULL;
BOOL isValid = SecKeyVerifySignature(
self.publicKey, algorithm, (__bridge CFDataRef)bodyData, (__bridge CFDataRef)signature, &error);
if (error) {
NSError *nsError = CFBridgingRelease(error);
// TODO:
DDLogError(@"%@ signature verification failed: %@.", self.logTag, nsError);
// OWSProdLogAndFail(@"%@ signature verification failed: %@.", self.logTag, nsError);
return NO;
}
if (!isValid) {
OWSProdLogAndFail(@"%@ signatures do not match.", self.logTag);
return NO;
}
DDLogVerbose(@"%@ signature verification succeeded.", self.logTag);
return YES;
}
+ (BOOL)verifyDistinguishedName:(SecCertificateRef)certificate
{
OWSAssert(certificate);
NSString *expectedDistinguishedName
= @"CN=Intel SGX Attestation Report Signing,O=Intel Corporation,L=Santa Clara,ST=CA,C=US";
// The Security framework doesn't offer access to certificate details like the name.
// TODO: Use OpenSSL to extract the name.
return YES;
}
@end
NS_ASSUME_NONNULL_END

@ -1,13 +1,9 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
//#import "SignalRecipient.h"
NS_ASSUME_NONNULL_BEGIN
//@class Contact;
@interface ContactDiscoveryService : NSObject
- (instancetype)init NS_UNAVAILABLE;
@ -16,31 +12,6 @@ NS_ASSUME_NONNULL_BEGIN
- (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<NSString *> *)identifiers
// success:(void (^)(NSArray<SignalRecipient *> *recipients))success
// failure:(void (^)(NSError *error))failure;
//
//- (void)updateSignalContactIntersectionWithABContacts:(NSArray<Contact *> *)abContacts
// success:(void (^)(void))success
// failure:(void (^)(NSError *error))failure;
@end
NS_ASSUME_NONNULL_END

@ -4,7 +4,9 @@
#import "ContactDiscoveryService.h"
#import "CDSQuote.h"
#import "CDSSigningCertificate.h"
#import "Cryptography.h"
#import "NSData+OWS.h"
#import "OWSRequestFactory.h"
#import "TSNetworkManager.h"
#import <Curve25519Kit/Curve25519.h>
@ -12,25 +14,6 @@
NS_ASSUME_NONNULL_BEGIN
@interface NSData (CDS)
@end
#pragma mark -
@implementation NSData (CDS)
- (NSData *)dataByAppendingData:(NSData *)data
{
NSMutableData *result = [self mutableCopy];
[result appendData:data];
return [result copy];
}
@end
#pragma mark -
@interface RemoteAttestationKeys : NSObject
@property (nonatomic) ECKeyPair *keyPair;
@ -63,26 +46,12 @@ NS_ASSUME_NONNULL_BEGIN
// Returns YES on success.
- (BOOL)deriveKeys
{
// private final byte[] clientKey = new byte[32];
// private final byte[] serverKey = new byte[32];
//
// public RemoteAttestationKeys(Curve25519KeyPair keyPair, byte[] serverPublicEphemeral, byte[] serverPublicStatic)
// {
//
// + (NSData*)generateSharedSecretFromPublicKey:(NSData*)theirPublicKey andKeyPair:(ECKeyPair*)keyPair;
//
// byte[] ephemeralToEphemeral =
// Curve25519.getInstance(Curve25519.BEST).calculateAgreement(serverPublicEphemeral, keyPair.getPrivateKey());
NSData *ephemeralToEphemeral =
[Curve25519 generateSharedSecretFromPublicKey:self.serverEphemeralPublic andKeyPair:self.keyPair];
// byte[] ephemeralToStatic = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(serverPublicStatic,
// keyPair.getPrivateKey());
NSData *ephemeralToStatic =
[Curve25519 generateSharedSecretFromPublicKey:self.serverStaticPublic andKeyPair:self.keyPair];
// byte[] masterSecret = ByteUtils.combine(ephemeralToEphemeral, ephemeralToStatic );
NSData *masterSecret = [ephemeralToEphemeral dataByAppendingData:ephemeralToStatic];
// byte[] publicKeys = ByteUtils.combine(keyPair.getPublicKey(), serverPublicEphemeral, serverPublicStatic);
NSData *publicKeys = [[self.keyPair.publicKey dataByAppendingData:self.serverEphemeralPublic]
dataByAppendingData:self.serverStaticPublic];
@ -117,56 +86,36 @@ NS_ASSUME_NONNULL_BEGIN
self.serverKey = serverKey;
return YES;
// HKDFBytesGenerator generator = new HKDFBytesGenerator(new SHA256Digest());
// generator.init(new HKDFParameters(masterSecret, publicKeys, null));
// generator.generateBytes(clientKey, 0, clientKey.length);
// generator.generateBytes(serverKey, 0, serverKey.length);
}
//+ (DHEResult*)DHEKeyAgreement:(id<AxolotlParameters>)parameters{
// NSMutableData *masterKey = [NSMutableData data];
//
// [masterKey appendData:[self discontinuityBytes]];
//
// if ([parameters isKindOfClass:[AliceAxolotlParameters class]]) {
// AliceAxolotlParameters *params = (AliceAxolotlParameters*)parameters;
//
// [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirSignedPreKey
// andKeyPair:params.ourIdentityKeyPair]]; [masterKey appendData:[Curve25519
// generateSharedSecretFromPublicKey:params.theirIdentityKey andKeyPair:params.ourBaseKey]]; [masterKey
// appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirSignedPreKey
// andKeyPair:params.ourBaseKey]]; if (params.theirOneTimePrekey) {
// [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirOneTimePrekey
// andKeyPair:params.ourBaseKey]];
// }
// } else if ([parameters isKindOfClass:[BobAxolotlParameters class]]){
// BobAxolotlParameters *params = (BobAxolotlParameters*)parameters;
//
// [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirIdentityKey
// andKeyPair:params.ourSignedPrekey]]; [masterKey appendData:[Curve25519
// generateSharedSecretFromPublicKey:params.theirBaseKey andKeyPair:params.ourIdentityKeyPair]]; [masterKey
// appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirBaseKey
// andKeyPair:params.ourSignedPrekey]]; if (params.ourOneTimePrekey) {
// [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirBaseKey
// andKeyPair:params.ourOneTimePrekey]];
// }
// }
//
// return [[DHEResult alloc] initWithMasterKey:masterKey];
//}
@end
#pragma mark -
@interface RemoteAttestation : NSObject
@property (nonatomic) RemoteAttestationKeys *keys;
// TODO: Do we need to support multiple cookies?
@property (nonatomic) NSString *cookie;
@property (nonatomic) NSData *requestId;
@end
#pragma mark -
@interface NSDictionary (OWS)
@implementation RemoteAttestation
@end
#pragma mark -
@implementation NSDictionary (OWS)
@interface NSDictionary (CDS)
@end
#pragma mark -
@implementation NSDictionary (CDS)
- (nullable NSData *)base64DataForKey:(NSString *)key
{
@ -245,22 +194,19 @@ NS_ASSUME_NONNULL_BEGIN
success:^(NSURLSessionDataTask *task, id responseDict) {
DDLogVerbose(@"%@ remote attestation auth success: %@", self.logTag, responseDict);
NSString *_Nullable authToken = [self parseAuthToken:responseDict];
if (!authToken) {
DDLogError(@"%@ remote attestation auth missing token: %@", self.logTag, responseDict);
return;
}
[self performRemoteAttestationWithToken:authToken];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *_Nullable authToken = [self parseAuthToken:responseDict];
if (!authToken) {
DDLogError(@"%@ remote attestation auth missing token: %@", self.logTag, responseDict);
return;
}
[self performRemoteAttestationWithToken:authToken];
});
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
DDLogVerbose(@"%@ remote attestation auth failure: %zd", self.logTag, response.statusCode);
}];
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// [self performRemoteAttestation];
// });
}
- (nullable NSString *)parseAuthToken:(id)response
@ -268,29 +214,29 @@ NS_ASSUME_NONNULL_BEGIN
if (![response isKindOfClass:[NSDictionary class]]) {
return nil;
}
NSDictionary *responseDict = response;
NSString *_Nullable tokenString = responseDict[@"token"];
if (![tokenString isKindOfClass:[NSString class]]) {
NSString *_Nullable token = responseDict[@"token"];
if (![token isKindOfClass:[NSString class]]) {
return nil;
}
if (tokenString.length < 1) {
if (token.length < 1) {
return nil;
}
NSRange range = [tokenString rangeOfString:@":"];
if (range.location == NSNotFound) {
NSString *_Nullable username = responseDict[@"username"];
if (![username isKindOfClass:[NSString class]]) {
return nil;
}
DDLogVerbose(@"%@ attestation raw token: %@", self.logTag, tokenString);
NSString *username = [tokenString substringToIndex:range.location];
NSString *password = [tokenString substringFromIndex:range.location + range.length];
if (username.length < 1 || password.length < 1) {
if (username.length < 1) {
return nil;
}
// To work around an idiosyncracy of the service implementation,
// we need to repeat the username twice in the token.
NSString *token = [username stringByAppendingFormat:@":%@", tokenString];
DDLogVerbose(@"%@ attestation modified token: %@", self.logTag, token);
return token;
NSString *modifiedToken = [username stringByAppendingFormat:@":%@", token];
DDLogVerbose(@"%@ attestation modified token: %@", self.logTag, modifiedToken);
return modifiedToken;
}
- (void)performRemoteAttestationWithToken:(NSString *)authToken
@ -304,7 +250,14 @@ NS_ASSUME_NONNULL_BEGIN
[[TSNetworkManager sharedManager] makeRequest:request
success:^(NSURLSessionDataTask *task, id responseJson) {
DDLogVerbose(@"%@ remote attestation success: %@", self.logTag, responseJson);
[self parseAttestationResponseJson:responseJson response:task.response keyPair:keyPair];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// TODO: Handle result.
[self parseAttestationResponseJson:responseJson
response:task.response
keyPair:keyPair
enclaveId:enclaveId];
});
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
@ -312,24 +265,21 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
- (nullable NSString *)parseAttestationResponseJson:(id)responseJson
response:(NSURLResponse *)response
keyPair:(ECKeyPair *)keyPair
- (nullable RemoteAttestation *)parseAttestationResponseJson:(id)responseJson
response:(NSURLResponse *)response
keyPair:(ECKeyPair *)keyPair
enclaveId:(NSString *)enclaveId
{
OWSAssert(responseJson);
OWSAssert(response);
OWSAssert(keyPair);
OWSAssert(enclaveId.length > 0);
if (![response isKindOfClass:[NSHTTPURLResponse class]]) {
OWSProdLogAndFail(@"%@ unexpected response type.", self.logTag);
return nil;
}
NSDictionary *responseHeaders = ((NSHTTPURLResponse *)response).allHeaderFields;
// DDLogVerbose(@"%@ responseHeaders: %@", self.logTag, responseHeaders);
// for (NSString *key in responseHeaders) {
// id value = responseHeaders[key];
// DDLogVerbose(@"%@ \t %@: %@, %@", self.logTag, key, [value class], value);
// }
NSString *_Nullable cookie = responseHeaders[@"Set-Cookie"];
if (![cookie isKindOfClass:[NSString class]]) {
@ -337,12 +287,15 @@ NS_ASSUME_NONNULL_BEGIN
return nil;
}
DDLogVerbose(@"%@ cookie: %@", self.logTag, cookie);
// The cookie header will have this form:
// Set-Cookie: __NSCFString, c2131364675-413235ic=c1656171-249545-958227; Path=/; Secure
// We want to strip everything after the semicolon (;).
NSRange cookieRange = [cookie rangeOfString:@";"];
if (cookieRange.length != NSNotFound) {
cookie = [cookie substringToIndex:cookieRange.location];
DDLogVerbose(@"%@ trimmed cookie: %@", self.logTag, cookie);
}
// Set-Cookie: __NSCFString, c2131364675-413235ic=c1656171-249545-958227; Path=/; Secure
if (![responseJson isKindOfClass:[NSDictionary class]]) {
return nil;
@ -353,7 +306,6 @@ NS_ASSUME_NONNULL_BEGIN
id value = responseDict[key];
DDLogVerbose(@"%@ \t %@: %@, %@", self.logTag, key, [value class], value);
}
// NSString *_Nullable serverEphemeralPublic = responseDict[@"serverEphemeralPublic"];
NSData *_Nullable serverEphemeralPublic =
[responseDict base64DataForKey:@"serverEphemeralPublic" expectedLength:32];
if (!serverEphemeralPublic) {
@ -385,8 +337,8 @@ NS_ASSUME_NONNULL_BEGIN
OWSProdLogAndFail(@"%@ couldn't parse quote data.", self.logTag);
return nil;
}
id _Nullable signatureBody = responseDict[@"signatureBody"];
if (!signatureBody) {
NSString *_Nullable signatureBody = responseDict[@"signatureBody"];
if (![signatureBody isKindOfClass:[NSString class]]) {
OWSProdLogAndFail(@"%@ couldn't parse signatureBody.", self.logTag);
return nil;
}
@ -428,19 +380,149 @@ NS_ASSUME_NONNULL_BEGIN
return nil;
}
// RemoteAttestationKeys keys = new RemoteAttestationKeys(keyPair, response.getServerEphemeralPublic(),
// response.getServerStaticPublic()); Quote quote = new Quote(response.getQuote()); byte[]
// requestId = getPlaintext(keys.getServerKey(), response.getIv(), response.getCiphertext(), response.getTag());
if (![self verifyServerQuote:quote keys:keys enclaveId:enclaveId]) {
OWSProdLogAndFail(@"%@ couldn't verify quote.", self.logTag);
return nil;
}
if (![self verifyIasSignature:nil
certificates:certificates
signatureBody:signatureBody
signature:signature
quote:quote]) {
OWSProdLogAndFail(@"%@ couldn't verify ias signature.", self.logTag);
return nil;
}
//+ RemoteAttestation remoteAttestation = new RemoteAttestation(requestId, keys);
//+ List<String> addressBook = new LinkedList<>();
//+
//+ for (String e164number : e164numbers) {
//+ addressBook.add(e164number.substring(1));
//+ }
//+
//+ DiscoveryRequest request = cipher.createDiscoveryRequest(addressBook, remoteAttestation);
//+ DiscoveryResponse response = this.pushServiceSocket.getContactDiscoveryRegisteredUsers(authorization,
//request, attestationResponse.second(), mrenclave);
//+ byte[] data = cipher.getDiscoveryResponseData(response, remoteAttestation);
//+
//+ Iterator<String> addressBookIterator = addressBook.iterator();
//+ List<String> results = new LinkedList<>();
//+
//+ for (byte aData : data) {
//+ String candidate = addressBookIterator.next();
//+
//+ if (aData != 0) results.add('+' + candidate);
//+ }
//+
//+ return results;
RemoteAttestation *result = [RemoteAttestation new];
result.cookie = cookie;
result.keys = keys;
result.requestId = requestId;
return result;
}
- (BOOL)verifyIasSignature:(nullable id)trustStore
certificates:(NSString *)certificates
signatureBody:(NSString *)signatureBody
signature:(NSData *)signature
quote:(CDSQuote *)quote
{
// OWSAssert(trustStore);
OWSAssert(certificates.length > 0);
OWSAssert(signatureBody.length > 0);
OWSAssert(signature.length > 0);
OWSAssert(quote);
CDSSigningCertificate *_Nullable certificate = [CDSSigningCertificate parseCertificateFromPem:certificates];
if (!certificate) {
OWSProdLogAndFail(@"%@ could not parse signing certificate.", self.logTag);
return NO;
}
if (![certificate verifySignatureOfBody:signatureBody signature:signature]) {
OWSProdLogAndFail(@"%@ could not verify signature.", self.logTag);
return NO;
}
////public void verifyIasSignature(KeyStore trustStore, String certificates, String signatureBody, String signature,
///Quote quote) /throws SignatureException
////{
//// try {
// SigningCertificate signingCertificate = new SigningCertificate(certificates, trustStore);
// signingCertificate.verifySignature(signatureBody, signature);
//
// SignatureBodyEntity signatureBodyEntity = JsonUtil.fromJson(signatureBody, SignatureBodyEntity.class);
//
// verifyServerQuote(quote, response.getServerStaticPublic(), mrenclave);
// verifyIasSignature(keyStore, response.getCertificates(), response.getSignatureBody(), response.getSignature(),
// quote);
// if (!MessageDigest.isEqual(ByteUtil.trim(signatureBodyEntity.getIsvEnclaveQuoteBody(), 432),
// ByteUtil.trim(quote.getQuoteBytes(), 432))) {
// throw new SignatureException("Signed quote is not the same as RA quote: " +
// Hex.toStringCondensed(signatureBodyEntity.getIsvEnclaveQuoteBody()) + " vs " +
// Hex.toStringCondensed(quote.getQuoteBytes()));
// }
//
// return new RemoteAttestation(requestId, keys, cookies);
//} catch (BadPaddingException e) {
// throw new UnauthenticatedResponseException(e);
//}
return nil;
// if (!"OK".equals(signatureBodyEntity.getIsvEnclaveQuoteStatus()) &&
// !"GROUP_OUT_OF_DATE".equals(signatureBodyEntity.getIsvEnclaveQuoteStatus())) {
// // if (!"OK".equals(signatureBodyEntity.getIsvEnclaveQuoteStatus())) {
// throw new SignatureException("Quote status is: " + signatureBodyEntity.getIsvEnclaveQuoteStatus());
// }
//
// if
// (Instant.from(ZonedDateTime.of(LocalDateTime.from(DateTimeFormatter.ofPattern("yyy-MM-dd'T'HH:mm:ss.SSSSSS").parse(signatureBodyEntity.getTimestamp())),
// ZoneId.of("UTC")))
// .plus(Period.ofDays(1))
// .isBefore(Instant.now()))
// {
// throw new SignatureException("Signature is expired");
// }
//
// } catch (CertificateException | CertPathValidatorException | IOException e) {
// throw new SignatureException(e);
// }
return YES;
}
- (BOOL)verifyServerQuote:(CDSQuote *)quote keys:(RemoteAttestationKeys *)keys enclaveId:(NSString *)enclaveId
{
OWSAssert(quote);
OWSAssert(keys);
OWSAssert(enclaveId.length > 0);
if (quote.reportData.length < keys.serverStaticPublic.length) {
OWSProdLogAndFail(@"%@ reportData has unexpected length: %zd != %zd.",
self.logTag,
quote.reportData.length,
keys.serverStaticPublic.length);
return NO;
}
NSData *_Nullable theirServerPublicStatic =
[quote.reportData subdataWithRange:NSMakeRange(0, keys.serverStaticPublic.length)];
if (theirServerPublicStatic.length != keys.serverStaticPublic.length) {
OWSProdLogAndFail(@"%@ could not extract server public static.", self.logTag);
return NO;
}
if (![keys.serverStaticPublic ows_constantTimeIsEqualToData:theirServerPublicStatic]) {
OWSProdLogAndFail(@"%@ server public statics do not match.", self.logTag);
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]) {
OWSProdLogAndFail(@"%@ enclave ids do not match.", self.logTag);
return NO;
}
// TODO: Reverse this condition in production.
if (!quote.isDebugQuote) {
OWSProdLogAndFail(@"%@ quote has invalid isDebugQuote value.", self.logTag);
return NO;
}
return YES;
}
- (nullable NSData *)decryptRequestId:(NSData *)encryptedRequestId

@ -4,7 +4,7 @@
#import "Cryptography.h"
#import "NSData+Base64.h"
#import "NSData+OWSConstantTimeCompare.h"
#import "NSData+OWS.h"
#import "OWSError.h"
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonHMAC.h>

@ -1,16 +1,20 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface NSData (OWSConstantTimeCompare)
@interface NSData (OWS)
/**
* Compares data in constant time so as to help avoid potential timing attacks.
*/
- (BOOL)ows_constantTimeIsEqualToData:(NSData *)other;
- (NSData *)dataByAppendingData:(NSData *)data;
- (NSString *)hexadecimalString;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,56 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "NSData+OWS.h"
NS_ASSUME_NONNULL_BEGIN
@implementation NSData (OWS)
- (BOOL)ows_constantTimeIsEqualToData:(NSData *)other
{
volatile UInt8 isEqual = 0;
if (self.length != other.length) {
return NO;
}
UInt8 *leftBytes = (UInt8 *)self.bytes;
UInt8 *rightBytes = (UInt8 *)other.bytes;
for (int i = 0; i < self.length; i++) {
// rather than returning as soon as we find a discrepency, we compare the rest of
// the byte stream to maintain a constant time comparison
isEqual |= leftBytes[i] ^ rightBytes[i];
}
return isEqual == 0;
}
- (NSData *)dataByAppendingData:(NSData *)data
{
NSMutableData *result = [self mutableCopy];
[result appendData:data];
return [result copy];
}
- (NSString *)hexadecimalString
{
/* Returns hexadecimal string of NSData. Empty string if data is empty. */
const unsigned char *dataBuffer = (const unsigned char *)[self bytes];
if (!dataBuffer) {
return @"";
}
NSUInteger dataLength = [self length];
NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
for (NSUInteger i = 0; i < dataLength; ++i) {
[hexString appendFormat:@"%02x", dataBuffer[i]];
}
return [hexString copy];
}
@end
NS_ASSUME_NONNULL_END

@ -1,32 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "NSData+OWSConstantTimeCompare.h"
NS_ASSUME_NONNULL_BEGIN
@implementation NSData (OWSConstantTimeCompare)
- (BOOL)ows_constantTimeIsEqualToData:(NSData *)other
{
volatile UInt8 isEqual = 0;
if (self.length != other.length) {
return NO;
}
UInt8 *leftBytes = (UInt8 *)self.bytes;
UInt8 *rightBytes = (UInt8 *)other.bytes;
for (int i = 0; i < self.length; i++) {
// rather than returning as soon as we find a discrepency, we compare the rest of
// the byte stream to maintain a constant time comparison
isEqual |= leftBytes[i] ^ rightBytes[i];
}
return isEqual == 0;
}
@end
NS_ASSUME_NONNULL_END

@ -1,9 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
@interface NSData (hexString)
- (NSString *)hexadecimalString;
@end

@ -1,25 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "NSData+hexString.h"
@implementation NSData (hexString)
- (NSString *)hexadecimalString {
/* Returns hexadecimal string of NSData. Empty string if data is empty. */
const unsigned char *dataBuffer = (const unsigned char *)[self bytes];
if (!dataBuffer) {
return @"";
}
NSUInteger dataLength = [self length];
NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
for (NSUInteger i = 0; i < dataLength; ++i) {
[hexString appendFormat:@"%02x", dataBuffer[i]];
}
return [hexString copy];
}
@end
Loading…
Cancel
Save