diff --git a/SignalServiceKit/src/Contacts/CDSQuote.h b/SignalServiceKit/src/Contacts/CDSQuote.h new file mode 100644 index 000000000..9e0cc45d0 --- /dev/null +++ b/SignalServiceKit/src/Contacts/CDSQuote.h @@ -0,0 +1,30 @@ +// +// 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; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/CDSQuote.m b/SignalServiceKit/src/Contacts/CDSQuote.m new file mode 100644 index 000000000..91a987380 --- /dev/null +++ b/SignalServiceKit/src/Contacts/CDSQuote.m @@ -0,0 +1,325 @@ +// +// 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; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/ContactDiscoveryService.m b/SignalServiceKit/src/Contacts/ContactDiscoveryService.m index 898008c65..985d5f03f 100644 --- a/SignalServiceKit/src/Contacts/ContactDiscoveryService.m +++ b/SignalServiceKit/src/Contacts/ContactDiscoveryService.m @@ -3,24 +3,159 @@ // #import "ContactDiscoveryService.h" +#import "CDSQuote.h" +#import "Cryptography.h" #import "OWSRequestFactory.h" #import "TSNetworkManager.h" #import +#import NS_ASSUME_NONNULL_BEGIN +@interface NSData (CDS) + +@end + +#pragma mark - + +@implementation NSData (CDS) + +- (NSData *)dataByAppendingData:(NSData *)data +{ + NSMutableData *result = [self mutableCopy]; + [result appendData:data]; + return [result copy]; +} + +@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 +{ + // private final byte[] clientKey = new byte[32]; + // private final byte[] serverKey = new byte[32]; + // + // public RemoteAttestationKeys(Curve25519KeyPair keyPair, byte[] serverPublicEphemeral, byte[] serverPublicStatic) + // { + // + // + (NSData*)generateSharedSecretFromPublicKey:(NSData*)theirPublicKey andKeyPair:(ECKeyPair*)keyPair; + // + // byte[] ephemeralToEphemeral = + // Curve25519.getInstance(Curve25519.BEST).calculateAgreement(serverPublicEphemeral, keyPair.getPrivateKey()); + NSData *ephemeralToEphemeral = + [Curve25519 generateSharedSecretFromPublicKey:self.serverEphemeralPublic andKeyPair:self.keyPair]; + // byte[] ephemeralToStatic = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(serverPublicStatic, + // keyPair.getPrivateKey()); + NSData *ephemeralToStatic = + [Curve25519 generateSharedSecretFromPublicKey:self.serverStaticPublic andKeyPair:self.keyPair]; + + // byte[] masterSecret = ByteUtils.combine(ephemeralToEphemeral, ephemeralToStatic ); + NSData *masterSecret = [ephemeralToEphemeral dataByAppendingData:ephemeralToStatic]; + // byte[] publicKeys = ByteUtils.combine(keyPair.getPublicKey(), serverPublicEphemeral, serverPublicStatic); + 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; + + // HKDFBytesGenerator generator = new HKDFBytesGenerator(new SHA256Digest()); + // generator.init(new HKDFParameters(masterSecret, publicKeys, null)); + // generator.generateBytes(clientKey, 0, clientKey.length); + // generator.generateBytes(serverKey, 0, serverKey.length); +} + +//+ (DHEResult*)DHEKeyAgreement:(id)parameters{ +// NSMutableData *masterKey = [NSMutableData data]; +// +// [masterKey appendData:[self discontinuityBytes]]; +// +// if ([parameters isKindOfClass:[AliceAxolotlParameters class]]) { +// AliceAxolotlParameters *params = (AliceAxolotlParameters*)parameters; +// +// [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirSignedPreKey +// andKeyPair:params.ourIdentityKeyPair]]; [masterKey appendData:[Curve25519 +// generateSharedSecretFromPublicKey:params.theirIdentityKey andKeyPair:params.ourBaseKey]]; [masterKey +// appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirSignedPreKey +// andKeyPair:params.ourBaseKey]]; if (params.theirOneTimePrekey) { +// [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirOneTimePrekey +// andKeyPair:params.ourBaseKey]]; +// } +// } else if ([parameters isKindOfClass:[BobAxolotlParameters class]]){ +// BobAxolotlParameters *params = (BobAxolotlParameters*)parameters; +// +// [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirIdentityKey +// andKeyPair:params.ourSignedPrekey]]; [masterKey appendData:[Curve25519 +// generateSharedSecretFromPublicKey:params.theirBaseKey andKeyPair:params.ourIdentityKeyPair]]; [masterKey +// appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirBaseKey +// andKeyPair:params.ourSignedPrekey]]; if (params.ourOneTimePrekey) { +// [masterKey appendData:[Curve25519 generateSharedSecretFromPublicKey:params.theirBaseKey +// andKeyPair:params.ourOneTimePrekey]]; +// } +// } +// +// return [[DHEResult alloc] initWithMasterKey:masterKey]; +//} + @end #pragma mark - @@ -245,9 +380,9 @@ NS_ASSUME_NONNULL_BEGIN OWSProdLogAndFail(@"%@ couldn't parse encryptedRequestTag.", self.logTag); return nil; } - NSData *_Nullable quote = [responseDict base64DataForKey:@"quote"]; - if (!quote) { - OWSProdLogAndFail(@"%@ couldn't parse quote.", self.logTag); + NSData *_Nullable quoteData = [responseDict base64DataForKey:@"quote"]; + if (!quoteData) { + OWSProdLogAndFail(@"%@ couldn't parse quote data.", self.logTag); return nil; } id _Nullable signatureBody = responseDict[@"signatureBody"]; @@ -271,10 +406,27 @@ NS_ASSUME_NONNULL_BEGIN return nil; } - RemoteAttestationKeys *keys = [RemoteAttestationKeys new]; - keys.keyPair = keyPair; - keys.serverEphemeralPublic = serverEphemeralPublic; - keys.serverStaticPublic = serverStaticPublic; + 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; + } // RemoteAttestationKeys keys = new RemoteAttestationKeys(keyPair, response.getServerEphemeralPublic(), // response.getServerStaticPublic()); Quote quote = new Quote(response.getQuote()); byte[] @@ -291,6 +443,32 @@ NS_ASSUME_NONNULL_BEGIN return nil; } +- (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 diff --git a/SignalServiceKit/src/Util/Cryptography.h b/SignalServiceKit/src/Util/Cryptography.h index b4a5deced..efd67f99c 100755 --- a/SignalServiceKit/src/Util/Cryptography.h +++ b/SignalServiceKit/src/Util/Cryptography.h @@ -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 diff --git a/SignalServiceKit/src/Util/Cryptography.m b/SignalServiceKit/src/Util/Cryptography.m index a74ac4cff..324cabe9f 100755 --- a/SignalServiceKit/src/Util/Cryptography.m +++ b/SignalServiceKit/src/Util/Cryptography.m @@ -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