Click here to Skip to main content
15,884,473 members
Articles / Web Development / ASP.NET

Secure Web Services via Message oriented Middleware

Rate me:
Please Sign up or sign in to vote.
4.96/5 (43 votes)
1 May 200372 min read 336.7K   821   198  
Describes an approach for delivery of Soap Messages serialised using ASP.NET Web Client Services over MSMQ and MQ
// --------------------------------------------------------------------------------
// Module:      WSCommon.cs
// Author:      SGregory
// Date:        09 March 2002
// Description:	Common routines to support the Web Service methods.
//
// --------------------------------------------------------------------------------
// $Archive: <VSS Path To File>/WSCommon.cs $
// $Revision: 1 $ changed on $Date: 09 March 2002 17:35 $
// Last changed by $Author: SGregory $
// --------------------------------------------------------------------------------
using System;
using System.Xml;							// For building the fault information
using System.Web;							// For IIS Context
using System.Net;
using System.Net.Sockets;					// For reverse DNS lookup on IP Address
using System.Text;							// For StringBuilder access
using System.Web.Services;					// Web Services exposure
using System.Web.Services.Protocols;		// SOAP exceptions
using System.Security.Cryptography;			// Managed Crypto functions
using System.Security.Cryptography.Xml;		// KeyInfo
using Microsoft.Web.Services;
using Microsoft.Web.Services.Security;		// For WS-Security support

// --------------------------------------------------------------------------------
// Namespace:   WSAltRouteWSE
// Author:      sgregory
// Date:        25 February 2002
// Description: 
// --------------------------------------------------------------------------------
namespace WSCommon
{
	/// <author>sgregory</author>
    /// <date>01 May 2002</date>
    /// <summary>This class contains helpful methods for use by the Production and Test Web Services classes</summary>
	internal class WSCommon : ContextBoundObject
	{
		/// <summary>Eqos.RegistrationService.Presentation.WebServiceFacade.WSCommon.BuildContextArray</summary>
        /// <author>Simon G</author>
        /// <date>19 September 2002</date>
        /// <remarks>
        /// Pulls the interesting things out of the IIS HTTP Context.
        /// This includes:
        ///		Client Dns Name
        ///		Client IP Address
        ///		Browser Platform
        ///		Browser
        ///		User Identity
        ///		User Is Auth'd flag
        /// </remarks>
        /// <param name="vobjContext" type="HttpContext">HTTP Context information</param>
        /// <returns type="string[]">Array with interesting stuff in</returns>
        static public string[] BuildContextArray(
			HttpContext vobjContext)
		{
			string[] arrContext = new string[6];
			arrContext[0] = vobjContext.Request.UserHostName;
			arrContext[1] = vobjContext.Request.UserHostAddress;
			// Attempt to resolve address ourselves using reverse Dns lookup
			// if IIS did not (this may take a few seconds)
			if (arrContext[0] == arrContext[1])
				arrContext[0] = ResolveIPAddressToHostName(arrContext[1]);
			arrContext[2] = vobjContext.Request.Browser.Browser + vobjContext.Request.Browser.Version;
			arrContext[3] = vobjContext.Request.Browser.Platform;
			arrContext[4] = vobjContext.User.Identity.Name;
			if (arrContext[4].Trim().Length == 0)
				arrContext[4] = "Anonymous";
			arrContext[5] = vobjContext.User.Identity.IsAuthenticated.ToString();

			return arrContext;
		}


		/// <summary>Eqos.RegistrationService.Presentation.WebServiceFacade.WSCommon.BuildContextArrayAsXML</summary>
        /// <author>sgregory</author>
        /// <date>25 September 2002</date>
        /// <remarks>Builds an XML blob representing the Client Context</remarks>
        /// <param name="vobjContext" type="System.Web.HttpContext">IIS Context</param>
        /// <returns type="string">
        /// Context as XML in the form:
        ///		<ClientContext Machine="humphrey.eqos.com" IP="100.100.100.115" Browser="IE6.0" Platform="Unknown" Identity="Anonymous" Authenticated="False"/>
        /// </returns>
        /// <preconditions></preconditions>
        /// <postconditions></postconditions>
        static public string BuildContextArrayAsXML(
			HttpContext vobjContext)
		{
			// Get the information as an array
			string[] arrContext = BuildContextArray(vobjContext);

			// Now dirtily build an XML piece representing the Client
			// Context details
			StringBuilder objSBContext = new StringBuilder(256);
			objSBContext.Append("<ClientContext Machine=\"");
			objSBContext.Append(arrContext[0]);
			objSBContext.Append("\" IP=\"");
			objSBContext.Append(arrContext[1]);
			objSBContext.Append("\" Browser=\"");
			objSBContext.Append(arrContext[2]);
			objSBContext.Append("\" Platform=\"");
			objSBContext.Append(arrContext[3]);
			objSBContext.Append("\" Identity=\"");
			objSBContext.Append(arrContext[4]);
			objSBContext.Append("\" Authenticated=\"");
			objSBContext.Append(arrContext[5]);
			objSBContext.Append("\"/>");

			return objSBContext.ToString();
		}


		/// <author>SGregory</author>
		/// <date>18 February 2002</date>
		/// <summary>Get Host Name from IP Address using DNS</summary>
		/// <remarks>
		/// A quick routines to take an IP and attempt to get a Hostname
		///	Note this may take several seconds to resolve, which will impact
		///	the overall response time of the Registration Web Service.
		/// </remarks>
		/// <param name="vstrIPAddress" type="string">IP Address to convert</param>
		/// <returns type="string">Host name, or Unknown</returns>
		/// <preconditions>IP Address valid</preconditions>
		/// <postconditions>The string is returned to be embedded in an EMail / SMS message</postconditions>
		static public string ResolveIPAddressToHostName(
			string vstrIPAddress)
		{
			string strResolvedHostName = "Unknown";

			try
			{
				// Ask DNS for an answer to the question
				IPHostEntry hostInfo = Dns.GetHostByAddress(vstrIPAddress);
				// and set up the return
				strResolvedHostName = hostInfo.HostName;
			}
			catch(Exception /*e*/)
			{
				// Exception while performing reverse-DNS Lookup - don't log it
				// strResolvedHostName = "***Exception caught in ResolveIPAddressToHostName() - " + e.Message;
			}

			return strResolvedHostName;
		}



		/// <summary>Eqos.RegistrationService.Presentation.WebServiceFacade.WSCommon.ValidateCredentials</summary>
        /// <author>Simon G</author>
        /// <date>16 October 2002</date>
        /// <remarks>Checks that there is a valid Security token</remarks>
        /// <param name="vRequestContext" type="Microsoft.WSE.SoapContext">SOAP Request Context</param>
        /// <postconditions>Return OK, or an exception will be thrown to the caller</postconditions>
        static public void ValidateCredentials(
			SoapContext vRequestContext)
		{
			StringBuilder objSBDiagnostics = new StringBuilder(512);
			objSBDiagnostics.Append("SecCtx Info: ");

			try
			{
				// Valid context?
				if (vRequestContext == null)
				{
					objSBDiagnostics.Append("null");
					throw new SoapException(
						SEC_INFO_FAIL_MSG,
						new System.Xml.XmlQualifiedName("Bad.Config", "http://eqos.com/webservices/EqosNameService"));
				}

				// If so, let's examine the Request Context
				// Build a diagnostic block first...
				objSBDiagnostics.Append("\nSecTokens: ");
				objSBDiagnostics.Append(vRequestContext.Security.Tokens.Count);
				objSBDiagnostics.Append("\n");
				foreach (SecurityToken secT in vRequestContext.Security.Tokens)
				{
					objSBDiagnostics.Append("  Token[");
					objSBDiagnostics.Append(secT.Id);
					objSBDiagnostics.Append("] = ");
					objSBDiagnostics.Append(secT.ToString());
					objSBDiagnostics.Append("\n");
				}

				objSBDiagnostics.Append("Elements: ");
				objSBDiagnostics.Append(vRequestContext.Security.Elements.Count);
				objSBDiagnostics.Append("\n");
				foreach (object elT in vRequestContext.Security.Elements)
				{
					if (elT is Microsoft.Web.Services.Security.Signature)
					{
						objSBDiagnostics.Append("  Token (Signature)[");
						objSBDiagnostics.Append(((Microsoft.Web.Services.Security.Signature)elT).SecurityToken.Id);
						objSBDiagnostics.Append("] = ");
						objSBDiagnostics.Append(((Microsoft.Web.Services.Security.Signature)elT).SecurityToken.ToString());
						objSBDiagnostics.Append("  [");
						objSBDiagnostics.Append(((Microsoft.Web.Services.Security.Signature)elT).SignatureOptions.ToString());
						objSBDiagnostics.Append("]\n");
					}
					else
						if (elT is EncryptedData)
					{
						objSBDiagnostics.Append("  Token (EncryptedData)[");
						objSBDiagnostics.Append(((EncryptedData)elT).ToString());
						objSBDiagnostics.Append("]\n");
					}
				}

				objSBDiagnostics.Append("Attachments: ");
				objSBDiagnostics.Append(vRequestContext.Attachments.Count);
				objSBDiagnostics.Append("\nExtendedSec: ");
				objSBDiagnostics.Append(vRequestContext.ExtendedSecurity.Count);

				// Log it
//				Logger.Log(
//					Logger.ENV_UNKNOWN, 
//					Logger.USER_UNKNOWN, 
//					"Eqos Registration Service", 
//					"RegDotNetWS.WSCommon", 
//					"SOAP Client Credentials:\n" + objSBDiagnostics.ToString(), 
//					ELogSeverity.elsInfo,
//					null,
//					null);

				// No tokens means that the message can be rejected quickly
				if (vRequestContext.Security.Tokens.Count == 0)
					throw new SoapException(
						SEC_INFO_FAIL_MSG,
						new System.Xml.XmlQualifiedName("Bad.Tokens", "http://eqos.com/webservices/EqosNameService"));

				// Check for a Signature that signed the soap Body and uses a token that we accept.
				bool blnValid = false;
				for (int i = 0; blnValid == false && i < vRequestContext.Security.Elements.Count; i++)
				{
					Microsoft.Web.Services.Security.Signature signature = vRequestContext.Security.Elements[i] as Microsoft.Web.Services.Security.Signature;

					// We only care about signatures that signed the soap Body
					if (signature != null && 
						(signature.SignatureOptions & SignatureOptions.IncludeSoapBody) != 0 )
					{
						// Found it - fish it out
						UsernameToken usernameToken = signature.SecurityToken as UsernameToken;

						// Check we have one
						if (usernameToken != null)
						{
							// Let's check it's numeric - as we know our User Name should be
							string strUserName = usernameToken.Username;
							try
							{
								int intDummy = Convert.ToInt32(strUserName);
							}
							catch
							{
								throw new SoapException(
									SEC_INFO_FAIL_MSG,
									new System.Xml.XmlQualifiedName("Bad.UserCreds", "http://eqos.com/webservices/EqosNameService"));
							}

							// Add more checks as we require them
							// .....
						}
					}
				}
			}
			catch(Exception e)
			{
				// All WSE-based validation issues get logged here
//				Logger.Log(
//					Logger.ENV_UNKNOWN, 
//					Logger.USER_UNKNOWN, 
//					"Eqos Registration Service", 
//					"RegDotNetWS.WSCommon", 
//					"An exception was caught while processing Security Context information for the call to the Registration.NET Web Service.", 
//					ELogSeverity.elsHigh, 
//					objSBDiagnostics.ToString(), 
//					e);

				// Throw up to our caller
				throw(e);
			}
		}


		// WSE specific stuff

		/// <author>SGregory</author>
		/// <date>18 February 2002</date>
		/// <summary>SetupSOAPRequestContext</summary>
		/// <remarks>Sets up the WSDL Context from which the SOAP header is populated before calling the Web Service</remarks>
		/// <param name="vobjSoapContext" type="SoapContext">SOAP Context into which we can feed the user credentials and expiry information</param>
		///	<preconditions>Valid SoapContext</preconditions>
		///	<postconditions>SoapContext augmented with time-to-live, username etc.</postconditions>
		static public void SetupSOAPRequestContext(
			SoapContext vobjSoapContext)
		{
			// Get a symmetric encryption key to encrypt the body with
			EncryptionKey encKey = GetEncryptionKey();

			// Get the ms since machine switch on as string - we will use this as a User Name
			// since it changes every call, and we do not care about a real user of the service
			// per se.
			string strUserName = Environment.TickCount.ToString();

			// Create a User Token from a tick count and static User Name
			UsernameToken userToken = new UsernameToken(strUserName,
				CalculatePasswordForProxyUser(strUserName),
				PasswordOption.SendHashed);

			// Stick it in the SOAP Context
			vobjSoapContext.Security.Tokens.Add(userToken);

			// Request a signature for the message (based on the User Token)
			vobjSoapContext.Security.Elements.Add(new Microsoft.Web.Services.Security.Signature(userToken));

			// Request the SOAP body be encrypted (with a different symmetric key)
			vobjSoapContext.Security.Elements.Add(new Microsoft.Web.Services.Security.EncryptedData(encKey));

			// Set an expiry on this SOAP message too
			vobjSoapContext.Timestamp.Ttl = SOAP_MSG_EXPIRY_TIME;
		}


		/// <author>SGregory</author>
		/// <date>18 February 2002</date>
		/// <summary>CalculatePasswordForProxyUser</summary>
		/// <remarks>Takes the User Name, and tick count</remarks>
		/// <param name="vstrUserName" type="string">User Name to pass in SOAP message</param>
		///	<postconditions>
		///	String representing a unique 'password' - it may not look secure but it's about
		///	to be hashed to hell before being put in the SOAP message
		///	</postconditions>
		static private string CalculatePasswordForProxyUser(
			string vstrUserName)
		{
			StringBuilder objSB = new StringBuilder(128);
			objSB.Append(vstrUserName);
			objSB.Append(PROXY_USER_NAME);
			return objSB.ToString();
		}


		/// <summary>RegDotNetAccess.Wrapper.GetEncryptionKey</summary>
		/// <author>Simon G</author>
		/// <date>16 October 2002</date>
		/// <remarks>
		/// Returns a Symmtric encryption key for use when encrypting
		/// the outbound SOAP body.  The key is used both by this Client
		/// Proxy Wrapper (RegDotNetWS) and the target Registration.NET
		/// Web Service.
		/// </remarks>
		/// <returns type="Microsoft.Web.Services.Security.EncryptionKey">Symmetric key for encrypting data</returns>
		/// <postconditions>Symmetric key generated using TripleDESCryptoServiceProvider</postconditions>
		static private EncryptionKey GetEncryptionKey()
		{            
			// Creates an instance of a class deriving from
			// SymmetricAlgorithm. The TripleDESCryptoServiceProvider provides
			// an implenentation of the Triple DES algorithm.
			SymmetricAlgorithm algo = new TripleDESCryptoServiceProvider();

			// Defines the 128-bit secret key shared by the XML Web service
			// and this client.
			byte[] keyBytes = { 30, 8, 67, 16, 12, 68, 26, 12, 
								  98, 27, 5, 20, 01, 15, 2, 69 };
			// Defines the initialization vector for the algorithm.
			byte[] ivBytes =  { 9, 8, 1, 6, 2, 2, 9 };

			// Sets the 128-bit secret key for the Triple DES algorithm.
			algo.Key = keyBytes;

			// Sets the initialization vector for the Triple DES algoritm.
			algo.IV = ivBytes;

			// Creates a key that will be used by the WSE to encrypt the
			// outbound SOAP message.
			EncryptionKey key = new SymmetricEncryptionKey(algo);

			//
			// Finally, we must build the KeyInfo for the key. For this sample, we use
			// a simple name for the key.
			KeyInfoName keyName = new KeyInfoName();
			keyName.Value  = "WSAltRouteWSE KeyXfer";
			key.KeyInfo.AddClause(keyName);

			// Give back to WSE builder
			return key;
		}


		// Constants
		private const string SEC_INFO_FAIL_MSG = "The security information supplied was not valid.";
		// WSE specific support
		internal const string PROXY_USER_NAME = "SecureWSAltRoute";	// User Credentials
		internal const int SOAP_MSG_EXPIRY_TIME = 60000;				// 60s
	}
}

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
Web Developer
United Kingdom United Kingdom
I am the Technical Director for Myphones.com, which specialises in developing integrated VoIP products and services for the consumer and SME markets. Technology-wise, we are heavily into the 2nd generation WS stack atop .NET, and basically most things Microsoft, as well as C++ on Linux.

Comments and Discussions