diff --git a/src/ios/CordovaHttpPlugin.h b/src/ios/CordovaHttpPlugin.h index 3b35e17..0e5d867 100644 --- a/src/ios/CordovaHttpPlugin.h +++ b/src/ios/CordovaHttpPlugin.h @@ -5,6 +5,7 @@ @interface CordovaHttpPlugin : CDVPlugin - (void)setServerTrustMode:(CDVInvokedUrlCommand*)command; +- (void)setClientAuthMode:(CDVInvokedUrlCommand*)command; - (void)post:(CDVInvokedUrlCommand*)command; - (void)put:(CDVInvokedUrlCommand*)command; - (void)patch:(CDVInvokedUrlCommand*)command; diff --git a/src/ios/CordovaHttpPlugin.m b/src/ios/CordovaHttpPlugin.m index 2f8c750..7e5ebb0 100644 --- a/src/ios/CordovaHttpPlugin.m +++ b/src/ios/CordovaHttpPlugin.m @@ -21,6 +21,7 @@ @implementation CordovaHttpPlugin { AFSecurityPolicy *securityPolicy; + NSURLCredential *x509Credential; } - (void)pluginInitialize { @@ -39,6 +40,22 @@ } } +- (void)setupClientCertAuth:(AFHTTPSessionManager*)manager { + [manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession * _Nonnull session, NSURLAuthenticationChallenge * _Nonnull challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential) { + + if ([challenge.protectionSpace.authenticationMethod isEqualToString: NSURLAuthenticationMethodClientCertificate]) { + if (self->x509Credential) { + *credential = self->x509Credential; + return NSURLSessionAuthChallengeUseCredential; + } else { + return NSURLSessionAuthChallengePerformDefaultHandling; + } + } + + return NSURLSessionAuthChallengePerformDefaultHandling; + }]; +} + - (void)setRequestHeaders:(NSDictionary*)headers forManager:(AFHTTPSessionManager*)manager { [headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { [manager.requestSerializer setValue:obj forHTTPHeaderField:key]; @@ -156,6 +173,7 @@ NSString *responseType = [command.arguments objectAtIndex:4]; [self setRequestSerializer: @"default" forManager: manager]; + [self setupClientCertAuth: manager]; [self setRequestHeaders: headers forManager: manager]; [self setTimeout:timeoutInSeconds forManager:manager]; [self setRedirect:followRedirect forManager:manager]; @@ -210,6 +228,7 @@ NSString *responseType = [command.arguments objectAtIndex:6]; [self setRequestSerializer: serializerName forManager: manager]; + [self setupClientCertAuth: manager]; [self setRequestHeaders: headers forManager: manager]; [self setTimeout:timeoutInSeconds forManager:manager]; [self setRedirect:followRedirect forManager:manager]; @@ -302,6 +321,51 @@ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } +- (void)setClientAuthMode:(CDVInvokedUrlCommand*)command { + CDVPluginResult* pluginResult; + NSString *mode = [command.arguments objectAtIndex:0]; + + if ([mode isEqualToString:@"none"]) { + x509Credential = nil; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } + + if ([mode isEqualToString:@"systemstore"]) { + NSString *alias = [command.arguments objectAtIndex:1]; + + // TODO + + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"mode 'systemstore' not supported on iOS"]; + } + + if ([mode isEqualToString:@"buffer"]) { + CFDataRef container = (__bridge CFDataRef) [command.arguments objectAtIndex:2]; + CFStringRef password = (__bridge CFStringRef) [command.arguments objectAtIndex:3]; + + const void *keys[] = { kSecImportExportPassphrase }; + const void *values[] = { password }; + + CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); + CFArrayRef items; + OSStatus securityError = SecPKCS12Import(container, options, &items); + CFRelease(options); + + if (securityError != noErr) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; + } else { + CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0); + SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity); + + self->x509Credential = [NSURLCredential credentialWithIdentity:identity certificates: nil persistence:NSURLCredentialPersistenceForSession]; + CFRelease(items); + + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } + } + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + - (void)post:(CDVInvokedUrlCommand*)command { [self executeRequestWithData: command withMethod:@"POST"]; } diff --git a/test/e2e-specs.js b/test/e2e-specs.js index c11b244..5f1bf78 100644 --- a/test/e2e-specs.js +++ b/test/e2e-specs.js @@ -946,18 +946,16 @@ const tests = [ }); } }, - - // TODO: not ready yet - // { - // description: 'should authenticate correctly when client cert auth is configured with a PKCS12 container', - // expected: 'resolved: {"status": 200, ...', - // before: helpers.setBufferClientAuthMode, - // func: function (resolve, reject) { cordova.plugin.http.get('https://client.badssl.com/', {}, {}, resolve, reject); }, - // validationFunc: function (driver, result) { - // result.type.should.be.equal('resolved'); - // result.data.data.should.include('TLS handshake'); - // } - // } + { + description: 'should authenticate correctly when client cert auth is configured with a PKCS12 container', + expected: 'resolved: {"status": 200, ...', + before: helpers.setBufferClientAuthMode, + func: function (resolve, reject) { cordova.plugin.http.get('https://client.badssl.com/', {}, {}, resolve, reject); }, + validationFunc: function (driver, result) { + result.type.should.be.equal('resolved'); + result.data.data.should.include('TLS handshake'); + } + } ]; if (typeof module !== 'undefined' && module.exports) {