Merge branch 'charlesmchen/remoteAttestation'

pull/1/head
Matthew Chen 7 years ago
commit e88e7ef759

@ -1 +1 @@
Subproject commit e9badd4baacc97c7159f7f8ffd2a51f4581fb869
Subproject commit 0a0ef07e7afa563b97ea03d5978b78b72a50ecf6

@ -4,7 +4,7 @@
#import "OWSDatabaseConverterTest.h"
#import <Curve25519Kit/Randomness.h>
#import <SignalServiceKit/NSData+hexString.h>
#import <SignalServiceKit/NSData+OWS.h>
#import <SignalServiceKit/OWSFileSystem.h>
#import <SignalServiceKit/OWSStorage.h>
#import <SignalServiceKit/YapDatabaseConnection+OWS.h>

@ -11,7 +11,7 @@
#import <SignalServiceKit/Cryptography.h>
#import <SignalServiceKit/MIMETypeUtil.h>
#import <SignalServiceKit/NSData+Image.h>
#import <SignalServiceKit/NSData+hexString.h>
#import <SignalServiceKit/NSData+OWS.h>
#import <SignalServiceKit/NSDate+OWS.h>
#import <SignalServiceKit/NSNotificationCenter+OWS.h>
#import <SignalServiceKit/OWSFileSystem.h>

@ -6,7 +6,7 @@
#import "NSString+OWS.h"
#import <SignalServiceKit/AppContext.h>
#import <SignalServiceKit/Cryptography.h>
#import <SignalServiceKit/NSData+hexString.h>
#import <SignalServiceKit/NSData+OWS.h>
#import <SignalServiceKit/NSNotificationCenter+OWS.h>
#import <SignalServiceKit/TSAccountManager.h>
#import <YapDatabase/YapDatabaseConnection.h>

@ -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"

@ -0,0 +1,32 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface CDSQuote : NSObject
@property (nonatomic, readonly) uint16_t version;
@property (nonatomic, readonly) uint16_t signType;
@property (nonatomic, readonly) BOOL isSigLinkable;
@property (nonatomic, readonly) uint32_t gid;
@property (nonatomic, readonly) uint16_t qeSvn;
@property (nonatomic, readonly) uint16_t pceSvn;
@property (nonatomic, readonly) NSData *basename;
@property (nonatomic, readonly) NSData *cpuSvn;
@property (nonatomic, readonly) uint64_t flags;
@property (nonatomic, readonly) uint64_t xfrm;
@property (nonatomic, readonly) NSData *mrenclave;
@property (nonatomic, readonly) NSData *mrsigner;
@property (nonatomic, readonly) uint16_t isvProdId;
@property (nonatomic, readonly) uint16_t isvSvn;
@property (nonatomic, readonly) NSData *reportData;
@property (nonatomic, readonly) NSData *signature;
+ (nullable CDSQuote *)parseQuoteFromData:(NSData *)quoteData;
- (BOOL)isDebugQuote;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,330 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "CDSQuote.h"
NS_ASSUME_NONNULL_BEGIN
@interface ByteParser : NSObject
@end
#pragma mark -
@interface ByteParser ()
@property (nonatomic, readonly) BOOL littleEndian;
@property (nonatomic, readonly) NSData *data;
@property (nonatomic) NSUInteger cursor;
@property (nonatomic) BOOL hasError;
@end
#pragma mark -
@implementation ByteParser
- (instancetype)initWithData:(NSData *)data littleEndian:(BOOL)littleEndian
{
if (self = [super init]) {
_littleEndian = littleEndian;
_data = data;
}
return self;
}
#pragma mark - Short
- (uint16_t)shortAtIndex:(NSUInteger)index
{
uint16_t value;
const size_t valueSize = sizeof(value);
OWSAssert(valueSize == 2);
if (index + valueSize > self.data.length) {
self.hasError = YES;
return 0;
}
[self.data getBytes:&value range:NSMakeRange(index, valueSize)];
if (self.littleEndian) {
return CFSwapInt16LittleToHost(value);
} else {
return CFSwapInt16BigToHost(value);
}
}
- (uint16_t)nextShort
{
uint16_t value = [self shortAtIndex:self.cursor];
self.cursor += sizeof(value);
return value;
}
#pragma mark - Int
- (uint32_t)intAtIndex:(NSUInteger)index
{
uint32_t value;
const size_t valueSize = sizeof(value);
OWSAssert(valueSize == 4);
if (index + valueSize > self.data.length) {
self.hasError = YES;
return 0;
}
[self.data getBytes:&value range:NSMakeRange(index, valueSize)];
if (self.littleEndian) {
return CFSwapInt32LittleToHost(value);
} else {
return CFSwapInt32BigToHost(value);
}
}
- (uint32_t)nextInt
{
uint32_t value = [self intAtIndex:self.cursor];
self.cursor += sizeof(value);
return value;
}
#pragma mark - Long
- (uint64_t)longAtIndex:(NSUInteger)index
{
uint64_t value;
const size_t valueSize = sizeof(value);
OWSAssert(valueSize == 8);
if (index + valueSize > self.data.length) {
self.hasError = YES;
return 0;
}
[self.data getBytes:&value range:NSMakeRange(index, valueSize)];
if (self.littleEndian) {
return CFSwapInt64LittleToHost(value);
} else {
return CFSwapInt64BigToHost(value);
}
}
- (uint64_t)nextLong
{
uint64_t value = [self longAtIndex:self.cursor];
self.cursor += sizeof(value);
return value;
}
#pragma mark -
- (BOOL)readZero:(NSUInteger)length
{
NSData *_Nullable subdata = [self readBytes:length];
if (!subdata) {
return NO;
}
uint8_t bytes[length];
[subdata getBytes:bytes range:NSMakeRange(0, length)];
for (int i = 0; i < length; i++) {
if (bytes[i] != 0) {
return NO;
}
}
return YES;
}
- (nullable NSData *)readBytes:(NSUInteger)length
{
NSUInteger index = self.cursor;
if (index + length > self.data.length) {
self.hasError = YES;
return nil;
}
NSData *_Nullable subdata = [self.data subdataWithRange:NSMakeRange(index, length)];
self.cursor += length;
return subdata;
}
@end
#pragma mark -
static const long SGX_FLAGS_INITTED = 0x0000000000000001L;
static const long SGX_FLAGS_DEBUG = 0x0000000000000002L;
static const long SGX_FLAGS_MODE64BIT = 0x0000000000000004L;
static const long SGX_FLAGS_PROVISION_KEY = 0x0000000000000004L;
static const long SGX_FLAGS_EINITTOKEN_KEY = 0x0000000000000004L;
static const long SGX_FLAGS_RESERVED = 0xFFFFFFFFFFFFFFC8L;
static const long SGX_XFRM_LEGACY = 0x0000000000000003L;
static const long SGX_XFRM_AVX = 0x0000000000000006L;
static const long SGX_XFRM_RESERVED = 0xFFFFFFFFFFFFFFF8L;
#pragma mark -
@interface CDSQuote ()
@property (nonatomic) uint16_t version;
@property (nonatomic) uint16_t signType;
@property (nonatomic) BOOL isSigLinkable;
@property (nonatomic) uint32_t gid;
@property (nonatomic) uint16_t qeSvn;
@property (nonatomic) uint16_t pceSvn;
@property (nonatomic) NSData *basename;
@property (nonatomic) NSData *cpuSvn;
@property (nonatomic) uint64_t flags;
@property (nonatomic) uint64_t xfrm;
@property (nonatomic) NSData *mrenclave;
@property (nonatomic) NSData *mrsigner;
@property (nonatomic) uint16_t isvProdId;
@property (nonatomic) uint16_t isvSvn;
@property (nonatomic) NSData *reportData;
@property (nonatomic) NSData *signature;
@end
#pragma mark -
@implementation CDSQuote
+ (nullable CDSQuote *)parseQuoteFromData:(NSData *)quoteData
{
ByteParser *_Nullable parser = [[ByteParser alloc] initWithData:quoteData littleEndian:YES];
uint16_t version = parser.nextShort;
if (version < 1 || version > 2) {
OWSProdLogAndFail(@"%@ unexpected quote version: %d", self.logTag, (int)version);
return nil;
}
uint16_t signType = parser.nextShort;
if ((signType & ~1) != 0) {
OWSProdLogAndFail(@"%@ invalid signType: %d", self.logTag, (int)signType);
return nil;
}
BOOL isSigLinkable = signType == 1;
uint32_t gid = parser.nextInt;
uint16_t qeSvn = parser.nextShort;
uint16_t pceSvn = 0;
if (version > 1) {
pceSvn = parser.nextShort;
} else {
if (![parser readZero:2]) {
OWSProdLogAndFail(@"%@ non-zero pceSvn.", self.logTag);
return nil;
}
}
if (![parser readZero:4]) {
OWSProdLogAndFail(@"%@ non-zero xeid.", self.logTag);
return nil;
}
NSData *_Nullable basename = [parser readBytes:32];
if (!basename) {
OWSProdLogAndFail(@"%@ couldn't read basename.", self.logTag);
return nil;
}
// report_body
NSData *_Nullable cpuSvn = [parser readBytes:16];
if (!cpuSvn) {
OWSProdLogAndFail(@"%@ couldn't read cpuSvn.", self.logTag);
return nil;
}
if (![parser readZero:4]) {
OWSProdLogAndFail(@"%@ non-zero misc_select.", self.logTag);
return nil;
}
if (![parser readZero:28]) {
OWSProdLogAndFail(@"%@ non-zero reserved1.", self.logTag);
return nil;
}
uint64_t flags = parser.nextLong;
if ((flags & SGX_FLAGS_RESERVED) != 0 || (flags & SGX_FLAGS_INITTED) == 0 || (flags & SGX_FLAGS_MODE64BIT) == 0) {
OWSProdLogAndFail(@"%@ invalid flags.", self.logTag);
return nil;
}
uint64_t xfrm = parser.nextLong;
if ((xfrm & SGX_XFRM_RESERVED) != 0) {
OWSProdLogAndFail(@"%@ invalid xfrm.", self.logTag);
return nil;
}
NSData *_Nullable mrenclave = [parser readBytes:32];
if (!mrenclave) {
OWSProdLogAndFail(@"%@ couldn't read mrenclave.", self.logTag);
return nil;
}
if (![parser readZero:32]) {
OWSProdLogAndFail(@"%@ non-zero reserved2.", self.logTag);
return nil;
}
NSData *_Nullable mrsigner = [parser readBytes:32];
if (!mrsigner) {
OWSProdLogAndFail(@"%@ couldn't read mrsigner.", self.logTag);
return nil;
}
if (![parser readZero:96]) {
OWSProdLogAndFail(@"%@ non-zero reserved3.", self.logTag);
return nil;
}
uint16_t isvProdId = parser.nextShort;
uint16_t isvSvn = parser.nextShort;
if (![parser readZero:60]) {
OWSProdLogAndFail(@"%@ non-zero reserved4.", self.logTag);
return nil;
}
NSData *_Nullable reportData = [parser readBytes:64];
if (!reportData) {
OWSProdLogAndFail(@"%@ couldn't read reportData.", self.logTag);
return nil;
}
// quote signature
uint32_t signatureLength = parser.nextInt;
if (signatureLength != quoteData.length - 436) {
OWSProdLogAndFail(@"%@ invalid signatureLength.", self.logTag);
return nil;
}
NSData *_Nullable signature = [parser readBytes:signatureLength];
if (!signature) {
OWSProdLogAndFail(@"%@ couldn't read signature.", self.logTag);
return nil;
}
if (parser.hasError) {
return nil;
}
CDSQuote *quote = [CDSQuote new];
quote.version = version;
quote.signType = signType;
quote.isSigLinkable = isSigLinkable;
quote.gid = gid;
quote.qeSvn = qeSvn;
quote.pceSvn = pceSvn;
quote.basename = basename;
quote.cpuSvn = cpuSvn;
quote.flags = flags;
quote.xfrm = xfrm;
quote.mrenclave = mrenclave;
quote.mrsigner = mrsigner;
quote.isvProdId = isvProdId;
quote.isvSvn = isvSvn;
quote.reportData = reportData;
quote.signature = signature;
return quote;
}
- (BOOL)isDebugQuote
{
return (self.flags & SGX_FLAGS_DEBUG) != 0;
}
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,15 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface CDSSigningCertificate : NSObject
+ (nullable CDSSigningCertificate *)parseCertificateFromPem:(NSString *)certificatePem;
- (BOOL)verifySignatureOfBody:(NSString *)body signature:(NSData *)theirSignature;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,297 @@
//
// 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<NSData *> *_Nullable anchorCertificates = [self anchorCertificates];
if (anchorCertificates.count < 1) {
OWSProdLogAndFail(@"%@ Could not load anchor certificates.", self.logTag);
return nil;
}
NSArray<NSData *> *_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;
}
// `kSecTrustResultUnspecified` is confusingly named. It indicates success.
// See the comments in the header where it is defined.
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<NSData *> *)convertPemToDer:(NSString *)pemString
{
NSMutableArray<NSData *> *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<NSData *> *)anchorCertificates
{
static NSArray<NSData *> *anchorCertificates = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 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);
OWSRaiseException(@"OWSSignalService_CouldNotLoadCertificate", @"%s", __PRETTY_FUNCTION__);
} else {
anchorCertificates = @[ anchorCertificate ];
}
});
return anchorCertificates;
}
+ (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

@ -0,0 +1,17 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface ContactDiscoveryService : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)sharedService;
- (void)testService;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,607 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "ContactDiscoveryService.h"
#import "CDSQuote.h"
#import "CDSSigningCertificate.h"
#import "Cryptography.h"
#import "NSData+OWS.h"
#import "OWSRequestFactory.h"
#import "TSNetworkManager.h"
#import <Curve25519Kit/Curve25519.h>
#import <HKDFKit/HKDFKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface RemoteAttestationAuth : NSObject
@property (nonatomic) NSString *username;
@property (nonatomic) NSString *authToken;
@end
#pragma mark -
@implementation RemoteAttestationAuth
@end
#pragma mark -
@interface RemoteAttestationKeys : NSObject
@property (nonatomic) ECKeyPair *keyPair;
@property (nonatomic) NSData *serverEphemeralPublic;
@property (nonatomic) NSData *serverStaticPublic;
@property (nonatomic) NSData *clientKey;
@property (nonatomic) NSData *serverKey;
@end
#pragma mark -
@implementation RemoteAttestationKeys
+ (nullable RemoteAttestationKeys *)keysForKeyPair:(ECKeyPair *)keyPair
serverEphemeralPublic:(NSData *)serverEphemeralPublic
serverStaticPublic:(NSData *)serverStaticPublic
{
RemoteAttestationKeys *keys = [RemoteAttestationKeys new];
keys.keyPair = keyPair;
keys.serverEphemeralPublic = serverEphemeralPublic;
keys.serverStaticPublic = serverStaticPublic;
if (![keys deriveKeys]) {
return nil;
}
return keys;
}
// Returns YES on success.
- (BOOL)deriveKeys
{
NSData *ephemeralToEphemeral =
[Curve25519 generateSharedSecretFromPublicKey:self.serverEphemeralPublic andKeyPair:self.keyPair];
NSData *ephemeralToStatic =
[Curve25519 generateSharedSecretFromPublicKey:self.serverStaticPublic andKeyPair:self.keyPair];
NSData *masterSecret = [ephemeralToEphemeral dataByAppendingData:ephemeralToStatic];
NSData *publicKeys = [[self.keyPair.publicKey dataByAppendingData:self.serverEphemeralPublic]
dataByAppendingData:self.serverStaticPublic];
NSData *_Nullable derivedMaterial;
@try {
derivedMaterial = [HKDFKit deriveKey:masterSecret info:nil salt:publicKeys outputSize:ECCKeyLength * 2];
} @catch (NSException *exception) {
DDLogError(@"%@ could not derive service key: %@", self.logTag, exception);
return NO;
}
if (!derivedMaterial) {
OWSProdLogAndFail(@"%@ missing derived service key.", self.logTag);
return NO;
}
if (derivedMaterial.length != ECCKeyLength * 2) {
OWSProdLogAndFail(@"%@ derived service key has unexpected length.", self.logTag);
return NO;
}
NSData *_Nullable clientKey = [derivedMaterial subdataWithRange:NSMakeRange(ECCKeyLength * 0, ECCKeyLength)];
NSData *_Nullable serverKey = [derivedMaterial subdataWithRange:NSMakeRange(ECCKeyLength * 1, ECCKeyLength)];
if (clientKey.length != ECCKeyLength) {
OWSProdLogAndFail(@"%@ clientKey has unexpected length.", self.logTag);
return NO;
}
if (serverKey.length != ECCKeyLength) {
OWSProdLogAndFail(@"%@ serverKey has unexpected length.", self.logTag);
return NO;
}
self.clientKey = clientKey;
self.serverKey = serverKey;
return YES;
}
@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 -
@implementation RemoteAttestation
@end
#pragma mark -
@interface NSDictionary (CDS)
@end
#pragma mark -
@implementation NSDictionary (CDS)
- (nullable NSData *)base64DataForKey:(NSString *)key
{
NSString *_Nullable valueString = self[key];
if (![valueString isKindOfClass:[NSString class]]) {
OWSProdLogAndFail(@"%@ couldn't parse base 64 value for key: %@", self.logTag, key);
return nil;
}
NSData *_Nullable valueData = [[NSData alloc] initWithBase64EncodedString:valueString options:0];
if (!valueData) {
OWSProdLogAndFail(@"%@ couldn't decode base 64 value for key: %@", self.logTag, key);
return nil;
}
return valueData;
}
- (nullable NSData *)base64DataForKey:(NSString *)key expectedLength:(NSUInteger)expectedLength
{
NSData *_Nullable valueData = [self base64DataForKey:key];
if (valueData && valueData.length != expectedLength) {
OWSProdLogAndFail(@"%@ decoded base 64 value for key: %@, has unexpected length: %zd != %zd",
self.logTag,
key,
valueData.length,
expectedLength);
return nil;
}
return valueData;
}
@end
#pragma mark -
@implementation ContactDiscoveryService
+ (instancetype)sharedService {
static dispatch_once_t onceToken;
static id sharedInstance = nil;
dispatch_once(&onceToken, ^{
sharedInstance = [[ContactDiscoveryService alloc] initDefault];
});
return sharedInstance;
}
- (instancetype)initDefault
{
self = [super init];
if (!self) {
return self;
}
OWSSingletonAssert();
return self;
}
- (void)testService
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performRemoteAttestation];
});
}
- (void)performRemoteAttestation
{
[self performRemoteAttestationAuth];
}
// TODO: Add success and failure?
- (void)performRemoteAttestationAuth
{
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];
if (!auth) {
DDLogError(@"%@ remote attestation auth could not be parsed: %@", self.logTag, responseDict);
return;
}
[self performRemoteAttestationWithAuth:auth];
});
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
DDLogVerbose(@"%@ remote attestation auth failure: %zd", self.logTag, response.statusCode);
}];
}
- (nullable RemoteAttestationAuth *)parseAuthToken:(id)response
{
if (![response isKindOfClass:[NSDictionary class]]) {
return nil;
}
NSDictionary *responseDict = response;
NSString *_Nullable token = responseDict[@"token"];
if (![token isKindOfClass:[NSString class]]) {
OWSProdLogAndFail(@"%@ missing or invalid token.", self.logTag);
return nil;
}
if (token.length < 1) {
OWSProdLogAndFail(@"%@ 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;
}
if (username.length < 1) {
OWSProdLogAndFail(@"%@ empty username.", self.logTag);
return nil;
}
RemoteAttestationAuth *result = [RemoteAttestationAuth new];
result.username = username;
result.authToken = token;
return result;
}
- (void)performRemoteAttestationWithAuth:(RemoteAttestationAuth *)auth
{
ECKeyPair *keyPair = [Curve25519 generateKeyPair];
// TODO:
NSString *enclaveId = @"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9";
TSRequest *request = [OWSRequestFactory remoteAttestationRequest:keyPair
enclaveId:enclaveId
username:auth.username
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
response:task.response
keyPair:keyPair
enclaveId:enclaveId];
});
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
DDLogVerbose(@"%@ remote attestation failure: %zd", self.logTag, response.statusCode);
}];
}
- (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;
NSString *_Nullable cookie = responseHeaders[@"Set-Cookie"];
if (![cookie isKindOfClass:[NSString class]]) {
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
// 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);
}
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) {
OWSProdLogAndFail(@"%@ couldn't parse serverEphemeralPublic.", self.logTag);
return nil;
}
NSData *_Nullable serverStaticPublic = [responseDict base64DataForKey:@"serverStaticPublic" expectedLength:32];
if (!serverStaticPublic) {
OWSProdLogAndFail(@"%@ couldn't parse serverStaticPublic.", self.logTag);
return nil;
}
NSData *_Nullable encryptedRequestId = [responseDict base64DataForKey:@"ciphertext"];
if (!encryptedRequestId) {
OWSProdLogAndFail(@"%@ couldn't parse encryptedRequestId.", self.logTag);
return nil;
}
NSData *_Nullable encryptedRequestIv = [responseDict base64DataForKey:@"iv" expectedLength:12];
if (!encryptedRequestIv) {
OWSProdLogAndFail(@"%@ couldn't parse encryptedRequestIv.", self.logTag);
return nil;
}
NSData *_Nullable encryptedRequestTag = [responseDict base64DataForKey:@"tag" expectedLength:16];
if (!encryptedRequestTag) {
OWSProdLogAndFail(@"%@ couldn't parse encryptedRequestTag.", self.logTag);
return nil;
}
NSData *_Nullable quoteData = [responseDict base64DataForKey:@"quote"];
if (!quoteData) {
OWSProdLogAndFail(@"%@ couldn't parse quote data.", self.logTag);
return nil;
}
NSString *_Nullable signatureBody = responseDict[@"signatureBody"];
if (![signatureBody isKindOfClass:[NSString class]]) {
OWSProdLogAndFail(@"%@ couldn't parse signatureBody.", self.logTag);
return nil;
}
NSData *_Nullable signature = [responseDict base64DataForKey:@"signature"];
if (!signature) {
OWSProdLogAndFail(@"%@ couldn't parse signature.", self.logTag);
return nil;
}
NSString *_Nullable encodedCertificates = responseDict[@"certificates"];
if (![encodedCertificates isKindOfClass:[NSString class]]) {
OWSProdLogAndFail(@"%@ couldn't parse encodedCertificates.", self.logTag);
return nil;
}
NSString *_Nullable certificates = [encodedCertificates stringByRemovingPercentEncoding];
if (!certificates) {
OWSProdLogAndFail(@"%@ couldn't parse certificates.", self.logTag);
return nil;
}
RemoteAttestationKeys *_Nullable keys = [RemoteAttestationKeys keysForKeyPair:keyPair
serverEphemeralPublic:serverEphemeralPublic
serverStaticPublic:serverStaticPublic];
if (!keys) {
OWSProdLogAndFail(@"%@ couldn't derive keys.", self.logTag);
return nil;
}
CDSQuote *_Nullable quote = [CDSQuote parseQuoteFromData:quoteData];
if (!quote) {
OWSProdLogAndFail(@"%@ couldn't parse quote.", self.logTag);
return nil;
}
NSData *_Nullable requestId = [self decryptRequestId:encryptedRequestId
encryptedRequestIv:encryptedRequestIv
encryptedRequestTag:encryptedRequestTag
keys:keys];
if (!requestId) {
OWSProdLogAndFail(@"%@ couldn't decrypt request id.", self.logTag);
return nil;
}
if (![self verifyServerQuote:quote keys:keys enclaveId:enclaveId]) {
OWSProdLogAndFail(@"%@ couldn't verify quote.", self.logTag);
return nil;
}
if (![self verifyIasSignatureWithCertificates:certificates
signatureBody:signatureBody
signature:signature
quote:quote]) {
OWSProdLogAndFail(@"%@ couldn't verify ias signature.", self.logTag);
return nil;
}
//+ RemoteAttestation remoteAttestation = new RemoteAttestation(requestId, keys);
//+ List<String> 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<String> addressBookIterator = addressBook.iterator();
//+ List<String> 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)verifyIasSignatureWithCertificates:(NSString *)certificates
signatureBody:(NSString *)signatureBody
signature:(NSData *)signature
quote:(CDSQuote *)quote
{
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);
//
// 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;
}
- (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
encryptedRequestIv:(NSData *)encryptedRequestIv
encryptedRequestTag:(NSData *)encryptedRequestTag
keys:(RemoteAttestationKeys *)keys
{
OWSAssert(encryptedRequestId.length > 0);
OWSAssert(encryptedRequestIv.length > 0);
OWSAssert(encryptedRequestTag.length > 0);
OWSAssert(keys);
OWSAES256Key *_Nullable key = [OWSAES256Key keyWithData:keys.serverKey];
if (!key) {
OWSProdLogAndFail(@"%@ invalid server key.", self.logTag);
return nil;
}
NSData *_Nullable decryptedData = [Cryptography decryptAESGCMWithInitializationVector:encryptedRequestIv
ciphertext:encryptedRequestId
authTag:encryptedRequestTag
key:key];
if (!decryptedData) {
OWSProdLogAndFail(@"%@ couldn't decrypt request id.", self.logTag);
return nil;
}
return decryptedData;
}
// A successful (HTTP 200) response json object consists of:
// serverEphemeralPublic: (32 bytes, base64) an ephemeral curve25519 public key generated by the server
// serverStaticPublic: (32 bytes, base64) a static curve25519 public key generated by the server
// ciphertext: (variable length, base64) a "request id" to be decrypted by the client (see below for derivation
// of server_key) (ciphertext, tag) = AES-256-GCM(key=server_key, plaintext=requestId, AAD=(), iv) iv: (12
// bytes, base64) an IV for encrypted ciphertext tag: (16 bytes, base64) a MAC for encrypted ciphertext
// quote: (variable length, base64) a binary structure from an Intel CPU containing runtime information
// about a running enclave signatureBody: (json object) a response from Intel Attestation Services attesting to
// the genuineness of the Quote signature: (base64) a signature over signatureBody, with public key in
// corresponding signing certificate certificates: (url-encoded PEM) signing certificate chain The response also
// contains HTTP session cookies which must be preserved for exactly one corresponding Contact Discovery request.
//
// After this PUT response is received, the client must:
// parse and verify fields in quote (see sample client code for parsing details):
// report_data: (64 bytes) must equal (serverStaticPublic || 0 ...)
// mrenclave: (32 bytes) must equal the request's enclaveId
// flags: (8 bytes) debug flag must be unset, as well as being validated against expected values during
// parsing all other fields must be validated against a range of expected values during parsing (as shown in example
// parsing code), but are otherwise ignored See client/src/main/java/org/whispersystems/contactdiscovery/Quote.java parse
// and verify fields in signatureBody json object: isvEnclaveQuoteBody: (base64) must equal quote
// isvEnclaveQuoteStatus: (ascii) must equal "OK"
//"GROUP_OUT_OF_DATE" may be allowed for testing only
// timestamp: (ascii) UTC timestamp formatted as "yyyy-MM-dd'T'HH:mm:ss.SSSSSS" which must fall within the
// last 24h verify validity of signature over signatureBody using the public key contained in the leaf signing
// certificate in certificates verify signing certificates chain, with fixed trust anchors to be hard-coded in clients
// client/src/main/java/org/whispersystems/contactdiscovery/SigningCertificate.java contains X.509 PKI certificate chain
// validation code to follow
//
// After Curve25519-DH/HKDF key derivation upon the three public keys (client ephemeral private key, server ephemeral
// public key, and server static public key, described below), the client can now decrypt the ciphertext in the Remote
// Attestation Response containing a requestId to be sent along with a Contact Discovery request, and encrypt the body of
// a Contact Discovery request using client_key, bound for the enclave it performed attestation with.
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,24 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "TSRequest.h"
NS_ASSUME_NONNULL_BEGIN
@interface CDSAttestationRequest : TSRequest
@property (nonatomic, readonly) NSString *authToken;
@property (nonatomic, readonly) NSString *username;
- (instancetype)init NS_UNAVAILABLE;
- (TSRequest *)initWithURL:(NSURL *)URL
method:(NSString *)method
parameters:(nullable NSDictionary<NSString *, id> *)parameters
username:(NSString *)username
authToken:(NSString *)authToken;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,29 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "CDSAttestationRequest.h"
NS_ASSUME_NONNULL_BEGIN
@implementation CDSAttestationRequest
- (TSRequest *)initWithURL:(NSURL *)URL
method:(NSString *)method
parameters:(nullable NSDictionary<NSString *, id> *)parameters
username:(NSString *)username
authToken:(NSString *)authToken
{
OWSAssert(authToken.length > 0);
if (self = [super initWithURL:URL method:method parameters:parameters]) {
_username = username;
_authToken = authToken;
}
return self;
}
@end
NS_ASSUME_NONNULL_END

@ -4,6 +4,7 @@
NS_ASSUME_NONNULL_BEGIN
@class ECKeyPair;
@class OWSDevice;
@class PreKeyRecord;
@class SignedPreKeyRecord;
@ -69,6 +70,13 @@ typedef NS_ENUM(NSUInteger, TSVerificationTransport) { TSVerificationTransportVo
signedPreKey:(SignedPreKeyRecord *)signedPreKey
preKeyLastResort:(PreKeyRecord *)preKeyLastResort;
+ (TSRequest *)remoteAttestationRequest:(ECKeyPair *)keyPair
enclaveId:(NSString *)enclaveId
username:(NSString *)username
authToken:(NSString *)authToken;
+ (TSRequest *)remoteAttestationAuthRequest;
@end
NS_ASSUME_NONNULL_END

@ -3,6 +3,7 @@
//
#import "OWSRequestFactory.h"
#import "CDSAttestationRequest.h"
#import "NSData+Base64.h"
#import "OWS2FAManager.h"
#import "OWSDevice.h"
@ -11,6 +12,7 @@
#import "TSRequest.h"
#import <AxolotlKit/NSData+keyVersionByte.h>
#import <AxolotlKit/SignedPreKeyRecord.h>
#import <Curve25519Kit/Curve25519.h>
NS_ASSUME_NONNULL_BEGIN
@ -274,6 +276,34 @@ NS_ASSUME_NONNULL_BEGIN
};
}
+ (TSRequest *)remoteAttestationRequest:(ECKeyPair *)keyPair
enclaveId:(NSString *)enclaveId
username:(NSString *)username
authToken:(NSString *)authToken
{
OWSAssert(keyPair);
OWSAssert(enclaveId.length > 0);
OWSAssert(username.length > 0);
OWSAssert(authToken.length > 0);
NSString *path =
[NSString stringWithFormat:@"https://api.contact-discovery.acton-signal.org/v1/attestation/%@", enclaveId];
return [[CDSAttestationRequest alloc] initWithURL:[NSURL URLWithString:path]
method:@"PUT"
parameters:@{
// We DO NOT prepend the "key type" byte.
@"clientPublic" : [keyPair.publicKey base64EncodedStringWithOptions:0],
}
username:username
authToken:authToken];
}
+ (TSRequest *)remoteAttestationAuthRequest
{
NSString *path = @"/v1/directory/auth";
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
}
@end
NS_ASSUME_NONNULL_END

@ -4,6 +4,7 @@
#import "TSNetworkManager.h"
#import "AppContext.h"
#import "CDSAttestationRequest.h"
#import "NSURLSessionDataTask+StatusCode.h"
#import "OWSSignalService.h"
#import "TSAccountManager.h"
@ -113,7 +114,11 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
[parameters removeObjectForKey:@"AuthKey"];
[sessionManager PUT:request.URL.absoluteString parameters:parameters success:success failure:failure];
} else {
if (request.shouldHaveAuthorizationHeaders) {
if ([request isKindOfClass:[CDSAttestationRequest class]]) {
CDSAttestationRequest *attestationRequest = (CDSAttestationRequest *)request;
[sessionManager.requestSerializer setAuthorizationHeaderFieldWithUsername:attestationRequest.username
password:attestationRequest.authToken];
} else if (request.shouldHaveAuthorizationHeaders) {
[sessionManager.requestSerializer
setAuthorizationHeaderFieldWithUsername:[TSAccountManager localNumber]
password:[TSAccountManager serverAuthToken]];

@ -72,6 +72,11 @@ typedef NS_ENUM(NSInteger, TSMACType) {
+ (nullable NSData *)encryptAESGCMWithData:(NSData *)plaintextData key:(OWSAES256Key *)key;
+ (nullable NSData *)decryptAESGCMWithData:(NSData *)encryptedData key:(OWSAES256Key *)key;
+ (nullable NSData *)decryptAESGCMWithInitializationVector:(NSData *)initializationVector
ciphertext:(NSData *)ciphertext
authTag:(NSData *)authTagFromEncrypt
key:(OWSAES256Key *)key;
@end
NS_ASSUME_NONNULL_END

@ -4,7 +4,7 @@
#import "Cryptography.h"
#import "NSData+Base64.h"
#import "NSData+OWSConstantTimeCompare.h"
#import "NSData+OWS.h"
#import "OWSError.h"
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonHMAC.h>
@ -560,6 +560,11 @@ const NSUInteger kAES256_KeyByteLength = 32;
authTag:(NSData *)authTagFromEncrypt
key:(OWSAES256Key *)key
{
OWSAssert(initializationVector.length == kAESGCM256_IVLength);
OWSAssert(ciphertext.length > 0);
OWSAssert(authTagFromEncrypt.length == kAESGCM256_TagLength);
OWSAssert(key);
NSMutableData *plaintext = [NSMutableData dataWithLength:ciphertext.length];
// Create and initialise the context

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

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

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

@ -1,9 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
@interface NSData (hexString)
- (NSString *)hexadecimalString;
@end

@ -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
Loading…
Cancel
Save