Add server cert pinning after pairing

This commit is contained in:
Cameron Gutman
2018-12-22 00:05:48 -08:00
parent 791a5b1ea1
commit fbae7f88b5
21 changed files with 157 additions and 72 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ static const int MAX_ATTEMPTS = 5;
- (void) main {
int attempts = 0;
while (![self isCancelled] && attempts++ < MAX_ATTEMPTS) {
HttpManager* hMan = [[HttpManager alloc] initWithHost:_host.activeAddress uniqueId:[IdManager getUniqueId] deviceName:deviceName cert:[CryptoManager readCertFromFile]];
HttpManager* hMan = [[HttpManager alloc] initWithHost:_host.activeAddress uniqueId:[IdManager getUniqueId] serverCert:_host.serverCert];
AppAssetResponse* appAssetResp = [[AppAssetResponse alloc] init];
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:appAssetResp withUrlRequest:[hMan newAppAssetRequestWithAppId:self.app.id]]];
+1 -1
View File
@@ -14,7 +14,7 @@
@interface ConnectionHelper : NSObject
+(AppListResponse*) getAppListForHostWithHostIP:(NSString*) hostIP deviceName:(NSString*)deviceName cert:(NSData*) cert uniqueID:(NSString*) uniqueId;
+(AppListResponse*) getAppListForHostWithHostIP:(NSString*) hostIP serverCert:(NSData*) serverCert uniqueID:(NSString*) uniqueId;
@end
+2 -2
View File
@@ -14,8 +14,8 @@
@implementation ConnectionHelper
+(AppListResponse*) getAppListForHostWithHostIP:(NSString*) hostIP deviceName:(NSString*)deviceName cert:(NSData*) cert uniqueID:(NSString*) uniqueId {
HttpManager* hMan = [[HttpManager alloc] initWithHost:hostIP uniqueId:uniqueId deviceName:deviceName cert:cert];
+(AppListResponse*) getAppListForHostWithHostIP:(NSString*) hostIP serverCert:(NSData*) cert uniqueID:(NSString*) uniqueId {
HttpManager* hMan = [[HttpManager alloc] initWithHost:hostIP uniqueId:uniqueId serverCert:cert];
// Try up to 5 times to get the app list
AppListResponse* appListResp;
+2 -2
View File
@@ -48,7 +48,7 @@
}
- (void) discoverHost:(NSString *)hostAddress withCallback:(void (^)(TemporaryHost *, NSString*))callback {
HttpManager* hMan = [[HttpManager alloc] initWithHost:hostAddress uniqueId:_uniqueId deviceName:deviceName cert:_cert];
HttpManager* hMan = [[HttpManager alloc] initWithHost:hostAddress uniqueId:_uniqueId serverCert:nil];
ServerInfoResponse* serverInfoResponse = [[ServerInfoResponse alloc] init];
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResponse withUrlRequest:[hMan newServerInfoRequest:false] fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]];
@@ -183,7 +183,7 @@
}
- (NSOperation*) createWorkerForHost:(TemporaryHost*)host {
DiscoveryWorker* worker = [[DiscoveryWorker alloc] initWithHost:host uniqueId:_uniqueId cert:_cert];
DiscoveryWorker* worker = [[DiscoveryWorker alloc] initWithHost:host uniqueId:_uniqueId];
return worker;
}
+1 -1
View File
@@ -10,7 +10,7 @@
@interface DiscoveryWorker : NSOperation
- (id) initWithHost:(TemporaryHost*)host uniqueId:(NSString*)uniqueId cert:(NSData*)cert;
- (id) initWithHost:(TemporaryHost*)host uniqueId:(NSString*)uniqueId;
- (void) discoverHost;
- (TemporaryHost*) getHost;
+4 -7
View File
@@ -16,16 +16,14 @@
@implementation DiscoveryWorker {
TemporaryHost* _host;
NSString* _uniqueId;
NSData* _cert;
}
static const float POLL_RATE = 2.0f; // Poll every 2 seconds
- (id) initWithHost:(TemporaryHost*)host uniqueId:(NSString*)uniqueId cert:(NSData*)cert {
- (id) initWithHost:(TemporaryHost*)host uniqueId:(NSString*)uniqueId {
self = [super init];
_host = host;
_uniqueId = uniqueId;
_cert = cert;
return self;
}
@@ -98,7 +96,7 @@ static const float POLL_RATE = 2.0f; // Poll every 2 seconds
return;
}
ServerInfoResponse* serverInfoResp = [self requestInfoAtAddress:address];
ServerInfoResponse* serverInfoResp = [self requestInfoAtAddress:address cert:_host.serverCert];
receivedResponse = [self checkResponse:serverInfoResp];
if (receivedResponse) {
[serverInfoResp populateHost:_host];
@@ -123,11 +121,10 @@ static const float POLL_RATE = 2.0f; // Poll every 2 seconds
}
}
- (ServerInfoResponse*) requestInfoAtAddress:(NSString*)address {
- (ServerInfoResponse*) requestInfoAtAddress:(NSString*)address cert:(NSData*)cert {
HttpManager* hMan = [[HttpManager alloc] initWithHost:address
uniqueId:_uniqueId
deviceName:deviceName
cert:_cert];
serverCert:cert];
ServerInfoResponse* response = [[ServerInfoResponse alloc] init];
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:response
withUrlRequest:[hMan newServerInfoRequest:true]
+2 -2
View File
@@ -12,8 +12,8 @@
@interface HttpManager : NSObject <NSURLSessionDelegate>
- (id) initWithHost:(NSString*) host uniqueId:(NSString*) uniqueId deviceName:(NSString*) deviceName cert:(NSData*) cert;
- (NSURLRequest*) newPairRequest:(NSData*)salt;
- (id) initWithHost:(NSString*) host uniqueId:(NSString*) uniqueId serverCert:(NSData*) serverCert;
- (NSURLRequest*) newPairRequest:(NSData*)salt clientCert:(NSData*)clientCert;
- (NSURLRequest*) newUnpairRequest;
- (NSURLRequest*) newChallengeRequest:(NSData*)challenge;
- (NSURLRequest*) newChallengeRespRequest:(NSData*)challengeResp;
+43 -13
View File
@@ -25,12 +25,12 @@
NSString* _baseHTTPSURL;
NSString* _uniqueId;
NSString* _deviceName;
NSData* _cert;
NSData* _serverCert;
NSMutableData* _respData;
NSData* _requestResp;
dispatch_semaphore_t _requestLock;
BOOL _errorOccurred;
NSError* _error;
}
static const NSString* HTTP_PORT = @"47989";
@@ -43,11 +43,11 @@ static const NSString* HTTPS_PORT = @"47984";
return [xmlString dataUsingEncoding:NSUTF8StringEncoding];
}
- (id) initWithHost:(NSString*) host uniqueId:(NSString*) uniqueId deviceName:(NSString*) deviceName cert:(NSData*) cert {
- (id) initWithHost:(NSString*) host uniqueId:(NSString*) uniqueId serverCert:(NSData*) serverCert {
self = [super init];
_uniqueId = uniqueId;
_deviceName = deviceName;
_cert = cert;
_serverCert = serverCert;
_requestLock = dispatch_semaphore_create(0);
_respData = [[NSMutableData alloc] init];
NSURLSessionConfiguration* config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
@@ -68,13 +68,15 @@ static const NSString* HTTPS_PORT = @"47984";
}
- (void) executeRequestSynchronously:(HttpRequest*)request {
Log(LOG_D, @"Making Request: %@", request);
[_respData setLength:0];
_error = nil;
Log(LOG_D, @"Making Request: %@", request);
[[_urlSession dataTaskWithRequest:request.request completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error) {
if (error != NULL) {
Log(LOG_D, @"Connection error: %@", error);
self->_errorOccurred = true;
self->_error = error;
}
else {
Log(LOG_D, @"Received response: %@", response);
@@ -94,7 +96,7 @@ static const NSString* HTTPS_PORT = @"47984";
}] resume];
dispatch_semaphore_wait(_requestLock, DISPATCH_TIME_FOREVER);
if (!_errorOccurred && request.response) {
if (!_error && request.response) {
[request.response populateWithData:_requestResp];
// If the fallback error code was detected, issue the fallback request
@@ -106,7 +108,15 @@ static const NSString* HTTPS_PORT = @"47984";
[self executeRequestSynchronously:request];
}
}
_errorOccurred = false;
else if (_error && [_error code] == NSURLErrorServerCertificateUntrusted && request.fallbackRequest) {
// This will fall back to HTTP on serverinfo queries to allow us to pair again
// and get the server cert updated.
Log(LOG_D, @"Attempting fallback request after certificate trust failure");
request.request = request.fallbackRequest;
request.fallbackError = 0;
request.fallbackRequest = NULL;
[self executeRequestSynchronously:request];
}
}
- (NSURLRequest*) createRequestFromString:(NSString*) urlString timeout:(int)timeout {
@@ -116,9 +126,9 @@ static const NSString* HTTPS_PORT = @"47984";
return request;
}
- (NSURLRequest*) newPairRequest:(NSData*)salt {
- (NSURLRequest*) newPairRequest:(NSData*)salt clientCert:(NSData*)clientCert {
NSString* urlString = [NSString stringWithFormat:@"%@/pair?uniqueid=%@&devicename=%@&updateState=1&phrase=getservercert&salt=%@&clientcert=%@",
_baseHTTPSURL, _uniqueId, _deviceName, [self bytesToHex:salt], [self bytesToHex:_cert]];
_baseHTTPURL, _uniqueId, _deviceName, [self bytesToHex:salt], [self bytesToHex:clientCert]];
// This call blocks while waiting for the user to input the PIN on the PC
return [self createRequestFromString:urlString timeout:EXTRA_LONG_TIMEOUT_SEC];
}
@@ -130,18 +140,18 @@ static const NSString* HTTPS_PORT = @"47984";
- (NSURLRequest*) newChallengeRequest:(NSData*)challenge {
NSString* urlString = [NSString stringWithFormat:@"%@/pair?uniqueid=%@&devicename=%@&updateState=1&clientchallenge=%@",
_baseHTTPSURL, _uniqueId, _deviceName, [self bytesToHex:challenge]];
_baseHTTPURL, _uniqueId, _deviceName, [self bytesToHex:challenge]];
return [self createRequestFromString:urlString timeout:NORMAL_TIMEOUT_SEC];
}
- (NSURLRequest*) newChallengeRespRequest:(NSData*)challengeResp {
NSString* urlString = [NSString stringWithFormat:@"%@/pair?uniqueid=%@&devicename=%@&updateState=1&serverchallengeresp=%@",
_baseHTTPSURL, _uniqueId, _deviceName, [self bytesToHex:challengeResp]];
_baseHTTPURL, _uniqueId, _deviceName, [self bytesToHex:challengeResp]];
return [self createRequestFromString:urlString timeout:NORMAL_TIMEOUT_SEC];
}
- (NSURLRequest*) newClientSecretRespRequest:(NSString*)clientPairSecret {
NSString* urlString = [NSString stringWithFormat:@"%@/pair?uniqueid=%@&devicename=%@&updateState=1&clientpairingsecret=%@", _baseHTTPSURL, _uniqueId, _deviceName, clientPairSecret];
NSString* urlString = [NSString stringWithFormat:@"%@/pair?uniqueid=%@&devicename=%@&updateState=1&clientpairingsecret=%@", _baseHTTPURL, _uniqueId, _deviceName, clientPairSecret];
return [self createRequestFromString:urlString timeout:NORMAL_TIMEOUT_SEC];
}
@@ -249,6 +259,26 @@ static const NSString* HTTPS_PORT = @"47984";
// Allow untrusted server certificates
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
if (_serverCert) {
SecCertificateRef actualCert = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, 0);
CFDataRef actualCertData;
actualCertData = SecCertificateCopyData(actualCert);
if (!CFEqual(actualCertData, (__bridge CFDataRef)_serverCert)) {
Log(LOG_E, @"Server certificate mismatch");
CFRelease(actualCertData);
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, NULL);
return;
}
CFRelease(actualCertData);
// Fall-through to TLS success
}
// Allow TLS handshake to proceed
completionHandler(NSURLSessionAuthChallengeUseCredential,
[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust]);
}
+2 -2
View File
@@ -11,14 +11,14 @@
@protocol PairCallback <NSObject>
- (void) showPIN:(NSString*)PIN;
- (void) pairSuccessful;
- (void) pairSuccessful:(NSData*)serverCert;
- (void) pairFailed:(NSString*)message;
- (void) alreadyPaired;
@end
@interface PairManager : NSOperation
- (id) initWithManager:(HttpManager*)httpManager andCert:(NSData*)cert callback:(id<PairCallback>)callback;
- (id) initWithManager:(HttpManager*)httpManager clientCert:(NSData*)clientCert callback:(id<PairCallback>)callback;
- (NSString*) generatePIN;
- (NSData*) saltPIN:(NSString*)PIN;
- (void) initiatePair:(int)serverMajorVersion;
+7 -6
View File
@@ -17,14 +17,14 @@
@implementation PairManager {
HttpManager* _httpManager;
NSData* _cert;
NSData* _clientCert;
id<PairCallback> _callback;
}
- (id) initWithManager:(HttpManager*)httpManager andCert:(NSData*)cert callback:(id<PairCallback>)callback {
- (id) initWithManager:(HttpManager*)httpManager clientCert:(NSData*)clientCert callback:(id<PairCallback>)callback {
self = [super init];
_httpManager = httpManager;
_cert = cert;
_clientCert = clientCert;
_callback = callback;
return self;
}
@@ -62,7 +62,7 @@
[_callback showPIN:PIN];
HttpResponse* pairResp = [[HttpResponse alloc] init];
[_httpManager executeRequestSynchronously:[HttpRequest requestForResponse:pairResp withUrlRequest:[_httpManager newPairRequest:salt]]];
[_httpManager executeRequestSynchronously:[HttpRequest requestForResponse:pairResp withUrlRequest:[_httpManager newPairRequest:salt clientCert:_clientCert]]];
if (![self verifyResponseStatus:pairResp]) {
return;
}
@@ -113,7 +113,7 @@
NSData* serverChallenge = [decServerChallengeResp subdataWithRange:NSMakeRange(hashLength, 16)];
NSData* clientSecret = [Utils randomBytes:16];
NSData* challengeRespHashInput = [self concatData:[self concatData:serverChallenge with:[CryptoManager getSignatureFromCert:_cert]] with:clientSecret];
NSData* challengeRespHashInput = [self concatData:[self concatData:serverChallenge with:[CryptoManager getSignatureFromCert:_clientCert]] with:clientSecret];
NSData* challengeRespHash;
if (serverMajorVersion >= 7) {
challengeRespHash = [cryptoMan SHA256HashData: challengeRespHashInput];
@@ -180,7 +180,8 @@
[_callback pairFailed:@"Pairing stage #5 failed"];
return;
}
[_callback pairSuccessful];
[_callback pairSuccessful: [CryptoManager pemToDer:[Utils hexToBytes:plainCert]]];
}
- (BOOL) verifyResponseStatus:(HttpResponse*)resp {