X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FSystem%2FSystem.Net%2FServicePointManager.cs;h=23c56bd28e71cb96c1b108306a38205fa4ab0095;hb=c3220ab0a3bca026605b0b01431b8f86bf495eb8;hp=a197f37ef2a6e80ce2d42d0d7a89bf75ba812399;hpb=937839bcebb886c9e512e47e8c05e7adf9e9a095;p=mono.git diff --git a/mcs/class/System/System.Net/ServicePointManager.cs b/mcs/class/System/System.Net/ServicePointManager.cs index a197f37ef2a..23c56bd28e7 100644 --- a/mcs/class/System/System.Net/ServicePointManager.cs +++ b/mcs/class/System/System.Net/ServicePointManager.cs @@ -1,8 +1,11 @@ // // System.Net.ServicePointManager // -// Author: +// Authors: // Lawrence Pit (loz@cable.a2000.nl) +// Gonzalo Paniagua Javier (gonzalo@novell.com) +// +// Copyright (c) 2003-2010 Novell, Inc (http://www.novell.com) // // @@ -33,8 +36,15 @@ using System.Configuration; using System.Net.Configuration; using System.Security.Cryptography.X509Certificates; -#if NET_2_0 +using System.Globalization; using System.Net.Security; +#if SECURITY_DEP +using System.Text.RegularExpressions; +using Mono.Security; +using Mono.Security.Cryptography; +using Mono.Security.X509.Extensions; +using Mono.Security.Protocol.Tls; +using MSX = Mono.Security.X509; #endif // @@ -57,8 +67,42 @@ using System.Net.Security; namespace System.Net { - public class ServicePointManager - { +#if MOONLIGHT + internal class ServicePointManager { +#else + public class ServicePointManager { +#endif + class SPKey { + Uri uri; // schema/host/port + bool use_connect; + + public SPKey (Uri uri, bool use_connect) { + this.uri = uri; + this.use_connect = use_connect; + } + + public Uri Uri { + get { return uri; } + } + + public bool UseConnect { + get { return use_connect; } + } + + public override int GetHashCode () { + return uri.GetHashCode () + ((use_connect) ? 1 : 0); + } + + public override bool Equals (object obj) { + SPKey other = obj as SPKey; + if (obj == null) { + return false; + } + + return (uri.Equals (other.uri) && other.use_connect == use_connect); + } + } + private static HybridDictionary servicePoints = new HybridDictionary (); // Static properties @@ -70,26 +114,35 @@ namespace System.Net private static bool _checkCRL = false; private static SecurityProtocolType _securityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls; -#if NET_1_1 #if TARGET_JVM - static bool expectContinue = true; -#else static bool expectContinue = false; +#else + static bool expectContinue = true; #endif static bool useNagle; -#endif + static RemoteCertificateValidationCallback server_cert_cb; + static bool tcp_keepalive; + static int tcp_keepalive_time; + static int tcp_keepalive_interval; // Fields public const int DefaultNonPersistentConnectionLimit = 4; +#if MONOTOUCH + public const int DefaultPersistentConnectionLimit = 10; +#else public const int DefaultPersistentConnectionLimit = 2; +#endif +#if !NET_2_1 const string configKey = "system.net/connectionManagement"; static ConnectionManagementData manager; +#endif static ServicePointManager () { -#if NET_2_0 && CONFIGURATION_DEP +#if !NET_2_1 +#if CONFIGURATION_DEP object cfg = ConfigurationManager.GetSection (configKey); ConnectionManagementSection s = cfg as ConnectionManagementSection; if (s != null) { @@ -97,10 +150,15 @@ namespace System.Net foreach (ConnectionManagementElement e in s.ConnectionManagement) manager.Add (e.Address, e.MaxConnection); + defaultConnectionLimit = (int) manager.GetMaxConnections ("*"); return; } #endif manager = (ConnectionManagementData) ConfigurationSettings.GetConfig (configKey); + if (manager != null) { + defaultConnectionLimit = (int) manager.GetMaxConnections ("*"); + } +#endif } // Constructors @@ -110,23 +168,14 @@ namespace System.Net // Properties -#if NET_2_0 - [Obsolete ("Use ServerCertificateValidationCallback instead", - false)] -#endif + [Obsolete ("Use ServerCertificateValidationCallback instead", false)] public static ICertificatePolicy CertificatePolicy { get { return policy; } set { policy = value; } } -#if NET_1_0 - // we need it for SslClientStream - internal -#else [MonoTODO("CRL checks not implemented")] - public -#endif - static bool CheckCertificateRevocationList { + public static bool CheckCertificateRevocationList { get { return _checkCRL; } set { _checkCRL = false; } // TODO - don't yet accept true } @@ -141,7 +190,6 @@ namespace System.Net } } -#if NET_2_0 static Exception GetMustImplement () { return new NotImplementedException (); @@ -168,7 +216,6 @@ namespace System.Net throw GetMustImplement (); } } -#endif public static int MaxServicePointIdleTime { get { @@ -205,20 +252,16 @@ namespace System.Net set { _securityProtocol = value; } } -#if NET_2_0 && SECURITY_DEP - [MonoTODO] public static RemoteCertificateValidationCallback ServerCertificateValidationCallback { get { - throw GetMustImplement (); + return server_cert_cb; } set { - throw GetMustImplement (); + server_cert_cb = value; } } -#endif -#if NET_1_1 public static bool Expect100Continue { get { return expectContinue; } set { expectContinue = value; } @@ -228,9 +271,22 @@ namespace System.Net get { return useNagle; } set { useNagle = value; } } -#endif + // Methods - + public static void SetTcpKeepAlive (bool enabled, int keepAliveTime, int keepAliveInterval) + { + if (enabled) { + if (keepAliveTime <= 0) + throw new ArgumentOutOfRangeException ("keepAliveTime", "Must be greater than 0"); + if (keepAliveInterval <= 0) + throw new ArgumentOutOfRangeException ("keepAliveInterval", "Must be greater than 0"); + } + + tcp_keepalive = enabled; + tcp_keepalive_time = keepAliveTime; + tcp_keepalive_interval = keepAliveInterval; + } + public static ServicePoint FindServicePoint (Uri address) { return FindServicePoint (address, GlobalProxySelection.Select); @@ -240,7 +296,7 @@ namespace System.Net { return FindServicePoint (new Uri(uriString), proxy); } - + public static ServicePoint FindServicePoint (Uri address, IWebProxy proxy) { if (address == null) @@ -265,7 +321,7 @@ namespace System.Net ServicePoint sp = null; lock (servicePoints) { - int key = address.GetHashCode () + (int) ((useConnect) ? 1 : 0); + SPKey key = new SPKey (address, useConnect); sp = servicePoints [key] as ServicePoint; if (sp != null) return sp; @@ -274,14 +330,17 @@ namespace System.Net throw new InvalidOperationException ("maximum number of service points reached"); string addr = address.ToString (); +#if NET_2_1 + int limit = defaultConnectionLimit; +#else int limit = (int) manager.GetMaxConnections (addr); +#endif sp = new ServicePoint (address, limit, maxServicePointIdleTime); -#if NET_1_1 sp.Expect100Continue = expectContinue; sp.UseNagleAlgorithm = useNagle; -#endif sp.UsesProxy = usesProxy; sp.UseConnect = useConnect; + sp.SetTcpKeepAlive (tcp_keepalive, tcp_keepalive_time, tcp_keepalive_interval); servicePoints.Add (key, sp); } @@ -324,5 +383,395 @@ namespace System.Net servicePoints.Remove (list.GetByIndex (i)); } } +#if MOONLIGHT && SECURITY_DEP + internal class ChainValidationHelper { + object sender; + + public ChainValidationHelper (object sender) + { + this.sender = sender; + } + + // no need to check certificates since we are either + // (a) loading from the site of origin (and we accepted its certificate to load from it) + // (b) loading from a cross-domain site and we downloaded the policy file using the browser stack + // i.e. the certificate was accepted (or the policy would not be valid) + internal ValidationResult ValidateChain (Mono.Security.X509.X509CertificateCollection certs) + { + return new ValidationResult (true, false, 0); + } + } +#elif SECURITY_DEP + internal class ChainValidationHelper { + object sender; + string host; + static bool is_macosx = System.IO.File.Exists (MSX.OSX509Certificates.SecurityLibrary); + static X509RevocationMode revocation_mode; + +#if MONODROID + static readonly Converter monodroidCallback; +#endif + + static ChainValidationHelper () + { +#if MONODROID + monodroidCallback = (Converter) + Delegate.CreateDelegate (typeof(Converter), + Type.GetType ("Android.Runtime.AndroidEnvironment, Mono.Android", true) + .GetMethod ("TrustEvaluateSsl", + System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)); +#endif +#if !MONOTOUCH + revocation_mode = X509RevocationMode.NoCheck; + try { + string str = Environment.GetEnvironmentVariable ("MONO_X509_REVOCATION_MODE"); + if (String.IsNullOrEmpty (str)) + return; + revocation_mode = (X509RevocationMode) Enum.Parse (typeof (X509RevocationMode), str, true); + } catch { + } +#endif + } + + public ChainValidationHelper (object sender) + { + this.sender = sender; + } + + public string Host { + get { + if (host == null && sender is HttpWebRequest) + host = ((HttpWebRequest) sender).Address.Host; + return host; + } + + set { host = value; } + } + + // Used when the obsolete ICertificatePolicy is set to DefaultCertificatePolicy + // and the new ServerCertificateValidationCallback is not null + internal ValidationResult ValidateChain (Mono.Security.X509.X509CertificateCollection certs) + { + // user_denied is true if the user callback is called and returns false + bool user_denied = false; + if (certs == null || certs.Count == 0) + return null; + + ICertificatePolicy policy = ServicePointManager.CertificatePolicy; + RemoteCertificateValidationCallback cb = ServicePointManager.ServerCertificateValidationCallback; + + X509Certificate2 leaf = new X509Certificate2 (certs [0].RawData); + int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback + SslPolicyErrors errors = 0; + X509Chain chain = null; +#if !MONOTOUCH + chain = new X509Chain (); + chain.ChainPolicy = new X509ChainPolicy (); + chain.ChainPolicy.RevocationMode = revocation_mode; + for (int i = 1; i < certs.Count; i++) { + X509Certificate2 c2 = new X509Certificate2 (certs [i].RawData); + chain.ChainPolicy.ExtraStore.Add (c2); + } + + try { + if (!chain.Build (leaf)) + errors |= GetErrorsFromChain (chain); + } catch (Exception e) { + Console.Error.WriteLine ("ERROR building certificate chain: {0}", e); + Console.Error.WriteLine ("Please, report this problem to the Mono team"); + errors |= SslPolicyErrors.RemoteCertificateChainErrors; + } +#endif + if (!CheckCertificateUsage (leaf)) { + errors |= SslPolicyErrors.RemoteCertificateChainErrors; + status11 = -2146762490; //CERT_E_PURPOSE 0x800B0106 + } + + if (!CheckServerIdentity (certs [0], Host)) { + errors |= SslPolicyErrors.RemoteCertificateNameMismatch; + status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F + } + + bool result = false; + // No certificate root found means no mozroots or monotouch +#if !MONOTOUCH + if (is_macosx) { +#endif + // Attempt to use OSX certificates + // Ideally we should return the SecTrustResult + MSX.OSX509Certificates.SecTrustResult trustResult = MSX.OSX509Certificates.SecTrustResult.Deny; + try { + trustResult = MSX.OSX509Certificates.TrustEvaluateSsl (certs); + // We could use the other values of trustResult to pass this extra information + // to the .NET 2 callback for values like SecTrustResult.Confirm + result = (trustResult == MSX.OSX509Certificates.SecTrustResult.Proceed || + trustResult == MSX.OSX509Certificates.SecTrustResult.Unspecified); + + } catch { + // Ignore + } + // Clear error status if the OS told us to trust the certificate + if (result) { + status11 = 0; + errors = 0; + } else { + // callback and DefaultCertificatePolicy needs this since 'result' is not specified + status11 = (int) trustResult; + errors |= SslPolicyErrors.RemoteCertificateChainErrors; + } +#if !MONOTOUCH + } +#endif + +#if MONODROID + result = monodroidCallback (certs); + if (result) { + status11 = 0; + errors = 0; + } +#endif + + if (policy != null && (!(policy is DefaultCertificatePolicy) || cb == null)) { + ServicePoint sp = null; + HttpWebRequest req = sender as HttpWebRequest; + if (req != null) + sp = req.ServicePointNoLock; + if (status11 == 0 && errors != 0) + status11 = GetStatusFromChain (chain); + + // pre 2.0 callback + result = policy.CheckValidationResult (sp, leaf, req, status11); + user_denied = !result && !(policy is DefaultCertificatePolicy); + } + // If there's a 2.0 callback, it takes precedence + if (cb != null) { + result = cb (sender, leaf, chain, errors); + user_denied = !result; + } + return new ValidationResult (result, user_denied, status11); + } + + static int GetStatusFromChain (X509Chain chain) + { + long result = 0; + foreach (var status in chain.ChainStatus) { + X509ChainStatusFlags flags = status.Status; + if (flags == X509ChainStatusFlags.NoError) + continue; + + // CERT_E_EXPIRED + if ((flags & X509ChainStatusFlags.NotTimeValid) != 0) result = 0x800B0101; + // CERT_E_VALIDITYPERIODNESTING + else if ((flags & X509ChainStatusFlags.NotTimeNested) != 0) result = 0x800B0102; + // CERT_E_REVOKED + else if ((flags & X509ChainStatusFlags.Revoked) != 0) result = 0x800B010C; + // TRUST_E_CERT_SIGNATURE + else if ((flags & X509ChainStatusFlags.NotSignatureValid) != 0) result = 0x80096004; + // CERT_E_WRONG_USAGE + else if ((flags & X509ChainStatusFlags.NotValidForUsage) != 0) result = 0x800B0110; + // CERT_E_UNTRUSTEDROOT + else if ((flags & X509ChainStatusFlags.UntrustedRoot) != 0) result = 0x800B0109; + // CRYPT_E_NO_REVOCATION_CHECK + else if ((flags & X509ChainStatusFlags.RevocationStatusUnknown) != 0) result = 0x80092012; + // CERT_E_CHAINING + else if ((flags & X509ChainStatusFlags.Cyclic) != 0) result = 0x800B010A; + // TRUST_E_FAIL - generic + else if ((flags & X509ChainStatusFlags.InvalidExtension) != 0) result = 0x800B010B; + // CERT_E_UNTRUSTEDROOT + else if ((flags & X509ChainStatusFlags.InvalidPolicyConstraints) != 0) result = 0x800B010D; + // TRUST_E_BASIC_CONSTRAINTS + else if ((flags & X509ChainStatusFlags.InvalidBasicConstraints) != 0) result = 0x80096019; + // CERT_E_INVALID_NAME + else if ((flags & X509ChainStatusFlags.InvalidNameConstraints) != 0) result = 0x800B0114; + // CERT_E_INVALID_NAME + else if ((flags & X509ChainStatusFlags.HasNotSupportedNameConstraint) != 0) result = 0x800B0114; + // CERT_E_INVALID_NAME + else if ((flags & X509ChainStatusFlags.HasNotDefinedNameConstraint) != 0) result = 0x800B0114; + // CERT_E_INVALID_NAME + else if ((flags & X509ChainStatusFlags.HasNotPermittedNameConstraint) != 0) result = 0x800B0114; + // CERT_E_INVALID_NAME + else if ((flags & X509ChainStatusFlags.HasExcludedNameConstraint) != 0) result = 0x800B0114; + // CERT_E_CHAINING + else if ((flags & X509ChainStatusFlags.PartialChain) != 0) result = 0x800B010A; + // CERT_E_EXPIRED + else if ((flags & X509ChainStatusFlags.CtlNotTimeValid) != 0) result = 0x800B0101; + // TRUST_E_CERT_SIGNATURE + else if ((flags & X509ChainStatusFlags.CtlNotSignatureValid) != 0) result = 0x80096004; + // CERT_E_WRONG_USAGE + else if ((flags & X509ChainStatusFlags.CtlNotValidForUsage) != 0) result = 0x800B0110; + // CRYPT_E_NO_REVOCATION_CHECK + else if ((flags & X509ChainStatusFlags.OfflineRevocation) != 0) result = 0x80092012; + // CERT_E_ISSUERCHAINING + else if ((flags & X509ChainStatusFlags.NoIssuanceChainPolicy) != 0) result = 0x800B0107; + else result = 0x800B010B; // TRUST_E_FAIL - generic + + break; // Exit the loop on the first error + } + return (int) result; + } + + static SslPolicyErrors GetErrorsFromChain (X509Chain chain) + { + SslPolicyErrors errors = SslPolicyErrors.None; + foreach (var status in chain.ChainStatus) { + if (status.Status == X509ChainStatusFlags.NoError) + continue; + errors |= SslPolicyErrors.RemoteCertificateChainErrors; + break; + } + return errors; + } + + static X509KeyUsageFlags s_flags = X509KeyUsageFlags.DigitalSignature | + X509KeyUsageFlags.KeyAgreement | + X509KeyUsageFlags.KeyEncipherment; + // Adapted to System 2.0+ from TlsServerCertificate.cs + //------------------------------ + // Note: this method only works for RSA certificates + // DH certificates requires some changes - does anyone use one ? + static bool CheckCertificateUsage (X509Certificate2 cert) + { + try { + // certificate extensions are required for this + // we "must" accept older certificates without proofs + if (cert.Version < 3) + return true; + + X509KeyUsageExtension kux = (cert.Extensions ["2.5.29.15"] as X509KeyUsageExtension); + X509EnhancedKeyUsageExtension eku = (cert.Extensions ["2.5.29.37"] as X509EnhancedKeyUsageExtension); + if (kux != null && eku != null) { + // RFC3280 states that when both KeyUsageExtension and + // ExtendedKeyUsageExtension are present then BOTH should + // be valid + if ((kux.KeyUsages & s_flags) == 0) + return false; + return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null || + eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null; + } else if (kux != null) { + return ((kux.KeyUsages & s_flags) != 0); + } else if (eku != null) { + // Server Authentication (1.3.6.1.5.5.7.3.1) or + // Netscape Server Gated Crypto (2.16.840.1.113730.4) + return eku.EnhancedKeyUsages ["1.3.6.1.5.5.7.3.1"] != null || + eku.EnhancedKeyUsages ["2.16.840.1.113730.4.1"] != null; + } + + // last chance - try with older (deprecated) Netscape extensions + X509Extension ext = cert.Extensions ["2.16.840.1.113730.1.1"]; + if (ext != null) { + string text = ext.NetscapeCertType (false); + return text.IndexOf ("SSL Server Authentication", StringComparison.Ordinal) != -1; + } + return true; + } catch (Exception e) { + Console.Error.WriteLine ("ERROR processing certificate: {0}", e); + Console.Error.WriteLine ("Please, report this problem to the Mono team"); + return false; + } + } + + // RFC2818 - HTTP Over TLS, Section 3.1 + // http://www.ietf.org/rfc/rfc2818.txt + // + // 1. if present MUST use subjectAltName dNSName as identity + // 1.1. if multiples entries a match of any one is acceptable + // 1.2. wildcard * is acceptable + // 2. URI may be an IP address -> subjectAltName.iPAddress + // 2.1. exact match is required + // 3. Use of the most specific Common Name (CN=) in the Subject + // 3.1 Existing practice but DEPRECATED + static bool CheckServerIdentity (Mono.Security.X509.X509Certificate cert, string targetHost) + { + try { + Mono.Security.X509.X509Extension ext = cert.Extensions ["2.5.29.17"]; + // 1. subjectAltName + if (ext != null) { + SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext); + // 1.1 - multiple dNSName + foreach (string dns in subjectAltName.DNSNames) { + // 1.2 TODO - wildcard support + if (Match (targetHost, dns)) + return true; + } + // 2. ipAddress + foreach (string ip in subjectAltName.IPAddresses) { + // 2.1. Exact match required + if (ip == targetHost) + return true; + } + } + // 3. Common Name (CN=) + return CheckDomainName (cert.SubjectName, targetHost); + } catch (Exception e) { + Console.Error.WriteLine ("ERROR processing certificate: {0}", e); + Console.Error.WriteLine ("Please, report this problem to the Mono team"); + return false; + } + } + + static bool CheckDomainName (string subjectName, string targetHost) + { + string domainName = String.Empty; + Regex search = new Regex(@"CN\s*=\s*([^,]*)"); + MatchCollection elements = search.Matches(subjectName); + if (elements.Count == 1) { + if (elements[0].Success) + domainName = elements[0].Groups[1].Value.ToString(); + } + + return Match (targetHost, domainName); + } + + // ensure the pattern is valid wrt to RFC2595 and RFC2818 + // http://www.ietf.org/rfc/rfc2595.txt + // http://www.ietf.org/rfc/rfc2818.txt + static bool Match (string hostname, string pattern) + { + // check if this is a pattern + int index = pattern.IndexOf ('*'); + if (index == -1) { + // not a pattern, do a direct case-insensitive comparison + return (String.Compare (hostname, pattern, true, CultureInfo.InvariantCulture) == 0); + } + + // check pattern validity + // A "*" wildcard character MAY be used as the left-most name component in the certificate. + + // unless this is the last char (valid) + if (index != pattern.Length - 1) { + // then the next char must be a dot .'. + if (pattern [index + 1] != '.') + return false; + } + + // only one (A) wildcard is supported + int i2 = pattern.IndexOf ('*', index + 1); + if (i2 != -1) + return false; + + // match the end of the pattern + string end = pattern.Substring (index + 1); + int length = hostname.Length - end.Length; + // no point to check a pattern that is longer than the hostname + if (length <= 0) + return false; + + if (String.Compare (hostname, length, end, 0, end.Length, true, CultureInfo.InvariantCulture) != 0) + return false; + + // special case, we start with the wildcard + if (index == 0) { + // ensure we hostname non-matched part (start) doesn't contain a dot + int i3 = hostname.IndexOf ('.'); + return ((i3 == -1) || (i3 >= (hostname.Length - end.Length))); + } + + // match the start of the pattern + string start = pattern.Substring (0, index); + return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0); + } + } +#endif } } +