Click here to Skip to main content
15,884,849 members
Articles / Web Development / HTML

Implementing WS-SecureConversation in Microsoft IssueVision

Rate me:
Please Sign up or sign in to vote.
4.61/5 (12 votes)
27 Sep 20056 min read 73.2K   776   38  
Adding secure communications to the Microsoft IssueVision sample application using WSE 2.0.
// ================================================================================
// Wse2HelperClient.cs
//
// This file contains the implementation of the Wse2HelperClient class
//
// ================================================================================
// config file settings:
// <appSettings file="appdata.config" />
//
// Optional appdata.config file settings:
// <?xml version="1.0" encoding="utf-8" ?> 
// <appSettings>
// <add key="ClientBase64KeyId" value="gBfo0147lM6cKnTbbMSuMVvmFY4=" />
// <add key="ServerBase64KeyId" value="bBwPfItvKp3b6TNDq+14qs58VJQ=" />
// <add key="tokenIssuer" value="http://localhost/IssueVisionWebWseCS/IssueVisionServices.asmx" />
// </appSettings>
// ================================================================================

using System;
using System.Configuration;
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Security;
using Microsoft.Web.Services2.Security.Tokens;
using Microsoft.Web.Services2.Security.X509;

namespace IssueVision
{
	/// <summary>
	/// Summary description for Wse2HelperClient.
	/// </summary>
	public class Wse2HelperClient
	{
		#region private data members

		// Use the appdata.config file to replace the key id below
		// You can get these values for any cert with the WSE 2.0 Certificate Tool
		private static String m_clientBase64KeyId;
		private static String m_serverBase64KeyId;
		// class for storing security context token in cache
		private class SctData
		{
			public String m_username;
			public String m_password;
			public SecurityContextToken m_sct;

			public SctData(String username, String password, SecurityContextToken sct)
			{
				m_username = username;
				m_password = password;
				m_sct = sct;
			}
		}
		private static SctData m_sctData;

		#endregion private data members

		#region private utility methods & constructors

		// Since this class provides only static methods, make the default constructor private
		// to prevent instances from being created with "new Wse2HelperClient()"
		private Wse2HelperClient() {}

		// Type constructor
		static Wse2HelperClient()
		{
			m_clientBase64KeyId = ConfigurationSettings.AppSettings["ClientBase64KeyId"];
			m_serverBase64KeyId = ConfigurationSettings.AppSettings["ServerBase64KeyId"];
			m_sctData = new SctData(string.Empty, string.Empty, null);
		}

		/// <summary>
		/// This method checks whether the signature signed all the necessary parts for the soap message.
		/// such as soap:Body, all the addressing headers and timestamp.
		/// </summary>
		/// <param name="context">A SoapContext object</param>
		/// <param name="signature">A MessageSignature that signed the SoapContext object</param>
		/// <returns>True or false</returns>
		private static Boolean CheckSignature(SoapContext context, MessageSignature signature)
		{
			//
			// Now verify which parts of the message were actually signed.
			//
			SignatureOptions actualOptions   = signature.SignatureOptions;
			SignatureOptions expectedOptions = SignatureOptions.IncludeSoapBody;
          
			if (context.Security != null && context.Security.Timestamp != null ) 
				expectedOptions |= SignatureOptions.IncludeTimestamp;
            
			//
			// The <Action> and <To> are required addressing elements.
			//
			expectedOptions |= SignatureOptions.IncludeAction;
			expectedOptions |= SignatureOptions.IncludeTo;

			if ( context.Addressing.FaultTo != null && context.Addressing.FaultTo.TargetElement != null )
				expectedOptions |= SignatureOptions.IncludeFaultTo;

			if ( context.Addressing.From != null && context.Addressing.From.TargetElement != null )
				expectedOptions |= SignatureOptions.IncludeFrom;

			if ( context.Addressing.MessageID != null && context.Addressing.MessageID.TargetElement != null )
				expectedOptions |= SignatureOptions.IncludeMessageId;

			if ( context.Addressing.RelatesTo != null && context.Addressing.RelatesTo.TargetElement != null )
				expectedOptions |= SignatureOptions.IncludeRelatesTo;

			if ( context.Addressing.ReplyTo != null && context.Addressing.ReplyTo.TargetElement != null )
				expectedOptions |= SignatureOptions.IncludeReplyTo;
			//
			// Check if the all the expected options are the present.
			//
			return ( ( expectedOptions & actualOptions ) == expectedOptions );
                
		}

		/// <summary>
		/// This method retrieves the X509SecurityToken with the keyIdentifier from X509Certificate store.
		/// </summary>
		/// <param name="store">The X509CertificateStore where key is retrieved from</param>
		/// <param name="keyIdentifier">key Identifier</param>
		/// <returns>A X509SecurityToken object</returns>
		private static X509SecurityToken RetrieveTokenFromStore(X509CertificateStore store, String keyIdentifier) 
		{
			if ( store == null )
				throw new ArgumentNullException("store");
            
			X509SecurityToken token = null;

			try 
			{
				if( store.OpenRead() ) 
				{
					// Retrieve the X509 certificate from the certificate store
					X509CertificateCollection certs = store.FindCertificateByKeyIdentifier( Convert.FromBase64String( keyIdentifier ) );

					if (certs.Count > 0)
					{
						// Get the first certificate in the collection
						token = new X509SecurityToken( ((X509Certificate) certs[0]) );
					}        
				} 
			}
			finally
			{
				if ( store != null )
					store.Close();
			}
  
			return token;
		}

		#endregion private utility methods & constructors

		#region public static methods related to SecurityToken

		/// <summary>
		/// This method returns the signing token.  It also checks whether the signatue is properly signed.
		/// </summary>
		/// <param name="context">The SoapContext object where signing token is retrieved</param>
		/// <returns>The signing token</returns>
		public static SecurityToken GetSigningToken(SoapContext context)
		{
			if ( context == null )
				throw new ArgumentNullException("context");

			foreach ( ISecurityElement element in context.Security.Elements )
			{
				if ( element is MessageSignature )
				{
					// The given context contains a Signature element.
					MessageSignature sig = element as MessageSignature;

					if (CheckSignature(context, sig))
					{
						// The SOAP Body is signed.
						return sig.SigningToken;
					}
				}
			}

			return null;
		}

		/// <summary>
		/// This method checks if the incoming message has encrypted the soap message body 
		/// </summary>
		/// <param name="context">The soap context to search for</param>
		/// <returns>The encrypting token</returns>
		public static SecurityToken GetEncryptingToken(SoapContext context)
		{
			if ( context == null )
				throw new ArgumentNullException("context");

			SecurityToken encryptingToken = null;

			foreach (ISecurityElement element in context.Security.Elements)
			{
				if (element is EncryptedData)
				{
					EncryptedData encryptedData = element as EncryptedData;
					System.Xml.XmlElement targetElement = encryptedData.TargetElement;										
							
					if ( SoapEnvelope.IsSoapBody(targetElement) )
					{
						// The given context has the Body element Encrypted.
						encryptingToken = encryptedData.SecurityToken;
					}
				}
			}

			return encryptingToken;
		}

		/// <summary>
		/// Return the client certificate usually used for signing the message by the client.
		/// </summary>
		/// <returns>X509SecurityToken found for client</returns>
		public static X509SecurityToken GetClientToken()
		{
			X509SecurityToken token = null;

			// Open the CurrentUser Certificate Store and try MyStore only
			X509CertificateStore store = X509CertificateStore.CurrentUserStore( X509CertificateStore.MyStore );
            
			token = RetrieveTokenFromStore(store, m_clientBase64KeyId);

			return token;
		}

		/// <summary>
		/// Return the server certificate.  For encrypting the message by the client,
		/// or for signing the message by the server.
		/// </summary>
		/// <param name="isClient">A flag indicating whether client is retrieving the X.509 certificate</param>
		/// <returns>X509SecurityToken found for server</returns>
		public static X509SecurityToken GetServerToken(Boolean isClient)
		{
			X509SecurityToken token = null;
			X509CertificateStore store = null;

			if (isClient)
			{
				// Open the CurrentUser Certificate Store and try OtherPeople first
				store = X509CertificateStore.CurrentUserStore( X509CertificateStore.OtherPeople );
			}
			else
			{
				// For server, open the LocalMachine Certificate Store and try Personal store.
				store = X509CertificateStore.LocalMachineStore( X509CertificateStore.MyStore );
			}

			token = RetrieveTokenFromStore(store, m_serverBase64KeyId);
           
			//
			// If we failed to retrieve it from the OtherPeople,
			// we now try the MyStore
			//
			if ( token == null )
			{
				if (isClient)
				{
					store = X509CertificateStore.CurrentUserStore( X509CertificateStore.MyStore );
					token = RetrieveTokenFromStore(store, m_serverBase64KeyId);
				}
				else
				{
					// For server the server Certificate should be installed in the Personal 
					// store. We tried that store and either we were unable to open the store
					// or the certificate does not exist. So we don't have a fallback for this
					// case.
				}
			}

			return token;
		}

		#endregion public static methods related to SecurityToken

		#region public static methods related to WS-SecureConversation

		/// <summary>
		/// Return a security context token based on username and password.  This function looks
		/// for a valid token in the local cache before automatically requesting a new one.
		/// </summary>
		/// <param name="username">user name</param>
		/// <param name="password">password</param>
		/// <returns>A SecurityContextToken object</returns>
		public static SecurityContextToken RequestSCTByUsername(String username, String password)
		{
			SecurityContextToken sct = null;
			
			// Request a new security context token if one was not available from m_sctData
			if (m_sctData.m_username == string.Empty || m_sctData.m_password == string.Empty || 
				m_sctData.m_username != username || m_sctData.m_password != password || m_sctData.m_sct.IsExpired)
			{
				// Create a UsernameToken to use as the base for the security context token
				SecurityToken token = new UsernameToken(username, password, PasswordOption.SendPlainText);
			
				// Retrieve the server certificate
				SecurityToken issuerToken = GetServerToken(true);
				
				// Create a SecurityContextTokenServiceClient (STSClient) that will get the SecurityContextToken
				String secureConvEndpoint = ConfigurationSettings.AppSettings["tokenIssuer"];
				SecurityContextTokenServiceClient STSClient = new SecurityContextTokenServiceClient(new Uri(secureConvEndpoint));
					
				// Request the security context token, use the client's signing token as the base
				sct = STSClient.IssueSecurityContextTokenAuthenticated(token, issuerToken);
				
				// Cache the security context token in m_sctData
				m_sctData = new SctData(username, password, sct);
			}

			return m_sctData.m_sct;
		}

		#endregion public static methods related to WS-SecureConversation
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United States United States
Weidong has been an information system professional since 1990. He has a Master's degree in Computer Science, and is currently a MCSD .NET

Comments and Discussions