X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FSystem%2FSystem.Net%2FServicePointManager.cs;h=b277d9f89cc9430d8633987824445e1783f177e6;hb=8d373d7da5a91e52779e812e50324c87fad06d1b;hp=bea42e48cc30dfc838ff743830182e460c2e373c;hpb=a5e40870bd3bb18e1681afed6c71e7edfdb80534;p=mono.git diff --git a/mcs/class/System/System.Net/ServicePointManager.cs b/mcs/class/System/System.Net/ServicePointManager.cs index bea42e48cc3..b277d9f89cc 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,11 @@ 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; @@ -109,17 +122,21 @@ namespace System.Net #endif static bool useNagle; #endif + static RemoteCertificateValidationCallback server_cert_cb; // Fields public const int DefaultNonPersistentConnectionLimit = 4; public const int DefaultPersistentConnectionLimit = 2; +#if !NET_2_1 const string configKey = "system.net/connectionManagement"; static ConnectionManagementData manager; +#endif static ServicePointManager () { +#if !NET_2_1 #if NET_2_0 && CONFIGURATION_DEP object cfg = ConfigurationManager.GetSection (configKey); ConnectionManagementSection s = cfg as ConnectionManagementSection; @@ -136,6 +153,7 @@ namespace System.Net if (manager != null) { defaultConnectionLimit = (int) manager.GetMaxConnections ("*"); } +#endif } // Constructors @@ -146,8 +164,7 @@ namespace System.Net // Properties #if NET_2_0 - [Obsolete ("Use ServerCertificateValidationCallback instead", - false)] + [Obsolete ("Use ServerCertificateValidationCallback instead", false)] #endif public static ICertificatePolicy CertificatePolicy { get { return policy; } @@ -240,18 +257,15 @@ 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 { @@ -309,7 +323,11 @@ 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; @@ -359,5 +377,354 @@ 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 NET_2_0 && SECURITY_DEP + internal class ChainValidationHelper { + object sender; + string host; + static bool is_macosx = System.IO.File.Exists (MSX.OSX509Certificates.SecurityLibrary); + + 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; + + X509Chain chain = new X509Chain (); + chain.ChainPolicy = new X509ChainPolicy (); + for (int i = 1; i < certs.Count; i++) { + X509Certificate2 c2 = new X509Certificate2 (certs [i].RawData); + chain.ChainPolicy.ExtraStore.Add (c2); + } + + X509Certificate2 leaf = new X509Certificate2 (certs [0].RawData); + int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback + SslPolicyErrors errors = 0; + 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; + } + + 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; + 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; + } +#if !MONOTOUCH + } +#endif + + if (policy != null && (!(policy is DefaultCertificatePolicy) || cb == null)) { + ServicePoint sp = null; + HttpWebRequest req = sender as HttpWebRequest; + if (req != null) + sp = req.ServicePoint; + 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 = (X509KeyUsageExtension) cert.Extensions ["2.5.29.15"]; + X509EnhancedKeyUsageExtension eku = (X509EnhancedKeyUsageExtension) cert.Extensions ["2.5.29.37"]; + 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") != -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 } } +