diff --git a/SignalServiceKit/src/Contacts/CDSSigningCertificate.m b/SignalServiceKit/src/Contacts/CDSSigningCertificate.m index e25b4ad56..cfe7a2abf 100644 --- a/SignalServiceKit/src/Contacts/CDSSigningCertificate.m +++ b/SignalServiceKit/src/Contacts/CDSSigningCertificate.m @@ -3,8 +3,10 @@ // #import "CDSSigningCertificate.h" +#import "Cryptography.h" #import "NSData+Base64.h" #import "NSData+OWS.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -95,14 +97,12 @@ NS_ASSUME_NONNULL_BEGIN 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); @@ -229,54 +229,31 @@ NS_ASSUME_NONNULL_BEGIN return certificateData; } -- (BOOL)verifySignatureOfBody:(NSString *)body signature:(NSData *)theirSignature +- (BOOL)verifySignatureOfBody:(NSString *)body signature:(NSData *)signature { - 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; - } + size_t signedHashBytesSize = SecKeyGetBlockSize(self.publicKey); + const void *signedHashBytes = [signature bytes]; - 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); + NSData *_Nullable hashData = [Cryptography computeSHA256Digest:bodyData]; + if (hashData.length != CC_SHA256_DIGEST_LENGTH) { + OWSProdLogAndFail(@"%@ could not SHA256 for signature verification.", self.logTag); return NO; } + size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH; + const void *hashBytes = [hashData bytes]; + + OSStatus status = SecKeyRawVerify( + self.publicKey, kSecPaddingPKCS1SHA256, hashBytes, hashBytesSize, signedHashBytes, signedHashBytesSize); + + BOOL isValid = status == errSecSuccess; if (!isValid) { OWSProdLogAndFail(@"%@ signatures do not match.", self.logTag); return NO; } - DDLogVerbose(@"%@ signature verification succeeded.", self.logTag); return YES; } diff --git a/SignalServiceKit/src/Contacts/ContactDiscoveryService.m b/SignalServiceKit/src/Contacts/ContactDiscoveryService.m index aa738d7b4..2acf7e5d5 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]; @@ -207,7 +234,6 @@ NS_ASSUME_NONNULL_BEGIN TSRequest *request = [OWSRequestFactory remoteAttestationAuthRequest]; [[TSNetworkManager sharedManager] makeRequest:request success:^(NSURLSessionDataTask *task, id responseDict) { - DDLogVerbose(@"%@ remote attestation auth success: %@", self.logTag, responseDict); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ RemoteAttestationAuth *_Nullable auth = [self parseAuthToken:responseDict]; @@ -231,23 +257,15 @@ NS_ASSUME_NONNULL_BEGIN } NSDictionary *responseDict = response; - NSString *_Nullable token = responseDict[@"token"]; - if (![token isKindOfClass:[NSString class]]) { - OWSProdLogAndFail(@"%@ missing or invalid token.", self.logTag); - return nil; - } + NSString *_Nullable token = [responseDict stringForKey:@"token"]; if (token.length < 1) { - OWSProdLogAndFail(@"%@ empty token.", self.logTag); + OWSProdLogAndFail(@"%@ missing or empty token.", self.logTag); return nil; } - NSString *_Nullable username = responseDict[@"username"]; - if (![username isKindOfClass:[NSString class]]) { - OWSProdLogAndFail(@"%@ missing or invalid username.", self.logTag); - return nil; - } + NSString *_Nullable username = [responseDict stringForKey:@"username"]; if (username.length < 1) { - OWSProdLogAndFail(@"%@ empty username.", self.logTag); + OWSProdLogAndFail(@"%@ missing or empty username.", self.logTag); return nil; } @@ -270,8 +288,6 @@ NS_ASSUME_NONNULL_BEGIN authToken:auth.authToken]; [[TSNetworkManager sharedManager] makeRequest:request success:^(NSURLSessionDataTask *task, id responseJson) { - DDLogVerbose(@"%@ remote attestation success: %@", self.logTag, responseJson); - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // TODO: Handle result. [self parseAttestationResponseJson:responseJson @@ -302,12 +318,11 @@ NS_ASSUME_NONNULL_BEGIN } NSDictionary *responseHeaders = ((NSHTTPURLResponse *)response).allHeaderFields; - NSString *_Nullable cookie = responseHeaders[@"Set-Cookie"]; - if (![cookie isKindOfClass:[NSString class]]) { + NSString *_Nullable cookie = [responseHeaders stringForKey:@"Set-Cookie"]; + if (cookie.length < 1) { 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 +330,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) { @@ -358,7 +367,7 @@ NS_ASSUME_NONNULL_BEGIN OWSProdLogAndFail(@"%@ couldn't parse quote data.", self.logTag); return nil; } - NSString *_Nullable signatureBody = responseDict[@"signatureBody"]; + NSString *_Nullable signatureBody = [responseDict stringForKey:@"signatureBody"]; if (![signatureBody isKindOfClass:[NSString class]]) { OWSProdLogAndFail(@"%@ couldn't parse signatureBody.", self.logTag); return nil; @@ -368,7 +377,7 @@ NS_ASSUME_NONNULL_BEGIN OWSProdLogAndFail(@"%@ couldn't parse signature.", self.logTag); return nil; } - NSString *_Nullable encodedCertificates = responseDict[@"certificates"]; + NSString *_Nullable encodedCertificates = [responseDict stringForKey:@"certificates"]; if (![encodedCertificates isKindOfClass:[NSString class]]) { OWSProdLogAndFail(@"%@ couldn't parse encodedCertificates.", self.logTag); return nil; @@ -409,11 +418,18 @@ 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; } + RemoteAttestation *result = [RemoteAttestation new]; + result.cookie = cookie; + result.keys = keys; + result.requestId = requestId; + + DDLogVerbose(@"%@ remote attestation complete.", self.logTag); + //+ RemoteAttestation remoteAttestation = new RemoteAttestation(requestId, keys); //+ List addressBook = new LinkedList<>(); //+ @@ -437,23 +453,18 @@ NS_ASSUME_NONNULL_BEGIN //+ //+ return results; - RemoteAttestation *result = [RemoteAttestation new]; - result.cookie = cookie; - result.keys = keys; - result.requestId = requestId; - return result; } - (BOOL)verifyIasSignatureWithCertificates:(NSString *)certificates signatureBody:(NSString *)signatureBody signature:(NSData *)signature - 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) { @@ -464,44 +475,102 @@ NS_ASSUME_NONNULL_BEGIN 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); - // - // 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); - // } + + 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; + } 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);