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);
374 internal class ChainValidationHelper {
377 RemoteCertificateValidationCallback cb;
380 static bool is_macosx = System.IO.File.Exists (OSX509Certificates.SecurityLibrary);
381 static X509RevocationMode revocation_mode;
383 static ChainValidationHelper ()
385 revocation_mode = X509RevocationMode.NoCheck;
387 string str = Environment.GetEnvironmentVariable ("MONO_X509_REVOCATION_MODE");
388 if (String.IsNullOrEmpty (str))
390 revocation_mode = (X509RevocationMode) Enum.Parse (typeof (X509RevocationMode), str, true);
396 public ChainValidationHelper (object sender, string hostName)
398 this.sender = sender;
402 public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
405 cb = ServicePointManager.ServerCertificateValidationCallback;
411 // Used when the obsolete ICertificatePolicy is set to DefaultCertificatePolicy
412 // and the new ServerCertificateValidationCallback is not null
413 internal ValidationResult ValidateChain (MSX.X509CertificateCollection certs)
415 // user_denied is true if the user callback is called and returns false
416 bool user_denied = false;
417 if (certs == null || certs.Count == 0)
420 ICertificatePolicy policy = ServicePointManager.CertificatePolicy;
422 X509Certificate2 leaf = new X509Certificate2 (certs [0].RawData);
423 int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback
424 SslPolicyErrors errors = 0;
425 X509Chain chain = null;
428 // The X509Chain is not really usable with MonoTouch (since the decision is not based on this data)
429 // However if someone wants to override the results (good or bad) from iOS then they will want all
430 // the certificates that the server provided (which generally does not include the root) so, only
431 // if there's a user callback, we'll create the X509Chain but won't build it
432 // ref: https://bugzilla.xamarin.com/show_bug.cgi?id=7245
433 if (ServerCertificateValidationCallback != null) {
435 chain = new X509Chain ();
436 chain.ChainPolicy = new X509ChainPolicy ();
438 chain.ChainPolicy.RevocationMode = revocation_mode;
440 for (int i = 1; i < certs.Count; i++) {
441 X509Certificate2 c2 = new X509Certificate2 (certs [i].RawData);
442 chain.ChainPolicy.ExtraStore.Add (c2);
448 if (!chain.Build (leaf))
449 errors |= GetErrorsFromChain (chain);
450 } catch (Exception e) {
451 Console.Error.WriteLine ("ERROR building certificate chain: {0}", e);
452 Console.Error.WriteLine ("Please, report this problem to the Mono team");
453 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
456 // for OSX and iOS we're using the native API to check for the SSL server policy and host names
458 if (!CheckCertificateUsage (leaf)) {
459 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
460 status11 = -2146762490; //CERT_E_PURPOSE 0x800B0106
463 if (!CheckServerIdentity (certs [0], host)) {
464 errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
465 status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F
469 // Attempt to use OSX certificates
470 // Ideally we should return the SecTrustResult
471 OSX509Certificates.SecTrustResult trustResult = OSX509Certificates.SecTrustResult.Deny;
473 trustResult = OSX509Certificates.TrustEvaluateSsl (certs, host);
474 // We could use the other values of trustResult to pass this extra information
475 // to the .NET 2 callback for values like SecTrustResult.Confirm
476 result = (trustResult == OSX509Certificates.SecTrustResult.Proceed ||
477 trustResult == OSX509Certificates.SecTrustResult.Unspecified);
483 // TrustEvaluateSsl was successful so there's no trust error
484 // IOW we discard our own chain (since we trust OSX one instead)
487 // callback and DefaultCertificatePolicy needs this since 'result' is not specified
488 status11 = (int) trustResult;
489 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
495 #if MONODROID && SECURITY_DEP
496 result = AndroidPlatform.TrustEvaluateSsl (certs, sender, leaf, chain, errors);
498 // chain.Build() + GetErrorsFromChain() (above) will ALWAYS fail on
499 // Android (there are no mozroots or preinstalled root certificates),
500 // thus `errors` will ALWAYS have RemoteCertificateChainErrors.
501 // Android just verified the chain; clear RemoteCertificateChainErrors.
502 errors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
506 if (policy != null && (!(policy is DefaultCertificatePolicy) || cb == null)) {
507 ServicePoint sp = null;
508 HttpWebRequest req = sender as HttpWebRequest;
510 sp = req.ServicePointNoLock;
511 if (status11 == 0 && errors != 0)
512 status11 = GetStatusFromChain (chain);
515 result = policy.CheckValidationResult (sp, leaf, req, status11);
516 user_denied = !result && !(policy is DefaultCertificatePolicy);
518 // If there's a 2.0 callback, it takes precedence
519 if (ServerCertificateValidationCallback != null) {
520 result = ServerCertificateValidationCallback (sender, leaf, chain, errors);
521 user_denied = !result;
523 return new ValidationResult (result, user_denied, status11);
526 static int GetStatusFromChain (X509Chain chain)
529 foreach (var status in chain.ChainStatus) {
530 X509ChainStatusFlags flags = status.Status;
531 if (flags == X509ChainStatusFlags.NoError)
535 if ((flags & X509ChainStatusFlags.NotTimeValid) != 0) result = 0x800B0101;
536 // CERT_E_VALIDITYPERIODNESTING
537 else if ((flags & X509ChainStatusFlags.NotTimeNested) != 0) result = 0x800B0102;
539 else if ((flags & X509ChainStatusFlags.Revoked) != 0) result = 0x800B010C;
540 // TRUST_E_CERT_SIGNATURE
541 else if ((flags & X509ChainStatusFlags.NotSignatureValid) != 0) result = 0x80096004;
542 // CERT_E_WRONG_USAGE
543 else if ((flags & X509ChainStatusFlags.NotValidForUsage) != 0) result = 0x800B0110;
544 // CERT_E_UNTRUSTEDROOT
545 else if ((flags & X509ChainStatusFlags.UntrustedRoot) != 0) result = 0x800B0109;
546 // CRYPT_E_NO_REVOCATION_CHECK
547 else if ((flags & X509ChainStatusFlags.RevocationStatusUnknown) != 0) result = 0x80092012;
549 else if ((flags & X509ChainStatusFlags.Cyclic) != 0) result = 0x800B010A;
550 // TRUST_E_FAIL - generic
551 else if ((flags & X509ChainStatusFlags.InvalidExtension) != 0) result = 0x800B010B;
552 // CERT_E_UNTRUSTEDROOT
553 else if ((flags & X509ChainStatusFlags.InvalidPolicyConstraints) != 0) result = 0x800B010D;
554 // TRUST_E_BASIC_CONSTRAINTS
555 else if ((flags & X509ChainStatusFlags.InvalidBasicConstraints) != 0) result = 0x80096019;
556 // CERT_E_INVALID_NAME
557 else if ((flags & X509ChainStatusFlags.InvalidNameConstraints) != 0) result = 0x800B0114;
558 // CERT_E_INVALID_NAME
559 else if ((flags & X509ChainStatusFlags.HasNotSupportedNameConstraint) != 0) result = 0x800B0114;
560 // CERT_E_INVALID_NAME
561 else if ((flags & X509ChainStatusFlags.HasNotDefinedNameConstraint) != 0) result = 0x800B0114;
562 // CERT_E_INVALID_NAME
563 else if ((flags & X509ChainStatusFlags.HasNotPermittedNameConstraint) != 0) result = 0x800B0114;
564 // CERT_E_INVALID_NAME
565 else if ((flags & X509ChainStatusFlags.HasExcludedNameConstraint) != 0) result = 0x800B0114;
567 else if ((flags & X509ChainStatusFlags.PartialChain) != 0) result = 0x800B010A;
569 else if ((flags & X509ChainStatusFlags.CtlNotTimeValid) != 0) result = 0x800B0101;
570 // TRUST_E_CERT_SIGNATURE
571 else if ((flags & X509ChainStatusFlags.CtlNotSignatureValid) != 0) result = 0x80096004;
572 // CERT_E_WRONG_USAGE
573 else if ((flags & X509ChainStatusFlags.CtlNotValidForUsage) != 0) result = 0x800B0110;
574 // CRYPT_E_NO_REVOCATION_CHECK
575 else if ((flags & X509ChainStatusFlags.OfflineRevocation) != 0) result = 0x80092012;
576 // CERT_E_ISSUERCHAINING
577 else if ((flags & X509ChainStatusFlags.NoIssuanceChainPolicy) != 0) result = 0x800B0107;
578 else result = 0x800B010B; // TRUST_E_FAIL - generic
580 break; // Exit the loop on the first error
585 static SslPolicyErrors GetErrorsFromChain (X509Chain chain)
587 SslPolicyErrors errors = SslPolicyErrors.None;
588 foreach (var status in chain.ChainStatus) {
589 if (status.Status == X509ChainStatusFlags.NoError)
591 errors |= SslPolicyErrors.RemoteCertificateChainErrors;
597 static X509KeyUsageFlags s_flags = X509KeyUsageFlags.DigitalSignature |
598 X509KeyUsageFlags.KeyAgreement |
599 X509KeyUsageFlags.KeyEncipherment;
600 // Adapted to System 2.0+ from TlsServerCertificate.cs
601 //------------------------------
602 // Note: this method only works for RSA certificates
603 // DH certificates requires some changes - does anyone use one ?
604 static bool CheckCertificateUsage (X509Certificate2 cert)
607 // certificate extensions are required for this
608 // we "must" accept older certificates without proofs
609 if (cert.Version < 3)
612 X509KeyUsageExtension kux = (cert.Extensions ["2.5.29.15"] as X509KeyUsageExtension);
613 X509EnhancedKeyUsageExtension eku = (cert.Extensions ["2.5.29.37"] as X509EnhancedKeyUsageExtension);
614 if (kux != null && eku != null) {
615 // RFC3280 states that when both KeyUsageExtension and
616 // ExtendedKeyUsageExtension are present then BOTH should
618 if ((kux.KeyUsages & s_flags) == 0)
620 return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
621 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
622 } else if (kux != null) {
623 return ((kux.KeyUsages & s_flags) != 0);
624 } else if (eku != null) {
625 // Server Authentication (1.3.6.1.5.5.7.3.1) or
626 // Netscape Server Gated Crypto (2.16.840.1.113730.4)
627 return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null ||
628 eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null;
631 // last chance - try with older (deprecated) Netscape extensions
632 X509Extension ext = cert.Extensions ["2.16.840.1.113730.1.1"];
634 string text = ext.NetscapeCertType (false);
635 return text.IndexOf ("SSL Server Authentication", StringComparison.Ordinal) != -1;
638 } catch (Exception e) {
639 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
640 Console.Error.WriteLine ("Please, report this problem to the Mono team");
645 // RFC2818 - HTTP Over TLS, Section 3.1
646 // http://www.ietf.org/rfc/rfc2818.txt
648 // 1. if present MUST use subjectAltName dNSName as identity
649 // 1.1. if multiples entries a match of any one is acceptable
650 // 1.2. wildcard * is acceptable
651 // 2. URI may be an IP address -> subjectAltName.iPAddress
652 // 2.1. exact match is required
653 // 3. Use of the most specific Common Name (CN=) in the Subject
654 // 3.1 Existing practice but DEPRECATED
655 static bool CheckServerIdentity (MSX.X509Certificate cert, string targetHost)
658 MSX.X509Extension ext = cert.Extensions ["2.5.29.17"];
661 SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
662 // 1.1 - multiple dNSName
663 foreach (string dns in subjectAltName.DNSNames) {
664 // 1.2 TODO - wildcard support
665 if (Match (targetHost, dns))
669 foreach (string ip in subjectAltName.IPAddresses) {
670 // 2.1. Exact match required
671 if (ip == targetHost)
675 // 3. Common Name (CN=)
676 return CheckDomainName (cert.SubjectName, targetHost);
677 } catch (Exception e) {
678 Console.Error.WriteLine ("ERROR processing certificate: {0}", e);
679 Console.Error.WriteLine ("Please, report this problem to the Mono team");
684 static bool CheckDomainName (string subjectName, string targetHost)
686 string domainName = String.Empty;
687 Regex search = new Regex(@"CN\s*=\s*([^,]*)");
688 MatchCollection elements = search.Matches(subjectName);
689 if (elements.Count == 1) {
690 if (elements[0].Success)
691 domainName = elements[0].Groups[1].Value.ToString();
694 return Match (targetHost, domainName);
697 // ensure the pattern is valid wrt to RFC2595 and RFC2818
698 // http://www.ietf.org/rfc/rfc2595.txt
699 // http://www.ietf.org/rfc/rfc2818.txt
700 static bool Match (string hostname, string pattern)
702 // check if this is a pattern
703 int index = pattern.IndexOf ('*');
705 // not a pattern, do a direct case-insensitive comparison
706 return (String.Compare (hostname, pattern, true, CultureInfo.InvariantCulture) == 0);
709 // check pattern validity
710 // A "*" wildcard character MAY be used as the left-most name component in the certificate.
712 // unless this is the last char (valid)
713 if (index != pattern.Length - 1) {
714 // then the next char must be a dot .'.
715 if (pattern [index + 1] != '.')
719 // only one (A) wildcard is supported
720 int i2 = pattern.IndexOf ('*', index + 1);
724 // match the end of the pattern
725 string end = pattern.Substring (index + 1);
726 int length = hostname.Length - end.Length;
727 // no point to check a pattern that is longer than the hostname
731 if (String.Compare (hostname, length, end, 0, end.Length, true, CultureInfo.InvariantCulture) != 0)
734 // special case, we start with the wildcard
736 // ensure we hostname non-matched part (start) doesn't contain a dot
737 int i3 = hostname.IndexOf ('.');
738 return ((i3 == -1) || (i3 >= (hostname.Length - end.Length)));
741 // match the start of the pattern
742 string start = pattern.Substring (0, index);
743 return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);