Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

client cert support #169

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ build/
*.userprefs
build/
*.xam
*.suo
39 changes: 38 additions & 1 deletion src/ModernHttpClient/Android/OkHttpNetworkHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
1 change: 0 additions & 1 deletion src/ModernHttpClient/ModernHttpClient.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<AssemblyName>ModernHttpClient</AssemblyName>
<TargetFrameworkVersion>v2.3</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down
77 changes: 72 additions & 5 deletions src/ModernHttpClient/iOS/NSUrlSessionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -142,10 +148,16 @@ protected override async Task<HttpResponseMessage> 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<NSUrlSessionResponseDisposition> completionHandler)
Expand Down Expand Up @@ -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<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> 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;
}
Expand Down