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 MONOTOUCH || MONODROID
35 using Mono.Security.Protocol.Tls;
36 using MSX = Mono.Security.X509;
37 using Mono.Security.X509.Extensions;
39 extern alias MonoSecurity;
40 using MonoSecurity::Mono.Security.X509.Extensions;
41 using MonoSecurity::Mono.Security.Protocol.Tls;
42 using MSX = MonoSecurity::Mono.Security.X509;
45 using System.Text.RegularExpressions;
49 using System.Threading;
50 using System.Collections;
51 using System.Collections.Generic;
52 using System.Collections.Specialized;
53 using System.Configuration;
54 using System.Net.Configuration;
55 using System.Security.Cryptography.X509Certificates;
57 using System.Globalization;
58 using System.Net.Security;
59 using System.Diagnostics;
63 // A service point manager manages service points (duh!).
64 // A service point maintains a list of connections (per scheme + authority).
65 // According to HttpWebRequest.ConnectionGroupName each connection group
66 // creates additional connections. therefor, a service point has a hashtable
67 // of connection groups where each value is a list of connections.
69 // when we need to make an HttpWebRequest, we need to do the following:
70 // 1. find service point, given Uri and Proxy
71 // 2. find connection group, given service point and group name
72 // 3. find free connection in connection group, or create one (if ok due to limits)
73 // 4. lease connection
75 // 6. when finished, return connection
81 public partial class ServicePointManager {
83 Uri uri; // schema/host/port
87 public SPKey (Uri uri, Uri proxy, bool use_connect) {
90 this.use_connect = use_connect;
97 public bool UseConnect {
98 get { return use_connect; }
101 public bool UsesProxy {
102 get { return proxy != null; }
105 public override int GetHashCode () {
107 hash = hash * 31 + ((use_connect) ? 1 : 0);
108 hash = hash * 31 + uri.GetHashCode ();
109 hash = hash * 31 + (proxy != null ? proxy.GetHashCode () : 0);
113 public override bool Equals (object obj) {
114 SPKey other = obj as SPKey;
119 if (!uri.Equals (other.uri))
121 if (use_connect != other.use_connect || UsesProxy != other.UsesProxy)
123 if (UsesProxy && !proxy.Equals (other.proxy))
129 private static HybridDictionary servicePoints = new HybridDictionary ();
133 private static ICertificatePolicy policy = new DefaultCertificatePolicy ();
134 private static int defaultConnectionLimit = DefaultPersistentConnectionLimit;
135 private static int maxServicePointIdleTime = 100000; // 100 seconds
136 private static int maxServicePoints = 0;
137 private static bool _checkCRL = false;
138 private static SecurityProtocolType _securityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
140 static bool expectContinue = true;
141 static bool useNagle;
142 static RemoteCertificateValidationCallback server_cert_cb;
143 static bool tcp_keepalive;
144 static int tcp_keepalive_time;
145 static int tcp_keepalive_interval;
149 public const int DefaultNonPersistentConnectionLimit = 4;
151 public const int DefaultPersistentConnectionLimit = 10;
153 public const int DefaultPersistentConnectionLimit = 2;
157 const string configKey = "system.net/connectionManagement";
158 static ConnectionManagementData manager;
161 static ServicePointManager ()
164 #if CONFIGURATION_DEP
165 object cfg = ConfigurationManager.GetSection (configKey);
166 ConnectionManagementSection s = cfg as ConnectionManagementSection;
168 manager = new ConnectionManagementData (null);
169 foreach (ConnectionManagementElement e in s.ConnectionManagement)
170 manager.Add (e.Address, e.MaxConnection);
172 defaultConnectionLimit = (int) manager.GetMaxConnections ("*");
176 manager = (ConnectionManagementData) ConfigurationSettings.GetConfig (configKey);
177 if (manager != null) {
178 defaultConnectionLimit = (int) manager.GetMaxConnections ("*");
184 private ServicePointManager ()
190 [Obsolete ("Use ServerCertificateValidationCallback instead", false)]
191 public static ICertificatePolicy CertificatePolicy {
192 get { return policy; }
193 set { policy = value; }
196 [MonoTODO("CRL checks not implemented")]
197 public static bool CheckCertificateRevocationList {
198 get { return _checkCRL; }
199 set { _checkCRL = false; } // TODO - don't yet accept true
202 public static int DefaultConnectionLimit {
203 get { return defaultConnectionLimit; }
206 throw new ArgumentOutOfRangeException ("value");
208 defaultConnectionLimit = value;
211 manager.Add ("*", defaultConnectionLimit);
216 static Exception GetMustImplement ()
218 return new NotImplementedException ();
222 public static int DnsRefreshTimeout
225 throw GetMustImplement ();
228 throw GetMustImplement ();
233 public static bool EnableDnsRoundRobin
236 throw GetMustImplement ();
239 throw GetMustImplement ();
243 public static int MaxServicePointIdleTime {
245 return maxServicePointIdleTime;
248 if (value < -2 || value > Int32.MaxValue)
249 throw new ArgumentOutOfRangeException ("value");
250 maxServicePointIdleTime = value;
254 public static int MaxServicePoints {
256 return maxServicePoints;
260 throw new ArgumentException ("value");
262 maxServicePoints = value;
267 // we need it for SslClientStream
272 static SecurityProtocolType SecurityProtocol {
273 get { return _securityProtocol; }
274 set { _securityProtocol = value; }
277 public static RemoteCertificateValidationCallback ServerCertificateValidationCallback
280 return server_cert_cb;
283 server_cert_cb = value;
287 public static bool Expect100Continue {
288 get { return expectContinue; }
289 set { expectContinue = value; }
292 public static bool UseNagleAlgorithm {
293 get { return useNagle; }
294 set { useNagle = value; }
298 public static void SetTcpKeepAlive (bool enabled, int keepAliveTime, int keepAliveInterval)
301 if (keepAliveTime <= 0)
302 throw new ArgumentOutOfRangeException ("keepAliveTime", "Must be greater than 0");
303 if (keepAliveInterval <= 0)
304 throw new ArgumentOutOfRangeException ("keepAliveInterval", "Must be greater than 0");
307 tcp_keepalive = enabled;
308 tcp_keepalive_time = keepAliveTime;
309 tcp_keepalive_interval = keepAliveInterval;
312 public static ServicePoint FindServicePoint (Uri address)
314 return FindServicePoint (address, GlobalProxySelection.Select);
317 public static ServicePoint FindServicePoint (string uriString, IWebProxy proxy)
319 return FindServicePoint (new Uri(uriString), proxy);
322 public static ServicePoint FindServicePoint (Uri address, IWebProxy proxy)
325 throw new ArgumentNullException ("address");
327 var origAddress = new Uri (address.Scheme + "://" + address.Authority);
329 bool usesProxy = false;
330 bool useConnect = false;
331 if (proxy != null && !proxy.IsBypassed(address)) {
333 bool isSecure = address.Scheme == "https";
334 address = proxy.GetProxy (address);
335 if (address.Scheme != "http" && !isSecure)
336 throw new NotSupportedException ("Proxy scheme not supported.");
338 if (isSecure && address.Scheme == "http")
342 address = new Uri (address.Scheme + "://" + address.Authority);
344 ServicePoint sp = null;
345 SPKey key = new SPKey (origAddress, usesProxy ? address : null, useConnect);
346 lock (servicePoints) {
347 sp = servicePoints [key] as ServicePoint;
351 if (maxServicePoints > 0 && servicePoints.Count >= maxServicePoints)
352 throw new InvalidOperationException ("maximum number of service points reached");
356 limit = defaultConnectionLimit;
358 string addr = address.ToString ();
359 limit = (int) manager.GetMaxConnections (addr);
361 sp = new ServicePoint (address, limit, maxServicePointIdleTime);
362 sp.Expect100Continue = expectContinue;
363 sp.UseNagleAlgorithm = useNagle;
364 sp.UsesProxy = usesProxy;
365 sp.UseConnect = useConnect;
366 sp.SetTcpKeepAlive (tcp_keepalive, tcp_keepalive_time, tcp_keepalive_interval);
367 servicePoints.Add (key, sp);
373 internal static void CloseConnectionGroup (string connectionGroupName)
375 lock (servicePoints) {
376 foreach (ServicePoint sp in servicePoints.Values) {
377 sp.CloseConnectionGroup (connectionGroupName);
383 internal class ChainValidationHelper {
386 RemoteCertificateValidationCallback cb;
389 static bool is_macosx = System.IO.File.Exists (OSX509Certificates.SecurityLibrary);
390 static X509RevocationMode revocation_mode;
392 static ChainValidationHelper ()
394 revocation_mode = X509RevocationMode.NoCheck;
396 string str = Environment.GetEnvironmentVariable ("MONO_X509_REVOCATION_MODE");
397 if (String.IsNullOrEmpty (str))
399 revocation_mode = (X509RevocationMode) Enum.Parse (typeof (X509RevocationMode), str, true);
405 public ChainValidationHelper (object sender, string hostName)
407 this.sender = sender;
411 public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
414 cb = ServicePointManager.ServerCertificateValidationCallback;
420 // Used when the obsolete ICertificatePolicy is set to DefaultCertificatePolicy
421 // and the new ServerCertificateValidationCallback is not null
422 internal ValidationResult ValidateChain (MSX.X509CertificateCollection certs)
424 // user_denied is true if the user callback is called and returns false
425 bool user_denied = false;
426 if (certs == null || certs.Count == 0)
429 ICertificatePolicy policy = ServicePointManager.CertificatePolicy;
431 X509Certificate2 leaf = new X509Certificate2 (certs [0].RawData);
432 int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback
433 SslPolicyErrors errors = 0;
434 X509Chain chain = null;
437 // The X509Chain is not really usable with MonoTouch (since the decision is not based on this data)
438 // However if someone wants to override the results (good or bad) from iOS then they will want all
439 // the certificates that the server provided (which generally does not include the root) so, only
440 // if there's a user callback, we'll create the X509Chain but won't build it
441 // ref: https://bugzilla.xamarin.com/show_bug.cgi?id=7245
442 if (ServerCertificateValidationCallback != null) {
444 chain = new X509Chain ();
445 chain.ChainPolicy = new X509ChainPolicy ();
447 chain.ChainPolicy.RevocationMode = revocation_mode;
449 for (int i = 1; i < certs.Count; i++) {
450 X509Certificate2 c2 = new X509Certificate2 (certs [i].RawData);
451 chain.ChainPolicy.ExtraStore.Add (c2);
457 if (!chain.Build (leaf))
458 errors |= GetErrorsFromChain (chain);
459 } catch (Exception e) {
460 Console.Error.WriteLine ("ERROR building certificate chain: {0}", e);
461 Console.Error.WriteLine ("Please, report this problem to the Mono team");
462 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
465 // for OSX and iOS we're using the native API to check for the SSL server policy and host names
467 if (!CheckCertificateUsage (leaf)) {
468 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
469 status11 = -2146762490; //CERT_E_PURPOSE 0x800B0106
472 if (!CheckServerIdentity (certs [0], host)) {
473 errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
474 status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F
478 // Attempt to use OSX certificates
479 // Ideally we should return the SecTrustResult
480 OSX509Certificates.SecTrustResult trustResult = OSX509Certificates.SecTrustResult.Deny;
482 trustResult = OSX509Certificates.TrustEvaluateSsl (certs, host);
483 // We could use the other values of trustResult to pass this extra information
484 // to the .NET 2 callback for values like SecTrustResult.Confirm
485 result = (trustResult == OSX509Certificates.SecTrustResult.Proceed ||
486 trustResult == OSX509Certificates.SecTrustResult.Unspecified);
492 // TrustEvaluateSsl was successful so there's no trust error
493 // IOW we discard our own chain (since we trust OSX one instead)
496 // callback and DefaultCertificatePolicy needs this since 'result' is not specified
497 status11 = (int) trustResult;
498 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
504 #if MONODROID && SECURITY_DEP
505 result = AndroidPlatform.TrustEvaluateSsl (certs, sender, leaf, chain, errors);
507 // chain.Build() + GetErrorsFromChain() (above) will ALWAYS fail on
508 // Android (there are no mozroots or preinstalled root certificates),
509 // thus `errors` will ALWAYS have RemoteCertificateChainErrors.
510 // Android just verified the chain; clear RemoteCertificateChainErrors.
511 errors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
515 if (policy != null && (!(policy is DefaultCertificatePolicy) || cb == null)) {
516 ServicePoint sp = null;
517 HttpWebRequest req = sender as HttpWebRequest;
519 sp = req.ServicePointNoLock;
520 if (status11 == 0 && errors != 0)
521 status11 = GetStatusFromChain (chain);
524 result = policy.CheckValidationResult (sp, leaf, req, status11);
525 user_denied = !result && !(policy is DefaultCertificatePolicy);
527 // If there's a 2.0 callback, it takes precedence
528 if (ServerCertificateValidationCallback != null) {
529 result = ServerCertificateValidationCallback (sender, leaf, chain, errors);
530 user_denied = !result;
532 return new ValidationResult (result, user_denied, status11);
535 static int GetStatusFromChain (X509Chain chain)
538 foreach (var status in chain.ChainStatus) {
539 X509ChainStatusFlags flags = status.Status;
540 if (flags == X509ChainStatusFlags.NoError)
544 if ((flags & X509ChainStatusFlags.NotTimeValid) != 0) result = 0x800B0101;
545 // CERT_E_VALIDITYPERIODNESTING
546 else if ((flags & X509ChainStatusFlags.NotTimeNested) != 0) result = 0x800B0102;
548 else if ((flags & X509ChainStatusFlags.Revoked) != 0) result = 0x800B010C;
549 // TRUST_E_CERT_SIGNATURE
550 else if ((flags & X509ChainStatusFlags.NotSignatureValid) != 0) result = 0x80096004;
551 // CERT_E_WRONG_USAGE
552 else if ((flags & X509ChainStatusFlags.NotValidForUsage) != 0) result = 0x800B0110;
553 // CERT_E_UNTRUSTEDROOT
554 else if ((flags & X509ChainStatusFlags.UntrustedRoot) != 0) result = 0x800B0109;
555 // CRYPT_E_NO_REVOCATION_CHECK
556 else if ((flags & X509ChainStatusFlags.RevocationStatusUnknown) != 0) result = 0x80092012;
558 else if ((flags & X509ChainStatusFlags.Cyclic) != 0) result = 0x800B010A;
559 // TRUST_E_FAIL - generic
560 else if ((flags & X509ChainStatusFlags.InvalidExtension) != 0) result = 0x800B010B;
561 // CERT_E_UNTRUSTEDROOT
562 else if ((flags & X509ChainStatusFlags.InvalidPolicyConstraints) != 0) result = 0x800B010D;
563 // TRUST_E_BASIC_CONSTRAINTS
564 else if ((flags & X509ChainStatusFlags.InvalidBasicConstraints) != 0) result = 0x80096019;
565 // CERT_E_INVALID_NAME
566 else if ((flags & X509ChainStatusFlags.InvalidNameConstraints) != 0) result = 0x800B0114;
567 // CERT_E_INVALID_NAME
568 else if ((flags & X509ChainStatusFlags.HasNotSupportedNameConstraint) != 0) result = 0x800B0114;
569 // CERT_E_INVALID_NAME
570 else if ((flags & X509ChainStatusFlags.HasNotDefinedNameConstraint) != 0) result = 0x800B0114;
571 // CERT_E_INVALID_NAME
572 else if ((flags & X509ChainStatusFlags.HasNotPermittedNameConstraint) != 0) result = 0x800B0114;
573 // CERT_E_INVALID_NAME
574 else if ((flags & X509ChainStatusFlags.HasExcludedNameConstraint) != 0) result = 0x800B0114;
576 else if ((flags & X509ChainStatusFlags.PartialChain) != 0) result = 0x800B010A;
578 else if ((flags & X509ChainStatusFlags.CtlNotTimeValid) != 0) result = 0x800B0101;
579 // TRUST_E_CERT_SIGNATURE
580 else if ((flags & X509ChainStatusFlags.CtlNotSignatureValid) != 0) result = 0x80096004;
581 // CERT_E_WRONG_USAGE
582 else if ((flags & X509ChainStatusFlags.CtlNotValidForUsage) != 0) result = 0x800B0110;
583 // CRYPT_E_NO_REVOCATION_CHECK
584 else if ((flags & X509ChainStatusFlags.OfflineRevocation) != 0) result = 0x80092012;
585 // CERT_E_ISSUERCHAINING
586 else if ((flags & X509ChainStatusFlags.NoIssuanceChainPolicy) != 0) result = 0x800B0107;
587 else result = 0x800B010B; // TRUST_E_FAIL - generic
589 break; // Exit the loop on the first error
594 static SslPolicyErrors GetErrorsFromChain (X509Chain chain)
596 SslPolicyErrors errors = SslPolicyErrors.None;
597 foreach (var status in chain.ChainStatus) {
598 if (status.Status == X509ChainStatusFlags.NoError)
600 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
606 static X509KeyUsageFlags s_flags = X509KeyUsageFlags.DigitalSignature |
607 X509KeyUsageFlags.KeyAgreement |
608 X509KeyUsageFlags.KeyEncipherment;
609 // Adapted to System 2.0+ from TlsServerCertificate.cs
610 //------------------------------
611 // Note: this method only works for RSA certificates
612 // DH certificates requires some changes - does anyone use one ?
613 static bool CheckCertificateUsage (X509Certificate2 cert)
616 // certificate extensions are required for this
617 // we "must" accept older certificates without proofs
618 if (cert.Version < 3)
621 X509KeyUsageExtension kux = (cert.Extensions ["2.5.29.15"] as X509KeyUsageExtension);
622 X509EnhancedKeyUsageExtension eku = (cert.Extensions ["2.5.29.37"] as X509EnhancedKeyUsageExtension);
623 if (kux != null && eku != null) {
624 // RFC3280 states that when both KeyUsageExtension and
625 // ExtendedKeyUsageExtension are present then BOTH should
627 if ((kux.KeyUsages & s_flags) == 0)
629 return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
630 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
631 } else if (kux != null) {
632 return ((kux.KeyUsages & s_flags) != 0);
633 } else if (eku != null) {
634 // Server Authentication (1.3.6.1.5.5.7.3.1) or
635 // Netscape Server Gated Crypto (2.16.840.1.113730.4)
636 return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
637 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
640 // last chance - try with older (deprecated) Netscape extensions
641 X509Extension ext = cert.Extensions ["2.16.840.1.113730.1.1"];
643 string text = ext.NetscapeCertType (false);
644 return text.IndexOf ("SSL Server Authentication", StringComparison.Ordinal) != -1;
647 } catch (Exception e) {
648 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
649 Console.Error.WriteLine ("Please, report this problem to the Mono team");
654 // RFC2818 - HTTP Over TLS, Section 3.1
655 // http://www.ietf.org/rfc/rfc2818.txt
657 // 1. if present MUST use subjectAltName dNSName as identity
658 // 1.1. if multiples entries a match of any one is acceptable
659 // 1.2. wildcard * is acceptable
660 // 2. URI may be an IP address -> subjectAltName.iPAddress
661 // 2.1. exact match is required
662 // 3. Use of the most specific Common Name (CN=) in the Subject
663 // 3.1 Existing practice but DEPRECATED
664 static bool CheckServerIdentity (MSX.X509Certificate cert, string targetHost)
667 MSX.X509Extension ext = cert.Extensions ["2.5.29.17"];
670 SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
671 // 1.1 - multiple dNSName
672 foreach (string dns in subjectAltName.DNSNames) {
673 // 1.2 TODO - wildcard support
674 if (Match (targetHost, dns))
678 foreach (string ip in subjectAltName.IPAddresses) {
679 // 2.1. Exact match required
680 if (ip == targetHost)
684 // 3. Common Name (CN=)
685 return CheckDomainName (cert.SubjectName, targetHost);
686 } catch (Exception e) {
687 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
688 Console.Error.WriteLine ("Please, report this problem to the Mono team");
693 static bool CheckDomainName (string subjectName, string targetHost)
695 string domainName = String.Empty;
696 Regex search = new Regex(@"CN\s*=\s*([^,]*)");
697 MatchCollection elements = search.Matches(subjectName);
698 if (elements.Count == 1) {
699 if (elements[0].Success)
700 domainName = elements[0].Groups[1].Value.ToString();
703 return Match (targetHost, domainName);
706 // ensure the pattern is valid wrt to RFC2595 and RFC2818
707 // http://www.ietf.org/rfc/rfc2595.txt
708 // http://www.ietf.org/rfc/rfc2818.txt
709 static bool Match (string hostname, string pattern)
711 // check if this is a pattern
712 int index = pattern.IndexOf ('*');
714 // not a pattern, do a direct case-insensitive comparison
715 return (String.Compare (hostname, pattern, true, CultureInfo.InvariantCulture) == 0);
718 // check pattern validity
719 // A "*" wildcard character MAY be used as the left-most name component in the certificate.
721 // unless this is the last char (valid)
722 if (index != pattern.Length - 1) {
723 // then the next char must be a dot .'.
724 if (pattern [index + 1] != '.')
728 // only one (A) wildcard is supported
729 int i2 = pattern.IndexOf ('*', index + 1);
733 // match the end of the pattern
734 string end = pattern.Substring (index + 1);
735 int length = hostname.Length - end.Length;
736 // no point to check a pattern that is longer than the hostname
740 if (String.Compare (hostname, length, end, 0, end.Length, true, CultureInfo.InvariantCulture) != 0)
743 // special case, we start with the wildcard
745 // ensure we hostname non-matched part (start) doesn't contain a dot
746 int i3 = hostname.IndexOf ('.');
747 return ((i3 == -1) || (i3 >= (hostname.Length - end.Length)));
750 // match the start of the pattern
751 string start = pattern.Substring (0, index);
752 return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);