diff --git a/MessagesBridge.xcodeproj/xcshareddata/xcschemes/kordophoned.xcscheme b/MessagesBridge.xcodeproj/xcshareddata/xcschemes/kordophoned.xcscheme index d505b12..b3bcf4c 100644 --- a/MessagesBridge.xcodeproj/xcshareddata/xcschemes/kordophoned.xcscheme +++ b/MessagesBridge.xcodeproj/xcshareddata/xcschemes/kordophoned.xcscheme @@ -63,7 +63,7 @@ diff --git a/README.md b/README.md index 3e5a0df..b08eeed 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,14 @@ If you want to run with SSL, you have to generate a self-signed certificate, and ### Start kordophone with the SSL options and provide the p12 `kordophoned -s -c certificate.p12` +## Authentication +Basic Authentication is also optional, but requires SSL to be enabled as well. To configure basic authentication, create a file containing your username and password on two separate lines encrypted with your GPG key. + +`echo "username\npassword" > password.txt"` +`gpg -e -r (your email) -o password.asc password.txt` + +Then run kordophoned with the following option +`kordophone -s -c certificate.p12 -a password.asc` + +You may need to unlock your GPG keyring (via gpg-agent) when running kordophoned the first time. diff --git a/kordophone/Bridge/MBIMBridge.h b/kordophone/Bridge/MBIMBridge.h index d44ce62..5a6d031 100644 --- a/kordophone/Bridge/MBIMBridge.h +++ b/kordophone/Bridge/MBIMBridge.h @@ -18,6 +18,10 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) UInt16 port; @property (nonatomic, readonly) NSOperationQueue *operationQueue; +@property (nonatomic, assign) BOOL usesAccessControl; +@property (nonatomic, strong) NSString *authUsername; +@property (nonatomic, strong) NSString *authPassword; + @property (nonatomic, assign) BOOL usesSSL; @property (nonatomic, strong) NSString *sslCertPath; diff --git a/kordophone/Bridge/MBIMHTTPConnection.m b/kordophone/Bridge/MBIMHTTPConnection.m index da32de6..5f3ef9a 100644 --- a/kordophone/Bridge/MBIMHTTPConnection.m +++ b/kordophone/Bridge/MBIMHTTPConnection.m @@ -29,6 +29,28 @@ return [[MBIMBridge sharedInstance] sslCertificateAndIdentity]; } +- (BOOL)isPasswordProtected:(NSString *)path +{ + return [[MBIMBridge sharedInstance] usesAccessControl]; +} + +- (NSString *)passwordForUser:(NSString *)username +{ + MBIMBridge *bridge = [MBIMBridge sharedInstance]; + if ([username isEqualToString:bridge.authUsername]) { + return bridge.authPassword; + } + + return @""; +} + +- (BOOL)useDigestAccessAuthentication +{ + // TODO: should use digest all the time, but it's a bit more complicated. Allow this to be NO if + // SSL is on, because then at least basic auth is encrypted + return ![[MBIMBridge sharedInstance] usesSSL]; +} + - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path { if ([method isEqualToString:@"GET"] || [method isEqualToString:@"POST"]) { diff --git a/kordophone/main.m b/kordophone/main.m index a9a4e22..50689b5 100644 --- a/kordophone/main.m +++ b/kordophone/main.m @@ -12,20 +12,83 @@ void printUsage() { - fprintf(stderr, "Usage: kordophoned [-h] [-s | -c (certificate.p12)]\n"); + fprintf(stderr, "Usage: kordophoned [-h] [-s | -c (certificate.p12)] [-a (access control file)\n"); fprintf(stderr, "\t-h \t Show this help message\n"); fprintf(stderr, "\t-s \t Use SSL (requires -c option)\n"); fprintf(stderr, "\t-c \t SSL certificate path encoded as pkcs12\n"); + fprintf(stderr, "\t-a \t Optional GPG encrypted access control file\n"); +} + +BOOL acquireCredentials(const char *accessFile, NSString **out_username, NSString **out_password) +{ + NSPipe *stdoutPipe = [NSPipe pipe]; + NSPipe *stderrPipe = [NSPipe pipe]; + NSTask *task = [[NSTask alloc] init]; + task.launchPath = @"/usr/local/bin/gpg"; + task.arguments = @[ @"-q", @"-d", [NSString stringWithUTF8String:accessFile] ]; + task.standardOutput = stdoutPipe; + task.standardError = stderrPipe; + + NSError *launchError = nil; + BOOL success = [task launchAndReturnError:&launchError]; + [task waitUntilExit]; + + if (success) { + NSFileHandle *stdoutFile = stdoutPipe.fileHandleForReading; + NSData *data = [stdoutFile readDataToEndOfFile]; // blocks + [stdoutFile closeFile]; + + if ([task terminationStatus] != 0) { + NSData *stderrData = [[stderrPipe fileHandleForReading] readDataToEndOfFile]; + MBIMLogFatal(@"GPG error when decrypting access file: %@", [[NSString alloc] initWithData:stderrData encoding:NSUTF8StringEncoding]); + return NO; + } + + NSString *asString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSScanner *scanner = [NSScanner scannerWithString:asString]; + + BOOL scannerSuccess = NO; + NSString *username = nil; + scannerSuccess = [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] + intoString:&username]; + if (!scannerSuccess) { + MBIMLogFatal(@"Error parsing username from access file"); + return NO; + } + + NSString *password = nil; + scannerSuccess = [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] + intoString:&password]; + if (!scannerSuccess) { + MBIMLogFatal(@"Error parsing password from access file"); + return NO; + } + + if ([username length] && [password length]) { + *out_username = username; + *out_password = password; + } else { + MBIMLogFatal(@"Error parsing username or password from access file"); + return NO; + } + } else { + MBIMLogFatal(@"Unable to launch GPG executable to decrypt access file: %@", [launchError localizedDescription]); + return NO; + } + + return YES; } int main(int argc, char *const argv[]) { @autoreleasepool { BOOL usesSSL = NO; BOOL showHelp = NO; + BOOL usesAccessControl = NO; const char *certPath = NULL; + const char *accessFilePath = NULL; int c = -1; - while ( (c = getopt(argc, argv, "hsc:")) != -1 ) { + while ( (c = getopt(argc, argv, "hsc:a:")) != -1 ) { switch (c) { case 's': usesSSL = YES; @@ -33,6 +96,10 @@ int main(int argc, char *const argv[]) { case 'c': certPath = optarg; break; + case 'a': + usesAccessControl = YES; + accessFilePath = optarg; + break; case 'h': showHelp = YES; break; @@ -62,6 +129,33 @@ int main(int argc, char *const argv[]) { MBIMBridge *bridge = [MBIMBridge sharedInstance]; + if (usesAccessControl) { + NSString *username = nil; + NSString *password = nil; + + BOOL success = acquireCredentials(accessFilePath, &username, &password); + if (!success) { + MBIMLogInfo( + @"Access file must be a GPG encrypted file (encrypted with your private key, to your pub key) " + "with the follwing format: \n" + "(username)\n" + "(password)" + ); + + return 1; + } else { + const ssize_t ast_len = 55; + const unichar asterisks[ast_len] = u"*******************************************************"; + NSString *obscuredPassword = [NSString stringWithCharacters:asterisks + length:MIN([password length], ast_len)]; + MBIMLogNotify(@"Using access control credentials: username(%@) password(%@)", username, obscuredPassword); + + bridge.usesAccessControl = YES; + bridge.authUsername = username; + bridge.authPassword = password; + } + } + if (usesSSL && certPath != NULL) { bridge.usesSSL = YES; bridge.sslCertPath = [NSString stringWithCString:certPath encoding:NSASCIIStringEncoding];