Skip to content

Commit

Permalink
Merge pull request #81 from paulcbetts/ios-cert-validation
Browse files Browse the repository at this point in the history
Ios cert validation
  • Loading branch information
Paul Betts committed Sep 11, 2014
2 parents 54ccda9 + 3a7084c commit 4634487
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 4 deletions.
6 changes: 5 additions & 1 deletion src/ModernHttpClient/Facades.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ public NativeMessageHandler(): base()
/// captive network (ie: a captive network is usually a wifi network
/// where an authentication html form is shown instead of the real
/// content).</param>
public NativeMessageHandler(bool throwOnCaptiveNetwork) : base()
/// <param name="customSSLVerification">Enable custom SSL certificate
/// verification via ServicePointManager. Disabled by default for
/// performance reasons (i.e. the OS default certificate verification
/// will take place)</param>
public NativeMessageHandler(bool throwOnCaptiveNetwork, bool customSSLVerification) : base()
{
}

Expand Down
82 changes: 81 additions & 1 deletion src/ModernHttpClient/iOS/NSUrlSessionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;

namespace ModernHttpClient
{
Expand All @@ -31,15 +34,17 @@ public class NativeMessageHandler : HttpMessageHandler
new Dictionary<HttpRequestMessage, ProgressDelegate>();

readonly bool throwOnCaptiveNetwork;
readonly bool customSSLVerification;

public NativeMessageHandler(): this(false) { }
public NativeMessageHandler(bool throwOnCaptiveNetwork)
public NativeMessageHandler(bool throwOnCaptiveNetwork, bool customSSLVerification)
{
session = NSUrlSession.FromConfiguration(
NSUrlSessionConfiguration.DefaultSessionConfiguration,
new DataTaskDelegate(this), null);

this.throwOnCaptiveNetwork = throwOnCaptiveNetwork;
this.customSSLVerification = customSSLVerification;
}

public void RegisterForProgress(HttpRequestMessage request, ProgressDelegate callback)
Expand Down Expand Up @@ -206,6 +211,81 @@ InflightOperation getResponseForTask(NSUrlSessionTask task)
}
}

static readonly Regex cnRegex = new Regex("CN=(.*?),.*", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline);

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;
}

if (ServicePointManager.ServerCertificateValidationCallback == null) {
goto doDefault;
}

// Convert Mono Certificates to .NET certificates and build cert
// chain from root certificate
var serverCertChain = challenge.ProtectionSpace.ServerSecTrust;
var chain = new X509Chain();
X509Certificate2 root = null;
var errors = SslPolicyErrors.None;

if (serverCertChain == null || serverCertChain.Count == 0) {
errors = SslPolicyErrors.RemoteCertificateNotAvailable;
goto sslErrorVerify;
}

if (serverCertChain.Count == 1) {
errors = SslPolicyErrors.RemoteCertificateChainErrors;
goto sslErrorVerify;
}

var netCerts = Enumerable.Range(0, serverCertChain.Count)
.Select(x => serverCertChain[x].ToX509Certificate2())
.ToArray();

for (int i = 1; i < netCerts.Length; i++) {
chain.ChainPolicy.ExtraStore.Add(netCerts[i]);
}

root = netCerts[0];

chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;

if (!chain.Build(root)) {
errors = SslPolicyErrors.RemoteCertificateChainErrors;
goto sslErrorVerify;
}

var subject = root.Subject;
var subjectCn = cnRegex.Match(subject).Groups[1].Value;

if (String.IsNullOrWhiteSpace(subjectCn) || subjectCn != task.CurrentRequest.Url.Host) {
errors = SslPolicyErrors.RemoteCertificateNameMismatch;
goto sslErrorVerify;
}

sslErrorVerify:
bool result = ServicePointManager.ServerCertificateValidationCallback(this, root, chain, errors);
if (result) {
completionHandler(NSUrlSessionAuthChallengeDisposition.UseCredential, null);
} else {
completionHandler(NSUrlSessionAuthChallengeDisposition.RejectProtectionSpace, null);
}
return;

doDefault:
completionHandler(NSUrlSessionAuthChallengeDisposition.PerformDefaultHandling, challenge.ProposedCredential);
return;
}

Exception createExceptionForNSError(NSError error)
{
var ret = default(Exception);
Expand Down
25 changes: 23 additions & 2 deletions src/Playground.iOS/Playground_iOSViewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using System.Security.Cryptography;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Net;
using System.Linq;

namespace Playground.iOS
{
Expand All @@ -28,6 +30,25 @@ public Playground_iOSViewController () : base ("Playground_iOSViewController", n
result.EnsureSuccessStatusCode();
});
*/

//This API is only available in Mono and Xamarin products.
//You can filter and/or re-order the ciphers suites that the SSL/TLS server will accept from a client.
//The following example removes weak (export) ciphers from the list that will be offered to the server.
ServicePointManager.ClientCipherSuitesCallback += (protocol, allCiphers) =>
allCiphers.Where(x => !x.Contains("EXPORT")).ToList();

//Here we accept any certificate and just print the cert's data.
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => {
System.Diagnostics.Debug.WriteLine("Callback Server Certificate: " + sslPolicyErrors);
foreach(var el in chain.ChainElements) {
System.Diagnostics.Debug.WriteLine(el.Certificate.GetCertHashString());
System.Diagnostics.Debug.WriteLine(el.Information);
}
return true;
};

}

CancellationTokenSource currentToken;
Expand Down Expand Up @@ -59,8 +80,8 @@ void HandleDownloadProgress(long bytes, long totalBytes, long totalBytesExpected

st.Start();
try {
//var url = "https://github.com/paulcbetts/ModernHttpClient/releases/download/0.9.0/ModernHttpClient-0.9.zip";
var url = "https://github.com/downloads/nadlabak/android/cm-9.1.0a-umts_sholes.zip";
var url = "https://github.com/paulcbetts/ModernHttpClient/releases/download/0.9.0/ModernHttpClient-0.9.zip";
//var url = "https://github.com/downloads/nadlabak/android/cm-9.1.0a-umts_sholes.zip";

var request = new HttpRequestMessage(HttpMethod.Get, url);
handler.RegisterForProgress(request, HandleDownloadProgress);
Expand Down

0 comments on commit 4634487

Please sign in to comment.