using System.Net.Configuration;
using System.Security.Cryptography.X509Certificates;
-#if NET_2_0
using System.Globalization;
using System.Net.Security;
#if SECURITY_DEP
using Mono.Security.Protocol.Tls;
using MSX = Mono.Security.X509;
#endif
-#endif
//
// notes:
private static bool _checkCRL = false;
private static SecurityProtocolType _securityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
-#if NET_1_1
#if TARGET_JVM
static bool expectContinue = false;
#else
static bool expectContinue = true;
#endif
static bool useNagle;
-#endif
-#if NET_2_0
static RemoteCertificateValidationCallback server_cert_cb;
-#endif
+ 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 ServicePointManager ()
{
#if !NET_2_1
-#if NET_2_0 && CONFIGURATION_DEP
+#if CONFIGURATION_DEP
object cfg = ConfigurationManager.GetSection (configKey);
ConnectionManagementSection s = cfg as ConnectionManagementSection;
if (s != null) {
// Properties
-#if NET_2_0
[Obsolete ("Use ServerCertificateValidationCallback instead", false)]
-#endif
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
}
throw new ArgumentOutOfRangeException ("value");
defaultConnectionLimit = value;
+ if (manager != null)
+ manager.Add ("*", defaultConnectionLimit);
}
}
-#if NET_2_0
static Exception GetMustImplement ()
{
return new NotImplementedException ();
throw GetMustImplement ();
}
}
-#endif
public static int MaxServicePointIdleTime {
get {
set { _securityProtocol = value; }
}
-#if NET_2_0
public static RemoteCertificateValidationCallback ServerCertificateValidationCallback
{
get {
server_cert_cb = value;
}
}
-#endif
-#if NET_1_1
public static bool Expect100Continue {
get { return expectContinue; }
set { expectContinue = value; }
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);
throw new ArgumentNullException ("address");
RecycleServicePoints ();
+
+ var origAddress = new Uri (address.Scheme + "://" + address.Authority);
bool usesProxy = false;
bool useConnect = false;
ServicePoint sp = null;
lock (servicePoints) {
- SPKey key = new SPKey (address, useConnect);
+ SPKey key = new SPKey (origAddress, useConnect);
sp = servicePoints [key] as ServicePoint;
if (sp != null)
return sp;
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);
}
servicePoints.Remove (list.GetByIndex (i));
}
}
-#if NET_2_0 && SECURITY_DEP
+#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;
+
+ static ChainValidationHelper ()
+ {
+#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)
{
ICertificatePolicy policy = ServicePointManager.CertificatePolicy;
RemoteCertificateValidationCallback cb = ServicePointManager.ServerCertificateValidationCallback;
- X509Chain chain = new X509Chain ();
+ X509Certificate2 leaf = new X509Certificate2 (certs [0].RawData);
+ int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback
+ SslPolicyErrors errors = 0;
+ X509Chain chain = null;
+ bool result = false;
+#if MONOTOUCH
+ // The X509Chain is not really usable with MonoTouch (since the decision is not based on this data)
+ // However if someone wants to override the results (good or bad) from iOS then they will want all
+ // the certificates that the server provided (which generally does not include the root) so, only
+ // if there's a user callback, we'll create the X509Chain but won't build it
+ // ref: https://bugzilla.xamarin.com/show_bug.cgi?id=7245
+ if (cb != null) {
+#endif
+ 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);
}
-
- 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 MONOTOUCH
}
- if (!CheckServerIdentity (leaf, Host)) {
- errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
- status11 = -2146762481; // CERT_E_CN_NO_MATCH 0x800B010F
+#else
+ 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;
}
- bool result = false;
- // No certificate root found means no mozroots or monotouch
- if (Environment.OSVersion.Platform == PlatformID.MacOSX) {
-#if !MONOTOUCH
- if (System.IO.File.Exists (MSX.OSX509Certificates.SecurityLibrary)) {
+ // for OSX and iOS we're using the native API to check for the SSL server policy and host names
+ if (!is_macosx) {
+ 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
+ }
+ } else {
#endif
// Attempt to use OSX certificates
// Ideally we should return the SecTrustResult
- MSX.OSX509Certificates.SecTrustResult trustResult;
+ MSX.OSX509Certificates.SecTrustResult trustResult = MSX.OSX509Certificates.SecTrustResult.Deny;
try {
- trustResult = MSX.OSX509Certificates.TrustEvaluateSsl (certs);
+ trustResult = MSX.OSX509Certificates.TrustEvaluateSsl (certs, Host);
// 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;
+ // TrustEvaluateSsl was successful so there's no trust error
+ // IOW we discard our own chain (since we trust OSX one instead)
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 = AndroidPlatform.TrustEvaluateSsl (certs, sender, leaf, chain, errors);
+ if (result) {
+ // chain.Build() + GetErrorsFromChain() (above) will ALWAYS fail on
+ // Android (there are no mozroots or preinstalled root certificates),
+ // thus `errors` will ALWAYS have RemoteCertificateChainErrors.
+ // Android just verified the chain; clear RemoteCertificateChainErrors.
+ errors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
}
+#endif
if (policy != null && (!(policy is DefaultCertificatePolicy) || cb == null)) {
ServicePoint sp = null;
HttpWebRequest req = sender as HttpWebRequest;
if (req != null)
- sp = req.ServicePoint;
+ sp = req.ServicePointNoLock;
if (status11 == 0 && errors != 0)
status11 = GetStatusFromChain (chain);
}
return (int) result;
}
-
+#if !MONOTOUCH
static SslPolicyErrors GetErrorsFromChain (X509Chain chain)
{
SslPolicyErrors errors = SslPolicyErrors.None;
// 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;
- }
+ 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") != -1;
+ // 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;
}
- return true;
}
// RFC2818 - HTTP Over TLS, Section 3.1
// 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)
+ static bool CheckServerIdentity (Mono.Security.X509.X509Certificate 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;
+ 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;
}
- // 3. Common Name (CN=)
- return CheckDomainName (cert.SubjectName.Format (false), targetHost);
}
static bool CheckDomainName (string subjectName, string targetHost)
string start = pattern.Substring (0, index);
return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);
}
+#endif
}
#endif
}