Merge branch 'master' of github.com:limelight-stream/limelight-ios

This commit is contained in:
Cameron Gutman
2014-10-21 04:19:13 -04:00
35 changed files with 150 additions and 135 deletions

View File

@@ -0,0 +1,29 @@
//
// HttpManager.h
// Limelight
//
// Created by Diego Waxemberg on 10/16/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface HttpManager : NSObject <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
+ (NSString*) getStringFromXML:(NSData*)xml tag:(NSString*)tag;
- (id) initWithHost:(NSString*) host uniqueId:(NSString*) uniqueId deviceName:(NSString*) deviceName cert:(NSData*) cert;
- (NSURLRequest*) newPairRequest:(NSData*)salt;
- (NSURLRequest*) newUnpairRequest;
- (NSURLRequest*) newChallengeRequest:(NSData*)challenge;
- (NSURLRequest*) newChallengeRespRequest:(NSData*)challengeResp;
- (NSURLRequest*) newClientSecretRespRequest:(NSString*)clientPairSecret;
- (NSURLRequest*) newPairChallenge;
- (NSURLRequest*) newAppListRequest;
- (NSURLRequest*) newServerInfoRequest;
- (NSURLRequest*) newLaunchRequest:(NSString*)appId width:(int)width height:(int)height refreshRate:(int)refreshRate rikey:(NSString*)rikey rikeyid:(int)rikeyid;
- (NSURLRequest*) newResumeRequestWithRiKey:(NSString*)riKey riKeyId:(int)riKeyId;
- (NSData*) executeRequestSynchronously:(NSURLRequest*)request;
@end

View File

@@ -0,0 +1,234 @@
//
// HttpManager.m
// Limelight
//
// Created by Diego Waxemberg on 10/16/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import "HttpManager.h"
#import "CryptoManager.h"
#include <libxml2/libxml/xmlreader.h>
#include <string.h>
@implementation HttpManager {
NSString* _baseURL;
NSString* _host;
NSString* _uniqueId;
NSString* _deviceName;
NSData* _cert;
NSMutableData* _respData;
NSData* _requestResp;
dispatch_semaphore_t _requestLock;
}
static const NSString* PORT = @"47984";
+ (NSString*) getStringFromXML:(NSData*)xml tag:(NSString*)tag {
xmlDocPtr docPtr = xmlParseMemory([xml bytes], (int)[xml length]);
if (docPtr == NULL) {
NSLog(@"ERROR: An error occured trying to parse xml.");
return NULL;
}
NSString* value;
xmlNodePtr node;
xmlNodePtr rootNode = node = xmlDocGetRootElement(docPtr);
// Check root status_code
if (![HttpManager verifyStatus: rootNode]) {
//TODO: handle error
}
// Skip the root node
node = node->children;
while (node != NULL) {
//NSLog(@"node: %s", node->name);
if (!xmlStrcmp(node->name, (const xmlChar*)[tag UTF8String])) {
xmlChar* nodeVal = xmlNodeListGetString(docPtr, node->xmlChildrenNode, 1);
value = [[NSString alloc] initWithCString:(const char*)nodeVal encoding:NSUTF8StringEncoding];
xmlFree(nodeVal);
}
node = node->next;
}
//NSLog(@"xmlValue: %@", value);
xmlFree(rootNode);
xmlFree(docPtr);
return value;
}
+ (bool) verifyStatus:(xmlNodePtr)docRoot {
xmlChar* statusStr = xmlGetProp(docRoot, (const xmlChar*)"status_code");
NSLog(@"status: %s", statusStr);
int status = [[NSString stringWithUTF8String:(const char*)statusStr] intValue];
xmlFree(statusStr);
return status == 200;
}
+ (NSData*) fixXmlVersion:(NSData*) xmlData {
NSString* xmlString = [[[NSString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding] stringByReplacingOccurrencesOfString:@"UTF-16" withString:@"UTF-8" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [xmlData length])];
//NSLog(@"xmlString: %@", xmlString);
return [NSData dataWithBytes:[xmlString UTF8String] length:[xmlString length]];
}
- (id) initWithHost:(NSString*) host uniqueId:(NSString*) uniqueId deviceName:(NSString*) deviceName cert:(NSData*) cert {
self = [super init];
_host = host;
_uniqueId = uniqueId;
_deviceName = deviceName;
_cert = cert;
_baseURL = [[NSString stringWithFormat:@"https://%@:%@", host, PORT]
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
_requestLock = dispatch_semaphore_create(0);
_respData = [[NSMutableData alloc] init];
return self;
}
- (NSData*) executeRequestSynchronously:(NSURLRequest*)request {
NSLog(@"Making Request: %@", request);
[_respData setLength:0];
dispatch_sync(dispatch_get_main_queue(), ^{
[NSURLConnection connectionWithRequest:request delegate:self];
});
dispatch_semaphore_wait(_requestLock, DISPATCH_TIME_FOREVER);
return _requestResp;
}
- (void) executeRequest:(NSURLRequest*)request {
[NSURLConnection connectionWithRequest:request delegate:self];
}
- (NSURLRequest*) createRequestFromString:(NSString*) urlString {
NSString* escapedUrl = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL* url = [[NSURL alloc] initWithString:escapedUrl];
return [NSURLRequest requestWithURL:url];
}
- (NSURLRequest*) newPairRequest:(NSData*)salt {
NSString* urlString = [NSString stringWithFormat:@"%@/pair?uniqueid=%@&devicename=%@&updateState=1&phrase=getservercert&salt=%@&clientcert=%@",
_baseURL, _uniqueId, _deviceName, [self bytesToHex:salt], [self bytesToHex:_cert]];
return [self createRequestFromString:urlString];
}
- (NSURLRequest*) newUnpairRequest {
NSString* urlString = [NSString stringWithFormat:@"%@/unpair?uniqueid=%@", _baseURL, _uniqueId];
return [self createRequestFromString:urlString];
}
- (NSURLRequest*) newChallengeRequest:(NSData*)challenge {
NSString* urlString = [NSString stringWithFormat:@"%@/pair?uniqueid=%@&devicename=%@&updateState=1&clientchallenge=%@",
_baseURL, _uniqueId, _deviceName, [self bytesToHex:challenge]];
return [self createRequestFromString:urlString];
}
- (NSURLRequest*) newChallengeRespRequest:(NSData*)challengeResp {
NSString* urlString = [NSString stringWithFormat:@"%@/pair?uniqueid=%@&devicename=%@&updateState=1&serverchallengeresp=%@",
_baseURL, _uniqueId, _deviceName, [self bytesToHex:challengeResp]];
return [self createRequestFromString:urlString];
}
- (NSURLRequest*) newClientSecretRespRequest:(NSString*)clientPairSecret {
NSString* urlString = [NSString stringWithFormat:@"%@/pair?uniqueid=%@&devicename=%@&updateState=1&clientpairingsecret=%@", _baseURL, _uniqueId, _deviceName, clientPairSecret];
return [self createRequestFromString:urlString];
}
- (NSURLRequest*) newPairChallenge {
NSString* urlString = [NSString stringWithFormat:@"%@/pair?uniqueid=%@&devicename=%@&updateState=1&phrase=pairchallenge", _baseURL, _uniqueId, _deviceName];
return [self createRequestFromString:urlString];
}
- (NSURLRequest *)newAppListRequest {
NSString* urlString = [NSString stringWithFormat:@"%@/applist?uniqueid=%@", _baseURL, _uniqueId];
return [self createRequestFromString:urlString];
}
- (NSURLRequest *)newServerInfoRequest {
NSString* urlString = [NSString stringWithFormat:@"%@/serverinfo?uniqueid=%@", _baseURL, _uniqueId];
return [self createRequestFromString:urlString];
}
- (NSURLRequest*) newLaunchRequest:(NSString*)appId width:(int)width height:(int)height refreshRate:(int)refreshRate rikey:(NSString*)rikey rikeyid:(int)rikeyid {
NSString* urlString = [NSString stringWithFormat:@"%@/launch?uniqueid=%@&appid=%@&mode=%dx%dx%d&additionalStates=1&sops=1&rikey=%@&rikeyid=%d", _baseURL, _uniqueId, appId, width, height, refreshRate, rikey, rikeyid];
return [self createRequestFromString:urlString];
}
- (NSURLRequest*) newResumeRequestWithRiKey:(NSString*)riKey riKeyId:(int)riKeyId {
NSString* urlString = [NSString stringWithFormat:@"%@/resume?uniqueid=%@&rikey=%@&rikeyid=%d", _baseURL, _uniqueId, riKey, riKeyId];
return [self createRequestFromString:urlString];
}
- (NSString*) bytesToHex:(NSData*)data {
const unsigned char* bytes = [data bytes];
NSMutableString *hex = [[NSMutableString alloc] init];
for (int i = 0; i < [data length]; i++) {
[hex appendFormat:@"%02X" , bytes[i]];
}
return hex;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(@"Received response: %@", response);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(@"Received data: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
[_respData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
_requestResp = [HttpManager fixXmlVersion:_respData];
dispatch_semaphore_signal(_requestLock);
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
SecIdentityRef identity = [self getClientCertificate];
NSArray *certArray = [self getCertificate:identity];
NSURLCredential *newCredential = [NSURLCredential credentialWithIdentity:identity certificates:certArray persistence:NSURLCredentialPersistencePermanent];
[challenge.sender useCredential:newCredential forAuthenticationChallenge:challenge];
}
// Returns an array containing the certificate
- (NSArray*)getCertificate:(SecIdentityRef) identity {
SecCertificateRef certificate = nil;
SecIdentityCopyCertificate(identity, &certificate);
return [[NSArray alloc] initWithObjects:(__bridge id)certificate, nil];
}
// Returns the identity
- (SecIdentityRef)getClientCertificate {
SecIdentityRef identityApp = nil;
CFDataRef p12Data = (__bridge CFDataRef)[CryptoManager readP12FromFile];
CFStringRef password = CFSTR("limelight");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import(p12Data, options, &items);
if (securityError == errSecSuccess) {
//NSLog(@"Success opening p12 certificate. Items: %ld", CFArrayGetCount(items));
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
} else {
NSLog(@"Error opening Certificate.");
}
CFRelease(options);
CFRelease(password);
return identityApp;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"connection error: %@", error);
}
@end

View File

@@ -0,0 +1,28 @@
//
// MDNSManager.h
// Limelight
//
// Created by Diego Waxemberg on 10/14/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol MDNSCallback <NSObject>
- (void) updateHosts:(NSArray*)hosts;
@end
@interface MDNSManager : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
@property id<MDNSCallback> callback;
- (id) initWithCallback:(id<MDNSCallback>) callback;
- (void) searchForHosts;
- (void) stopSearching;
@end

View File

@@ -0,0 +1,79 @@
//
// MDNSManager.m
// Limelight
//
// Created by Diego Waxemberg on 10/14/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import "MDNSManager.h"
#import "Computer.h"
@implementation MDNSManager : NSObject
NSNetServiceBrowser* mDNSBrowser;
NSMutableArray* domains;
NSMutableArray* services;
static NSString* NV_SERVICE_TYPE = @"_nvstream._tcp";
- (id) initWithCallback:(id<MDNSCallback>)callback {
self = [super init];
self.callback = callback;
mDNSBrowser = [[NSNetServiceBrowser alloc] init];
[mDNSBrowser setDelegate:self];
domains = [[NSMutableArray alloc] init];
services = [[NSMutableArray alloc] init];
return self;
}
- (void) searchForHosts {
NSLog(@"Starting mDNS discovery");
[mDNSBrowser searchForServicesOfType:NV_SERVICE_TYPE inDomain:@""];
}
- (void) stopSearching {
NSLog(@"Stopping mDNS discovery");
[mDNSBrowser stop];
}
- (NSArray*) getFoundHosts {
NSMutableArray* hosts = [[NSMutableArray alloc] init];
for (NSNetService* service in services) {
if (service.hostName != nil) {
[hosts addObject:[[Computer alloc] initWithHost:service]];
}
}
return [[NSArray alloc] initWithArray:hosts];
}
- (void)netServiceDidResolveAddress:(NSNetService *)service {
NSLog(@"Resolved address: %@ -> %@", service, service.hostName);
[self.callback updateHosts:[self getFoundHosts]];
}
- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict {
NSLog(@"Did not resolve address for: %@\n%@", sender, [errorDict description]);
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
NSLog(@"Found service: %@", aNetService);
[aNetService setDelegate:self];
[aNetService resolveWithTimeout:5];
[services addObject:aNetService];
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
NSLog(@"Removing service: %@", aNetService);
[services removeObject:aNetService];
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didNotSearch:(NSDictionary *)errorDict {
NSLog(@"Did not perform search");
NSLog(@"%@", [errorDict description]);
}
@end

View File

@@ -0,0 +1,26 @@
//
// PairManager.h
// Limelight
//
// Created by Diego Waxemberg on 10/19/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "HttpManager.h"
@protocol PairCallback <NSObject>
- (void) showPIN:(NSString*)PIN;
- (void) pairSuccessful;
- (void) pairFailed:(NSString*)message;
@end
@interface PairManager : NSOperation
- (id) initWithManager:(HttpManager*)httpManager andCert:(NSData*)cert callback:(id<PairCallback>)callback;
- (NSString*) generatePIN;
- (NSData*) saltPIN:(NSString*)PIN;
- (void) initiatePair;
@end

View File

@@ -0,0 +1,145 @@
//
// PairManager.m
// Limelight
//
// Created by Diego Waxemberg on 10/19/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import "PairManager.h"
#import "CryptoManager.h"
#import "Utils.h"
#include <dispatch/dispatch.h>
@implementation PairManager {
HttpManager* _httpManager;
NSData* _cert;
id<PairCallback> _callback;
}
- (id) initWithManager:(HttpManager*)httpManager andCert:(NSData*)cert callback:(id<PairCallback>)callback {
self = [super init];
_httpManager = httpManager;
_cert = cert;
_callback = callback;
return self;
}
- (void) main {
NSData* serverInfo = [_httpManager executeRequestSynchronously:[_httpManager newServerInfoRequest]];
if (![[HttpManager getStringFromXML:serverInfo tag:@"PairStatus"] isEqual:@"1"]) {
[self initiatePair];
} else {
[_callback pairFailed:@"Already Paired"];
}
[_httpManager executeRequestSynchronously:[_httpManager newAppListRequest]];
}
- (void) initiatePair {
NSString* PIN = [self generatePIN];
NSData* salt = [self saltPIN:PIN];
NSLog(@"PIN: %@, saltedPIN: %@", PIN, salt);
[_callback showPIN:PIN];
NSData* pairResp = [_httpManager executeRequestSynchronously:[_httpManager newPairRequest:salt]];
if ([[HttpManager getStringFromXML:pairResp tag:@"paired"] intValue] != 1) {
[_httpManager executeRequestSynchronously:[_httpManager newUnpairRequest]];
//TODO: better message
[_callback pairFailed:@"pairResp failed"];
return;
}
NSString* plainCert = [HttpManager getStringFromXML:pairResp tag:@"plaincert"];
CryptoManager* cryptoMan = [[CryptoManager alloc] init];
NSData* aesKey = [cryptoMan createAESKeyFromSalt:salt];
NSData* randomChallenge = [Utils randomBytes:16];
NSData* encryptedChallenge = [cryptoMan aesEncrypt:randomChallenge withKey:aesKey];
NSData* challengeResp = [_httpManager executeRequestSynchronously:[_httpManager newChallengeRequest:encryptedChallenge]];
if ([[HttpManager getStringFromXML:challengeResp tag:@"paired"] intValue] != 1) {
[_httpManager executeRequestSynchronously:[_httpManager newUnpairRequest]];
//TODO: better message
[_callback pairFailed:@"challengeResp failed"];
return;
}
NSData* encServerChallengeResp = [Utils hexToBytes:[HttpManager getStringFromXML:challengeResp tag:@"challengeresponse"]];
NSData* decServerChallengeResp = [cryptoMan aesDecrypt:encServerChallengeResp withKey:aesKey];
NSData* serverResponse = [decServerChallengeResp subdataWithRange:NSMakeRange(0, 20)];
NSData* serverChallenge = [decServerChallengeResp subdataWithRange:NSMakeRange(20, 16)];
NSData* clientSecret = [Utils randomBytes:16];
NSData* challengeRespHash = [cryptoMan SHA1HashData:[self concatData:[self concatData:serverChallenge with:[CryptoManager getSignatureFromCert:_cert]] with:clientSecret]];
NSData* challengeRespEncrypted = [cryptoMan aesEncrypt:challengeRespHash withKey:aesKey];
NSData* secretResp = [_httpManager executeRequestSynchronously:[_httpManager newChallengeRespRequest:challengeRespEncrypted]];
if ([[HttpManager getStringFromXML:secretResp tag:@"paired"] intValue] != 1) {
[_httpManager executeRequestSynchronously:[_httpManager newUnpairRequest]];
//TODO: better message
[_callback pairFailed:@"secretResp failed"];
return;
}
NSData* serverSecretResp = [Utils hexToBytes:[HttpManager getStringFromXML:secretResp tag:@"pairingsecret"]];
NSData* serverSecret = [serverSecretResp subdataWithRange:NSMakeRange(0, 16)];
NSData* serverSignature = [serverSecretResp subdataWithRange:NSMakeRange(16, 256)];
if (![cryptoMan verifySignature:serverSecret withSignature:serverSignature andCert:[Utils hexToBytes:plainCert]]) {
[_httpManager executeRequestSynchronously:[_httpManager newUnpairRequest]];
//TODO: better message
[_callback pairFailed:@"verifySignature failed"];
return;
}
NSData* serverChallengeRespHash = [cryptoMan SHA1HashData:[self concatData:[self concatData:randomChallenge with:[CryptoManager getSignatureFromCert:[Utils hexToBytes:plainCert]]] with:serverSecret]];
if (![serverChallengeRespHash isEqual:serverResponse]) {
[_httpManager executeRequestSynchronously:[_httpManager newUnpairRequest]];
//TODO: better message
[_callback pairFailed:@"serverChallengeResp failed"];
return;
}
NSData* clientPairingSecret = [self concatData:clientSecret with:[cryptoMan signData:clientSecret withKey:[CryptoManager readKeyFromFile]]];
NSData* clientSecretResp = [_httpManager executeRequestSynchronously:[_httpManager newClientSecretRespRequest:[Utils bytesToHex:clientPairingSecret]]];
if (![[HttpManager getStringFromXML:clientSecretResp tag:@"paired"] isEqual:@"1"]) {
[_httpManager executeRequestSynchronously:[_httpManager newUnpairRequest]];
//TODO: better message
[_callback pairFailed:@"clientSecretResp failed"];
return;
}
NSData* clientPairChallenge = [_httpManager executeRequestSynchronously:[_httpManager newPairChallenge]];
if (![[HttpManager getStringFromXML:clientPairChallenge tag:@"paired"] isEqual:@"1"]) {
[_httpManager executeRequestSynchronously:[_httpManager newUnpairRequest]];
//TODO: better message
[_callback pairFailed:@"clientPairChallenge failed"];
return;
}
[_callback pairSuccessful];
}
- (NSData*) concatData:(NSData*)data with:(NSData*)moreData {
NSMutableData* concatData = [[NSMutableData alloc] initWithData:data];
[concatData appendData:moreData];
return concatData;
}
- (NSString*) generatePIN {
NSString* PIN = [NSString stringWithFormat:@"%d%d%d%d",
arc4random() % 10, arc4random() % 10,
arc4random() % 10, arc4random() % 10];
return PIN;
}
- (NSData*) saltPIN:(NSString*)PIN {
NSMutableData* saltedPIN = [[NSMutableData alloc] initWithCapacity:20];
[saltedPIN appendData:[Utils randomBytes:16]];
[saltedPIN appendBytes:[PIN UTF8String] length:4];
return saltedPIN;
}
@end