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