2 // System.Net.ServicePointManager
5 // Lawrence Pit (loz@cable.a2000.nl)
6 // Gonzalo Paniagua Javier (gonzalo@novell.com)
8 // Copyright (c) 2003-2010 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 #if MONO_SECURITY_ALIAS
35 extern alias MonoSecurity;
38 extern alias PrebuiltSystem;
41 #if MONO_SECURITY_ALIAS
42 using MonoSecurity::Mono.Security.Interface;
43 using MSX = MonoSecurity::Mono.Security.X509;
44 using MonoSecurity::Mono.Security.X509.Extensions;
46 using Mono.Security.Interface;
47 using MSX = Mono.Security.X509;
48 using Mono.Security.X509.Extensions;
51 using XX509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
53 using XX509CertificateCollection = System.Security.Cryptography.X509Certificates.X509CertificateCollection;
58 using System.Threading;
59 using System.Collections;
60 using System.Collections.Generic;
61 using System.Collections.Specialized;
62 using System.Configuration;
63 using System.Net.Configuration;
64 using System.Text.RegularExpressions;
65 using System.Security.Cryptography.X509Certificates;
67 using System.Globalization;
68 using System.Net.Security;
69 using System.Diagnostics;
71 namespace Mono.Net.Security
73 internal delegate bool ServerCertValidationCallbackWrapper (ServerCertValidationCallback callback, X509Certificate certificate, X509Chain chain, MonoSslPolicyErrors sslPolicyErrors);
75 internal class ChainValidationHelper : ICertificateValidator
77 readonly object sender;
78 readonly MonoTlsSettings settings;
79 readonly ServerCertValidationCallback certValidationCallback;
80 readonly LocalCertSelectionCallback certSelectionCallback;
81 readonly ServerCertValidationCallbackWrapper callbackWrapper;
82 readonly MonoTlsStream tlsStream;
83 readonly HttpWebRequest request;
85 static bool is_macosx;
86 static bool is_mobile;
88 static X509RevocationMode revocation_mode;
91 static ChainValidationHelper ()
100 is_macosx = System.IO.File.Exists (OSX509Certificates.SecurityLibrary);
105 revocation_mode = X509RevocationMode.NoCheck;
107 string str = Environment.GetEnvironmentVariable ("MONO_X509_REVOCATION_MODE");
108 if (String.IsNullOrEmpty (str))
110 revocation_mode = (X509RevocationMode)Enum.Parse (typeof(X509RevocationMode), str, true);
116 internal static ICertificateValidator GetDefaultValidator (MonoTlsSettings settings)
118 if (settings.CertificateValidator == null)
119 settings.CertificateValidator = new ChainValidationHelper (settings, false, null, null);
120 return settings.CertificateValidator;
123 #region SslStream support
126 * This is a hack which is used in SslStream - see ReferenceSources/SslStream.cs for details.
128 internal static ChainValidationHelper CloneWithCallbackWrapper (ref MonoTlsSettings settings, ServerCertValidationCallbackWrapper wrapper)
130 var helper = (ChainValidationHelper)settings.CertificateValidator;
132 helper = new ChainValidationHelper (settings, true, null, wrapper);
134 helper = new ChainValidationHelper (helper, settings, wrapper);
135 settings = helper.settings;
139 internal static bool InvokeCallback (ServerCertValidationCallback callback, object sender, X509Certificate certificate, X509Chain chain, MonoSslPolicyErrors sslPolicyErrors)
141 return callback.Invoke (sender, certificate, chain, (SslPolicyErrors)sslPolicyErrors);
146 ChainValidationHelper (ChainValidationHelper other, MonoTlsSettings settings, ServerCertValidationCallbackWrapper callbackWrapper = null)
148 sender = other.sender;
149 certValidationCallback = other.certValidationCallback;
150 certSelectionCallback = other.certSelectionCallback;
151 tlsStream = other.tlsStream;
152 request = other.request;
154 this.settings = settings = settings.CloneWithValidator (this);
155 this.callbackWrapper = callbackWrapper;
158 internal static ChainValidationHelper Create (ref MonoTlsSettings settings, MonoTlsStream stream)
160 var helper = new ChainValidationHelper (settings, true, stream, null);
161 settings = helper.settings;
165 ChainValidationHelper (MonoTlsSettings settings, bool cloneSettings, MonoTlsStream stream, ServerCertValidationCallbackWrapper callbackWrapper)
168 settings = settings.CloneWithValidator (this);
170 this.settings = settings;
171 this.tlsStream = stream;
172 this.callbackWrapper = callbackWrapper;
174 var fallbackToSPM = false;
176 if (settings != null) {
177 if (settings.ServerCertificateValidationCallback != null) {
178 var callback = Private.CallbackHelpers.MonoToPublic (settings.ServerCertificateValidationCallback);
179 certValidationCallback = new ServerCertValidationCallback (callback);
181 certSelectionCallback = Private.CallbackHelpers.MonoToInternal (settings.ClientCertificateSelectionCallback);
182 fallbackToSPM = settings.UseServicePointManagerCallback;
185 if (stream != null) {
186 this.request = stream.Request;
187 this.sender = request;
189 if (certValidationCallback == null)
190 certValidationCallback = request.ServerCertValidationCallback;
191 if (certSelectionCallback == null)
192 certSelectionCallback = new LocalCertSelectionCallback (DefaultSelectionCallback);
194 if (settings == null)
195 fallbackToSPM = true;
198 if (fallbackToSPM && certValidationCallback == null)
199 certValidationCallback = ServicePointManager.ServerCertValidationCallback;
202 static X509Certificate DefaultSelectionCallback (string targetHost, XX509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
204 X509Certificate clientCertificate;
205 if (localCertificates == null || localCertificates.Count == 0)
206 clientCertificate = null;
208 clientCertificate = localCertificates [0];
209 return clientCertificate;
212 public MonoTlsSettings Settings {
213 get { return settings; }
216 public bool HasCertificateSelectionCallback {
217 get { return certSelectionCallback != null; }
220 public X509Certificate SelectClientCertificate (
221 string targetHost, XX509CertificateCollection localCertificates, X509Certificate remoteCertificate,
222 string[] acceptableIssuers)
224 if (certSelectionCallback == null)
226 return certSelectionCallback (targetHost, localCertificates, remoteCertificate, acceptableIssuers);
229 internal bool ValidateClientCertificate (X509Certificate certificate, MonoSslPolicyErrors errors)
231 var certs2 = new X509Certificate2Collection ();
232 certs2.Add (new X509Certificate2 (certificate.GetRawCertData ()));
234 var result = ValidateChain (null, certs2, (SslPolicyErrors)errors);
238 return result.Trusted && !result.UserDenied;
241 static X509Certificate2Collection Convert (MSX.X509CertificateCollection certificates)
243 if (certificates == null)
246 var certs2 = new X509Certificate2Collection ();
247 for (int i = 0; i < certificates.Count; i++)
248 certs2.Add (new X509Certificate2 (certificates [i].RawData));
252 static X509Certificate2Collection Convert (XX509CertificateCollection certificates)
254 var certs2 = (object)certificates as X509Certificate2Collection;
255 if (certs2 != null || certificates == null)
258 certs2 = new X509Certificate2Collection ();
259 for (int i = 0; i < certificates.Count; i++)
260 certs2.Add ((X509Certificate2)certificates [i]);
264 public ValidationResult ValidateClientCertificate (XX509CertificateCollection certs)
266 var certs2 = Convert (certs);
267 return ValidateChain (null, certs2, 0);
270 public ValidationResult ValidateChain (string host, XX509CertificateCollection certs)
273 var certs2 = Convert (certs);
274 var result = ValidateChain (host, certs2, 0);
275 if (tlsStream != null)
276 tlsStream.CertificateValidationFailed = result == null || !result.Trusted || result.UserDenied;
279 if (tlsStream != null)
280 tlsStream.CertificateValidationFailed = true;
285 internal ValidationResult ValidateChain (string host, MSX.X509CertificateCollection certs)
288 var certs2 = Convert (certs);
289 var result = ValidateChain (host, certs2, 0);
290 if (tlsStream != null)
291 tlsStream.CertificateValidationFailed = result == null || !result.Trusted || result.UserDenied;
294 if (tlsStream != null)
295 tlsStream.CertificateValidationFailed = true;
300 ValidationResult ValidateChain (string host, X509Certificate2Collection certs, SslPolicyErrors errors)
302 // user_denied is true if the user callback is called and returns false
303 bool user_denied = false;
306 var hasCallback = certValidationCallback != null || callbackWrapper != null;
308 X509Certificate2 leaf;
309 if (certs == null || certs.Count == 0)
314 if (tlsStream != null)
315 request.ServicePoint.SetServerCertificate (leaf);
318 errors |= SslPolicyErrors.RemoteCertificateNotAvailable;
320 if (callbackWrapper != null)
321 result = callbackWrapper.Invoke (certValidationCallback, leaf, null, (MonoSslPolicyErrors)errors);
323 result = certValidationCallback.Invoke (sender, leaf, null, errors);
324 user_denied = !result;
326 return new ValidationResult (result, user_denied, 0, (MonoSslPolicyErrors)errors);
330 bool skipSystemValidators = false;
331 if (!CertificateValidationHelper.SupportsX509Chain || is_mobile || is_macosx) {
333 } else if (settings != null) {
334 skipSystemValidators = settings.SkipSystemValidators;
335 needsChain = !settings.SkipSystemValidators || settings.CallbackNeedsCertificateChain;
340 ICertificatePolicy policy = ServicePointManager.GetLegacyCertificatePolicy ();
342 int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback
343 X509Chain chain = null;
346 chain = new X509Chain ();
347 chain.ChainPolicy = new X509ChainPolicy ();
351 chain.ChainPolicy.RevocationMode = revocation_mode;
353 for (int i = 1; i < certs.Count; i++) {
354 chain.ChainPolicy.ExtraStore.Add (certs [i]);
361 if (!chain.Build (leaf))
362 errors |= GetErrorsFromChain (chain);
363 } catch (Exception e) {
364 Console.Error.WriteLine ("ERROR building certificate chain: {0}", e);
365 Console.Error.WriteLine ("Please, report this problem to the Mono team");
366 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
370 // for OSX and iOS we're using the native API to check for the SSL server policy and host names
372 if (!CheckCertificateUsage (leaf)) {
373 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
374 status11 = -2146762490; //CERT_E_PURPOSE 0x800B0106
377 if (host != null && !CheckServerIdentity (leaf, host)) {
378 errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
379 status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F
384 if (is_macosx && !skipSystemValidators) {
385 // Attempt to use OSX certificates
386 // Ideally we should return the SecTrustResult
387 OSX509Certificates.SecTrustResult trustResult = OSX509Certificates.SecTrustResult.Deny;
389 trustResult = OSX509Certificates.TrustEvaluateSsl (certs, host);
390 // We could use the other values of trustResult to pass this extra information
391 // to the .NET 2 callback for values like SecTrustResult.Confirm
392 result = (trustResult == OSX509Certificates.SecTrustResult.Proceed ||
393 trustResult == OSX509Certificates.SecTrustResult.Unspecified);
399 // TrustEvaluateSsl was successful so there's no trust error
400 // IOW we discard our own chain (since we trust OSX one instead)
403 // callback and DefaultCertificatePolicy needs this since 'result' is not specified
404 status11 = (int)trustResult;
405 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
410 #if MONODROID && SECURITY_DEP
411 if (!skipSystemValidators) {
412 result = AndroidPlatform.TrustEvaluateSsl (certs, sender, leaf, chain, errors);
414 // chain.Build() + GetErrorsFromChain() (above) will ALWAYS fail on
415 // Android (there are no mozroots or preinstalled root certificates),
416 // thus `errors` will ALWAYS have RemoteCertificateChainErrors.
417 // Android just verified the chain; clear RemoteCertificateChainErrors.
418 errors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
423 if (policy != null && (!(policy is DefaultCertificatePolicy) || certValidationCallback == null)) {
424 ServicePoint sp = null;
426 sp = request.ServicePointNoLock;
427 if (status11 == 0 && errors != 0)
428 status11 = GetStatusFromChain (chain);
431 result = policy.CheckValidationResult (sp, leaf, request, status11);
432 user_denied = !result && !(policy is DefaultCertificatePolicy);
434 // If there's a 2.0 callback, it takes precedence
436 if (callbackWrapper != null)
437 result = callbackWrapper.Invoke (certValidationCallback, leaf, chain, (MonoSslPolicyErrors)errors);
439 result = certValidationCallback.Invoke (sender, leaf, chain, errors);
440 user_denied = !result;
442 return new ValidationResult (result, user_denied, status11, (MonoSslPolicyErrors)errors);
445 static int GetStatusFromChain (X509Chain chain)
448 foreach (var status in chain.ChainStatus) {
449 X509ChainStatusFlags flags = status.Status;
450 if (flags == X509ChainStatusFlags.NoError)
454 if ((flags & X509ChainStatusFlags.NotTimeValid) != 0)
456 // CERT_E_VALIDITYPERIODNESTING
457 else if ((flags & X509ChainStatusFlags.NotTimeNested) != 0)
460 else if ((flags & X509ChainStatusFlags.Revoked) != 0)
462 // TRUST_E_CERT_SIGNATURE
463 else if ((flags & X509ChainStatusFlags.NotSignatureValid) != 0)
465 // CERT_E_WRONG_USAGE
466 else if ((flags & X509ChainStatusFlags.NotValidForUsage) != 0)
468 // CERT_E_UNTRUSTEDROOT
469 else if ((flags & X509ChainStatusFlags.UntrustedRoot) != 0)
471 // CRYPT_E_NO_REVOCATION_CHECK
472 else if ((flags & X509ChainStatusFlags.RevocationStatusUnknown) != 0)
475 else if ((flags & X509ChainStatusFlags.Cyclic) != 0)
477 // TRUST_E_FAIL - generic
478 else if ((flags & X509ChainStatusFlags.InvalidExtension) != 0)
480 // CERT_E_UNTRUSTEDROOT
481 else if ((flags & X509ChainStatusFlags.InvalidPolicyConstraints) != 0)
483 // TRUST_E_BASIC_CONSTRAINTS
484 else if ((flags & X509ChainStatusFlags.InvalidBasicConstraints) != 0)
486 // CERT_E_INVALID_NAME
487 else if ((flags & X509ChainStatusFlags.InvalidNameConstraints) != 0)
489 // CERT_E_INVALID_NAME
490 else if ((flags & X509ChainStatusFlags.HasNotSupportedNameConstraint) != 0)
492 // CERT_E_INVALID_NAME
493 else if ((flags & X509ChainStatusFlags.HasNotDefinedNameConstraint) != 0)
495 // CERT_E_INVALID_NAME
496 else if ((flags & X509ChainStatusFlags.HasNotPermittedNameConstraint) != 0)
498 // CERT_E_INVALID_NAME
499 else if ((flags & X509ChainStatusFlags.HasExcludedNameConstraint) != 0)
502 else if ((flags & X509ChainStatusFlags.PartialChain) != 0)
505 else if ((flags & X509ChainStatusFlags.CtlNotTimeValid) != 0)
507 // TRUST_E_CERT_SIGNATURE
508 else if ((flags & X509ChainStatusFlags.CtlNotSignatureValid) != 0)
510 // CERT_E_WRONG_USAGE
511 else if ((flags & X509ChainStatusFlags.CtlNotValidForUsage) != 0)
513 // CRYPT_E_NO_REVOCATION_CHECK
514 else if ((flags & X509ChainStatusFlags.OfflineRevocation) != 0)
516 // CERT_E_ISSUERCHAINING
517 else if ((flags & X509ChainStatusFlags.NoIssuanceChainPolicy) != 0)
520 result = 0x800B010B; // TRUST_E_FAIL - generic
522 break; // Exit the loop on the first error
529 static SslPolicyErrors GetErrorsFromChain (X509Chain chain)
531 SslPolicyErrors errors = SslPolicyErrors.None;
532 foreach (var status in chain.ChainStatus) {
533 if (status.Status == X509ChainStatusFlags.NoError)
535 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
541 static X509KeyUsageFlags s_flags = X509KeyUsageFlags.DigitalSignature |
542 X509KeyUsageFlags.KeyAgreement |
543 X509KeyUsageFlags.KeyEncipherment;
544 // Adapted to System 2.0+ from TlsServerCertificate.cs
545 //------------------------------
546 // Note: this method only works for RSA certificates
547 // DH certificates requires some changes - does anyone use one ?
548 static bool CheckCertificateUsage (X509Certificate2 cert)
551 // certificate extensions are required for this
552 // we "must" accept older certificates without proofs
553 if (cert.Version < 3)
556 X509KeyUsageExtension kux = (cert.Extensions ["2.5.29.15"] as X509KeyUsageExtension);
557 X509EnhancedKeyUsageExtension eku = (cert.Extensions ["2.5.29.37"] as X509EnhancedKeyUsageExtension);
558 if (kux != null && eku != null) {
559 // RFC3280 states that when both KeyUsageExtension and
560 // ExtendedKeyUsageExtension are present then BOTH should
562 if ((kux.KeyUsages & s_flags) == 0)
564 return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
565 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
566 } else if (kux != null) {
567 return ((kux.KeyUsages & s_flags) != 0);
568 } else if (eku != null) {
569 // Server Authentication (1.3.6.1.5.5.7.3.1) or
570 // Netscape Server Gated Crypto (2.16.840.1.113730.4)
571 return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
572 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
575 // last chance - try with older (deprecated) Netscape extensions
576 X509Extension ext = cert.Extensions ["2.16.840.1.113730.1.1"];
578 string text = ext.NetscapeCertType (false);
579 return text.IndexOf ("SSL Server Authentication", StringComparison.Ordinal) != -1;
582 } catch (Exception e) {
583 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
584 Console.Error.WriteLine ("Please, report this problem to the Mono team");
589 // RFC2818 - HTTP Over TLS, Section 3.1
590 // http://www.ietf.org/rfc/rfc2818.txt
592 // 1. if present MUST use subjectAltName dNSName as identity
593 // 1.1. if multiples entries a match of any one is acceptable
594 // 1.2. wildcard * is acceptable
595 // 2. URI may be an IP address -> subjectAltName.iPAddress
596 // 2.1. exact match is required
597 // 3. Use of the most specific Common Name (CN=) in the Subject
598 // 3.1 Existing practice but DEPRECATED
599 static bool CheckServerIdentity (X509Certificate2 cert, string targetHost)
602 var mcert = new MSX.X509Certificate (cert.RawData);
603 MSX.X509Extension ext = mcert.Extensions ["2.5.29.17"];
606 SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
607 // 1.1 - multiple dNSName
608 foreach (string dns in subjectAltName.DNSNames) {
609 // 1.2 TODO - wildcard support
610 if (Match (targetHost, dns))
614 foreach (string ip in subjectAltName.IPAddresses) {
615 // 2.1. Exact match required
616 if (ip == targetHost)
620 // 3. Common Name (CN=)
621 return CheckDomainName (mcert.SubjectName, targetHost);
622 } catch (Exception e) {
623 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
624 Console.Error.WriteLine ("Please, report this problem to the Mono team");
629 static bool CheckDomainName (string subjectName, string targetHost)
631 string domainName = String.Empty;
632 Regex search = new Regex (@"CN\s*=\s*([^,]*)");
633 MatchCollection elements = search.Matches (subjectName);
634 if (elements.Count == 1) {
635 if (elements [0].Success)
636 domainName = elements [0].Groups [1].Value.ToString ();
639 return Match (targetHost, domainName);
642 // ensure the pattern is valid wrt to RFC2595 and RFC2818
643 // http://www.ietf.org/rfc/rfc2595.txt
644 // http://www.ietf.org/rfc/rfc2818.txt
645 static bool Match (string hostname, string pattern)
647 // check if this is a pattern
648 int index = pattern.IndexOf ('*');
650 // not a pattern, do a direct case-insensitive comparison
651 return (String.Compare (hostname, pattern, true, CultureInfo.InvariantCulture) == 0);
654 // check pattern validity
655 // A "*" wildcard character MAY be used as the left-most name component in the certificate.
657 // unless this is the last char (valid)
658 if (index != pattern.Length - 1) {
659 // then the next char must be a dot .'.
660 if (pattern [index + 1] != '.')
664 // only one (A) wildcard is supported
665 int i2 = pattern.IndexOf ('*', index + 1);
669 // match the end of the pattern
670 string end = pattern.Substring (index + 1);
671 int length = hostname.Length - end.Length;
672 // no point to check a pattern that is longer than the hostname
676 if (String.Compare (hostname, length, end, 0, end.Length, true, CultureInfo.InvariantCulture) != 0)
679 // special case, we start with the wildcard
681 // ensure we hostname non-matched part (start) doesn't contain a dot
682 int i3 = hostname.IndexOf ('.');
683 return ((i3 == -1) || (i3 >= (hostname.Length - end.Length)));
686 // match the start of the pattern
687 string start = pattern.Substring (0, index);
688 return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);