From 460f7344adefa169620ed31404b4becb74bd6d43 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 19 Jul 2018 11:10:22 -0400 Subject: [PATCH] Remote attestation. --- Signal/src/AppDelegate.m | 6 +- Signal/test/util/OWSDatabaseConverterTest.m | 2 +- SignalMessaging/profiles/OWSProfileManager.m | 2 +- SignalMessaging/profiles/OWSUserProfile.m | 2 +- .../Resources/Certificates/ias-root.cer | Bin 0 -> 1359 bytes .../src/Account/TSAccountManager.m | 2 +- SignalServiceKit/src/Contacts/CDSQuote.h | 2 + SignalServiceKit/src/Contacts/CDSQuote.m | 5 + .../src/Contacts/CDSSigningCertificate.h | 17 + .../src/Contacts/CDSSigningCertificate.m | 290 ++++++++++++++++ .../src/Contacts/ContactDiscoveryService.h | 31 +- .../src/Contacts/ContactDiscoveryService.m | 318 +++++++++++------- SignalServiceKit/src/Util/Cryptography.m | 2 +- ...+OWSConstantTimeCompare.h => NSData+OWS.h} | 8 +- SignalServiceKit/src/Util/NSData+OWS.m | 56 +++ .../src/Util/NSData+OWSConstantTimeCompare.m | 32 -- SignalServiceKit/src/Util/NSData+hexString.h | 9 - SignalServiceKit/src/Util/NSData+hexString.m | 25 -- 18 files changed, 586 insertions(+), 223 deletions(-) create mode 100644 SignalServiceKit/Resources/Certificates/ias-root.cer create mode 100644 SignalServiceKit/src/Contacts/CDSSigningCertificate.h create mode 100644 SignalServiceKit/src/Contacts/CDSSigningCertificate.m rename SignalServiceKit/src/Util/{NSData+OWSConstantTimeCompare.h => NSData+OWS.h} (54%) create mode 100644 SignalServiceKit/src/Util/NSData+OWS.m delete mode 100644 SignalServiceKit/src/Util/NSData+OWSConstantTimeCompare.m delete mode 100644 SignalServiceKit/src/Util/NSData+hexString.h delete mode 100644 SignalServiceKit/src/Util/NSData+hexString.m diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 972f2d0ce..61b16934c 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -1108,8 +1108,10 @@ static NSTimeInterval launchStartedAt; // Resume lazy restore. [OWSBackupLazyRestoreJob runAsync]; #endif - - [[ContactDiscoveryService sharedService] testService]; + + if ([TSAccountManager isRegistered]) { + [[ContactDiscoveryService sharedService] testService]; + } } - (void)registrationStateDidChange diff --git a/Signal/test/util/OWSDatabaseConverterTest.m b/Signal/test/util/OWSDatabaseConverterTest.m index dd73a7e9f..85c6c8864 100644 --- a/Signal/test/util/OWSDatabaseConverterTest.m +++ b/Signal/test/util/OWSDatabaseConverterTest.m @@ -4,7 +4,7 @@ #import "OWSDatabaseConverterTest.h" #import -#import +#import #import #import #import diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 93254a9c4..246819050 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -11,7 +11,7 @@ #import #import #import -#import +#import #import #import #import diff --git a/SignalMessaging/profiles/OWSUserProfile.m b/SignalMessaging/profiles/OWSUserProfile.m index 2fcc75ace..760573c4b 100644 --- a/SignalMessaging/profiles/OWSUserProfile.m +++ b/SignalMessaging/profiles/OWSUserProfile.m @@ -6,7 +6,7 @@ #import "NSString+OWS.h" #import #import -#import +#import #import #import #import diff --git a/SignalServiceKit/Resources/Certificates/ias-root.cer b/SignalServiceKit/Resources/Certificates/ias-root.cer new file mode 100644 index 0000000000000000000000000000000000000000..a11a49f87c9312e5539e242832597a64fea023b7 GIT binary patch literal 1359 zcmXqLV)Zs?V&1%fnTe5!iId?Xds(c};tf*_c-c6$+C196^D;7WvoaXe8FCwNvN4CU zun9AT2E#ZUJWS4xh9U++AQ^TZ?%>3{l0*gPoW!C;Ln#9ZkSG_Apl4o5YL0?)eo;Yw zQDRAEex9L$fgVVXnMWNW7wjIP;8;?UT3iB_R|rZ4DlJh6&P>nC%u82rb~I2B=QT7l zG&D3ZG&MFiHjI+sH!?7>G&C{>B2!CK%cuc&VH4vb}N1&V(emSVr*oX zZnf$NTOyF(ZJns-kxW#Y_dO|m-$t{Rq`bC$bhWyQI-ow;=P^%ar1 zS^CMUL31zioNPDWP(DYB{m~rvh3gnRJ=}9V=DRLk7+ZDX&9ODdT-R=~ZBL*3bq=TX z(M2yab3!UYq^Hj-^$xcb*%x|Q|I2~7_th0=nw*`GgmbQlsHTe zS#Fbvil@^zzrWB_Zm1QQU7VBk#lCEBIey)CU zK9HGLqVF8!qYq53`bfzYl3JmO)!9)mxhTg#7Gwh-ix`VYg>!Y;vbsGN*4H_izc#J0 zZf6X%F^~sIE3-%#h&5nWzz0&m&&c?ng$0;1*}yqWRv5(RFkk~xOpFZ3sSTJ#fvK&L zp(0*4;phpjFItsl0*aQRx0+6Fn%DVYf|lyCUM zG;uQ2D_>?g<>vIMCtkUhHQBuCy7+M7`4vgOcC0kId~-u^6g$&}(D;(43Xho$KASW= zUwiWU2|0xVL#O==M>SS(yBRnBP0(sO&K%UZ>gm*^Gh2=QR!p!<xKr3x zML25nkrXNY4i1yK#m1U(d}Z&vk17=zCH4JkxITB%YCl^+h0njEE=r#54e?rdWm#KX zcKE(E;e`fudNw=Rd6!gs|LMqlAR_#V)70@^SEYvFb~m%5FGYgR^Sq1v^xMJfEcelt z({q|bSc-l4>W(GE+&{1X#d6z~!ubXId_49G{BH{_JFRtbwal!KKfWAZ(08|Ju{QVC s3u{&L|BBAau-1x_mlgb#6s*sDJ>=AT-lfG+a#0;Vb)7ebBu-uj01{LUm;e9( literal 0 HcmV?d00001 diff --git a/SignalServiceKit/src/Account/TSAccountManager.m b/SignalServiceKit/src/Account/TSAccountManager.m index e8805612d..dc2b797c8 100644 --- a/SignalServiceKit/src/Account/TSAccountManager.m +++ b/SignalServiceKit/src/Account/TSAccountManager.m @@ -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" diff --git a/SignalServiceKit/src/Contacts/CDSQuote.h b/SignalServiceKit/src/Contacts/CDSQuote.h index 9e0cc45d0..6f17f7a5c 100644 --- a/SignalServiceKit/src/Contacts/CDSQuote.h +++ b/SignalServiceKit/src/Contacts/CDSQuote.h @@ -25,6 +25,8 @@ NS_ASSUME_NONNULL_BEGIN + (nullable CDSQuote *)parseQuoteFromData:(NSData *)quoteData; +- (BOOL)isDebugQuote; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/CDSQuote.m b/SignalServiceKit/src/Contacts/CDSQuote.m index 91a987380..6a34e04c1 100644 --- a/SignalServiceKit/src/Contacts/CDSQuote.m +++ b/SignalServiceKit/src/Contacts/CDSQuote.m @@ -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 diff --git a/SignalServiceKit/src/Contacts/CDSSigningCertificate.h b/SignalServiceKit/src/Contacts/CDSSigningCertificate.h new file mode 100644 index 000000000..8a43b3f37 --- /dev/null +++ b/SignalServiceKit/src/Contacts/CDSSigningCertificate.h @@ -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 diff --git a/SignalServiceKit/src/Contacts/CDSSigningCertificate.m b/SignalServiceKit/src/Contacts/CDSSigningCertificate.m new file mode 100644 index 000000000..0ae874ec3 --- /dev/null +++ b/SignalServiceKit/src/Contacts/CDSSigningCertificate.m @@ -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 *_Nullable anchorCertificates = [self anchorCertificates]; + if (anchorCertificates.count < 1) { + OWSProdLogAndFail(@"%@ Could not load anchor certificates.", self.logTag); + return nil; + } + + NSArray *_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 *)convertPemToDer:(NSString *)pemString +{ + NSMutableArray *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 *)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 diff --git a/SignalServiceKit/src/Contacts/ContactDiscoveryService.h b/SignalServiceKit/src/Contacts/ContactDiscoveryService.h index 825684f86..929e7bfcb 100644 --- a/SignalServiceKit/src/Contacts/ContactDiscoveryService.h +++ b/SignalServiceKit/src/Contacts/ContactDiscoveryService.h @@ -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 *)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 index 985d5f03f..f0aa6d059 100644 --- a/SignalServiceKit/src/Contacts/ContactDiscoveryService.m +++ b/SignalServiceKit/src/Contacts/ContactDiscoveryService.m @@ -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 @@ -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)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 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 addressBookIterator = addressBook.iterator(); + //+ List 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 diff --git a/SignalServiceKit/src/Util/Cryptography.m b/SignalServiceKit/src/Util/Cryptography.m index 324cabe9f..514b2869a 100755 --- a/SignalServiceKit/src/Util/Cryptography.m +++ b/SignalServiceKit/src/Util/Cryptography.m @@ -4,7 +4,7 @@ #import "Cryptography.h" #import "NSData+Base64.h" -#import "NSData+OWSConstantTimeCompare.h" +#import "NSData+OWS.h" #import "OWSError.h" #import #import diff --git a/SignalServiceKit/src/Util/NSData+OWSConstantTimeCompare.h b/SignalServiceKit/src/Util/NSData+OWS.h similarity index 54% rename from SignalServiceKit/src/Util/NSData+OWSConstantTimeCompare.h rename to SignalServiceKit/src/Util/NSData+OWS.h index cc017b367..d725945a8 100644 --- a/SignalServiceKit/src/Util/NSData+OWSConstantTimeCompare.h +++ b/SignalServiceKit/src/Util/NSData+OWS.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 diff --git a/SignalServiceKit/src/Util/NSData+OWS.m b/SignalServiceKit/src/Util/NSData+OWS.m new file mode 100644 index 000000000..ad1fe17e8 --- /dev/null +++ b/SignalServiceKit/src/Util/NSData+OWS.m @@ -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 diff --git a/SignalServiceKit/src/Util/NSData+OWSConstantTimeCompare.m b/SignalServiceKit/src/Util/NSData+OWSConstantTimeCompare.m deleted file mode 100644 index fff804496..000000000 --- a/SignalServiceKit/src/Util/NSData+OWSConstantTimeCompare.m +++ /dev/null @@ -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 diff --git a/SignalServiceKit/src/Util/NSData+hexString.h b/SignalServiceKit/src/Util/NSData+hexString.h deleted file mode 100644 index 8e037153e..000000000 --- a/SignalServiceKit/src/Util/NSData+hexString.h +++ /dev/null @@ -1,9 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -@interface NSData (hexString) - -- (NSString *)hexadecimalString; - -@end diff --git a/SignalServiceKit/src/Util/NSData+hexString.m b/SignalServiceKit/src/Util/NSData+hexString.m deleted file mode 100644 index 6afaa6391..000000000 --- a/SignalServiceKit/src/Util/NSData+hexString.m +++ /dev/null @@ -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