context: //
  // System.Net.Security.SslStream.cs
  //
  // Authors:
  //	Tim Coleman (tim@timcoleman.com)
  //	Atsushi Enomoto (atsushi@ximian.com)
  //	Marek Safar (marek.safar@gmail.com)
  //
  // Copyright (C) Tim Coleman, 2004
  // (c) 2004,2007 Novell, Inc. (http://www.novell.com)
  // Copyright 2011 Xamarin Inc.
  //
  
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  #if SECURITY_DEP
  
  #if MONOTOUCH || MONODROID
  using Mono.Security.Protocol.Tls;
  
  using CipherAlgorithmType = System.Security.Authentication.CipherAlgorithmType;
  using HashAlgorithmType = System.Security.Authentication.HashAlgorithmType;
  using ExchangeAlgorithmType = System.Security.Authentication.ExchangeAlgorithmType;
  
  using MonoCipherAlgorithmType = Mono.Security.Protocol.Tls.CipherAlgorithmType;
  using MonoHashAlgorithmType = Mono.Security.Protocol.Tls.HashAlgorithmType;
  using MonoExchangeAlgorithmType = Mono.Security.Protocol.Tls.ExchangeAlgorithmType;
  using MonoSecurityProtocolType = Mono.Security.Protocol.Tls.SecurityProtocolType;
  #else
  extern alias PrebuiltSystem;
  extern alias MonoSecurity;
  
  using X509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
  
  using CipherAlgorithmType = System.Security.Authentication.CipherAlgorithmType;
  using HashAlgorithmType = System.Security.Authentication.HashAlgorithmType;
  using ExchangeAlgorithmType = System.Security.Authentication.ExchangeAlgorithmType;
  
  using MonoCipherAlgorithmType = MonoSecurity::Mono.Security.Protocol.Tls.CipherAlgorithmType;
  using MonoHashAlgorithmType = MonoSecurity::Mono.Security.Protocol.Tls.HashAlgorithmType;
  using MonoExchangeAlgorithmType = MonoSecurity::Mono.Security.Protocol.Tls.ExchangeAlgorithmType;
  using MonoSecurityProtocolType = MonoSecurity::Mono.Security.Protocol.Tls.SecurityProtocolType;
  
  using MonoSecurity::Mono.Security.Protocol.Tls;
  #endif
  
  using System;
  using System.IO;
  using System.Net;
  using System.Security.Authentication;
  using System.Security.Cryptography.X509Certificates;
  using System.Security.Principal;
  using System.Security.Cryptography;
  
  using System.Threading.Tasks;
  
  namespace System.Net.Security 
  {
  	[MonoTODO ("Non-X509Certificate2 certificate is not supported")]
  	public class SslStream : AuthenticatedStream
  	{
  		#region Fields
  
  		SslStreamBase ssl_stream;
  		RemoteCertificateValidationCallback validation_callback;
  		LocalCertificateSelectionCallback selection_callback;
  
  		#endregion // Fields
  
  		#region Constructors
  
  		public SslStream (Stream innerStream)
  			: this (innerStream, false)
  		{
  		}
  
  		public SslStream (Stream innerStream, bool leaveInnerStreamOpen)
  			: base (innerStream, leaveInnerStreamOpen)
  		{
  		}
  
  		[MonoTODO ("userCertificateValidationCallback is not passed X509Chain and SslPolicyErrors correctly")]
  		public SslStream (Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback)
  			: this (innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, null)
  		{
  		}
  
  		[MonoTODO ("userCertificateValidationCallback is not passed X509Chain and SslPolicyErrors correctly")]
  		public SslStream (Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback)
  			: base (innerStream, leaveInnerStreamOpen)
  		{
  			// they are nullable.
  			validation_callback = userCertificateValidationCallback;
  			selection_callback = userCertificateSelectionCallback;
  		}
  		#endregion // Constructors
  
  		#region Properties
  
  		public override bool CanRead {
  			get { return InnerStream.CanRead; }
  		}
  
  		public override bool CanSeek {
  			get { return InnerStream.CanSeek; }
  		}
  
  		public override bool CanTimeout {
  			get { return InnerStream.CanTimeout; }
  		}
  
  		public override bool CanWrite {
  			get { return InnerStream.CanWrite; }
  		}
  
  		public override long Length {
  			get { return InnerStream.Length; }
  		}
  
  		public override long Position {
  			get { return InnerStream.Position; }
  			set {
  				throw new NotSupportedException ("This stream does not support seek operations");
  			}
  		}
  
  		// AuthenticatedStream overrides
  
  		public override bool IsAuthenticated { 
  			get { return ssl_stream != null; }
  		}
  
  		public override bool IsEncrypted { 
  			get { return IsAuthenticated; }
  		}
  
  		public override bool IsMutuallyAuthenticated { 
  			get { return IsAuthenticated && (IsServer ? RemoteCertificate != null : LocalCertificate != null); }
  		}
  
  		public override bool IsServer { 
  			get { return ssl_stream is SslServerStream; }
  		}
  
  		public override bool IsSigned { 
  			get { return IsAuthenticated; }
  		}
  
  		public override int ReadTimeout {
  			get { return InnerStream.ReadTimeout; }
  			set { InnerStream.ReadTimeout = value; }
  		}
  
  		public override int WriteTimeout {
  			get { return InnerStream.WriteTimeout; }
  			set { InnerStream.WriteTimeout = value; }
  		}
  
  		// SslStream
  
  		public virtual bool CheckCertRevocationStatus {
  			get {
  				if (!IsAuthenticated)
  					return false;
  
  				return ssl_stream.CheckCertRevocationStatus;
  			}
  		}
  
  		public virtual CipherAlgorithmType CipherAlgorithm  {
  			get {
  				CheckConnectionAuthenticated ();
  
  				switch (ssl_stream.CipherAlgorithm) {
  				case MonoCipherAlgorithmType.Des:
  					return CipherAlgorithmType.Des;
  				case MonoCipherAlgorithmType.None:
  					return CipherAlgorithmType.None;
  				case MonoCipherAlgorithmType.Rc2:
  					return CipherAlgorithmType.Rc2;
  				case MonoCipherAlgorithmType.Rc4:
  					return CipherAlgorithmType.Rc4;
  				case MonoCipherAlgorithmType.SkipJack:
  					break;
  				case MonoCipherAlgorithmType.TripleDes:
  					return CipherAlgorithmType.TripleDes;
  				case MonoCipherAlgorithmType.Rijndael:
  					switch (ssl_stream.CipherStrength) {
  					case 128:
  						return CipherAlgorithmType.Aes128;
  					case 192:
  						return CipherAlgorithmType.Aes192;
  					case 256:
  						return CipherAlgorithmType.Aes256;
  					}
  					break;
  				}
  
  				throw new InvalidOperationException ("Not supported cipher algorithm is in use. It is likely a bug in SslStream.");
  			}
  		}
  
  		public virtual int CipherStrength  {
  			get {
  				CheckConnectionAuthenticated ();
  
  				return ssl_stream.CipherStrength;
  			}
  		}
  
  		public virtual HashAlgorithmType HashAlgorithm  {
  			get {
  				CheckConnectionAuthenticated ();
  
  				switch (ssl_stream.HashAlgorithm) {
  				case MonoHashAlgorithmType.Md5:
  					return HashAlgorithmType.Md5;
  				case MonoHashAlgorithmType.None:
  					return HashAlgorithmType.None;
  				case MonoHashAlgorithmType.Sha1:
  					return HashAlgorithmType.Sha1;
  				}
  
  				throw new InvalidOperationException ("Not supported hash algorithm is in use. It is likely a bug in SslStream.");
  			}
  		}
  
  		public virtual int HashStrength  {
  			get {
  				CheckConnectionAuthenticated ();
  
  				return ssl_stream.HashStrength;
  			}
  		}
  
  		public virtual ExchangeAlgorithmType KeyExchangeAlgorithm { 
  			get {
  				CheckConnectionAuthenticated ();
  
  				switch (ssl_stream.KeyExchangeAlgorithm) {
  				case MonoExchangeAlgorithmType.DiffieHellman:
  					return ExchangeAlgorithmType.DiffieHellman;
  				case MonoExchangeAlgorithmType.Fortezza:
  					break;
  				case MonoExchangeAlgorithmType.None:
  					return ExchangeAlgorithmType.None;
  				case MonoExchangeAlgorithmType.RsaKeyX:
  					return ExchangeAlgorithmType.RsaKeyX;
  				case MonoExchangeAlgorithmType.RsaSign:
  					return ExchangeAlgorithmType.RsaSign;
  				}
  
  				throw new InvalidOperationException ("Not supported exchange algorithm is in use. It is likely a bug in SslStream.");
  			}
  		}
  
  		public virtual int KeyExchangeStrength { 
  			get {
  				CheckConnectionAuthenticated ();
  
  				return ssl_stream.KeyExchangeStrength;
  			}
  		}
  
  		public virtual X509Certificate LocalCertificate {
  			get {
  				CheckConnectionAuthenticated ();
  
  				return IsServer ? ssl_stream.ServerCertificate : ((SslClientStream) ssl_stream).SelectedClientCertificate;
  			}
  		}
  
  		public virtual X509Certificate RemoteCertificate {
  			get {
  				CheckConnectionAuthenticated ();
  				return !IsServer ? ssl_stream.ServerCertificate : ((SslServerStream) ssl_stream).ClientCertificate;
  			}
  		}
  
  		public virtual SslProtocols SslProtocol {
  			get {
  				CheckConnectionAuthenticated ();
  
  				switch (ssl_stream.SecurityProtocol) {
  				case MonoSecurityProtocolType.Default:
  					return SslProtocols.Default;
  				case MonoSecurityProtocolType.Ssl2:
  					return SslProtocols.Ssl2;
  				case MonoSecurityProtocolType.Ssl3:
  					return SslProtocols.Ssl3;
  				case MonoSecurityProtocolType.Tls:
  					return SslProtocols.Tls;
  				}
  
  				throw new InvalidOperationException ("Not supported SSL/TLS protocol is in use. It is likely a bug in SslStream.");
  			}
  		}
  
  		#endregion // Properties
  
  		#region Methods
  /*
  		AsymmetricAlgorithm GetPrivateKey (X509Certificate cert, string targetHost)
  		{
  			// FIXME: what can I do for non-X509Certificate2 ?
  			X509Certificate2 cert2 = cert as X509Certificate2;
  			return cert2 != null ? cert2.PrivateKey : null;
  		}
  */
  		X509Certificate OnCertificateSelection (X509CertificateCollection clientCerts, X509Certificate serverCert, string targetHost, X509CertificateCollection serverRequestedCerts)
  		{
  			string [] acceptableIssuers = new string [serverRequestedCerts != null ? serverRequestedCerts.Count : 0];
  			for (int i = 0; i < acceptableIssuers.Length; i++)
  				acceptableIssuers [i] = serverRequestedCerts [i].GetIssuerName ();
  			return selection_callback (this, targetHost, clientCerts, serverCert, acceptableIssuers);
  		}
  
  		public virtual IAsyncResult BeginAuthenticateAsClient (string targetHost, AsyncCallback asyncCallback, object asyncState)
  		{
  			return BeginAuthenticateAsClient (targetHost, new X509CertificateCollection (), SslProtocols.Tls, false, asyncCallback, asyncState);
  		}
  
  		public virtual IAsyncResult BeginAuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
  		{
  			if (IsAuthenticated)
  				throw new InvalidOperationException ("This SslStream is already authenticated");
  
  			SslClientStream s = new SslClientStream (InnerStream, targetHost, !LeaveInnerStreamOpen, GetMonoSslProtocol (enabledSslProtocols), clientCertificates);
  			s.CheckCertRevocationStatus = checkCertificateRevocation;
  
  			// Due to the Mono.Security internal, it cannot reuse
  			// the delegated argument, as Mono.Security creates 
  			// another instance of X509Certificate which lacks 
  			// private key but is filled the private key via this
  			// delegate.
  			s.PrivateKeyCertSelectionDelegate = delegate (X509Certificate cert, string host) {
  				string hash = cert.GetCertHashString ();
  				// ... so, we cannot use the delegate argument.
  				foreach (X509Certificate cc in clientCertificates) {
  					if (cc.GetCertHashString () != hash)
  						continue;
  					X509Certificate2 cert2 = cc as X509Certificate2;
  					cert2 = cert2 ?? new X509Certificate2 (cc);
  					return cert2.PrivateKey;
  				}
  				return null;
  			};
  
  #if MONOTOUCH || MONODROID
  			// Even if validation_callback is null this allows us to verify requests where the user
  			// does not provide a verification callback but attempts to authenticate with the website
  			// as a client (see https://bugzilla.xamarin.com/show_bug.cgi?id=18962 for an example)
  			var helper = new ServicePointManager.ChainValidationHelper (this, targetHost);
  			helper.ServerCertificateValidationCallback = validation_callback;
  			s.ServerCertValidation2 += new CertificateValidationCallback2 (helper.ValidateChain);
  #else
  			if (validation_callback != null) {
  				s.ServerCertValidationDelegate = delegate (X509Certificate cert, int [] certErrors) {
  					X509Chain chain = new X509Chain ();
  					X509Certificate2 x2 = (cert as X509Certificate2);
  					if (x2 == null)
  						x2 = new X509Certificate2 (cert);
  
  					if (!ServicePointManager.CheckCertificateRevocationList)
  						chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
  
  					// SSL specific checks (done by Mono.Security.dll SSL/TLS implementation) 
  					SslPolicyErrors errors = SslPolicyErrors.None;
  					foreach (int i in certErrors) {
  						switch (i) {
  						case -2146762490: // CERT_E_PURPOSE
  							errors |= SslPolicyErrors.RemoteCertificateNotAvailable;
  							break;
  						case -2146762481: // CERT_E_CN_NO_MATCH
  							errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
  							break;
  						default:
  							errors |= SslPolicyErrors.RemoteCertificateChainErrors;
  							break;
  						}
  					}
  
  					chain.Build (x2);
  
  					// 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
  							errors |= SslPolicyErrors.RemoteCertificateChainErrors;
  					}
  
  					return validation_callback (this, cert, chain, errors);
  				};
  			}
  #endif
  			if (selection_callback != null)
  				s.ClientCertSelectionDelegate = OnCertificateSelection;
  
  			ssl_stream = s;
  
  			return BeginWrite (new byte [0], 0, 0, asyncCallback, asyncState);
  		}
  
  		public override IAsyncResult BeginRead (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
  		{
  			CheckConnectionAuthenticated ();
  
  			return ssl_stream.BeginRead (buffer, offset, count, asyncCallback, asyncState);
  		}
  
  		public virtual IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, AsyncCallback asyncCallback, object asyncState)
  		{
  			return BeginAuthenticateAsServer (serverCertificate, false, SslProtocols.Tls, false, asyncCallback, asyncState);
  		}
  
  		public virtual IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
  		{
  			if (IsAuthenticated)
  				throw new InvalidOperationException ("This SslStream is already authenticated");
  
  			SslServerStream s = new SslServerStream (InnerStream, serverCertificate, clientCertificateRequired, !LeaveInnerStreamOpen, GetMonoSslProtocol (enabledSslProtocols));
  			s.CheckCertRevocationStatus = checkCertificateRevocation;
  			// Due to the Mono.Security internal, it cannot reuse
  			// the delegated argument, as Mono.Security creates 
  			// another instance of X509Certificate which lacks 
  			// private key but is filled the private key via this
  			// delegate.
  			s.PrivateKeyCertSelectionDelegate = delegate (X509Certificate cert, string targetHost) {
  				// ... so, we cannot use the delegate argument.
  				X509Certificate2 cert2 = serverCertificate as X509Certificate2 ?? new X509Certificate2 (serverCertificate);
  				return cert2 != null ? cert2.PrivateKey : null;
  			};
  
  			if (validation_callback != null)
  				s.ClientCertValidationDelegate = delegate (X509Certificate cert, int [] certErrors) {
  					X509Chain chain = null;
  					if (cert is X509Certificate2) {
  						chain = new X509Chain ();
  						chain.Build ((X509Certificate2) cert);
  					}
  					// FIXME: SslPolicyErrors is incomplete
  					SslPolicyErrors errors = certErrors.Length > 0 ? SslPolicyErrors.RemoteCertificateChainErrors : SslPolicyErrors.None;
  					return validation_callback (this, cert, chain, errors);
  				};
  
  			ssl_stream = s;
  
  			return BeginWrite (new byte[0], 0, 0, asyncCallback, asyncState);
  		}
  
  		MonoSecurityProtocolType GetMonoSslProtocol (SslProtocols ms)
  		{
  			switch (ms) {
  			case SslProtocols.Ssl2:
  				return MonoSecurityProtocolType.Ssl2;
  			case SslProtocols.Ssl3:
  				return MonoSecurityProtocolType.Ssl3;
  			case SslProtocols.Tls:
  				return MonoSecurityProtocolType.Tls;
  			default:
  				return MonoSecurityProtocolType.Default;
  			}
  		}
  
  		public override IAsyncResult BeginWrite (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
  		{
  			CheckConnectionAuthenticated ();
  
  			return ssl_stream.BeginWrite (buffer, offset, count, asyncCallback, asyncState);
  		}
  
  		public virtual void AuthenticateAsClient (string targetHost)
  		{
  			AuthenticateAsClient (targetHost, new X509CertificateCollection (), SslProtocols.Tls, false);
  		}
  
  		public virtual void AuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
  		{
  			EndAuthenticateAsClient (BeginAuthenticateAsClient (
  				targetHost, clientCertificates, enabledSslProtocols, checkCertificateRevocation, null, null));
  		}
  
  		public virtual void AuthenticateAsServer (X509Certificate serverCertificate)
  		{
  			AuthenticateAsServer (serverCertificate, false, SslProtocols.Tls, false);
  		}
  
  		public virtual void AuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
  		{
  			EndAuthenticateAsServer (BeginAuthenticateAsServer (
  				serverCertificate, clientCertificateRequired, enabledSslProtocols, checkCertificateRevocation, null, null));
  		}
  
  		protected override void Dispose (bool disposing)
  		{
  
gt: 			if (disposing) {