Merge branch 'charlesmchen/remoteAttestation2'

This commit is contained in:
Matthew Chen 2018-07-23 13:00:50 -04:00
commit 2427df23f1
2 changed files with 152 additions and 106 deletions

View file

@ -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;
}

View file

@ -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);