Remote attestation.

pull/1/head
Matthew Chen 7 years ago
parent d7bb2b7505
commit 6686ecb125

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

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

@ -3,24 +3,159 @@
//
#import "ContactDiscoveryService.h"
#import "CDSQuote.h"
#import "Cryptography.h"
#import "OWSRequestFactory.h"
#import "TSNetworkManager.h"
#import <Curve25519Kit/Curve25519.h>
#import <HKDFKit/HKDFKit.h>
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<AxolotlParameters>)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

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

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

Loading…
Cancel
Save