Colabora .NET |
Encriptar mensajes SOAPManejar extensiones SOAP de .NET
Fecha: 31/Oct/2006 (24/10/2006)
|
IntroducciónEn este artículo se trata la forma de acceder al mensaje Soap enviado y recibido por un servicio web desarrollado en ASP .NET y como podemos encriptar dicho mensaje, total o parcialmente, para crear una comunicación segura. Ciclo de vida de un mensaje Soap.Cuando se hace una llamada a un servicio web .NET genera un objeto SoapMessage que pasa por diferentes estados a lo largo de la comunicación entre el cliente que consume el servicio y el servidor donde está publicado dicho servicio. Cada uno de estos estados está definido por la propiedad Stage del mensaje:
Utilizando extensiones Soap de .NET.Para poder acceder al buffer de memoria donde .NET almacena el mensaje Soap enviado y recibido por un servicio web se encuentran a nuestra disposición dentro del Framework de .NET las denominadas extensiones Soap. Para crear nuestra propia extensión Soap simplemente deberemos crear una clase que derive de SoapExtension sobeescribiendo una serie de métodos abstractos. Relacionada directamente con estas extensiones existe la clase SoapExtensionAttribute . Para poder utilizarla deberemos crear igualmente una clase derivada. Esta extensión de atributo va a ser la que diga a nuestro servicio web la extensión Soap asociada que debe ejecutar. El Código.Lo primero que hacemos es definir un par de enumerados que nos van a permitir personalizar si queremos encriptar la solicitud del cliente o la respuesta del servidor y que parte del mensaje Soap generado tras la serialización queremos procesar en cada caso. // Lista de los tipos mensajes que intervienen en la comunicación. public enum EncryptMode { None = 0, Request = 1, Response = 2, Message = 3 } // Lista de las distintas partes del mensaje SOAP que podemos enciptar public enum Target { Header = 1, Body = 2, Envelope = 3 } Para generar nuesta propia extensión de atributo Soap creamos una clase derivada de SoapExtensionAttribute. [AttributeUsage(AttributeTargets.Method)] public class SoapEncryptionExtensionAttribute : SoapExtensionAttribute { } Nota: Dentro de esta clase debemos sobreescribir obligatoriamente dos propiedades abstractas: Priority y TypeExtension. La primera indica la prioridad en la que debe ejecutarse la extensión Soap asociada al atributo con respecto a otras extensiones configuradas para el mismo método de nuestro servicio web. La segunda y mas importante es una propiedad de solo lectura y es la que le indica al servicio web que extensión Soap debe ejecutar. /// <summary> /// Prioridad de la extensión SOAP. /// </summary> private int m_priority = 0; public override int Priority { get { return m_priority; } set { m_priority = value; } } /// <summary> /// Declaración del tipo de la extensión SOAP que queremos /// que se ejecute junto con nuestro servicio web. /// </summary> public override Type ExtensionType { get { return typeof(SoapEncryptionExtension); } } /// <summary> /// Parte de la comunicación que se desea a encriptar. /// </summary> private EncryptMode m_encrypt; public EncryptMode Encrypt { get { return m_encrypt; } set { m_encrypt = value; } } Es muy importante tener en cuenta que siempre que generemos una clase derivada de SoapExtensionAttribute deberan aparecer las dos propiedades descritas anteriormente. En nuestro caso particular también deberemos definir dos propiedades mas, una por cada enumerado. Una vez definida nuestra extensión de atributo debemos generar la propia extensión Soap que es la que va a llevar toda la lógica del proceso. Para ello creamos una clase derivada de SoapExtension. public class SoapEncryptionExtension : SoapExtension { } Siempre que generemos una clase derivada de SoapExtension existen varios métodos abstractos que debemos sobreescribir.
/// <summary> /// Este método se llama la primera vez que el servicio web accede a la extensión SOAP. /// Almacena en Caché el objeto devuelto. /// </summary> /// <param name="methodInfo"></param> /// <param name="attribute"></param> public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute) { return (SoapEncryptionExtensionAttribute)attribute; } /// <summary> /// Este método se llama la primera vaz que el servicio web accede a la extensión SOAP. /// </summary> /// <param name="initializer"> /// Objeto almacenado en Caché tras la llamada a la función GetInitializer /// </param> public override void Initialize(object initializer) { SoapEncryptionExtensionAttribute attribute = (SoapEncryptionExtensionAttribute)initializer; m_logSOAPMessage = attribute.LogSOAPMessage; m_logFileName = attribute.LogFileName; m_encrypt = attribute.Encrypt; m_SOAPTarget = attribute.SOAPTarget; } /// <summary> /// Permite acceder al buffer de memoria que contiene la solicitud o el mensaje SOAP. /// </summary> /// <param name="stream">Stream que contiene el mensaje SOAP serializado /// en el campo BeforeDeserialize de SoapMessage. /// <returns>Stream que contiene el mensaje SOAP serializado /// en el campo AfterSerialize de SoapMessage.</param> /// <returns></returns> public override System.IO.Stream ChainStream(System.IO.Stream stream) { m_oldStream = stream; m_newStream = new MemoryStream(); return m_newStream; } /// <summary> /// Procesa el mensaje SOAP en cada una de sus distintas fases. /// </summary> /// <param name="message">Mensaje SOAP.</param> public override void ProcessMessage(SoapMessage message) { try { switch(message.Stage) { case SoapMessageStage.BeforeSerialize: break; case SoapMessageStage.AfterSerialize: EncryptSoap(message); break; case SoapMessageStage.BeforeDeserialize: DecryptSoap(message); break; case SoapMessageStage.AfterDeserialize: break; default: throw new Exception("Estado de mensaje SOAP no válido."); } } catch(Exception ex) { System.Diagnostics.Trace.WriteLine(ex.Message); } } Cuando se realiza una llamada al método del servicio web configurado con nuestra extensión Soap .NET llama a la función GetInitializer de la clase que devuelve un objeto de tipo SoapEncryptionExtensionAttribute cuyo estado se almacena en Caché. Inmediatamente despues .NET llama al método Initializer pasando como parámetro el objeto almacenado en Caché, el cual nos sirve para inicializar las variables necesarias en el proceso. El método ProcessMessage se utiliza para poder detectar los distintos estados por los que va a pasar el mensaje Soap dentro del proceso de comunicación. Para saber el estado actual del mensaje accedemos a la propiedad Stage del objeto SoapMessage recibido como parámetro. Dependiendo si el servicio web se ha configurado del lado del cliente o del lado del servidor el parámetro recibido podrá almacenarse en un objeto del tipo SoapClientMessage o SoapServerMessage respectivamente. Tanto para mensajes del lado del cliente como del servidor haremos la llamada al método que encripta cuando el mensaje se haya serializado (Afterserialize) y lo desencriptaremos justo antes de que vuelva a serializarse (BeforeDeserialize). Para poder modificar el mensaje Soap inicializamos un objeto XmlDocument con un stream que va a contener, según corresponda, la solicitud o la respuesta Soap. Dependiendo de que parte del mensaje queramos encriptar accderemos a un nodo en concreto utilizando una consulta xpath que pasamos como parámetro a la función SelecySingleNode definida para el objeto XmlDocument. El texto al que le vamos a aplicar el algoritmo será el valor devuelto por la propiedad InnerXml del objeto XmlNode que retorna esta función. public CryptoSoap(CryptoSoapDelegate cryptoSoapDelegate, string xpath, Stream stream) { try { // inicializo vsariables System.Xml.XmlDocument xdoc = new System.Xml.XmlDocument();; System.Xml.XmlNamespaceManager nsmgr = null; System.Xml.XmlNode xnode = null; // cargo el mensaje soap en un xml stream.Position = 0; xdoc.Load(stream); // agrego los namespaces nsmgr = new System.Xml.XmlNamespaceManager(xdoc.NameTable); nsmgr.AddNamespace("soap", @"http://schemas.xmlsoap.org/soap/envelope/"); // accedo al nodo del mensaje soap que voy a tratar xnode = xdoc.SelectSingleNode(xpath, nsmgr); if(xnode != null) xnode.InnerXml = cryptoSoapDelegate(xnode.InnerXml); m_newStream.Position = 0; xdoc.Save(m_newStream); m_newStream.Position = 0; } catch(Exception ex) { encryptedText = ex.Message; } } Para realizar la encriptación y desencriptación del mensaje utilizamos un algoritmo simétrico o de clave privada. Esto es debido a que es posible que podamos trabajar con cantidades grandes de datos y los algoritmos asimétricos o de clave pública son mas recomendables cuando se trabaja con pequeñas cantidades. El principal inconveniente que tienen este tipo de algoritmos de clave pública es que las dos partes que intervienen en el proceso de comunicación deben acordar previamente unos valores de clave privada y de vector de inicialización. En el ejemplo que estamos viendo se almacena la clava privada y el VI en dos variables para simplificar el proceso. Una práctica que se recomienda en estos casos es trabajar con una mezcla de algoritmo simétrico y asimétrico. En primera instancia el emisor del mensaje genera una clave pública utilizando el cifrado asimétrico que es enviada al receptor. Seguidamente este último genera una clave privada y un VI con un cifrado simétrico, los cuales son enviados al emisor en un mensaje cifrado mediante la clave pública recibida en primera instancia. De esta forma las dos partes que participan en la comunicación serán conoceras de la clave privada y el VI necesarios para trabajar con algoritmo simétrico y esta clave será diferente en cada comunicación. public string EncryptTextNode(string text) { string encryptedText = string.Empty; try { // creo un proveedor criptográfico para el algoritmo simétrico TripleDES TripleDESCryptoServiceProvider tDESalg = new TripleDESCryptoServiceProvider(); // creo un stream para escibir el texto encriptado MemoryStream mstream = new MemoryStream(); CryptoStream cstream = new CryptoStream( mstream, tDESalg.CreateEncryptor(m_key, m_IV), CryptoStreamMode.Write); StreamWriter swriter = new StreamWriter(cstream); // escribo el texto a encriptar swriter.Write(text); // cierro todos los streams swriter.Flush(); swriter.Close(); cstream.Close(); mstream.Close(); encryptedText = Convert.ToBase64String(mstream.ToArray()); } catch(Exception ex) { encryptedText = ex.Message; } return encryptedText; } public string DecryptTextNode(string text) { string decryptedText = string.Empty; try { // creo un proveedor criptográfico para el algoritmo simétrico TripleDES TripleDESCryptoServiceProvider tDESalg = new TripleDESCryptoServiceProvider(); // creo un stream para escibir el texto desencriptado MemoryStream mstream = new MemoryStream(Convert.FromBase64String(text)); CryptoStream cstream = new CryptoStream( mstream, tDESalg.CreateDecryptor(m_key, m_IV), CryptoStreamMode.Read); StreamReader sreader = new StreamReader(cstream); // escribo el texto a desencriptar decryptedText = sreader.ReadToEnd(); // cierro todos los streams sreader.Close(); cstream.Close(); mstream.Close(); } catch(Exception ex) { decryptedText = ex.Message; } return decryptedText; }
Nota:
|
Código de ejemplo (ZIP): |
Fichero con el código de ejemplo: davidel_EncriptarMensajesSoap.zip - 30.4 KB
|