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 <CommonCrypto/CommonCrypto.h>
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 <Curve25519Kit/Curve25519.h>
@@ -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<String> 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);