// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
+#if SECURITY_DEP
+
+#if MONOTOUCH
+using Mono.Security.Protocol.Tls;
+using MSX = Mono.Security.X509;
+using Mono.Security.X509.Extensions;
+#else
+extern alias MonoSecurity;
+using MonoSecurity::Mono.Security.X509.Extensions;
+using MonoSecurity::Mono.Security.Protocol.Tls;
+using MSX = MonoSecurity::Mono.Security.X509;
+#endif
+
+using System.Text.RegularExpressions;
+#endif
+
using System;
+using System.Threading;
using System.Collections;
+using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Net.Configuration;
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
+using System.Diagnostics;
//
// notes:
namespace System.Net
{
-#if MOONLIGHT
- internal class ServicePointManager {
-#else
public class ServicePointManager {
-#endif
class SPKey {
Uri uri; // schema/host/port
+ Uri proxy;
bool use_connect;
- public SPKey (Uri uri, bool use_connect) {
+ public SPKey (Uri uri, Uri proxy, bool use_connect) {
this.uri = uri;
+ this.proxy = proxy;
this.use_connect = use_connect;
}
get { return use_connect; }
}
+ public bool UsesProxy {
+ get { return proxy != null; }
+ }
+
public override int GetHashCode () {
- return uri.GetHashCode () + ((use_connect) ? 1 : 0);
+ int hash = 23;
+ hash = hash * 31 + ((use_connect) ? 1 : 0);
+ hash = hash * 31 + uri.GetHashCode ();
+ hash = hash * 31 + (proxy != null ? proxy.GetHashCode () : 0);
+ return hash;
}
public override bool Equals (object obj) {
return false;
}
- return (uri.Equals (other.uri) && other.use_connect == use_connect);
+ if (!uri.Equals (other.uri))
+ return false;
+ if (use_connect != other.use_connect || UsesProxy != other.UsesProxy)
+ return false;
+ if (UsesProxy && !proxy.Equals (other.proxy))
+ return false;
+ return true;
}
}
private static ICertificatePolicy policy = new DefaultCertificatePolicy ();
private static int defaultConnectionLimit = DefaultPersistentConnectionLimit;
- private static int maxServicePointIdleTime = 900000; // 15 minutes
+ private static int maxServicePointIdleTime = 100000; // 100 seconds
private static int maxServicePoints = 0;
private static bool _checkCRL = false;
private static SecurityProtocolType _securityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
// 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";
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 !NET_2_1
+ if (manager != null)
+ manager.Add ("*", defaultConnectionLimit);
+#endif
}
}
if (address == null)
throw new ArgumentNullException ("address");
- RecycleServicePoints ();
+ if ((servicePoints.Count % 4) == 0)
+ RecycleServicePoints ();
+
+ var origAddress = new Uri (address.Scheme + "://" + address.Authority);
bool usesProxy = false;
bool useConnect = false;
address = new Uri (address.Scheme + "://" + address.Authority);
ServicePoint sp = null;
+ SPKey key = new SPKey (origAddress, usesProxy ? address : null, useConnect);
lock (servicePoints) {
- SPKey key = new SPKey (address, useConnect);
sp = servicePoints [key] as ServicePoint;
if (sp != null)
return sp;
if (maxServicePoints > 0 && servicePoints.Count >= maxServicePoints)
throw new InvalidOperationException ("maximum number of service points reached");
- string addr = address.ToString ();
+ int limit;
#if NET_2_1
- int limit = defaultConnectionLimit;
+ limit = defaultConnectionLimit;
#else
- int limit = (int) manager.GetMaxConnections (addr);
+ string addr = address.ToString ();
+ limit = (int) manager.GetMaxConnections (addr);
#endif
sp = new ServicePoint (address, limit, maxServicePointIdleTime);
sp.Expect100Continue = expectContinue;
// Internal Methods
- internal static void RecycleServicePoints ()
+ static void RecycleServicePoints ()
{
- ArrayList toRemove = new ArrayList ();
lock (servicePoints) {
+ var toRemove = new ArrayList ();
+ var idleList = new SortedDictionary<DateTime, ServicePoint> ();
IDictionaryEnumerator e = servicePoints.GetEnumerator ();
while (e.MoveNext ()) {
ServicePoint sp = (ServicePoint) e.Value;
- if (sp.AvailableForRecycling) {
+ DateTime idleSince;
+ if (sp.CheckAvailableForRecycling (out idleSince)) {
toRemove.Add (e.Key);
+ continue;
}
+
+ while (idleList.ContainsKey (idleSince))
+ idleSince = idleSince.AddMilliseconds (1);
+ idleList.Add (idleSince, sp);
}
for (int i = 0; i < toRemove.Count; i++)
return;
// get rid of the ones with the longest idle time
- SortedList list = new SortedList (servicePoints.Count);
- e = servicePoints.GetEnumerator ();
- while (e.MoveNext ()) {
- ServicePoint sp = (ServicePoint) e.Value;
- if (sp.CurrentConnections == 0) {
- while (list.ContainsKey (sp.IdleSince))
- sp.IdleSince = sp.IdleSince.AddMilliseconds (1);
- list.Add (sp.IdleSince, sp.Address);
- }
+ foreach (var sp in idleList.Values) {
+ if (servicePoints.Count <= maxServicePoints)
+ break;
+ servicePoints.Remove (sp);
}
-
- for (int i = 0; i < list.Count && servicePoints.Count > maxServicePoints; i++)
- 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 SECURITY_DEP
+#if SECURITY_DEP
internal class ChainValidationHelper {
object sender;
string host;
- static bool is_macosx = System.IO.File.Exists (MSX.OSX509Certificates.SecurityLibrary);
- static X509RevocationMode revocation_mode;
+ RemoteCertificateValidationCallback cb;
-#if MONODROID
- static readonly Converter<Mono.Security.X509.X509CertificateCollection, bool> monodroidCallback;
-#endif
+#if !MONOTOUCH
+ static bool is_macosx = System.IO.File.Exists (OSX509Certificates.SecurityLibrary);
+ static X509RevocationMode revocation_mode;
static ChainValidationHelper ()
{
-#if MONODROID
- monodroidCallback = (Converter<Mono.Security.X509.X509CertificateCollection, bool>)
- Delegate.CreateDelegate (typeof(Converter<Mono.Security.X509.X509CertificateCollection, bool>),
- Type.GetType ("Android.Runtime.AndroidEnvironment, Mono.Android", true)
- .GetMethod ("TrustEvaluateSsl",
- System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic));
-#endif
-#if !MONOTOUCH
revocation_mode = X509RevocationMode.NoCheck;
try {
string str = Environment.GetEnvironmentVariable ("MONO_X509_REVOCATION_MODE");
revocation_mode = (X509RevocationMode) Enum.Parse (typeof (X509RevocationMode), str, true);
} catch {
}
-#endif
}
+#endif
- public ChainValidationHelper (object sender)
+ public ChainValidationHelper (object sender, string hostName)
{
this.sender = sender;
+ host = hostName;
}
- public string Host {
+ public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
get {
- if (host == null && sender is HttpWebRequest)
- host = ((HttpWebRequest) sender).Address.Host;
- return host;
+ if (cb == null)
+ cb = ServicePointManager.ServerCertificateValidationCallback;
+ return cb;
}
-
- set { host = value; }
+ set { cb = 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)
+ internal ValidationResult ValidateChain (MSX.X509CertificateCollection certs)
{
// user_denied is true if the user callback is called and returns false
bool user_denied = false;
return null;
ICertificatePolicy policy = ServicePointManager.CertificatePolicy;
- RemoteCertificateValidationCallback cb = ServicePointManager.ServerCertificateValidationCallback;
X509Certificate2 leaf = new X509Certificate2 (certs [0].RawData);
int status11 = 0; // Error code passed to the obsolete ICertificatePolicy callback
SslPolicyErrors errors = 0;
X509Chain chain = null;
-#if !MONOTOUCH
+ 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 (ServerCertificateValidationCallback != null) {
+#endif
chain = new X509Chain ();
chain.ChainPolicy = new X509ChainPolicy ();
+#if !MONOTOUCH
chain.ChainPolicy.RevocationMode = revocation_mode;
+#endif
for (int i = 1; i < certs.Count; i++) {
X509Certificate2 c2 = new X509Certificate2 (certs [i].RawData);
chain.ChainPolicy.ExtraStore.Add (c2);
}
-
+#if MONOTOUCH
+ }
+#else
try {
if (!chain.Build (leaf))
errors |= GetErrorsFromChain (chain);
Console.Error.WriteLine ("Please, report this problem to the Mono team");
errors |= SslPolicyErrors.RemoteCertificateChainErrors;
}
-#endif
- 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
- }
+ // 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
+ }
- bool result = false;
- // No certificate root found means no mozroots or monotouch
-#if !MONOTOUCH
- if (is_macosx) {
+ 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;
+ OSX509Certificates.SecTrustResult trustResult = OSX509Certificates.SecTrustResult.Deny;
try {
- trustResult = MSX.OSX509Certificates.TrustEvaluateSsl (certs);
+ trustResult = 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);
-
+ result = (trustResult == OSX509Certificates.SecTrustResult.Proceed ||
+ trustResult == 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 = monodroidCallback (certs);
+#if MONODROID && SECURITY_DEP
+ result = AndroidPlatform.TrustEvaluateSsl (certs, sender, leaf, chain, errors);
if (result) {
- status11 = 0;
- errors = 0;
+ // 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
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);
+ if (ServerCertificateValidationCallback != null) {
+ result = ServerCertificateValidationCallback (sender, leaf, chain, errors);
user_denied = !result;
}
return new ValidationResult (result, user_denied, status11);
}
return (int) result;
}
-
+#if !MONOTOUCH
static SslPolicyErrors GetErrorsFromChain (X509Chain chain)
{
SslPolicyErrors errors = SslPolicyErrors.None;
// 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)
+ static bool CheckServerIdentity (MSX.X509Certificate cert, string targetHost)
{
try {
- Mono.Security.X509.X509Extension ext = cert.Extensions ["2.5.29.17"];
+ MSX.X509Extension ext = cert.Extensions ["2.5.29.17"];
// 1. subjectAltName
if (ext != null) {
SubjectAltNameExtension subjectAltName = new SubjectAltNameExtension (ext);
string start = pattern.Substring (0, index);
return (String.Compare (hostname, 0, start, 0, start.Length, true, CultureInfo.InvariantCulture) == 0);
}
+#endif
}
#endif
}