From 7476ef123d932eadb23815a213a80b463dda5ceb Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 19 Jul 2018 13:31:10 -0400 Subject: [PATCH] Remote attestation. --- Signal.xcodeproj/project.pbxproj | 12 -- .../xcshareddata/xcschemes/Signal.xcscheme | 3 - .../src/Contacts/ContactDiscoveryService.m | 171 +++++++++++++----- 3 files changed, 126 insertions(+), 60 deletions(-) diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index ddadf0c38..9850e6157 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -3918,13 +3918,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = YES; - CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; - CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; - CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; CLANG_ENABLE_MODULES = YES; - CLANG_UNDEFINED_BEHAVIOR_SANITIZER_INTEGER = YES; - CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES; CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -3969,7 +3963,6 @@ PRODUCT_NAME = Signal; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; - RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SWIFT_OBJC_BRIDGING_HEADER = "Signal/src/Signal-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -3987,10 +3980,6 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = NO; - CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; - CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; - CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; @@ -4035,7 +4024,6 @@ PRODUCT_BUNDLE_IDENTIFIER = org.whispersystems.signal; PRODUCT_NAME = Signal; PROVISIONING_PROFILE = ""; - RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SWIFT_OBJC_BRIDGING_HEADER = "Signal/src/Signal-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = Off; diff --git a/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme b/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme index 78b9c56c1..8911e150f 100644 --- a/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme +++ b/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme @@ -76,9 +76,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - enableAddressSanitizer = "YES" - enableASanStackUseAfterReturn = "YES" - enableUBSanitizer = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/SignalServiceKit/src/Contacts/ContactDiscoveryService.m b/SignalServiceKit/src/Contacts/ContactDiscoveryService.m index aa738d7b4..60b0cd293 100644 --- a/SignalServiceKit/src/Contacts/ContactDiscoveryService.m +++ b/SignalServiceKit/src/Contacts/ContactDiscoveryService.m @@ -7,6 +7,7 @@ #import "CDSSigningCertificate.h" #import "Cryptography.h" #import "NSData+OWS.h" +#import "NSDate+OWS.h" #import "OWSRequestFactory.h" #import "TSNetworkManager.h" #import @@ -124,6 +125,22 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - +@interface SignatureBodyEntity : NSObject + +@property (nonatomic) NSData *isvEnclaveQuoteBody; +@property (nonatomic) NSString *isvEnclaveQuoteStatus; +@property (nonatomic) NSString *timestamp; + +@end + +#pragma mark - + +@implementation SignatureBodyEntity + +@end + +#pragma mark - + @interface NSDictionary (CDS) @end @@ -132,6 +149,16 @@ NS_ASSUME_NONNULL_BEGIN @implementation NSDictionary (CDS) +- (nullable NSString *)stringForKey:(NSString *)key +{ + NSString *_Nullable valueString = self[key]; + if (![valueString isKindOfClass:[NSString class]]) { + OWSProdLogAndFail(@"%@ couldn't parse string for key: %@", self.logTag, key); + return nil; + } + return valueString; +} + - (nullable NSData *)base64DataForKey:(NSString *)key { NSString *_Nullable valueString = self[key]; @@ -307,7 +334,6 @@ NS_ASSUME_NONNULL_BEGIN OWSProdLogAndFail(@"%@ couldn't parse cookie.", self.logTag); return nil; } - DDLogVerbose(@"%@ cookie: %@", self.logTag, cookie); // The cookie header will have this form: // Set-Cookie: __NSCFString, c2131364675-413235ic=c1656171-249545-958227; Path=/; Secure @@ -315,18 +341,12 @@ NS_ASSUME_NONNULL_BEGIN NSRange cookieRange = [cookie rangeOfString:@";"]; if (cookieRange.length != NSNotFound) { cookie = [cookie substringToIndex:cookieRange.location]; - DDLogVerbose(@"%@ trimmed cookie: %@", self.logTag, cookie); } if (![responseJson isKindOfClass:[NSDictionary class]]) { return nil; } NSDictionary *responseDict = responseJson; - DDLogVerbose(@"%@ parseAttestationResponse: %@", self.logTag, responseDict); - for (NSString *key in responseDict) { - id value = responseDict[key]; - DDLogVerbose(@"%@ \t %@: %@, %@", self.logTag, key, [value class], value); - } NSData *_Nullable serverEphemeralPublic = [responseDict base64DataForKey:@"serverEphemeralPublic" expectedLength:32]; if (!serverEphemeralPublic) { @@ -409,7 +429,7 @@ NS_ASSUME_NONNULL_BEGIN if (![self verifyIasSignatureWithCertificates:certificates signatureBody:signatureBody signature:signature - quote:quote]) { + quoteData:quoteData]) { OWSProdLogAndFail(@"%@ couldn't verify ias signature.", self.logTag); return nil; } @@ -448,12 +468,13 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)verifyIasSignatureWithCertificates:(NSString *)certificates signatureBody:(NSString *)signatureBody signature:(NSData *)signature - quote:(CDSQuote *)quote + // quote:(CDSQuote *)quote + quoteData:(NSData *)quoteData { OWSAssert(certificates.length > 0); OWSAssert(signatureBody.length > 0); OWSAssert(signature.length > 0); - OWSAssert(quote); + OWSAssert(quoteData); CDSSigningCertificate *_Nullable certificate = [CDSSigningCertificate parseCertificateFromPem:certificates]; if (!certificate) { @@ -461,47 +482,107 @@ NS_ASSUME_NONNULL_BEGIN return NO; } if (![certificate verifySignatureOfBody:signatureBody signature:signature]) { - OWSProdLogAndFail(@"%@ could not verify signature.", self.logTag); + // TODO: + DDLogError(@"%@ could not verify signature.", self.logTag); + // OWSProdLogAndFail(@"%@ could not verify signature.", self.logTag); + // return NO; + } + + SignatureBodyEntity *_Nullable signatureBodyEntity = [self parseSignatureBodyEntity:signatureBody]; + if (!signatureBodyEntity) { + OWSProdLogAndFail(@"%@ could not parse signature body.", self.logTag); + 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) { + OWSProdLogAndFail(@"%@ isvEnclaveQuoteBody has unexpected length.", self.logTag); + return NO; + } + if (quoteData.length < kQuoteBodyComparisonLength) { + OWSProdLogAndFail(@"%@ quoteData has unexpected length.", self.logTag); + return NO; + } + NSData *isvEnclaveQuoteBodyForComparison = + [signatureBodyEntity.isvEnclaveQuoteBody subdataWithRange:NSMakeRange(0, kQuoteBodyComparisonLength)]; + NSData *quoteDataForComparison = [quoteData subdataWithRange:NSMakeRange(0, kQuoteBodyComparisonLength)]; + if (![isvEnclaveQuoteBodyForComparison ows_constantTimeIsEqualToData:quoteDataForComparison]) { + OWSProdLogAndFail(@"%@ isvEnclaveQuoteBody and quoteData do not match.", self.logTag); + return NO; + } + + // TODO: Before going to production, remove GROUP_OUT_OF_DATE. + if (![@"OK" isEqualToString:signatureBodyEntity.isvEnclaveQuoteStatus] + && ![@"GROUP_OUT_OF_DATE" isEqualToString:signatureBodyEntity.isvEnclaveQuoteStatus]) { + OWSProdLogAndFail( + @"%@ invalid isvEnclaveQuoteStatus: %@.", self.logTag, signatureBodyEntity.isvEnclaveQuoteStatus); + 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) { + OWSProdLogAndFail(@"%@ could not parse signature body timestamp.", self.logTag); + 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) { + OWSProdLogAndFail(@"%@ Signature is expired.", 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); - // - // 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())); - // } - // - // 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; } +- (nullable SignatureBodyEntity *)parseSignatureBodyEntity:(NSString *)signatureBody +{ + OWSAssert(signatureBody.length > 0); + + NSError *error = nil; + NSDictionary *_Nullable jsonDict = + [NSJSONSerialization JSONObjectWithData:[signatureBody dataUsingEncoding:NSUTF8StringEncoding] + options:0 + error:&error]; + if (error || ![jsonDict isKindOfClass:[NSDictionary class]]) { + OWSProdLogAndFail(@"%@ could not parse signature body JSON: %@.", self.logTag, error); + return nil; + } + NSString *_Nullable timestamp = [jsonDict stringForKey:@"timestamp"]; + if (timestamp.length < 1) { + OWSProdLogAndFail(@"%@ could not parse signature timestamp.", self.logTag); + return nil; + } + NSData *_Nullable isvEnclaveQuoteBody = [jsonDict base64DataForKey:@"isvEnclaveQuoteBody"]; + if (isvEnclaveQuoteBody.length < 1) { + OWSProdLogAndFail(@"%@ could not parse signature isvEnclaveQuoteBody.", self.logTag); + return nil; + } + NSString *_Nullable isvEnclaveQuoteStatus = [jsonDict stringForKey:@"isvEnclaveQuoteStatus"]; + if (isvEnclaveQuoteStatus.length < 1) { + OWSProdLogAndFail(@"%@ could not parse signature isvEnclaveQuoteStatus.", self.logTag); + return nil; + } + + SignatureBodyEntity *result = [SignatureBodyEntity new]; + result.isvEnclaveQuoteBody = isvEnclaveQuoteBody; + result.isvEnclaveQuoteStatus = isvEnclaveQuoteStatus; + result.timestamp = timestamp; + return result; +} + - (BOOL)verifyServerQuote:(CDSQuote *)quote keys:(RemoteAttestationKeys *)keys enclaveId:(NSString *)enclaveId { OWSAssert(quote);