-
Notifications
You must be signed in to change notification settings - Fork 559
NSUrlSessionHandler: Adds support for X509 client certificates #20434
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
Changes from all commits
be5b13f
3d580dc
4be0f2e
757e266
7671348
87f8250
1904d64
423a995
7d70e95
759703a
6d2d25b
7a92d92
05a5488
71c0a31
d67f4e1
d77ddc6
6b9b9ce
48f1e50
674a8e1
3245b94
93d0e47
8a88172
8d3017b
8285149
04d41a6
1afd980
21a0c69
59ad500
fadf9b9
58a6a26
35d6a45
85a6cb6
ebc030b
7de13b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -567,15 +567,20 @@ public DecompressionMethods AutomaticDecompression { | |||||
| [EditorBrowsable (EditorBrowsableState.Never)] | ||||||
| public bool CheckCertificateRevocationList { get; set; } = false; | ||||||
|
|
||||||
| // We're ignoring this property, just like Xamarin.Android does: | ||||||
| // https://github.com/xamarin/xamarin-android/blob/09e8cb5c07ea6c39383185a3f90e53186749b802/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs#L150 | ||||||
| // Note: we can't return null (like Xamarin.Android does), because the return type isn't nullable. | ||||||
| [UnsupportedOSPlatform ("ios")] | ||||||
| [UnsupportedOSPlatform ("maccatalyst")] | ||||||
| [UnsupportedOSPlatform ("tvos")] | ||||||
| [UnsupportedOSPlatform ("macos")] | ||||||
| [EditorBrowsable (EditorBrowsableState.Never)] | ||||||
| public X509CertificateCollection ClientCertificates { get { return new X509CertificateCollection (); } } | ||||||
|
|
||||||
| X509CertificateCollection? _clientCertificates; | ||||||
|
|
||||||
| /// <summary>Gets the collection of security certificates that are associated with requests to the server.</summary> | ||||||
| /// <remarks>Client certificates are only supported when ClientCertificateOptions is set to ClientCertificateOptions.Manual.</remarks> | ||||||
| public X509CertificateCollection ClientCertificates { | ||||||
| get { | ||||||
| if (ClientCertificateOptions != ClientCertificateOption.Manual) { | ||||||
| throw new InvalidOperationException ($"Enable manual options first on {nameof (ClientCertificateOptions)}"); | ||||||
| } | ||||||
|
|
||||||
| return _clientCertificates ?? (_clientCertificates = new X509CertificateCollection ()); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // We're ignoring this property, just like Xamarin.Android does: | ||||||
| // https://github.com/xamarin/xamarin-android/blob/09e8cb5c07ea6c39383185a3f90e53186749b802/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs#L148 | ||||||
|
|
@@ -1085,6 +1090,19 @@ void DidReceiveChallengeImpl (NSUrlSession session, NSUrlSessionTask task, NSUrl | |||||
| } | ||||||
| return; | ||||||
| } | ||||||
| #if NET | ||||||
| if (sessionHandler.ClientCertificateOptions == ClientCertificateOption.Manual && challenge.ProtectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodClientCertificate) { | ||||||
| var certificate = CertificateHelper.GetEligibleClientCertificate (sessionHandler.ClientCertificates); | ||||||
| if (certificate is not null) { | ||||||
| var cert = new SecCertificate (certificate); | ||||||
| var identity = SecIdentity.Import (certificate); | ||||||
| var credential = new NSUrlCredential (identity, new SecCertificate [] { cert }, NSUrlCredentialPersistence.ForSession); | ||||||
| completionHandler (NSUrlSessionAuthChallengeDisposition.UseCredential, credential); | ||||||
| return; | ||||||
| } | ||||||
| } | ||||||
dotMorten marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| #endif | ||||||
|
|
||||||
| // case for the basic auth failing up front. As per apple documentation: | ||||||
| // The URL Loading System is designed to handle various aspects of the HTTP protocol for you. As a result, you should not modify the following headers using | ||||||
| // the addValue(_:forHTTPHeaderField:) or setValue(_:forHTTPHeaderField:) methods: | ||||||
|
|
@@ -1589,5 +1607,73 @@ protected override void Dispose (bool disposing) | |||||
| stream?.Dispose (); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| #if NET | ||||||
| static class CertificateHelper { | ||||||
| // Based on https://github.com/dotnet/runtime/blob/c2848c582f5d6ae42c89f5bfe0818687ab3345f0/src/libraries/Common/src/System/Net/Security/CertificateHelper.cs | ||||||
| // with the NetEventSource code removed and namespace changed. | ||||||
| const string ClientAuthenticationOID = "1.3.6.1.5.5.7.3.2"; | ||||||
|
|
||||||
| internal static X509Certificate2? GetEligibleClientCertificate (X509CertificateCollection? candidateCerts) | ||||||
| { | ||||||
| if (candidateCerts is null || candidateCerts.Count == 0) { | ||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| var certs = new X509Certificate2Collection (); | ||||||
| certs.AddRange (candidateCerts); | ||||||
|
|
||||||
| return GetEligibleClientCertificate (certs); | ||||||
| } | ||||||
|
|
||||||
| internal static X509Certificate2? GetEligibleClientCertificate (X509Certificate2Collection? candidateCerts) | ||||||
| { | ||||||
| if (candidateCerts is null || candidateCerts.Count == 0) { | ||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| foreach (X509Certificate2 cert in candidateCerts) { | ||||||
| if (!cert.HasPrivateKey) { | ||||||
| continue; | ||||||
| } | ||||||
|
|
||||||
| if (IsValidClientCertificate (cert)) { | ||||||
| return cert; | ||||||
| } | ||||||
| } | ||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| static bool IsValidClientCertificate (X509Certificate2 cert) | ||||||
| { | ||||||
| foreach (X509Extension extension in cert.Extensions) { | ||||||
| if ((extension is X509EnhancedKeyUsageExtension eku) && !IsValidForClientAuthenticationEKU (eku)) { | ||||||
| return false; | ||||||
| } else if ((extension is X509KeyUsageExtension ku) && !IsValidForDigitalSignatureUsage (ku)) { | ||||||
| return false; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| return true; | ||||||
| } | ||||||
|
|
||||||
| static bool IsValidForClientAuthenticationEKU (X509EnhancedKeyUsageExtension eku) | ||||||
| { | ||||||
| foreach (System.Security.Cryptography.Oid oid in eku.EnhancedKeyUsages) { | ||||||
| if (oid.Value == ClientAuthenticationOID) { | ||||||
| return true; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| static bool IsValidForDigitalSignatureUsage (X509KeyUsageExtension ku) | ||||||
| { | ||||||
| const X509KeyUsageFlags RequiredUsages = X509KeyUsageFlags.DigitalSignature; | ||||||
| return (ku.KeyUsages & RequiredUsages) == RequiredUsages; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have always preferred the HasFlag method over the &, but is a matter of taste. I just think it is easier to read.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is code taken from the .NET Runtime I tried not changing the logic from its origin. |
||||||
| } | ||||||
| } | ||||||
| #endif | ||||||
| } | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.