context: //
  // SslSecurityTokenProvider.cs
  //
  // Author:
  //	Atsushi Enomoto <atsushi@ximian.com>
  //
  // Copyright (C) 2006-2007 Novell, Inc.  http://www.novell.com
  //
  // 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.
  //
  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Net.Security;
  using System.IdentityModel.Selectors;
  using System.IdentityModel.Tokens;
  using System.Security.Cryptography;
  using System.Security.Cryptography.X509Certificates;
  using System.Security.Cryptography.Xml;
  using System.ServiceModel;
  using System.ServiceModel.Channels;
  using System.ServiceModel.Description;
  using System.ServiceModel.Security;
  using System.ServiceModel.Security.Tokens;
  using System.Xml;
  
  using ReqType = System.ServiceModel.Security.Tokens.ServiceModelSecurityTokenRequirement;
  
  namespace System.ServiceModel.Security.Tokens
  {
  	class SslSecurityTokenProvider : CommunicationSecurityTokenProvider
  	{
  		SslCommunicationObject comm;
  		ClientCredentialsSecurityTokenManager manager;
  
  		public SslSecurityTokenProvider (ClientCredentialsSecurityTokenManager manager, bool mutual)
  		{
  			this.manager = manager;
  			comm = new SslCommunicationObject (this, mutual);
  		}
  
  		public override ProviderCommunicationObject Communication {
  			get { return comm; }
  		}
  
  		public ClientCredentialsSecurityTokenManager Manager {
  			get { return manager; }
  		}
  
  		public override SecurityToken GetOnlineToken (TimeSpan timeout)
  		{
  			return comm.GetToken (timeout);
  		}
  	}
  
  	class SslCommunicationObject : ProviderCommunicationObject
  	{
  		SslSecurityTokenProvider owner;
  		WSTrustSecurityTokenServiceProxy proxy;
  		X509Certificate2 client_certificate;
  		
  
  		public SslCommunicationObject (SslSecurityTokenProvider owner, bool mutual)
  		{
  			if (mutual) {
  				client_certificate = owner.Manager.ClientCredentials.ClientCertificate.Certificate;
  				if (client_certificate == null)
  					throw new InvalidOperationException ("ClientCertificate is required for mutual SSL negotiation.");
  			}
  			this.owner = owner;
  		}
  
  		class TlsnegoClientSessionContext
  		{
  			XmlDocument doc = new XmlDocument ();
  			XmlDsigExcC14NTransform t = new XmlDsigExcC14NTransform ();
  			MemoryStream stream = new MemoryStream ();
  
  			public void StoreMessage (XmlReader reader)
  			{
  				doc.RemoveAll ();
  				doc.AppendChild (doc.ReadNode (reader));
  				t.LoadInput (doc);
  				MemoryStream s = (MemoryStream) t.GetOutput ();
  				byte [] bytes = s.ToArray ();
  				stream.Write (bytes, 0, bytes.Length);
  			}
  
  			public byte [] GetC14NResults ()
  			{
  				return stream.ToArray ();
  			}
  		}
  
  		public SecurityToken GetToken (TimeSpan timeout)
  		{
  			TlsnegoClientSessionContext tlsctx =
  				new TlsnegoClientSessionContext ();
  			TlsClientSession tls = new TlsClientSession (IssuerAddress.Uri.ToString (), client_certificate, owner.Manager.ClientCredentials.ServiceCertificate.Authentication);
  			WstRequestSecurityToken rst =
  				new WstRequestSecurityToken ();
  			string contextId = rst.Context;
  
  			// send ClientHello
  			rst.BinaryExchange = new WstBinaryExchange (Constants.WstBinaryExchangeValueTls);
  			rst.BinaryExchange.Value = tls.ProcessClientHello ();
  
  			Message request = Message.CreateMessage (IssuerBinding.MessageVersion, Constants.WstIssueAction, rst);
  			request.Headers.MessageId = new UniqueId ();
  			request.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
  			request.Headers.To = TargetAddress.Uri;
  			MessageBuffer buffer = request.CreateBufferedCopy (0x10000);
  			tlsctx.StoreMessage (buffer.CreateMessage ().GetReaderAtBodyContents ());
  			Message response = proxy.Issue (buffer.CreateMessage ());
  
  			// FIXME: use correct limitation
  			buffer = response.CreateBufferedCopy (0x10000);
  			tlsctx.StoreMessage (buffer.CreateMessage ().GetReaderAtBodyContents ());
  
  			// receive ServerHello
  			WSTrustRequestSecurityTokenResponseReader reader =
  				new WSTrustRequestSecurityTokenResponseReader (Constants.WstTlsnegoProofTokenType, buffer.CreateMessage ().GetReaderAtBodyContents (), SecurityTokenSerializer, null);
  			reader.Read ();
  			if (reader.Value.RequestedSecurityToken != null)
  				return reader.Value.RequestedSecurityToken;
  
  			tls.ProcessServerHello (reader.Value.BinaryExchange.Value);
  
  			// send ClientKeyExchange
  			WstRequestSecurityTokenResponse rstr =
  				new WstRequestSecurityTokenResponse (SecurityTokenSerializer);
  			rstr.Context = reader.Value.Context;
  			rstr.BinaryExchange = new WstBinaryExchange (Constants.WstBinaryExchangeValueTls);
  			rstr.BinaryExchange.Value = tls.ProcessClientKeyExchange ();
  
  			request = Message.CreateMessage (IssuerBinding.MessageVersion, Constants.WstIssueReplyAction, rstr);
  			request.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
  			request.Headers.To = TargetAddress.Uri;
  
  			buffer = request.CreateBufferedCopy (0x10000);
  			tlsctx.StoreMessage (buffer.CreateMessage ().GetReaderAtBodyContents ());
  //Console.WriteLine (System.Text.Encoding.UTF8.GetString (tlsctx.GetC14NResults ()));
  
  			// FIXME: regeneration of this instance is somehow required, but should not be.
  			proxy = new WSTrustSecurityTokenServiceProxy (
  				IssuerBinding, IssuerAddress);
  			response = proxy.IssueReply (buffer.CreateMessage ());
  			// FIXME: use correct limitation
  			buffer = response.CreateBufferedCopy (0x10000);
  
  			WstRequestSecurityTokenResponseCollection coll =
  				new WstRequestSecurityTokenResponseCollection ();
  			coll.Read (Constants.WstTlsnegoProofTokenType, buffer.CreateMessage ().GetReaderAtBodyContents (), SecurityTokenSerializer, null);
  			if (coll.Responses.Count != 2)
  				throw new SecurityNegotiationException (String.Format ("Expected response is RequestSecurityTokenResponseCollection which contains two RequestSecurityTokenResponse items, but it actually contains {0} items", coll.Responses.Count));
  
  			WstRequestSecurityTokenResponse r = coll.Responses [0];
  			tls.ProcessServerFinished (r.BinaryExchange.Value);
  			SecurityContextSecurityToken sctSrc =
  				r.RequestedSecurityToken;
  
  #if false // FIXME: should this final RSTR included in RSTRC considered too?
  			XmlDocument doc = new XmlDocument ();
  			doc.PreserveWhitespace = true;
  			using (XmlDictionaryWriter dw = XmlDictionaryWriter.CreateDictionaryWriter (doc.CreateNavigator ().AppendChild ())) {
  				if (r == null) throw new Exception ("r");
  				if (dw == null) throw new Exception ("dw");
  				r.WriteBodyContents (dw);
  			}
  			tlsctx.StoreMessage (XmlDictionaryReader.CreateDictionaryReader (new XmlNodeReader (doc)));
  #endif
  
  			// the RequestedProofToken is represented as 32 bytes
  			// of TLS ApplicationData.
  			// - According to WSE2 doc, it is *the* key, but not
  			//   sure it also applies to WCF.
  			// - WSS4J also seems to store the encryped shared key.
  			// - (Important) It seems that without tls decryption,
  			//   .NET fails to recover the key.
  			byte [] proof = tls.ProcessApplicationData (
  				(byte []) r.RequestedProofToken);
  			byte [] key = proof;
  
  			// Authenticate token.
  
  			byte [] actual = coll.Responses [1].Authenticator;
  			if (actual == null)
  				throw new SecurityNegotiationException ("Token authenticator is expected in the RequestSecurityTokenResponse but not found.");
  
  			if (coll.Responses [0].Context != contextId)
  				throw new SecurityNegotiationException ("The context Id does not match with that of the corresponding token authenticator.");
  
  			// H = sha1(exc14n(RST..RSTRs))
  			byte [] hash = SHA1.Create ().ComputeHash (tlsctx.GetC14NResults ());
  			byte [] referent = tls.CreateHash (key, hash, "AUTH-HASH");
  Console.WriteLine (System.Text.Encoding.ASCII.GetString (tlsctx.GetC14NResults ()));
  Console.Write ("Hash: ");
  foreach (byte b in hash) Console.Write ("{0:X02} ", b); Console.WriteLine ();
  Console.Write ("Referent: ");
  foreach (byte b in referent) Console.Write ("{0:X02} ", b); Console.WriteLine ();
  Console.Write ("Actual: ");
  foreach (byte b in actual) Console.Write ("{0:X02} ", b); Console.WriteLine ();
  Console.Write ("Proof: ");
  foreach (byte b in proof) Console.Write ("{0:X02} ", b); Console.WriteLine ();
  			bool mismatch = referent.Length != actual.Length;
  			if (!mismatch)
  				for (int i = 0; i < referent.Length; i++)
  					if (referent [i] != actual [i])
  						mismatch = true;
  
  			if (mismatch)
  				throw new SecurityNegotiationException ("The CombinedHash does not match the expected value.");
  
  			return sctSrc;
  		}
  
  		protected internal override TimeSpan DefaultCloseTimeout {
  			get { throw new NotImplementedException (); }
  		}
  
  		protected internal override TimeSpan DefaultOpenTimeout {
  			get { throw new NotImplementedException (); }
  		}
  
  		protected override void OnAbort ()
  		{
  			throw new NotImplementedException ();
  		}
  
  		protected override void OnOpen (TimeSpan timeout)
  		{
  			if (State == CommunicationState.Opened)
  				throw new InvalidOperationException ("Already opened.");
  
  			EnsureProperties ();
  
  			proxy = new WSTrustSecurityTokenServiceProxy (
  				IssuerBinding, IssuerAddress);
  		}
  
  		protected override IAsyncResult OnBeginOpen (TimeSpan timeout, AsyncCallback callback, object state)
  		{
  
gt: 			throw new NotImplementedException ();