2010-03-01 Gonzalo Paniagua Javier <gonzalo@novell.com>
authorGonzalo Paniagua Javier <gonzalo.mono@gmail.com>
Mon, 1 Mar 2010 09:01:14 +0000 (09:01 -0000)
committerGonzalo Paniagua Javier <gonzalo.mono@gmail.com>
Mon, 1 Mar 2010 09:01:14 +0000 (09:01 -0000)
* System.Net/WebConnection.cs: attach a validation callback to the new
event in Mono.Security.
* System.Net/ServicePointManager.cs: added a new ValidationHelper
class that takes care of building the certificate chain and perform
all the check that were done in Mono.Security before. This way the 2.0
server certificate validation callback can get all the data it needs.
* System.Net.Security/SslStream.cs: don't do anything if a chain
element has no error.

Fixes bug #584050.

svn path=/trunk/mcs/; revision=152654

mcs/class/System/System.Net.Security/ChangeLog
mcs/class/System/System.Net.Security/SslStream.cs
mcs/class/System/System.Net/ChangeLog
mcs/class/System/System.Net/ServicePointManager.cs
mcs/class/System/System.Net/WebConnection.cs

index 6b3d89cd536c7a2b776dcaec8360552a8c6ce07c..91853563b93aef0f89a5552768e32e93b8d4dcb1 100644 (file)
@@ -1,3 +1,8 @@
+2010-03-01 Gonzalo Paniagua Javier <gonzalo@novell.com>
+
+       * SslStream.cs: don't do anything if a chain element has
+       no error.
+
 2009-08-20  Sebastien Pouliot  <sebastien@ximian.com>
 
        * SslStream.cs : Always use/provide a X509Chain even if this 
index fd5927efacfc6cdbf37862228fe31b47a84ad75c..65673310efa3b88dd2a918d3b6518aa4a9871297 100644 (file)
@@ -376,6 +376,8 @@ namespace System.Net.Security
 
                                        // non-SSL specific X509 checks (i.e. RFC3280 related checks)
                                        foreach (X509ChainStatus status in chain.ChainStatus) {
+                                               if (status.Status == X509ChainStatusFlags.NoError)
+                                                       continue;
                                                if ((status.Status & X509ChainStatusFlags.PartialChain) != 0)
                                                        errors |= SslPolicyErrors.RemoteCertificateNotAvailable;
                                                else
index 137474ed58829be3edd7ea3c0b367cb9aec4fa2b..0378694e817e0aad0e30ee79ae148be9f357c190 100644 (file)
@@ -1,3 +1,14 @@
+2010-03-01 Gonzalo Paniagua Javier <gonzalo@novell.com>
+
+       * WebConnection.cs: attach a validation callback to the new
+       event in Mono.Security.
+       * ServicePointManager.cs: added a new ValidationHelper
+       class that takes care of building the certificate chain and perform
+       all the check that were done in Mono.Security before. This way the 2.0
+       server certificate validation callback can get all the data it needs.
+
+       Fixes bug #584050.
+
 2010-01-29  Mike Kestner <mkestner@novell.com>
 
        * HttpUtility.cs: port more of the methods from System.Web file.
index 811b0d510fb83b0db0711004f7fb6e55bae68a15..dca551f4bbc0fb893d38e03aaa58fc126788602d 100644 (file)
@@ -34,7 +34,14 @@ 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;
+#endif
 #endif
 
 //
@@ -369,5 +376,206 @@ namespace System.Net
                                        servicePoints.Remove (list.GetByIndex (i));
                        }
                }
+#if NET_2_0 && SECURITY_DEP
+               internal class ChainValidationHelper {
+                       object sender;
+                       string host;
+
+                       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 bool ValidateChain (Mono.Security.X509.X509CertificateCollection certs)
+                       {
+                               if (certs == null || certs.Count == 0)
+                                       return false;
+
+                               RemoteCertificateValidationCallback cb = ServicePointManager.ServerCertificateValidationCallback;
+                               if (cb == null)
+                                       return false;
+
+                               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);
+                               SslPolicyErrors errors = 0;
+                               if (!chain.Build (leaf))
+                                       errors |= GetErrorsFromChain (chain);
+                               if (!CheckCertificateUsage (leaf)) // -2146762490: CERT_E_PURPOSE
+                                       errors |= SslPolicyErrors.RemoteCertificateChainErrors;
+                               if (!CheckServerIdentity (leaf, Host))
+                                       errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
+                               return cb (sender, leaf, chain, errors);
+                       }
+
+                       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
        }
 }
+
index 9b20b7546d81bfbe21d30557f5534a8c1e93a62b..5bb638c6a18876906ef85d743501206015c030ac 100644 (file)
@@ -35,6 +35,9 @@ using System.Reflection;
 using System.Security.Cryptography.X509Certificates;
 using System.Text;
 using System.Threading;
+#if (NET_2_0 || MONOTOUCH) && SECURITY_DEP
+using Mono.Security.Protocol.Tls;
+#endif
 
 namespace System.Net
 {
@@ -352,6 +355,11 @@ namespace System.Net
                                                                                request.ClientCertificates,
                                                                                request, buffer};
                                                nstream = (Stream) Activator.CreateInstance (sslStream, args);
+#if (NET_2_0 || MONOTOUCH) && SECURITY_DEP
+                                               SslClientStream scs = (SslClientStream) nstream;
+                                               var helper = new ServicePointManager.ChainValidationHelper (request);
+                                               scs.ServerCertValidation2 += new CertificateValidationCallback2 (helper.ValidateChain);
+#endif
                                                certsAvailable = false;
                                        }
                                        // we also need to set ServicePoint.Certificate