diff --git a/.gitignore b/.gitignore
index f5dc20d..ac2791b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ build/
*.userprefs
build/
*.xam
+*.suo
\ No newline at end of file
diff --git a/src/ModernHttpClient/Android/OkHttpNetworkHandler.cs b/src/ModernHttpClient/Android/OkHttpNetworkHandler.cs
index de6d3c3..3355afa 100644
--- a/src/ModernHttpClient/Android/OkHttpNetworkHandler.cs
+++ b/src/ModernHttpClient/Android/OkHttpNetworkHandler.cs
@@ -13,6 +13,11 @@
using System.Globalization;
using Android.OS;
+// See http://chariotsolutions.com/blog/post/https-with-client-certificates-on/
+// for clue on Android client certs http://square.github.io/okhttp/javadoc/com/squareup/okhttp/CertificatePinner.html
+using Java.Security;
+
+
namespace ModernHttpClient
{
public class NativeMessageHandler : HttpClientHandler
@@ -32,12 +37,44 @@ public class NativeMessageHandler : HttpClientHandler
public NativeMessageHandler() : this(false, false) {}
- public NativeMessageHandler(bool throwOnCaptiveNetwork, bool customSSLVerification, NativeCookieHandler cookieHandler = null)
+ public NativeMessageHandler(
+ bool throwOnCaptiveNetwork, bool customSSLVerification,
+ NativeCookieHandler cookieHandler = null,
+ byte[] pfxData = null, string pfxPassword = null)
{
this.throwOnCaptiveNetwork = throwOnCaptiveNetwork;
if (customSSLVerification) client.SetHostnameVerifier(new HostnameVerifier());
noCacheCacheControl = (new CacheControl.Builder()).NoCache().Build();
+
+ if (pfxData != null && pfxData.Length > 0) {
+ var sslSocketFactory = createSSLSocketFactory(pfxData, pfxPassword);
+ client.SetSslSocketFactory(sslSocketFactory);
+ }
+ }
+
+ private SSLSocketFactory createSSLSocketFactory(byte[] pfxData, string pfxPassword)
+ {
+ var sslSocketFactory = client.SslSocketFactory;
+
+ try {
+ var stream = new System.IO.MemoryStream(pfxData);
+ KeyStore keyStore = KeyStore.GetInstance("PKCS12");
+ keyStore.Load(stream, pfxPassword.ToCharArray());
+
+ KeyManagerFactory kmf = KeyManagerFactory.GetInstance("X509");
+ kmf.Init(keyStore, pfxPassword.ToCharArray());
+ IKeyManager[] keyManagers = kmf.GetKeyManagers();
+
+ SSLContext sslContext = SSLContext.GetInstance("TLS");
+ sslContext.Init(keyManagers, null, null);
+ sslSocketFactory = sslContext.SocketFactory;
+
+ } catch (Exception e) {
+ throw e; // The system has no TLS. Just give up.
+ }
+
+ return sslSocketFactory;
}
public void RegisterForProgress(HttpRequestMessage request, ProgressDelegate callback)
diff --git a/src/ModernHttpClient/ModernHttpClient.Android.csproj b/src/ModernHttpClient/ModernHttpClient.Android.csproj
index 87271cd..52a5524 100644
--- a/src/ModernHttpClient/ModernHttpClient.Android.csproj
+++ b/src/ModernHttpClient/ModernHttpClient.Android.csproj
@@ -15,7 +15,6 @@
Assets
False
ModernHttpClient
- v2.3
true
diff --git a/src/ModernHttpClient/iOS/NSUrlSessionHandler.cs b/src/ModernHttpClient/iOS/NSUrlSessionHandler.cs
index 7226c51..78d59dd 100644
--- a/src/ModernHttpClient/iOS/NSUrlSessionHandler.cs
+++ b/src/ModernHttpClient/iOS/NSUrlSessionHandler.cs
@@ -11,11 +11,14 @@
using System.Text.RegularExpressions;
using ModernHttpClient.CoreFoundation;
using ModernHttpClient.Foundation;
+using System.Security;
#if UNIFIED
using Foundation;
+using Security;
#else
using MonoTouch.Foundation;
+using MonoTouch.Security;
using System.Globalization;
#endif
@@ -52,11 +55,14 @@ public class NativeMessageHandler : HttpClientHandler
public bool DisableCaching { get; set; }
public NativeMessageHandler(): this(false, false) { }
- public NativeMessageHandler(bool throwOnCaptiveNetwork, bool customSSLVerification, NativeCookieHandler cookieHandler = null)
+ public NativeMessageHandler(
+ bool throwOnCaptiveNetwork, bool customSSLVerification,
+ NativeCookieHandler cookieHandler = null,
+ byte[] pfxData = null, string pfxPassword = null)
{
session = NSUrlSession.FromConfiguration(
NSUrlSessionConfiguration.DefaultSessionConfiguration,
- new DataTaskDelegate(this), null);
+ new DataTaskDelegate(this, pfxData, pfxPassword), null);
this.throwOnCaptiveNetwork = throwOnCaptiveNetwork;
this.customSSLVerification = customSSLVerification;
@@ -142,10 +148,16 @@ protected override async Task SendAsync(HttpRequestMessage
class DataTaskDelegate : NSUrlSessionDataDelegate
{
NativeMessageHandler This { get; set; }
+ NSUrlCredential _credential;
- public DataTaskDelegate(NativeMessageHandler that)
+ public DataTaskDelegate(NativeMessageHandler that, byte[] pfxData = null, string pfxPassword = null)
{
this.This = that;
+
+ if (pfxData != null)
+ {
+ _credential = exportCredential(pfxData, pfxPassword);
+ }
}
public override void DidReceiveResponse(NSUrlSession session, NSUrlSessionDataTask dataTask, NSUrlResponse response, Action completionHandler)
@@ -242,16 +254,71 @@ InflightOperation getResponseForTask(NSUrlSessionTask task)
static readonly Regex cnRegex = new Regex(@"CN\s*=\s*([^,]*)", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline);
+ private static NSUrlCredential exportCredential(byte[] pfxData, string password = null)
+ {
+ NSDictionary[] items;
+ NSDictionary opt;
+
+ if (String.IsNullOrEmpty(password)) {
+ opt = new NSDictionary();
+ } else {
+ opt = NSDictionary.FromObjectsAndKeys(
+ new object[] { password }, new object[] { "passphrase" });
+ }
+
+ var status = SecImportExport.ImportPkcs12(pfxData, opt, out items);
+
+ if (status == SecStatusCode.Success)
+ {
+ var identityRef = items[0]["identity"];
+
+ var identity = new SecIdentity(identityRef.Handle);
+
+ SecCertificate[] certs = { identity.Certificate };
+
+ var credential = new NSUrlCredential(identity, certs, NSUrlCredentialPersistence.ForSession);
+ return credential;
+ }
+
+ return null;
+ }
+
public override void DidReceiveChallenge(NSUrlSession session, NSUrlSessionTask task, NSUrlAuthenticationChallenge challenge, Action completionHandler)
{
if (!This.customSSLVerification) {
goto doDefault;
}
- if (challenge.ProtectionSpace.AuthenticationMethod != "NSURLAuthenticationMethodServerTrust") {
- goto doDefault;
+ // https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html\
+ // For Client Certificate,
+ // http://stackoverflow.com/questions/21537203/ios-nsurlauthenticationmethodclientcertificate-not-requested-vs-activesync-serve
+ // http://www.sunethmendis.com/2013/01/11/certificate-based-client-authentication-in-ios/
+ // https://forums.xamarin.com/discussion/39535/didreceivechallenge-issue-with-x509-certificates
+ switch (challenge.ProtectionSpace.AuthenticationMethod)
+ {
+ case "NSURLAuthenticationMethodServerTrust":
+ goto serverTrust;
+ case "NSURLAuthenticationMethodClientCertificate":
+ goto clientCert;
+ case "NSURLAuthenticationMethodHTTPBasic":
+ case "NSURLAuthenticationMethodHTTPDigest":
+ case "NSURLAuthenticationMethodNTLM":
+ default:
+ goto doDefault;
}
+ clientCert:
+ if (_credential == null)
+ goto doDefault;
+
+#if UNIFIED
+ challenge.Sender.UseCredential(_credential, challenge);
+#else
+ challenge.Sender.UseCredentials(_credential, challenge);
+#endif
+ goto doDefault;
+
+ serverTrust:
if (ServicePointManager.ServerCertificateValidationCallback == null) {
goto doDefault;
}