New test.
[mono.git] / mcs / class / System / System.Net / ServicePointManager.cs
index bea42e48cc30dfc838ff743830182e460c2e373c..d22ba5cc9013ba0b046da1734435a2d8a1f24be5 100644 (file)
@@ -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)
 //
 
 //
@@ -34,7 +37,16 @@ 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
 #endif
 
 //
@@ -57,8 +69,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 +124,23 @@ namespace System.Net
 #endif
                static bool useNagle;
 #endif
+#if NET_2_0
+               static RemoteCertificateValidationCallback server_cert_cb;
+#endif
 
                // 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 +157,7 @@ namespace System.Net
                        if (manager != null) {
                                defaultConnectionLimit = (int) manager.GetMaxConnections ("*");                         
                        }
+#endif
                }
 
                // Constructors
@@ -146,8 +168,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,15 +261,14 @@ namespace System.Net
                        set { _securityProtocol = value; }
                }
 
-#if NET_2_0 && SECURITY_DEP
-               [MonoTODO]
+#if NET_2_0
                public static RemoteCertificateValidationCallback ServerCertificateValidationCallback
                {
                        get {
-                               throw GetMustImplement ();
+                               return server_cert_cb;
                        }
                        set {
-                               throw GetMustImplement ();
+                               server_cert_cb = value;
                        }
                }
 #endif
@@ -309,7 +329,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 +383,317 @@ namespace System.Net
                                        servicePoints.Remove (list.GetByIndex (i));
                        }
                }
+#if 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;
+                               if (!chain.Build (leaf))
+                                       errors |= GetErrorsFromChain (chain);
+                               if (!CheckCertificateUsage (leaf)) {
+                                       errors |= SslPolicyErrors.RemoteCertificateChainErrors;
+                                       status11 = -2146762490; //CERT_E_PURPOSE 0x800B0106
+                               }
+                               if (!CheckServerIdentity (leaf, 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) 
+                       {
+                               // 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;
+                       }
+
+                       // 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 (X509Certificate2 cert, string targetHost) 
+                       {
+                               X509Extension ext = cert.Extensions ["2.5.29.17"];
+                               // 1. subjectAltName
+                               if (ext != null) {
+                                       ASN1 asn = new ASN1 (ext.RawData);
+                                       SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (asn);
+                                       // 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.Format (false), targetHost);
+                       }
+
+                       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
        }
 }
+