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
//
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
}
}
+