Merge branch 'charlesmchen/certificateSubject'

pull/1/head
Matthew Chen 6 years ago
commit 6e94fd50bc

@ -7,6 +7,7 @@
#import "NSData+Base64.h"
#import "NSData+OWS.h"
#import <CommonCrypto/CommonCrypto.h>
#import <openssl/x509.h>
NS_ASSUME_NONNULL_BEGIN
@ -68,6 +69,17 @@ NS_ASSUME_NONNULL_BEGIN
return nil;
}
// The leaf is always the first certificate.
NSData *_Nullable leafCertificateData = [certificateDerDatas firstObject];
if (!leafCertificateData) {
DDLogError(@"%@ Could not extract leaf certificate data.", self.logTag);
return nil;
}
if (![self verifyDistinguishedNameOfCertificate:leafCertificateData]) {
OWSProdLogAndFail(@"%@ Leaf certificate has invalid name.", self.logTag);
return nil;
}
NSMutableArray *certificates = [NSMutableArray new];
for (NSData *certificateDerData in certificateDerDatas) {
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(certificateDerData));
@ -116,10 +128,6 @@ NS_ASSUME_NONNULL_BEGIN
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];
}
@ -257,18 +265,98 @@ NS_ASSUME_NONNULL_BEGIN
return YES;
}
+ (BOOL)verifyDistinguishedName:(SecCertificateRef)certificate
+ (BOOL)verifyDistinguishedNameOfCertificate:(NSData *)certificateData
{
OWSAssert(certificate);
OWSAssert(certificateData);
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.
// The Security framework doesn't offer access to certificate properties
// with API available on iOS 9. We use OpenSSL to extract the name.
NSDictionary<NSString *, NSString *> *_Nullable properties = [self propertiesForCertificate:certificateData];
if (!properties) {
OWSFail(@"%@ Could not retrieve certificate properties.", self.logTag);
return NO;
}
// NSString *expectedDistinguishedName
// = @"CN=Intel SGX Attestation Report Signing,O=Intel Corporation,L=Santa Clara,ST=CA,C=US";
NSDictionary<NSString *, NSString *> *expectedProperties = @{
@(SN_commonName) : // "CN"
@"Intel SGX Attestation Report Signing",
@(SN_organizationName) : // "O"
@"Intel Corporation",
@(SN_localityName) : // "L"
@"Santa Clara",
@(SN_stateOrProvinceName) : // "ST"
@"CA",
@(SN_countryName) : // "C"
@"US",
};
if (![properties isEqualToDictionary:expectedProperties]) {
OWSFail(@"%@ Unexpected certificate properties. %@ != %@", self.logTag, expectedProperties, properties);
return NO;
}
return YES;
}
+ (nullable NSDictionary<NSString *, NSString *> *)propertiesForCertificate:(NSData *)certificateData
{
OWSAssert(certificateData);
if (certificateData.length >= UINT32_MAX) {
OWSFail(@"%@ certificate data is too long.", self.logTag);
return nil;
}
const unsigned char *certificateDataBytes = (const unsigned char *)[certificateData bytes];
X509 *_Nullable certificateX509 = d2i_X509(NULL, &certificateDataBytes, [certificateData length]);
if (!certificateX509) {
OWSFail(@"%@ could not parse certificate.", self.logTag);
return nil;
}
X509_NAME *_Nullable subjectName = X509_get_subject_name(certificateX509);
if (!subjectName) {
OWSFail(@"%@ could not extract subject name.", self.logTag);
return nil;
}
NSMutableDictionary<NSString *, NSString *> *certificateProperties = [NSMutableDictionary new];
for (NSString *oid in @[
@(SN_commonName), // "CN"
@(SN_organizationName), // "O"
@(SN_localityName), // "L"
@(SN_stateOrProvinceName), // "ST"
@(SN_countryName), // "C"
]) {
int nid = OBJ_txt2nid(oid.UTF8String);
int index = X509_NAME_get_index_by_NID(subjectName, nid, -1);
X509_NAME_ENTRY *_Nullable entry = X509_NAME_get_entry(subjectName, index);
if (!entry) {
OWSFail(@"%@ could not extract entry.", self.logTag);
return nil;
}
ASN1_STRING *_Nullable entryData = X509_NAME_ENTRY_get_data(entry);
if (!entryData) {
OWSFail(@"%@ could not extract entry data.", self.logTag);
return nil;
}
unsigned char *entryName = ASN1_STRING_data(entryData);
if (entryName == NULL) {
OWSFail(@"%@ could not extract entry string.", self.logTag);
return nil;
}
NSString *_Nullable entryString = [NSString stringWithUTF8String:(char *)entryName];
if (!entryString) {
OWSFail(@"%@ could not parse entry name data.", self.logTag);
return nil;
}
certificateProperties[oid] = entryString;
}
return certificateProperties;
}
@end
NS_ASSUME_NONNULL_END

@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface RemoteAttestationAuth : NSObject
@property (nonatomic) NSString *username;
@property (nonatomic) NSString *password;
@property (nonatomic) NSString *authToken;
@end
@ -135,9 +135,9 @@ NS_ASSUME_NONNULL_BEGIN
return self.auth.username;
}
- (NSString *)password
- (NSString *)authToken
{
return self.auth.password;
return self.auth.authToken;
}
@end
@ -265,7 +265,7 @@ NS_ASSUME_NONNULL_BEGIN
[[TSNetworkManager sharedManager] makeRequest:request
success:^(NSURLSessionDataTask *task, id responseDict) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
RemoteAttestationAuth *_Nullable auth = [self parseAuthParams:responseDict];
RemoteAttestationAuth *_Nullable auth = [self parseAuthToken:responseDict];
if (!auth) {
DDLogError(@"%@ remote attestation auth could not be parsed: %@", self.logTag, responseDict);
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
@ -283,16 +283,16 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
- (nullable RemoteAttestationAuth *)parseAuthParams:(id)response
- (nullable RemoteAttestationAuth *)parseAuthToken:(id)response
{
if (![response isKindOfClass:[NSDictionary class]]) {
return nil;
}
NSDictionary *responseDict = response;
NSString *_Nullable password = [responseDict stringForKey:@"password"];
if (password.length < 1) {
OWSProdLogAndFail(@"%@ missing or empty password.", self.logTag);
NSString *_Nullable token = [responseDict stringForKey:@"token"];
if (token.length < 1) {
OWSProdLogAndFail(@"%@ missing or empty token.", self.logTag);
return nil;
}
@ -304,7 +304,7 @@ NS_ASSUME_NONNULL_BEGIN
RemoteAttestationAuth *result = [RemoteAttestationAuth new];
result.username = username;
result.password = password;
result.authToken = token;
return result;
}
@ -320,7 +320,7 @@ NS_ASSUME_NONNULL_BEGIN
TSRequest *request = [OWSRequestFactory remoteAttestationRequest:keyPair
enclaveId:enclaveId
authUsername:auth.username
authPassword:auth.password];
authPassword:auth.authToken];
[[TSNetworkManager sharedManager] makeRequest:request
success:^(NSURLSessionDataTask *task, id responseJson) {
@ -470,29 +470,6 @@ NS_ASSUME_NONNULL_BEGIN
DDLogVerbose(@"%@ remote attestation complete.", self.logTag);
//+ 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;
return result;
}
@ -679,39 +656,6 @@ NS_ASSUME_NONNULL_BEGIN
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

Loading…
Cancel
Save