Click here to Skip to main content
15,895,011 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.3K   776   38  
Adding secure communications to the Microsoft IssueVision sample application using WSE 2.0.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Web.Services.Protocols;
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Security;
using Microsoft.Web.Services2.Security.Tokens;

namespace IssueVisionWebWseCS
{
	/// <summary>
	/// Summary description for Wse2HelperServer.
	/// </summary>
	public sealed class Wse2HelperServer
	{
		#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 Wse2HelperServer()"
		private Wse2HelperServer() {}

		/// <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 );
                
		}

		#endregion private utility methods & constructors

		#region public static methods related to SOAP message verification

		/// <summary>
		/// This method checks if those required message parts exist.
		/// The required headers are Soap:body, To, Action, MessageID, and Timestamp
		/// </summary>
		/// <param name="context">The SoapContext object being verified</param>
		public static void VerifyMessageParts(SoapContext context) 
		{
			if ( context == null )
				throw new ArgumentNullException("context");

			// Body
			if ( context.Envelope.Body == null )
				throw new SoapException("The message must contain a soap:Body element", SoapException.ServerFaultCode, "Security");

			if ( context.Addressing.To == null || context.Addressing.To.TargetElement == null )
				throw new SoapException("The message must contain a wsa:To header", SoapException.ServerFaultCode, "Security");

			if ( context.Addressing.Action == null || context.Addressing.Action.TargetElement == null )
				throw new SoapException("The message must contain a wsa:Action header", SoapException.ServerFaultCode, "Security");

			if ( context.Addressing.MessageID == null || context.Addressing.MessageID.TargetElement == null )
				throw new SoapException("The message must contain a wsa:To header", SoapException.ServerFaultCode, "Security");

			if ( context.Security.Timestamp == null  )
				throw new SoapException("The message must contain a wsu:Timestamp header", SoapException.ServerFaultCode, "Security");
		}

		/// <summary>
		/// This method checks whether the message contains a required signature.
		/// </summary>
		/// <param name="context">The SoapContext object being verified</param>
		public static void VerifyMessageSignature(SoapContext context) 
		{
			if ( context == null )
				throw new ArgumentNullException("context");

			foreach ( ISecurityElement element in context.Security.Elements )
			{
				if ( element is MessageSignature ) return;
			}

			throw new SoapException("The message must contain a signature", SoapException.ServerFaultCode, "Security");
		}

		/// <summary>
		/// This method checks whether the message is encrypted.
		/// </summary>
		/// <param name="context">The SoapContext object being verified</param>
		public static void VerifyMessageEncryption(SoapContext context) 
		{
			if ( context == null )
				throw new ArgumentNullException("context");

			foreach ( ISecurityElement element in context.Security.Elements )
			{
				if ( element is EncryptedData ) return;
			}

			throw new SoapException("The message must be encrypted", SoapException.ServerFaultCode, "Security");
		}

		#endregion public static methods related to SOAP message verification

		#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;
		}

		#endregion public static methods related to SecurityToken
	}

	/// <summary>
	/// By implementing UsernameTokenManager we can verify the signature
	/// on messages received.
	/// </summary>
	/// <remarks>
	/// This class includes this demand to ensure that any untrusted
	/// assemblies cannot invoke this code. This helps mitigate
	/// brute-force discovery attacks.
	/// </remarks>
	[SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)]
	public class CustomUsernameTokenManager : UsernameTokenManager
	{
		/// <summary>
		/// Returns the password or password equivalent for the username provided.
		/// </summary>
		/// <param name="token">The username token</param>
		/// <returns>The password (or password equivalent) for the username</returns>
		protected override String AuthenticateToken( UsernameToken token )
		{
			// This method returns the password for the provided username
			// WSE will make the determination if they match
			DataSet dataSet = new DataSet();
			string dbPasswordHash;

			try 
			{
				SqlConnection conn = new SqlConnection(Common.ConnectionString);
				SqlCommand cmd = new SqlCommand("GetUser", conn);
				cmd.Parameters.Add("@UserName", token.Username);
				cmd.CommandType = CommandType.StoredProcedure;
				SqlDataAdapter da = new SqlDataAdapter(cmd);
				da.Fill(dataSet);
			} 
			catch (Exception ex) 
			{
				EventLogHelper.LogFailureAudit(string.Format("The GetUser stored procedure encounted a problem: {0}", ex.ToString()));
				throw new SoapException(string.Empty, SoapException.ServerFaultCode, "Database");
			}

			// does the user exist?
			if (dataSet.Tables[0].Rows.Count == 0) 
			{
				EventLogHelper.LogFailureAudit(string.Format("The username {0} does not exist.", token.Username));
				throw new SoapException(string.Empty, SoapException.ClientFaultCode, "Security");
			} 
			else 
			{
				// we found the user, verify the password hash by compare the Salt + PasswordHash
				DataRow dataRow = dataSet.Tables[0].Rows[0];
				dbPasswordHash = (string)dataRow["PasswordHash"];
				string dbPasswordSalt = (string)dataRow["PasswordSalt"];

				// create a hash based on the user's salt and the input password
				string passwordHash = HashString(dbPasswordSalt + token.Password);

				// does the computed hash match the database hash?
				if (string.Compare(dbPasswordHash, passwordHash) != 0)
				{
					EventLogHelper.LogFailureAudit(string.Format("The password for the username {0} was incorrect.", token.Username));
					throw new SoapException(string.Empty, SoapException.ClientFaultCode, "Security");
				}
				else
				{
					return token.Password;
				}
			}
		}

		// generates a hash of the input plain text
		private static string HashString(string textToHash) 
		{
			SHA1CryptoServiceProvider SHA1 = new SHA1CryptoServiceProvider();
			byte[] byteValue = System.Text.Encoding.UTF8.GetBytes(textToHash);
			byte[] byteHash = SHA1.ComputeHash(byteValue);
			SHA1.Clear();

			return Convert.ToBase64String(byteHash);
		}
	}
}

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