Creación de un cliente SOAP seguro para J2ME, Parte 3: Clases stub de API de servicios web seguros

Creación de una herramienta mejoradora de stubs

Aprenda a crear un cliente de servicios web seguro basado en Java™ 2, Micro Edition (J2ME), en esta serie de tutoriales de tres partes. Esta última entrega abarca importantes algoritmos de seguridad para J2ME. Reúne los artículos desarrollados en las dos entregas anteriores y presenta un mecanismo para que usted pruebe sus clientes de servicios web seguros. Usted creará también una herramienta mejoradora de stubs que puede reducir considerablemente el esfuerzo de programación manual requerido para crear clientes de servicios web seguros.

Bilal Siddiqui, Ingeniero Electrónico

Bilal Siddiqui es ingeniero electrónico, consultor XML y fundador de XML4Java.com, una empresa dedicada a simplificar el comercio electrónico. Luego de graduarse en 1995 en Ingeniería Electrónica en la Universidad de Ingeniería y Tecnología de Lahore, Bilal comenzó a diseñar soluciones de software para sistemas de control industrial. Luego, se volcó a XML y se valió de su experiencia en programación en C++ para construir herramientas de procesamiento XML basadas en Web y Wap, soluciones de análisis del extremo del servidor y aplicaciones de servicios. Bilal es evangelista tecnológico y un autor técnico muy publicado.



07-04-2014

Antes de comenzar

Acerca de la serie de este tutorial

Esta serie le demuestra cómo incorporar seguridad en el acceso inalámbrico a servicios web basados en Java 2, Micro Edition (J2ME). Usamos los siguientes componentes y tecnologías junto con un MIDlet J2ME:

  1. APIs de servicios web (WSA) para J2ME
  2. Cryptografía
  3. Firma digital XML (XMLDS)
  4. Java Card

En la Parte 1 de esta serie, usted vio cómo funcionan las clases stub de una API de servicios web (WSA). La Parte 2 le demostró cómo mejorar las clases stub WSA y cómo integrar otros componentes de tecnología, tales como la criptografía y las firmas XML en las clases stub WSA.

La Parte 3 comienza con la implementación de la codificación Base64 y los algoritmos para el cálculo de firma. La Parte 3 también demuestra un sistema de pruebas integral que usted puede usar para probar el cliente de servicios web seguros basado en J2ME. Terminaremos reuniendo todos los conceptos en una “herramienta mejoradora de stubs.” Esta herramienta mejora la funcionalidad de las clases stub WSA mediante la incorporación de características de seguridad.

Acerca de este tutorial

En la Parte 2 de esta serie de tutoriales, usted mejoró las clases stub WSA. La Parte 2 también presentaba cuatro clases auxiliares llamadas CanonicalAuthor, SHA1DigestCalculator, Base64Encoder, y SignatureCalculator. En la Parte 2 usted también implementó dos de las clases auxiliares: CanonicalAuthor y SHA1DigestCalculator.

En la Parte 3 implementamos las clases auxiliares restantes: Base64Encoder y SignatureCalculator. La Parte 3 pone después todos los stubs y las clases auxiliares en un sistema de pruebas. Esto le permitirá probar con facilidad sus clientes de servicios web seguros basados en J2ME.

Esta serie del tutorial finaliza con el desarrollo de una herramienta mejoradora de stubs. La herramienta está diseñada para soportar la mayor parte de la carga que implica la programación manual de las clases stub y la generación de clases auxiliares. Usted podrá usar la herramienta mejoradora de stubs para ahorrar gran parte del esfuerzo que implica la creación de clientes de servicios web seguros basados en J2ME.

Requisitos previos

  • Lea la Parte 1 y la Parte 2 de esta serie.
  • Usted debe contar con conocimientos básicos de los diversos componentes de tecnología comentados en esta serie. En especial, entendemos que posee los siguientes conocimientos:
    • Debe estar familiarizado con programación en Java y tener conocimientos básicos de MIDlets J2ME.
    • WSA usa Web Services Definition Language (Lenguaje de definición de servicios web, WSDL) y Simple Object Access Protocol (Protocolo de acceso a objetos simples, SOAP). Por lo tanto, deberá saber cómo están mapeadas las interfaces WSDL para llamadas de invocación del método SOAP.
  • También resultará de utilidad alguna formación en firmas XML.

Ver en Recursos los diversos artículos y excelentes tutoriales de developerWorks sobre estos temas.

¿Debo hacer este tutorial?

El propósito fundamental de esta serie de tutoriales es ayudarlo a desarrollar el acceso inalámbrico a sus servicios web. El foco principal es la seguridad, pero usted puede usar los conceptos WSA, que se presentan aquí, para desarrollar cualquier tipo de clientes inalámbricos para sus servicios web.

Esta tercera parte de la serie demuestra cómo implementar un algoritmo de codificación Base64 en un dispositivo inalámbrico de memoria limitada. Por lo tanto, este tutorial también lo puede ayudar a implementar algoritmos similares en dispositivos inalámbricos.

Esta parte también demuestra la construcción de mecanismos de prueba que usted puede usar en el momento de probar el acceso inalámbrico a sus servicios web. Puede usar el mecanismo de prueba de este tutorial o desarrollar sistemas de prueba similares para sus servicios web.

La herramienta mejoradora de stubs, que se comenta al final de este tutorial, demuestra cómo crear soluciones automatizadas para el mejoramiento de stubs WSA. Usted podrá usar la herramienta mejoradora de stubs para ahorrar tiempo en la creación de aplicaciones de servicios web seguros basados en J2ME.

Tópicos del tutorial

La parte 3 está organizada en las siete secciones siguientes:

  1. Introducción al tutorial
  2. Demostración de la implementación de un algoritmo codificado Base64 en J2ME
  3. Explicación de cómo comunicarse con las aplicaciones Java Card para MIDlets J2ME
  4. Demostración de cómo instalar aplicaciones Java Card
  5. Demostración de la creación de una aplicación Java Card capaz de computar valores de firmas criptográficas
  6. Comentario sobre cómo crear una herramienta mejoradora de stubs que pueda realizar la mayor parte del trabajo de programación requerido para mejorar las clases stub WSA
  7. Resumen

Requisitos del sistema


Codificación Base 64 de datos binarios

Codificación Base 64

La Parte 1 de este tutorial presentaba servicios de correo electrónico simples y ampliados y explicaba cómo un cliente J2ME usa clases WSA para enviar mensajes SOAP a servicios web remotos. La Parte 1 también explicaba cómo funcionan las clases WSA.

La Parte 2 demostraba cómo mejorar las clases stub WSA para crear un cliente J2ME seguro para servicios web. Cuando usted hizo la mejora de las clases stub, vio cuatro clases J2ME auxiliares: CanonicalAuthor, SHA1DigestCalculator, Base64Encoder, y SignatureCalculator. Usted implementó dos de las clases auxiliares en las secciones Autoría del formulario canónico de datos XML e Implementación del algoritmo de digest SHA-1 en J2ME de la Parte 2.

Presentamos la clase auxiliar Base64Encoder en la subsección Cálculo del valor de firma de la Parte 2. Comenzaré la Parte 3 con la implementación de la clase Base64Encoder.

La clase Base64Encoder tiene un método llamado getBase64EncodedValue(), que toma el dato binario con formato de matriz de bytes y devuelve un objeto String que ajusta el formato del valor binario codificado en Base64.

El algoritmo de codificación Base64 está definido por la especificación de codificación Base 64 de la Internet Engineering Task Force (Grupo de Trabajo en Ingeniería de Internet – IETF por su sigla inglés) (ver Recursos).

Usted encontrará muchas implementaciones del algoritmo de codificación Base64 en internet. Ver en Recursos un vínculo a una implementación de codificación Base64 de código abierto.

La implementación de la codificación Base64 demostrada en esta sección está optimizada para disminuir el uso de memoria (RAM) cuando se calcula el valor codificado Base64. Esto es útil en las aplicaciones J2ME, donde la memoria es un recurso limitado.

Para poder representar datos binarios, Base64 usa 64 caracteres del conjunto de caracteres del American Standard Code for Information Interchange (Código Estadounidense Estándar para el Intercambio de Información – ASCII). Los 64 caracteres usados en la codificación Base64 van de A a Z (26 caracteres), de a a z (26 caracteres), de 0 a 9 (10 caracteres), +, y / (2 caracteres).

La ventaja de la codificación Base64 en aplicaciones XML es que ninguno de estos caracteres tiene significado alguno en el formato XML. Por lo tanto, si alguno de estos caracteres aparece como parte de un elemento XML, se lo tratará como contenido de texto común del elemento XML.

No obstante, esta técnica de codificación también tiene una pequeña desventaja. Usted se quiere restringir a la representación de 64 caracteres en un byte. Esto significa que usted usa solamente 6 bits de un byte compuesto por 8 bits. Esto equivale a un 25 por ciento de desperdicio de ancho de banda cuando transmite datos codificados en Base64 en una red.

Quiere decir que, cuando usted codifica en Base64 una matriz de bytes compuesta por 24 bytes, su valor codificado en Base64 consumirá un 25 por ciento más de bytes y tendrá un tamaño de 30 bytes. Es el motivo por el que la codificación Base64 se usa únicamente en situaciones en las que usted no puede enviar directamente datos binarios sin codificar.

Dos pasos sencillos para una codificación Base64 simple

Primero consideraremos un caso muy sencillo de codificación Base64. Supongamos que éste es su dato de 3 bytes que debe ser convertido en un formulario Base64 codificado: 0111 1001 1001 1100 0110 1110.

Puede seguir dos pasos sencillos para codificar sus tres bytes de datos en Base64.

Paso 1: Creación de cuatro bytes a partir de tres bytes de entrada

El proceso de codificación Base64 comienza con la inserción de dos bits en cada byte de dato de entrada para crear cuatro bytes a partir de tres bytes de entrada: 00 011110 00 011001 00 110001 00 101110.

Observe que hemos escrito el dato de entrada real en negrita. Si usted considera solamente los bits de datos en negrita (e ignora los bits cero adicionales) puede ver que los bits de datos después del paso 1 son iguales al dato original.

El Paso 1 del proceso de codificación Base64 está implementado en un método llamado getFourOutOfThreeBytes(), como se muestra en el Listado 1.

Listado 1. Método getFourOutOfThreeBytes()

public byte[] getFourOutOfThreeBytes (byte[] inputData, short inputIndex) {

    byte[] fourBytes = new byte[4];
    byte mask = (byte) 0xFC;
    byte temp1 = (byte) (inputData [inputIndex] & mask);

    //Creating first of the four output bytes.
    temp1  = (byte) ( temp1 >>> 2 );
    mask   = (byte) 0x3F;
    temp1  = (byte) ( temp1 & mask );
    fourBytes[0] = temp1;
 
    //Creating second of the four output bytes.
    temp1  = 0;
    mask   = (byte) 0x3;
    temp1  = (byte) (inputData [inputIndex] & mask );
    temp1  = (byte) ( temp1 << 4 );
    byte temp2 = (byte) (inputData [inputIndex + 1] >>> 4 );
    mask   = 0xF;
    temp2  = (byte) (temp2 & mask);
    fourBytes[1] = (byte) (temp1 | temp2);

    //Creating third of the four output bytes.
    temp1  = 0;
    temp2  = 0;
    mask   = (byte) 0x3C;
    temp1  = (byte) (inputData [inputIndex + 1] << 2 );
    temp1  = (byte) ( temp1 & mask);
    mask   = (byte) 0x3;
    temp2  = (byte) (inputData [inputIndex + 2] >>> 6 );
    temp2  = (byte) ( temp2 & mask );
    fourBytes[2] = (byte) ( temp1 | temp2 );

    //Creating fourth of the four output bytes.
    temp1  = 0;
    temp2  = 0;
    mask   = (byte) 0x3F;
    temp1  = (byte) (inputData [inputIndex + 2] & mask );
    fourBytes[3] = temp1;
    
    return fourBytes;
}

He aquí cómo el método getFourOutOfThreeBytes() crea cuatro bytes a partir de tres bytes:

El método getFourOutOfThreeBytes() toma dos parámetros. El primer parámetro es una matriz de bytes llamada inputData, que lleva los tres bytes de entrada a partir de los cuales el método getFourOutOfThreeBytes() hará cuatro bytes. El segundo parámetro es un valor índice dentro de la matriz de datos inputData que apunta a la posición donde comienzan los tres bytes de entrada.

El método getFourOutOfThreeBytes() crea primero una instancia de matriz de cuatro bytes llamada fourBytes, que supuestamente contendrá los cuatro bytes que usted va a crear. Luego toma seis bits de los tres bytes de entrada y coloca los seis bits en el extremo derecho de un byte de salida. Llena con ceros los dos bits restantes del extremo izquierdo del byte de salida. Después de crear el primer byte de salida a partir de los primeros seis bits, el método getFourOutOfThreeBytes() crea el segundo byte de salida usando los seis bits siguientes de los bytes de entrada. Al tomar de esta manera seis bits por vez, crea los cuatro bytes de salida.

Paso 2: Codificación Base 64 de cuatro bytes

Usted hizo cuatro bytes a partir de tres bytes. Ahora modificará cada byte para que éste coincida con el rango que representa la A a la Z, la a a la z, el 0 al 9, el + y la / en ASCII. Cada uno de los cuatro bytes comienza con dos bits cero, por lo que el valor de cada byte no superará 63 (es decir que el valor abarcará desde el 0 al 63 decimal o desde 0x00 a 0x3F).

Por otra parte, la salida del proceso de codificación Base64 es de la A a la Z (del 65 al 91 decimal o de 0x41 a 0x5B), de la a a la z (del 97 al 122 decimal o de 0x61 a 0x7A), 0-9 (del 48 al 57 decimal o de 0x30 a 0x39), + (43 decimal o 0x2B), y / (47 decimal o 0x2F).

Es decir que usted deberá transformar el resultado del paso 1 según la Tabla 1:

Tabla 1. Requisitos de salida para codificación Base64

Resultado del paso 1Valor codificado en Base64Acción Requerida
DecimalHexadecimalDecimalHexadecimal
0 a 250x00 a 0x1965 a 910x41 a 0x5BSumar el decimal 65 a la salida del paso 1
26 a 510x1A a 0x3397 a 1220x61 a 0x7ASumar el decimal 71 a la salida del paso 1
52 a 610x34 a 0x3D48 a 570x30 a 0x39Restar el decimal 4 de la salida del paso 1
620x3E430x2BRestar el decimal 19 de la salida del paso 1
630x3F470x2FRestar el decimal 16 de la salida del paso 1

El Listado 2 muestra la implementación del Paso 2 en un método llamado getEncodedFormOfFourBytes(), que toma los cuatro bytes del paso 1 y devuelve otra matriz de bytes después de transformar los cuatro bytes según la Tabla 1:

Listado 2. Implementación de getEncodedFormOfFourBytes()

private byte[] getEncodedFormOfFourBytes(byte[] fourBytes) {
    
    byte[] encodedBytes = new byte[4];
    for (int i = 0; i < encodedBytes.length; i++)
        encodedBytes [i] = getEncodedFormOfOneByte (fourBytes[i]);
        return encodedBytes;
}

private byte getEncodedFormOfOneByte (byte value) {

    if (value >= 0 && value <= 25 )
        return (byte) (value + 65);

    if (value >= 26 && value <= 51 )
        return (byte) (value + 71);

    if (value >= 52 && value <= 61 )
        return (byte) (value - 4);

    if (value == 62)
        return (byte) 43;

    if (value == 63)
        return (byte) 47;
    
    return 0;    
}

Vea el método getBase64EncodedValue() que aparece en el Listado 3, que llama a los métodos getFourOutOfThreeBytes() y getEncodedFormOfFourBytes(), uno a continuación del otro, para obtener el formulario codificado Base64 de sus tres bytes de datos.

Listado 3. Método getBase64EncodedValue()

public String getBase64EncodedValue(byte inputData[])
{
    int length = inputData.length;

    if(length <= 0)
        return "";

    //Create an output byte array to hold Base64-encoded value.
    //Length of the output byte array is 25% more than the input bytes.
    int outputDataLength = 0;
    if (length % 3 == 0)
        outputDataLength = (length / 3) * 4;
    else
        outputDataLength = (length / 3) * 4 + 4;
    outputData[] = new byte[ outputDataLength ];
   
    //Declare index variables for input and output.
    int inputIndex = 0;
    int outputIndex = 0;
    
    //Encode inputData bytes, taking three bytes at a time.
    for (int i = length; i >= 3; i -= 3)
    {
        byte[] fourEncodedBytes = 
            getEncodedFormOfFourBytes (
                getFourOutOfThreeBytes(
                    inputData, (short)inputIndex));
        
        //Set encoded value in the output array.
        outputData [outputIndex ++] = fourEncodedBytes [0];
        outputData [outputIndex ++] = fourEncodedBytes [1];
        outputData [outputIndex ++] = fourEncodedBytes [2];
        outputData [outputIndex ++] = fourEncodedBytes [3];

        inputIndex += 3;
    }
    
   return new String (outputData);

}// getBase64EncodedValue

Observe que todo lo que hicimos fue colocar las llamadas de los métodos getFourOutOfThreeBytes() y getEnhancedFormOfFourBytes() del Listado 3 en un loop para manejar cualquier dato cuya longitud sea múltiplo de tres bytes. El dato binario de entrada, cuyo tamaño no sea múltiplo de tres bytes, necesita un manejo adicional, que se explica en la siguiente subsección.

Trabajo con datos de tamaño arbitrario

Si su dato binario de entrada tiene un tamaño de cuatro bytes, manejará los tres primeros bytes según el algoritmo de dos pasos demostrado en la subsección anterior. Después, le quedará un byte adicional restante para manejar.

Del mismo modo, si tiene cinco bytes de datos binarios, tiene dos bytes adicionales para manejo especial. Si puede manejar estos dos casos de uno y dos bytes adicionales restantes, usted podrá implementar un algoritmo capaz de codificar en Base64 datos de cualquier tamaño.

Por ejemplo, si su dato tiene un tamaño de 71 bytes, puede codificar en Base64 los primeros 69 bytes (múltiplo de 3) según el Listado 3 y manejar los dos últimos bytes como un caso especial.

La siguiente es una explicación de cómo se manejan uno y dos bytes adicionales para la codificación Base64.

Manejo especial de un byte adicional

Si usted tiene que manejar un byte adicional, puede crear directamente cuatro bytes a partir de ese byte adicional. A tal fin, puede realizar los siguientes pasos:

  1. Supongamos que el byte adicional a ser manejado es 10110101. Coloque los seis bits del extremo izquierdo (importantísimos) (101101) del byte adicional en el primer byte de los cuatro bytes de salida. El byte 1 de los cuatro bytes de salida se verá así: 00 101101 (0x2D o decimal 45).
  2. Coloque los dos bits restantes (los menos importantes) del extremo derecho del byte adicional (01) en el segundo byte de los cuatro bytes de salida. La posición de los bits del extremo derecho en el segundo byte es importante. Los dos bits del extremo derecho del byte adicional se colocan en las posiciones 5 y 6 del byte de salida. El byte 2 de los cuatro bytes de salida se verá así: 00 10 0000 (0x20 o decimal 32).
  3. Aplique las reglas de transformación de la Tabla 1 a los dos bytes de salida desde los pasos 1 y 2. El primer byte de salida se convertirá en 0x74 (decimal 116), y el segundo byte de salida se convertirá en 0x67 (decimal 103).
  4. Llene los bytes 3 y 4 de los cuatro bytes de salida con un carácter = (0x3D en ASCII). Observe que el carácter = no aparece en ninguna parte del proceso de codificación Base64 normal. Aparece únicamente al final, durante el manejo de bytes adicionales especiales.

Estos cuatro pasos están implementados en el Listado 4 como un método handleOneExtraByte():

Listado 4. Método handleOneExtraByte()

//Declare the byte array to hold output bytes.
byte[] outputData;

public void handleOneExtraByte ( 
    byte[] inputData, 
    int inputIndex, 
    int outputIndex) {
    
    //****Step 1***//
    byte mask = (byte) 0xFC;
    byte temp1 = (byte) (inputData [inputIndex] & mask );
    temp1 = (byte) (temp1 >>> 2);
    mask = (byte) 0x3F;
    temp1 = (byte) (temp1 & mask);

    //****Step 2***//
    mask = (byte) 0x3;
    byte temp2 = (byte) (inputData [inputIndex] & mask);

    //****Step 3***//
    outputData [outputIndex ++] = getEncodedFormOfOneByte (temp1);
    outputData [outputIndex ++] = getEncodedFormOfOneByte (temp2);

    //****Step 4***//
    outputData [outputIndex ++] = '=';
    outputData [outputIndex ++] = '=';                    

}//handleOneExtraByte

Manejo especial de dos bytes adicionales

El manejo de dos bytes adicionales es un poco más complejo que el de un byte adicional.

Supongamos que los dos bytes adicionales son 10110110 y 10111101. Usted crea cuatro bytes de salida a partir de los dos bytes adicionales usando los siguientes pasos:

  1. Copie los seis bits del extremo izquierdo (101101) del primer byte adicional en la posición del extremo derecho del primer byte de salida, y rellene sus dos bits restantes con ceros. El byte 1 de los cuatro bytes de salida se verá así: 00 101101 (0x2D o decimal 45).
  2. Copie los dos bits del extremo derecho (10) del primer byte adicional junto con los cuatro bits del extremo izquierdo (1011) del segundo byte adicional en la posición del extremo derecho del segundo byte de salida. El byte 2 de los cuatro bytes de salida se verá así: 00 101011 (0x2B o decimal 43).
  3. Copie los cuatro bits restantes (1101) del segundo byte adicional en el tercer byte de la matriz de bytes de salida. El byte 3 de los cuatro bytes de salida se verá así: 00 1101 00 (0x34 o decimal 52)
  4. Aplique las reglas de transformación de la Tabla 1 a los tres bytes de salida. Los bytes se convertirán en 0x74, 0x72, y 0x30, respectivamente.
  5. Llene el último (cuarto) byte de salida con el carácter =.

Hemos implementado estos cinco pasos del método handleTwoExtraBytes() que se muestra en el Listado 5:

Listado 5. Método handleTwoExtraBytes()

//Declare the byte array to hold output bytes.
byte[] outputData;
 
public void handleTwoExtraBytes ( 
    byte[] inputData, 
    int inputIndex, 
    int outputIndex) {

    /***Step 1***/
    byte mask = (byte) 0xFC;
    byte temp1 = (byte) (inputData [inputIndex] & mask);
    temp1 = (byte) (temp1 >>> 2);
    mask = (byte) 0x3F;
    temp1 = (byte) (temp1 & mask);

    /***Step 2***/
    mask = (byte) 0x3;
    byte temp2 = (byte) (inputData [inputIndex] & mask);
    temp2 = (byte) (temp2 << 4);
    byte temp3 = (byte) (inputData [inputIndex + 1] >>> 4);
    mask = 0xF;
    temp3 = (byte) (temp3 & mask);
    temp2 = (byte) (temp2 | temp3);

    /***Step 3***/
    mask = (byte) 0x3C;
    temp3 = (byte) (inputData [inputIndex + 1] << 2);
    temp3 = (byte) (temp3 & mask);

    /***Step 4***/
    outputData [outputIndex ++] = getEncodedFormOfOneByte (temp1);
    outputData [outputIndex ++] = getEncodedFormOfOneByte (temp2);
    outputData [outputIndex ++] = getEncodedFormOfOneByte (temp3);

    /***Step 5***/
    outputData [outputIndex ++] = '=';

}//handleTwoExtraBytes

Prueba del método getBase64EncodedValue()

Ahora vea el Listado 6, que muestra el formulario completo del método getBase64EncodedValue() que ya vio en el Listado 3:

Listado 6. Formulario completo del método getBase64EncodedValue()

//Declare the byte array to hold output bytes.
byte[] outputData;

public String getBase64EncodedValue(byte inputData[])
{
    int length = inputData.length;

    if(length <= 0)
        return "";

    //Create an output byte array to hold Base64-encoded value.
    //Length of the output byte array is 25% more than the input bytes.
    int outputDataLength = 0;
    if (length % 3 == 0)
        outputDataLength = (length / 3) * 4;
    else
        outputDataLength = (length / 3) * 4 + 4;
    outputData[] = new byte[ outputDataLength ];
   
    //Declare index variables for input and output.
    int inputIndex = 0;
    int outputIndex = 0;
    
    //Encode inputData bytes, taking three bytes at a time.
    for (int i = length; i >= 3; i -= 3)
    {
        byte[] fourEncodedBytes = 
            getEncodedFormOfFourBytes (
                getFourOutOfThreeBytes(
                    inputData, (short)inputIndex));
        
        //Set encoded value in the output array.
        outputData [outputIndex ++] = fourEncodedBytes [0];
        outputData [outputIndex ++] = fourEncodedBytes [1];
        outputData [outputIndex ++] = fourEncodedBytes [2];
        outputData [outputIndex ++] = fourEncodedBytes [3];

        inputIndex += 3;
    }
    
    //Handle extra bytes.  
    if((length - inputIndex) == 1)   
        handleOneExtraByte (inputData, inputIndex, outputIndex);
    else if((length - inputIndex) == 2)  
        handleTwoExtraBytes (inputData, inputIndex, outputIndex);

    return new String (outputData);

}//getBase64EncodedValue

Observe que el método completo de getBase64EncodedValue() del Listado 6 usa los cuatro métodos -- getFourOutOfThreeBytes(), getEncodedFormOfFourBytes(), handleOneExtraBytes(), y handleTwoExtraBytes()) -- que usted desarrolló en las subsecciones anteriores. La implementación del método getBase64EncodedValue() ya está completa, y es hora de probar el método para verificar si produce un formulario de datos binarios codificado en Base64 totalmente compatible.

Una aplicación J2ME llamada Base64TestingMIDlet se incluye en una carpeta llamada Base64Tester en la descarga del código fuente de este tutorial (ver Descargas).

Base64TestingMIDlet tiene diversas matrices de bytes predeterminadas e inamovibles que usa como dato binario de entrada. Éste pasa la matriz de bytes al método Base64Encoder.getBase64EncodedValue() que devuelve el formulario codificado Base64 del dato binario de entrada. El MIDlet imprime el dato binario sin procesar como así también su formulario codificado en la consola de salida del Sun Java Wireless Toolkit (Kit de herramientas Sun Java Wireless).

La descarga del código fuente también incluye una aplicación Java llamada Base64Tester, que puede usar para verificar la salida de su clase Base64Encoder. Puede ejecutar Base64Tester como una aplicación Java normal usando el siguiente argumento de la línea de comandos:
java -classpath .;.;%JWSDP_HOME%\jwsdp-shared\lib\xmlsec.jar; Base64Tester

Base64Tester codifica el mismo dato binario que usted codificó en Base64TestingMIDlet, usando una implementación de codificación Base64 que viene con el Java Web Services Developer Pack (Paquete desarrollador de servicios web de Java – WSDP) (ver Recursos).Base64Tester imprime el dato codificado Base64 en la consola de salida.

Puede comparar la salida codificada Base64 de su Base64TestingMIDlet con la salida del Base64Tester.

Integración de CanonicalAuthor, DigestCalculator, y Base64Encoder en un servicio de correo electrónico seguro

Recuerde que las cuatro clases auxiliares -- CanonicalAuthor, SHA1DigestCalculator, Base64Encoder, y SignatureCalculator -- están mencionadas en la sección Mejora de la clase SignedInfo de la Parte 2.

Usted probó su clase Base64Encoder en la sección precedente. Usted probó su clase de canonicalización (CanonicalAuthor) en la sección Prueba de rendimiento de CanonicalAuthor de la Parte 2. Del mismo modo, probó su clase de cálculo de digests (SHA1DigestCalculator) en la sección Prueba de rendimiento de SHA1DigetstCalculator de la Parte 2.

La única clase J2ME que falta crear es SignatureCalculator, que usted implementará en la próxima sección. No obstante, a esta altura puede integrar las tres clases auxiliares ya desarrolladas y probadas (SHA1DigestCalculator, CanonicalAuthor, y Base64Encoder) dentro de un MIDlet -- llamado SecureEverydayMIDlet -- para acceder al servicio de correo electrónico seguro.

Las clases desarrolladas hasta aquí pueden crear el formulario canónico de una solicitud SOAP, calcular su valor digest, y codificar el digest en un formulario codificado en Base64. La única funcionalidad faltante es el cálculo de firma. Por lo tanto, puede probar la funcionalidad disponible combinada mediante la implementación del siguiente sistema:

  1. Compilar y crear SecureEverydayMIDlet con formularios probados de las clases SHA1DigestCalculator, CanonicalAuthor, y Base64Encoder El código fuente y el formulario compilado de SecureEverydayMIDlet vienen junto con estas tres clases en la carpeta Base64EncodedDigestTester de la descarga de código fuente (ver Descargas).
  2. Construir una aplicación Java del lado del servidor que pueda escuchar una solicitud SOAP desde SecureEverydayMIDlet, analizar la solicitud para extraer el valor digest, y verificar el valor digest. La descarga del código fuente incluye una aplicación Java llamada Based64EncodedDigestVerifier que realiza estas tareas.

Puede ejecutar Based64EncodedDigestVerifier usando el siguiente argumento de la línea de comandos:
java -classpath .;%JWSDP_HOME%\jwsdp-shared\lib\xmlsec.jar;%JWSDP_HOME%\xmldsig\lib\xmldsig.jar;x:\xalan.jar; Based64EncodedDigestVerifier

Después de ejecutar Based64EncodedDigestVerifier, usted ejecuta SecureEverydayMIDlet, que envía una solicitud SOAP a Based64EncodedDigestVerifier. Una vez recibida la solicitud SOAP, Based64EncodedDigestVerifier analiza la solicitud, verifica el valor digest, y muestra el resultado de la verificación en la consola de salida.

En la próxima sección usted creará un soporte para la autoría de firma en la clase SignatureCalculator.


Creación del soporte para autoría de firma

Comunicación con aplicaciones Java Card

Esta sección demuestra cómo crear un soporte para autoría de firma dentro de SecureEverydayMIDlet.

La subsección Using Java Card technology de la Parte 1 le presentó una aplicación Java Card que contiene una clave criptográfica del usuario. La aplicación Java Card usa la clave para computar valores de firma para datos de usuarios.

En la subsección Cálculo del valor de firma (paso 3 del Listado 15 de la Parte 2), conoció una clase llamada SignatureCalculator. La clase SignatureCalculator ajusta toda la comunicación con la aplicación Java Card. SecureEverydayMIDlet usará la clase SignatureCalculator para comunicarse con la aplicación Java con el fin de traer el valor de la firma.

La Figura 1 muestra una vista gráfica de cómo la clase SignatureCalculator ayuda a SecureEverydayMIDlet a hablar con la aplicación Java Card.

Figura 1. Componentes de la comunicación entre el MIDlet J2ME y la aplicación Java Card

La Figura 1 muestra un dispositivo J2ME y una Tarjeta Java. Tome nota de que la Tarjeta Java puede residir físicamente o no dentro del dispositivo J2ME. Por ejemplo, el Subscriber Identification Module (Módulo de identificación del abonado – SIM), que está dentro de su teléfono celular puede ser una tarjeta Java. En este caso la tarjeta Java residirá físicamente dentro de un dispositivo J2ME. Su tarjeta de crédito también podría ser una tarjeta Java. En este caso la tarjeta Java no reside físicamente dentro de un dispositivo J2ME.

Ya sea que una tarjeta Java resida dentro o fuera de un dispositivo J2ME, existen diversos métodos de comunicación física con la tarjeta Java. La capa física de comunicación está fuera del alcance de este tutorial. (Ver en Recursos los vínculos para información acerca de comunicaciones físicas con aplicaciones Java Card.)

El foco de esta sección es explicar la capa de aplicación que un MIDlet J2ME, (por ejemplo, el SecureEverydayMIDlet de la Figura 1) puede usar para hablar con cualquier tipo de tarjeta Java. J2ME incluye una API llamada Security and Trust Services APIs (SATSA), que usted puede usar para integrar aplicaciones Java Card en sus MIDlets J2ME.

La Figura 1 muestra que la clase SignatureCalculator, que está dentro del dispositivo J2ME, usa SATSA para hablar con aplicaciones Java Card.

Todas las aplicaciones Java Card son applets que usted puede desarrollar como applets normales. Usted puede ver que la tarjeta Java de la Figura 1 contiene un applet llamado JavaCardSignatureCalculator. Usted desarrollará el JavaCardSignatureCalculator en la próxima sección Creación del applet JavaCardSignatureCalculator. Por el momento, comentaremos brevemente el propósito del uso de la tecnología Java Card para proteger aplicaciones inalámbricas.

Ventajas del uso de la tecnología Java Card

Las tarjetas Java son un tipo especial de tarjetas inteligentes (definidas por ISO 7816; ver Recursos) en las que se pueden instalar y ejecutar applets Java. La ventaja principal del uso de los applets Java es que diversas empresas proveedoras de servicios pueden instalar sus aplicaciones en la tarjeta Java de un usuario. Por ejemplo, un banco puede instalar alguna aplicación de pago móvil en las tarjetas Java de los titulares de sus cuentas.

Del mismo modo, usted instalará el applet JavaCardSignatureCalculator en las tarjetas Java de los usuarios de sus Everyday Web Services.

Esto significa que hay tres tipos de entidades relacionadas con una tarjeta Java:

  • El titular de la tarjeta Java (por ejemplo, los usuarios de sus Everyday Web Services).
  • Los proveedores del servicio (por ejemplo, usted se convertirá en un proveedor de servicios al instalar el applet JavaCardSignatureCalculator en la tarjeta Java de un usuario de Everyday Web Services).
  • El emisor de una tarjeta Java (o sea, la empresa que emitió la tarjeta Java para un usuario).

Observe que los emisores y proveedores de servicios de tarjetas Java pueden ser la misma empresa, o no. Esta es la principal ventaja del uso de las tarjetas Java. Los proveedores de servicios pueden desarrollar sus propias aplicaciones Java Card e instalarlas en las tarjetas Java emitidas por otras empresas. (Ver en Recursos los vínculos a empresas emisoras de tarjetas Java y proveedoras de servicios.)

El resto de esta sección explica la forma en que la clase SignatureCalculator ajusta toda la comunicación entre un MIDlet J2ME y un applet Java Card. Las siguientes subsecciones demuestran cómo crear el applet JavaCardSignatureCalculator.

Clase Signaturecalculator

El Listado 7 muestra al constructor de la clase SignatureCalculator.

Listado 7. Constructor de SignatureCalculator

public SignatureCalculator (
    GetSubjectsSignatureSignedInfo signedInfo, 
    String username, 
    String password)
{
    try {
        //**** Step 1 ****//
        String URL2SignatureApplet = 
                "jcrmi:0;AID=a0.0.0.0.62.3.1.c.8.2";

        //**** Step 2 ****//
        JavaCardRMIConnection rmiConnection = 
            (JavaCardRMIConnection)Connector.open(URL2SignatureApplet);

        //**** Step 3 ****//
        Remote remote = 
            rmiConnection.getInitialReference();
           
        //**** Step 4 ****//
        SignatureMethod signer = 
               (SignatureMethod) remote;

        //**** Step 5 ****//
        String base64EncodedDigest = 
            signedInfo.getReference().getDigestValue();
       
        //**** Step 6 ****
        signatureValue = 
            signer.sign( 
                username.getBytes(), 
                password.getBytes(), 
                base64EncodedDigest.getBytes());

    } 
    catch (java.io.IOException ie){ ie.printStackTrace(); }
    catch (javacard.framework.UserException ue){ ue.printStackTrace();}
 }

El constructor de SignatureCalculator toma tres parámetros. El primer parámetro es una instancia de la clase GetSubjectsSignatureSignedInfo que se explica en la subsección Creación de una instancia GetSubjectsSignatureSignedInfo de la Parte 2. Este objeto ajusta el contenido del elemento SignedInfo (Listado 6 de la Parte 1), que a su vez contiene el valor digest del formulario canonicalizado de su dato XML. La clase SignatureCalculator usará este valor digest para computar la firma criptográfica.

Los otros dos parámetros son un nombre de usuario y una contraseña que la clase SignatureCalculator necesita para acceder al applet Java Card que contiene la clave criptográfica necesaria para el cálculo de firma.

El constructor de SignatureCalculator sigue seis pasos (como marcado en el Listado 7) para usar SATSA.

El resto de esta sección describe los seis pasos.

Conexión a un applet de Java Card

Los seis pasos para el uso de SATSA aparecen en el constructor de SignatureCalculator del Listado 7. En los primeros pasos, usted se conectará al objeto remoto Signer, como se explica a continuación:

Paso 1

Usted especifica una URL que apunte al applet JavaCardSignatureCalculator. La cadena de la URL del paso 1 del Listado 7(jcrmi:0;AID=a0.0.0.0.62.3.1.c.8.2) contiene tres porciones.

La primera porción de la cadena de la URL (jcrmi:) especifica un protocolo para la URL. SATSA usa jcrmi para especificar que el protocolo para la comunicación con las aplicaciones Java Card es Remote Method Invocation (Invocación por método remoto – RMI).

Observe que RMI no es el único método del que dispone SATSA para poder hablar con aplicaciones Java Card. No obstante, en este tutorial sólo hablaremos del uso de RMI para la comunicación con aplicaciones Java Card.

Por lo general, las tarjetas Java se usan juntamente con dispositivos para lectura de tarjetas Java (ver Recursos). Un dispositivo para lectura de tarjetas Java puede tener cualquier cantidad de ranuras físicas. Cada ranura física puede alojar una tarjeta Java. La segunda porción de la cadena de la URL (0) especifica la ranura física en la que usted enchufa su tarjeta Java.

Una tarjeta Java puede contener una cantidad de applets simultáneamente. Cada applet contenido en una tarjeta Java tiene un identificador único llamado Application IDentifier (IDentificador de Aplicaciones – AID). Por lo tanto, la URL del applet de una tarjeta Java debe identificar al applet hacia el cual apunta. La tercera porción (AID=a0.0.0.0.62.3.1.c.8.2) especifica el AID para el applet.

Paso 2

En el segundo paso del Listado 7, usted usa la URL del paso 1 para conectarse al applet JavaCardSignatureCalculator. A tal fin, usted necesita dos clases: javax.microedition.io.Connector y javax.microedition.jcrmi.JavaCardRMIConnection. La clase Connector pertenece al marco de trabajo input/output (IO) (entrada/salida) de J2ME, y la clase JavaCardRMIConnection es parte de SATSA.

Usted llama a un método Connector.open(), pasando la URL del paso 1 junto con el llamado del método. El método Connector.open() se comunica con el applet JavaCardSignatureCalculator y devuelve una instancia de la clase JavaCardRMIConnection.

Observe que el método Connector.open() es inteligente. Devuelve objetos, según el protocolo especificado en la URL. Por ejemplo, si usted pasó una URL de HyperText Transfer Protocol (Protocolo de transferencia de hipertexto – HTTP), al método Connector.open(), le habría devuelto una instancia de otra clase llamada HttpConnection, que usan las aplicaciones J2ME para comunicaciones HTTP.

Trabajo con un objeto Java Card remoto

Ahora usted tiene un objeto JavaCardRMIConnection. La clase JavaCardRMIConnection tiene un método llamado getInitialReference(), que no toma parámetro alguno y devuelve un objeto que expone una interfaz llamada java.rmi.Remote. La interfaz Remote pertenece al marco de trabajo RMI y se usa para representar un objeto Java remoto que reside fuera de una aplicación J2ME.

Puede ver una llamada al método getInitialReference() en el paso 3 del Listado 7. Enseguida describiremos el paso 3 y los pasos posteriores del Listado 7, pero antes de eso, explicaremos tres cuestiones:

  • ¿Cómo maneja la clase Connector la comunicación con el applet JavaCardSignatureCalculator ?
  • ¿Cómo representa usted al objeto Java Card remoto como una clase J2ME local?
  • ¿Cómo trabaja la representación local del objeto Java Card remoto en J2ME?

Las subsecciones que vienen a continuación explican detalladamente estas tres cuestiones.

¿Cómo funciona Connector.open()?

La especificación Java Card define los formatos de los datos para la comunicación entre la aplicación del cliente (como SecureEverydayMIDlet) y los applets de Java Card (como JavaCardSignatureCalculator). Los formatos de los datos están definidos en el formulario de unidades de datos llamadas Application Protocol Data Units (Unidades de datos del protocolo de aplicaciones – APDUs).

Las APDUs ajustan los datos binarios de acuerdo al formato definido por la especificación Java Card. Existen diferentes tipos de APDUs. Por ejemplo, una APDU llamada SELECT se usa para seleccionar un applet en particular residente en una tarjeta Java. Del mismo modo, una APDU llamada INVOKE se usa para invocar las aplicaciones seleccionadas previamente por la APDU SELECT.

El método Connector.open() crea una APDU SELECT, envía la APDU al applet JavaCardSignatureCalculator, trae una APDU de respuesta, y la procesa para extraer una referencia a un objeto Java Card remoto que reside en el applet JavaCardSignatureCalculator.

Luego de extraerle una referencia al objeto Java Card remoto, ajusta la referencia dentro de un objeto llamado javax.microedition.jcrmi.RemoteRef. RemoteRef es parte de SATSA y se usa para mantener una referencia hacia un objeto Java Card remoto.

Para facilitarle a los desarrolladores de J2ME el uso de referencias hacia objetos Java Card remotos, SATSA proporciona una clase llamada javax.microedition.jcrmi.RemoteStub, que implementa la interfaz Remote (cuya instancia es devuelta por el método getInitialReference() del paso 3 del Listado 7). Más adelante describiremos cómo la clase RemoteStub facilita el uso (en Cómo funciona una clase stub SATSA). Por el momento, observe que el método Connection.open() ajusta el objeto RemoteRef que está dentro de un objeto RemoteStub.

Observe que Connector.open() es parte del marco de trabajo IO. Devuelve objetos, según el protocolo que usted use. El marco de trabajo IO proporciona una interfaz llamada javax.microedition.io.Connection, cuyas instancias, específicas del protocolo, son devueltas por el método Connector.open().

En este caso, usted usa SATSA para establecer la comunicación con un applet Java Card remoto. Por lo tanto, Connector.open() debe devolver una instancia específica de SATSA de la interfazConnection. La clase JavaCardRMIConnection (cuya instancia es devuelta por Connector.open() en el paso 2 del Listado 7) implementa la interfaz Connection para SATSA. Por lo tanto, Connector.open() ajusta el objeto RemoteStub que está dentro de un objeto JavaCardRMIConnection.

Podemos resumir este comentario en los tres puntos siguientes:

  1. Connector.open() obtiene una referencia al objeto Java Card remoto y ajusta la referencia dentro de un objeto RemoteRef.
  2. Connector.open() ajusta el objeto RemoteRef object que está dentro de un objeto RemoteStub.
  3. Por último, Connector.open() ajusta el objeto RemoteStub que está dentro de un objeto JavaCardRMIConnection y devuelve lo mismo del paso 2 del Listado 7.

Paso 3

El método JavaCardRMIConnection.getInitialReference() devuelve el objeto ajustado RemoteStub del paso 3 del Listado 7. El objeto RemoteStub ajusta una referencia al objeto Java Card remoto, por lo que de ahora en adelante, llamaremos a RemoteStub una representación local del objeto Java Card remoto.

Una vez que usted tiene una representación local del objeto Java Card remoto, puede usarla como si fuera un objeto local.

Representación de un objeto Java Card remoto como una clase J2ME local

Más adelante (en Implementación de la clase Signer), explicaremos el funcionamiento de una clase Java Card llamada Signer. La clase Signer es parte del applet JavaCardSignatureCalculator y tiene un método llamado sign(), que toma los datos de la aplicación y devuelve el valor de firma requerido.

Usted usará este objeto Signer (que es un objeto Java Card remoto) como un objeto J2ME local para firmar datos XML de los usuarios.

Para poder usar el objeto Signer localmente, usted deberá crear un stub local del objeto Signer A ese stub local lo llamaremos SignatureMethod_Stub. Esta clase SignatureMethod_Stub amplía a RemoteStub y, en consecuencia, actúa como representación local del objeto Signer.

Deberá seguir unos pocos pasos para crear la clase SignatureMethod_Stub:

  1. Escriba una interfaz que exponga el mismo método presente en la clase remota Signer y que amplía la interfaz Remote. Por ejemplo, hemos escrito una interfaz llamada SignatureMethod en el Listado 8:
    Listado 8. Interfaz SignatureMethod

    public interface SignatureMethod extends Remote { public static final short INVALID_PIN = (short)0x6002; public byte[] sign ( byte[] username, byte[] password, byte[] dataToSign) throws RemoteException, UserException; }
  2. Usted necesita una utilidad llamada jcrmic para poder generar la clase SignatureMethod_Stub. Lamentablemente, esta utilidad no está incluida en el kit de herramientas Sun Java Wireless que usted usa para desarrollar el SecureEverydayMIDlet de este tutorial. Sun proporciona una implementación de referencia aparte (RI) de SATSA 1.0 para J2ME (ver Recursos). La herramienta jcrmic es parte de SATSA RI.

    El siguiente argumento de la línea de comandos usa jcrmic para generar la clase SignatureMethod_Stub:
    java -jar x:\satsa1.0\bin\jcrmic.jar -classpath .;x:\WTK23\lib\jsr177.jar; x:\WTK23\lib\midpapi20.jar; x:\WTK23\lib\cldcapi11.jar; -d . SignatureApplet.SignatureMethod

    Este argumento de la línea de comandos genera la clase SignatureMethod_Stub. Usted compilará la clase SignatureMethod_Stub junto con otras clases del kit de herramientas Sun Java Wireless.

    Observe que el argumento de la línea de comandos descripto arriba, notificará un error que dice “Java compiler not found” (“No se pudo encontrar el compilador Java”). Usted puede ignorar el error porque no desea que jcrmic compile el archivo Java.

Para su comodidad, la clase SignatureMethod_Stub se encuentra en la descarga del código fuente de este tutorial (ver Descargas).

Paso 4

Esta clase SignatureMethod_Stub puede representar la clase remota Signer en un MIDlet J2ME. Por lo que, en el paso 4 del Listado 7, usted toma el objeto devuelto por el método getInitialReference() del paso 3 del Listado 7como un SignatureMethod_Stub.

A continuación, explicaremos cómo funciona la clase SignatureMethod_Stub.

Cómo funciona una clase stub SATSA

Ahora comentaremos cómo la clase SignatureMethod_Stub ajusta la funcionalidad SATSA.

La finalidad principal de la clase SignatureMethod_Stub es la facilidad de uso. Observe que la clase SignatureMethod_Stub cumple la misma finalidad para SATSA que las clases stub WSA cumplen para WSA. (O sea, la clase SignatureMethod_Stub ajusta la funcionalidad SATSA del mismo modo que las clases stub WSA ajustan la funcionalidad WSA.)

La Figura 2 ilustra el funcionamiento de SignatureMethod_Stub:

Figura 2. Funcionamiento de la clase SignatureMethod_Stub

A partir de la Figura 2 usted puede ver que la clase SignatureMethod_Stub funciona como un proxy. Un MIDlet J2ME puede invocar los métodos expuestos por la clase SignatureMethod_Stub. Por otra parte, la clase SignatureMethod_Stub usa la API SATSA de manera interna para todas las comunicaciones con el objeto remoto real representado por la clase SignatureMethod_Stub.

Ahora le mostraremos cómo la clase SignatureMethod_Stub facilita el uso de los MIDlets J2ME. El Listado 9 muestra una implementación de la clase SignatureMethod_Stub generada por jcrmic.

Listado 9. Clase SignatureMethod_Stub generada por jcrmic
    extends javax.microedition.jcrmi.RemoteStub
    implements SignatureApplet.SignatureMethod, java.rmi.Remote {
    
    // constructor
    public SignatureMethod_Stub() {
    super();
    }
    
    public byte[] sign(
        byte[] param1, 
        byte[] param2, 
        byte[] param3) 
        throws java.rmi.RemoteException, 
        javacard.framework.UserException 
   {
    
    try {
        Object result = 
                ref.invoke(
                    "sign([B[B[B)[B", 
                    new java.lang.Object[] { param1, param2, param3 });
        return (byte[]) result;
        
    } catch (java.lang.RuntimeException e) {
        throw e;
    } catch (java.rmi.RemoteException e) {
        throw e;
    } catch (javacard.framework.UserException e) {
        throw e;
    } catch (java.lang.Exception e) {
        throw new java.rmi.RemoteException("undeclared checked exception", e);
    }
    }
}

En el Listado 9, vea el método Sign() de la clase SignatureMethod_Stub, que toma tres parámetros: param1, param2, y param3. El param1 y el param2 ajustan el nombre de usuario y la contraseña, respectivamente. El param3 contiene el dato que debe ser firmado.

El método sign() del objeto remoto Signer toma los tres mismos parámetros. El método sign() del Listado 9 simplemente pasa los tres parámetros al objeto RemoteRef, que, a su vez, maneja la lógica para pasar los tres parámetros al objeto remoto Signer.

Invocación del método sign()

En el paso 4 del constructor de SignatureCalculator del Listado 7, usted obtuvo una referencia local (o sea, la clase SignatureMethod_Stub) para el objeto remoto Signer cuyo método sign() usted desea invocar.

Paso 5

No obstante, antes de que usted pueda invocar el método sign() necesita que el dato sea firmado. El objeto GetSubjectsSignatureSignedInfo (primer parámetro del creador de SignatureCalculator del Listado 7) contiene el dato que debe ser firmado. Por lo que, en el paso 5 del Listado 7, usted extrae el dato que debe ser firmado del objeto GetSubjectsSignatureSignedInfo.

Paso 6

Por último, en el paso 6 del constructor de SignatureCalculator, usted llama al método sign() de la clase SignatureMethod_Stub. El método sign() toma tres parámetros: el nombre de usuario, la contraseña y el dato que debe ser firmado.

El método sign() de la clase SignatureMethod_Stub gestiona internamente toda la comunicación con el objeto remoto Signer y devuelve el valor de firma requerido como una matriz de bytes. El constructor de SignatureCalculator almacena el valor de firma en una variable de clases llamada signatureValue.

Como puede ver partir del Listado 7, la clase SignatureCalculator tiene un método llamado getSignatureValue(), que devuelve el valor de firma a la aplicación que llama. Recuerde que en el paso 3 del Listado 15 de la Parte 2 el método populateDataMembers() llamaba a este método getSignatureValue() para traer el valor de firma requerido.

Con esto termina mi comentario acerca del funcionamiento de la clase SignatureCalculator. Las secciones que vienen a continuación, explican cómo funciona el applet JavaCardSignatureCalculator.


Creación del applet JavaCardSignatureCalculator

Dos tipos de código Java Card

Ahora le mostraremos cómo crear el applet JavaCardSignatureCalculator, que puede mantener la clave criptográfica del usuario almacenada de manera protegida con él, y usar la clave para firmar datos del usuario.

Una serie de artículos de developerWorks escritos por Faheem Khan describe el ciclo de vida completo de las aplicaciones Java Card (ver Recursos). Puede consultar esta serie si desea conocer detalladamente la arquitectura y el ciclo de vida de las aplicaciones Java Card.

En este tutorial nos concentramos en el comentario las características de Java Card específicas para el applet JavaCardSignatureCalculator. Si sólo desea saber cómo funciona JavaCardSignatureCalculator, no es necesario que lea la serie de Khan.

Para poder implementar cualquier applet de Java Card (como JavaCardSignatureCalculator) usted escribe dos tipos de código Java:

  • El código que se ejecuta durante la instalación del applet en una tarjeta Java. Al que llamaremos código de instalación de JavaCardSignatureCalculator.
  • El código se ejecuta cada vez que un cliente accede al applet Java Card. Este código implementa la lógica de negocios del applet JavaCardSignatureCalculator. Por lo que lo llamaremos lógica de negocios del applet JavaCardSignatureCalculator.

Primero explicaremos el código de instalación de JavaCardSignatureCalculator y luego demostraremos cómo implementar la lógica de negocios para JavaCardSignatureCalculator.

Código de instalación de JavaCardSignatureCalculator

Usamos el Java Card Development Kit V2.2.1(Kit de herramientas de desarrollo Java Card V2.2.1 – JCDK) de Sun para implementar JavaCardSignatureCalculator. JCDK viene con una cantidad de herramientas que usted puede usar para desarrollar, instalar, y probar sus applets Java Card. Más adelante, demostraremos el uso de estas herramientas (en Ejecución de JavaCardSignatureCalculator).

Por el momento, observe nada más que todos los applets Java Card deben ser ampliados desde una clase llamada javacard.framework.Applet, que contiene los métodos auxiliares que necesitan sus applets. La clase Applet tiene un método estático llamado install() que es llamado automáticamente cada vez que usted instala un applet en una tarjeta Java. Usted escribirá su código de instalación en el método install() como se muestra en el Listado 10:

Listado 10. Método install()
public static void install(
         byte[] installationData, 
         short offset, 
         byte length) {

    new JavaCardSignatureCalculator(
        installationData, 
        offset, 
        length);

}//install()

El método install() toma todos los datos que usted necesita almacenar en su applet Java Card durante la instalación. El dato viene como un conjunto de tres parámetros llamados installationData, offset, y length.

installationData es una matriz de bytes que contiene el dato de instalación. El dato de instalación siempre contiene un valor AID. El dato de instalación también puede contener otros componentes de datos. Por ejemplo, en el caso del applet JavaCardSignatureCalculator, el dato de instalación contiene un nombre de usuario, una contraseña y una clave criptográfica. Mostraremos el uso de estos componentes de instalación en las subsecciones siguientes.

El parámetro offset especifica la posición donde comienza el dato de instalación en la matriz de bytes installationData. La longitud del parámetro length especifica la cantidad de bytes del dato de instalación.

Todas las tarjetas Java contienen un entorno de tiempo de ejecución llamado Java Card Runtime Environment (JCRE). JCRE llama al método estático install() cuando se instala un applet Java Card.

El método install() del Listado 10 sólo debe crear una instancia de la clase JavaCardSignatureCalculator que pase el dato de instalación al constructor de JavaCardSignatureCalculator. El constructor de JavaCardSignatureCalculator realiza el procesamiento necesario del dato de instalación.

Constructor de JavaCardSignatureCalculator

El Listado 11 muestra el constructor de JavaCardSignatureCalculator:

Listado 11. Constructor de JavaCardSignatureCalculator
public JavaCardSignatureCalculator(byte[] installationData, short offset, byte length) {

    //**** Step1 *****//
    //The offset byte contains length of AID.
    byte aidLength = installationData[offset];
    byte[] appletAID = new byte[aidLength];
    Util.arrayCopy(
        appletAID, (short)0, installationData, (short)(offset+1), (short)aidLength);

    if (appletAID.length == 0)
        register();
    else
        register(appletAID, (short)0, aidLength);

    //**** Step2 *****//
    short kdOffset = (short)( offset + aidLength + 1 );
    byte[] username = new byte[USERNAME_LENGTH];
    byte[] password = new byte[PASSWORD_LENGTH];
    Util.arrayCopy(
        username, 
        (short)0, 
        installationData, 
        (short)(kdOffset), 
        (short) USERNAME_LENGTH);

    Util.arrayCopy(
        password, 
        (short)0, 
        installationData, 
        (short)(kdOffset + USERNAME_LENGTH), 
        (short) PASSWORD_LENGTH);

    AuthenticationService authenticationService = 
            new AuthenticationService(username, password);

    //**** Step3 *****//
    byte[] cryptogaphicKey = new byte[KEY_LENGTH];
    Util.arrayCopy(
        cryptogaphicKey, 
        (short)0, installationData, 
        (short)(kdOffset + USERNAME_LENGTH + PASSWORD_LENGTH), 
        (short) KEY_LENGTH);

    Signer signer = 
        new Signer(
            authenticationService, 
            cryptogaphicKey);

    //**** Step4 *****//
    RMIService rmiService = new RMIService(signer);

    //**** Step5 *****//
    disp = new Dispatcher( (short)2);
    disp.addService(secService, Dispatcher.PROCESS_COMMAND);
    disp.addService(rmiService, Dispatcher.PROCESS_COMMAND);
}

El constructor de JavaCardSignatureCalculator procesa el dato de instalación en cinco pasos durante la instalación. Las subsecciones que vienen a continuación explicarán los cinco pasos del procesamiento.

Paso 1: Registro del AID para un applet

El primer paso es extraer el valor AID del dato de instalación. El AID identifica al applet de JavaCardSignatureCalculator. Por lo tanto, usted deberá decirle a JCRE que el applet de JavaCardSignatureCalculator se identifica usando este valor AID.

A tal fin, en el paso 1 del Listado 11 usted llama a un método denominado register(), que está definido en la clase Applet. El método register() toma el valor AID y lo registra en JCRE.

Ahora JavaCardSignatureCalculator está registrado en JCRE usando el valor AID. Esto quiere decir que JCRE podrá invocar al applet correcto cada vez que reciba una solicitud de JavaCardSignatureCalculator. Esto es importante porque un mismo JCRE puede alojar múltiples applets.

Paso 2: Creación de un servicio de autenticación

Ahora, en el paso 2, usted extrae el nombre de usuario y la contraseña del dato de instalación. Después, usa el nombre de usuario y la contraseña para crear una instancia de clase llamada AuthenticationService.

La clase AuthenticationService mantiene almacenados el nombre de usuario y la contraseña, y los usa más adelante para autenticar al cliente que realiza la solicitud. La clase AuthenticationService está demostrada en Implementación de la clase AuthenticationService.

Paso 3: Creación de una clase de firma

En el paso 3, usted crea la instancia de otra clase llamada Signer, cuyo método sign() produce el valor de firma requerido. Como puede advertir fácilmente, Signer es la clase cuyo método sign(), usted invocó remotamente desde SecureEverydayMIDlet en el paso 6 del Listado 7.

Para poder crear la instancia de la clase Signer, usted necesita la clave criptográfica del usuario (por ejemplo, una clave privada). Usted extrae la clave criptográfica del dato de instalación.

El constructor de Signer toma dos parámetros:

  • La clave criptográfica. La clase Signer usa esta clave criptográfica para computar el valor de firma.
  • El objeto AuthenticationService cuya instancia creó en el paso 2. La clase Signer usa la clase AuthenticationService para verificar el estado de autenticación del usuario antes de computar el valor de firma.

Más adelante explicaremos cómo funciona el constructor de Signer (en Implementación de la clase Signer).

Paso 4: Creación de un servicio RMI

En el paso 4 del Listado 11, el constructor de JavaCardSignatureCalculator crea una instancia de clase llamada RMIService. Esta clase es parte del marco de trabajo Java Card que maneja la invocación de objetos Java Card (como Signer) a través de aplicaciones remotas (como SecureEverydayMIDlet).

La clase RMIService implementa la lógica para invocar objetos Java Card de manera remota.

El constructor de RMIService toma solamente un parámetro, que es el objeto Signer que, supuestamente es invocado de manera remota. La clase RMIService gestiona de manera interna todos los detalles de bajo nivel acerca de cómo invocar al método Signer.sign(), cómo pasar el dato de la aplicación al método sign() y cómo mandar el valor de la firma de vuelta a SecureEverydayMIDlet.

Paso 5: Paso 5: Control de la invocación de servicios Java Card

Usted ha creado dos clases de servicio: AuthenticationService en el paso 2 y RMIService en el paso 4. Ahora debe especificar el orden en el que se invocarán estas dos clases de servicio.

Como es natural, AuthenticationService debe ser invocada antes que RMIService, para que AuthenticationService pueda autenticar si RMIService debe cumplir con la solicitud de un cliente, o no.

A tal fin, usted crea un objeto Dispatcher en el paso 5 del Listado 11. Dispatcher es también parte del marco de trabajo Java Card que maneja la invocación de diversos servicios que proporcionan las aplicaciones Java. Sólo debe llamar al método addService() del objeto Dispatcher pasando el objeto proveedor del servicio (como AuthenticationService y RMIService) junto con la llamada del método. Dispatcher gestionará internamente la invocación de los dos servicios en el mismo orden.

Observe que usted debe llamar dos veces al método addService(), una vez para AuthenticationService y otra vez para RMIService. Eso está hecho en el paso 5 del Listado 11.

Usted ha realizado todos los pasos necesarios para procesar el dato de instalación, por lo que el código de instalación de su tarjeta Java ya está completo. Ahora llegó el momento de aprender a ejecutar el código de instalación que acaba de escribir.

Ejecución del código de instalación Java Card

El kit de herramientas Java Wireless de Sun proporciona varias herramientas que usted puede usar para probar sus applets Java Card. Tiene dos métodos posibles para hacerlo:

  • El primer método lleva varios pasos en los que usted compila el applet de su tarjeta Java y crea su imagen de memoria. La imagen de memoria representa al applet igual que como aparecería dentro de una tarjeta Java. Por último, usted ejecuta el applet usando una herramienta emuladora que viene con el kit de herramientas Sun Java Wireless. Los artículos de Faheem Khan, que mencionamos anteriormente, describen los detalles de este método (ver Recursos).
  • El segundo método es sencillo. No requiere la creación de imágenes de memoria y le permite ejecutar sus applets de Java Card directamente. Este método es adecuado para desarrollo y depuración.

Usaremos el segundo método para desarrollar y depurar el applet JavaCardSignatureCalculator. Puede consultar los artículos de Khan si desea usar el primer método.

No obstante, el segundo método no le permite enviar el nombre de usuario, la contraseña, y la clave criptográfica al método install() que ya vio en el Listado 10. En su lugar, éste llama al método install() sin pasar dato de instalación alguno.

Por lo tanto, al usar el segundo método, usted definirá de manera predeterminada e inamovible, un nombre de usuario, contraseña, y clave criptográfica en el método install() como se muestra en el Listado 12 (un formulario corregido del Listado 10):

Listado 12. Formulario corregido del Método install()
    byte[] installationData, 
    short offset, 
    byte length) 
{
    //Hard-coded username
    //ASCII for 'alice' 
    byte[] username = {
        (byte)0x61,
        (byte)0x6c,
        (byte)0x69,
        (byte)0x63,
        (byte)0x65
    };

    //Hard-coded password
    //ASCII for 'keyPass'
    byte[] password = {
        (byte)0x6b,
        (byte)0x65,
        (byte)0x79,
        (byte)0x50,
        (byte)0x61,
        (byte)0x73,
        (byte)0x73
    };
        
    //Hard-coded cryptographic key
    byte[] cryptographyKey = {(byte)0x45, (byte)0x09, (byte)0x36, (byte)0xDE, ...};
   
    //Copy bits of installation data into the installationData byte array. 
    installationData = putTogatherInstallationData (
        username, 
        password, 
        cryptographyKey
    ); 

    new JavaCardSignatureCalculator(
        installationData, 
        (short)0, 
        (byte)installationData.length);

}//install()

Más adelante, demostraremos el uso del segundo método (en Ejecución de JavaCardSignatureCalculator) después de la creación de la lógica de negocios del applet JavaCardSignatureCalculator de la sección siguiente.


Implementación de la lógica de negocios de JavaCardSignatureCalculator

Invocación de JavaCardSignatureCalculator

El paso siguiente es comentar cómo se implementa la lógica de negocios del applet JavaCardSignatureCalculator La lógica de negocios se ejecuta cada vez que SecureEverydayMIDlet invoca al applet JavaCardSignatureCalculator. Entonces, primero explicaremos la secuencia de eventos que suceden cuando se invoca a JavaCardSignatureCalculator de manera remota.

Vea la Figura 3, que muestra la secuencia de eventos que suceden cuando SecureEverydayMIDlet invoca a JavaCardSignatureCalculator:

Figura 3. Secuencia de eventos que suceden cuando SecureEverydayMIDlet invoca a JavaCardSignatureCalculator

En la Figura 3 puede encontrar los siguientes eventos:

  1. La primera tarea es seleccionar el applet que SecureEverydayMIDlet desea invocar. A tal fin, SecureEverydayMIDlet envía una APDU SELECT a JCRE. La APDU SELECT contiene el AID del applet JavaCardSignatureCalculator.
  2. JCRE extrae el AID de la APDU SELECT e identifica el applet JavaCardSignatureCalculator del AID.
  3. JCRE envía la respuesta de la APDU SELECT, estableciendo una sesión de comunicación con SecureEverydayMIDlet.
  4. SecureEverydayMIDlet invoca a JavaCardSignatureCalculator mediante el envío de una APDU INVOKE a JCRE. La APDU INVOKE contiene el dato de la aplicación, que consiste en tres componentes: el nombre de usuario y la contraseña del cliente que realiza la solicitud, y el dato que debe ser firmado. El nombre de usuario y la contraseña serán usados más adelante (en el paso 8) para autenticar al cliente que realiza la solicitud antes de firmar el dato.
  5. JCRE invoca al applet JavaCardSignatureCalculator applet. Invocar a un applet Java Card significa llamar a un método denominado process() del applet. El método process() del applet JavaCardSignatureCalculator contiene una sola línea de código:
    public void process(APDU apdu) throws ISOException { disp.process(apdu); }
  6. El método process() mostrado arriba le solicita a Dispatcher que comience con los servicios de invocación que usted agregó en Dispatcher antes, en el paso 5 del Listado 11 durante la instalación.
  7. El Dispatcher obtiene el control e invoca al servicio de autenticación.
  8. El servicio de autenticación realiza la autenticación del usuario y transfiere el control de vuelta al Dispatcher.
  9. El Dispatcher invoca al servicio RMI.
  10. El servicio RMI verifica con el servicio de autenticación si la autenticación del usuario tuvo éxito o falló. Si la autenticación del usuario fue exitosa, el servicio RMI le solicita a la clase Signer que compute el valor de firma requerido.
  11. El servicio RMI envía el valor de firma de vuelta a JCRE.
  12. JCRE envía el valor de firma a SecureEverydayMIDlet.

Como puede ver a partir de estos 12 eventos, usted debe implementar únicamente las clases AuthenticationService y Signer. El marco de trabajo Java Card se encarga de todo lo demás. Por lo que, las subsecciones siguientes demostrarán cómo implementar las clases AuthenticationService y Signer.

Implementación de la clase AuthenticationService

Recuerde que, en el paso 2 del Listado 11 el constructor de JavaCardSignatureCalculator extrae el nombre del usuario y la contraseña del dato de instalación y se lo pasa al creador de AuthenticationService El constructor de AuthenticationService (que aparece en el Listado 13) almacena el nombre del usuario y la contraseña como matrices de bytes en variables de clase. La clase AuthenticationService usa estas matrices de bytes para autenticar a un cliente que realiza la solicitud.

Listado 13. Constructor de AuthenticationService
public class AuthenticationService extends BasicService implements SecurityService {

    //Class-level variables  
    private byte[] username = null;
    private byte[] password = null;    
    private byte authenticationResult;

    public AuthenticationService (byte[] username, byte[] password) {
        this.username = username;
        this.password = password;
        authenticationResult = (short) 0;
    }//AuthenticationService

    //Other methods of the AuthenticationService class
}

Observe también que, en el Listado 13 el contructor de AuthenticationService inicializa una variable de clase llamada authenticationResult, que representa el resultado del proceso de autenticación. El valor de authenticationResult se establecerá según el resultado del proceso de autenticación.

AuthenticationService es una clase proveedora de servicios, que Dispatcher invoca, tal como se menciona en el paso 7 de la Figura 3. Para poder invocar a la clase AuthenticationService, el Dispatcher llama a un método denominado AuthenticationService.processCommand().

El método processCommand() está definido en una interfaz llamada Service. La interfaz Service es parte del marco de trabajo Java Card y contiene métodos que implementará cualquier clase proveedora de servicios para poder proporcionar diferentes tipos de servicios. En el caso de AuthenticationService, usted sólo tendrá que implementar el método processCommand() que comentaremos en la subsección siguiente.

El marco de trabajo Java Card también proporciona una clase de utilidad llamada BasicService, que contiene implementaciones esqueleto de todos los métodos de la interfaz Service. Por lo tanto, en lugar de implementar directamente la interfaz Service, es mejor que AuthenticationService amplíe la clase BasicService y anule únicamente el método processCommand().

La clase BasicService también proporciona varios métodos de utilidad que AuthenticationService usará para procesar el dato de la aplicación durante la autenticación.

La clase AuthenticationService también implementa una interfaz llamada SecurityService. La interfaz SecurityService contiene métodos relativos a la seguridad que deben implementar los servicios de seguridad. Por ejemplo, AuthenticationService implementa un método llamado isAuthenticated() (que aparece en el Listado 14), que devuelve el valor de la variable authenticationResult. La clase Signer llama al método isAuthenticated() para verificar los resultados de la autenticación antes de calcular el valor de firma.

Otro método definido en la clase AuthenticationServicese llama reset(). Éste restablece el valor de authenticationResult nuevamente en cero. La clase Signer vuelve a establecer authenticationResult en cero después de calcular el valor de firma. Esto asegura que el resultado de la autenticación exitosa se use una sola vez para el cálculo de firma.

Listado 14. Métodos isAuthenticated() y reset()
public class AuthenticationService 
    extends BasicService implements SecurityService 
{
    //Constructor and data members of the AuthenticationService class

    public boolean isAuthenticated(short principal) throws ServiceException {
         return (authenticationResult == principal);
    }//isAuthenticated

    public void reset(){
        authenticationResult = (short) 0;
    }//reset()

    //Other methods of the AuthenticationService class

}//AuthenticationService

La siguiente subsección comenta la implementación del método processCommand(), que proporciona la funcionalidad principal de la clase AuthenticationService.

Implementación del método processCommand()

La implementación del método processCommand() se muestra en el Listado 15:

Listado 15. Método processCommand()
public class AuthenticationService 
   extends BasicService implements SecurityService 
{
   /***Constructor and data members will fit here.***/

   public boolean processCommand(APDU apdu) 
   {
      /**** Step 1 ****/
      if (getINS(apdu) == (byte)0x38) 
      {
         /**** Step 2 ****/
         receiveInData(apdu);

         /**** Step 3 ****/
         byte[] name2BAuthenticated = new byte[5];
         Util.arrayCopy(
            apdu.getBuffer(), (short)10, name2BAuthenticated, (short)0, (short)5);

         byte[] pass2BAuthenticated = new byte[7];
         Util.arrayCopy(
            apdu.getBuffer(), (short)15, pass2BAuthenticated, (short)0, (short)7);

         /**** Step 4 ****/
         if (Util.arrayCompare(
            name2BAuthenticated, (short)0, username, (short)0, (short) 5) == 0 
            && Util.arrayCompare(
            pass2BAuthenticated, (short)0, password, (short)0, (short)7) == 0)
            authenticationResult = PRINCIPAL_CARDHOLDER;
      }
      return false;

   }//processCommand

   /*** Other methods will fit here.***/

}//AuthenticationService

El método processCommand() realiza la autenticación del usuario y almacena el resultado de la autenticación en la variable authenticationResult.

Cuando Dispatcher llama al método processCommand() en el paso 7 de la Figura 3, pasa un objeto APDU como parámetro de entrada junto con la llamada del método. Este objeto APDU contiene una matriz de bytes del búfer, en la que usted puede leer el dato de la aplicación. (Recuerde que el constructor de SignatureCalculator enviaba el dato de la aplicación cuando invocaba el método sign() en el paso 6 del Listado 7.)

El método processCommand() del Listado 15 autentica al usuario en cuatro pasos:

  1. El método processCommand() verifica si el objeto de entrada APDU representa a una APDU INVOKE.

    Observe que el método processCommand() usa un método de utilidad llamado getINS() para verificar el tipo de APDU de entrada. El método getINS() pertenece a la clase BasicService y toma un objeto APDU como parámetro de entrada. Devuelve el tipo de objeto APDU como un identificador de bytes.

    El método processCommand() no hace procesamiento alguno si la APDU de entrada es otra cosa que no sea una APDU INVOKE.

  2. processCommand() llama a un método denominado receiveInData(), que toma al objeto APDU de entrada y llena la matriz de datos de su búfer con el dato de la aplicación. Ahora el objeto APDU ajusta los tres componentes del dato de la aplicación que usted vio en el paso 4 de la Figura 3.
  3. El método processCommand() extrae el nombre de usuario y la contraseña del cliente que realiza la solicitud (que debe ser autenticado), de la matriz de bytes del búfer. El método processCommand() almacena el nombre de usuario y la contraseña en matrices de bytes llamadas user2BAuthenticated y password2BAuthenticated, respectivamente.
  4. Este paso muestra el proceso de autenticación real: el método processCommand() compara el nombre de usuario y la contraseña que deben ser autenticados (extraídos en el paso 3), con el nombre de usuario y la contraseña que usted usó para crear la instancia del objeto AuthenticationService en el paso 2 del Listado 11 durante la instalación.

    Si la comparación es exitosa, el método processCommand() establece el valor de la variable authenticationResult en PRINCIPAL_CARDHOLDER.

Usted debe saber lo que se entiende por PRINCIPAL_CARDHOLDER y por qué el método processCommand() lo usa. Java Card admite tres resultados posibles para que un proceso de autenticación sea exitoso, lo que significa que el proceso de autenticación puede encontrar que el cliente que realiza la solicitud sea uno de los siguientes:

  • El titular de la tarjeta
  • El proveedor del servicio
  • El emisor de la tarjeta

Para el caso de la clase AuthenticationService la única posibilidad relevante es la primera. O sea, la clase autenticación de servicio sólo está interesada en verificar si el cliente que realiza la solicitud es el titular de la tarjeta Java. Por eso, si el nombre de usuario y la contraseña coinciden, el método processCommand() establece el valor de authenticationResult en PRINCIPAL_CARDHOLDER. Esto quiere decir que el cliente que realiza la solicitud es el titular de la tarjeta Java.

Usted ya vio el proceso de autenticación. Recuerde que, en el paso 9 de la Figura 3 se produce la autenticación con éxito Dispatcher invoca a RMIService. RMIService a su vez le solicita a la clase Signer que compute el valor de firma requerido. La subsección que viene a continuación, describe cómo la clase Signer realiza el cálculo de firma.

Implementación de la clase Signer

La clase Signer (que aparece en el Listado 16) es la responsable del cálculo del valor de firma. Recuerde que, en el paso 3 del Listado 11 el constructor de JavaCardSignatureCalculator creaba la instancia de la clase Signer, pasando una clave criptográfica y un objeto AuthenticationService al constructor de Signer El constructor de Signer almacena los dos objetos como variables de clase.

Listado 16. Clase Signer
public class Signer 
   extends CardRemoteObject implements SignatureMethod
{
   
   private AuthenticationService authenticationService;
   byte[] cryptographicKey = null;
   
   public Signer(
      AuthenticationService authenticationService, 
      byte[] key)
   {
      this.authenticationService = authenticationService;
      cryptographicKey = key;
   }// Signer


   public byte[] sign(
       byte[] username, 
       byte[] password, 
       byte[] data2BSigned) throws RemoteException, UserException 
   {
       
      /*** Step 1***/ 
      if(!authenticationService.isAuthenticated(SecurityService.PRINCIPAL_CARDHOLDER)) {
         UserException.throwIt(ISO7816.SW_DATA_INVALID);
      }
      authenticationService.reset();
      
      /*** Step 2***/ 
      DSASignature signer = new DSASignature();

      /*** Step 3***/ 
      byte[] completeXMLStructure  = authorSignedInfo (data2BSigned);
      
      /*** Step 4***/ 
      byte[] signatureValue = 
         signer.getSignatureValue(
            cryptographicKey,
            completeXMLStructure
         );

      /*** Step 5***/ 
      return signatureValue;
      
   }//sign()

}//Signer

Observe en el Listado 16 que la clase Signer amplía una clase llamada CardRemoteObject. La clase CardRemoteObject es parte del marco de trabajo Java Card y contiene un método llamado export(). El método export() exporta la referencia de una instancia de CardRemoteObject para que la instancia esté disponible para su acceso desde aplicaciones remotas (como SecureEverydayMIDlet).

Por lo tanto, al hacer la ampliación desde CardRemoteObject, usted ha hecho que su clase Signer pueda ser accedida por SecureEverydayMIDlet. Observe que usted no deberá preocuparse por el momento y la forma en que el método export() será llamado. La clase RMIService se ocupará de manera interna de llamar al método export(). Es por eso que usted pasó su objeto Signer al constructor de RMIService en el paso 4 del Listado 11.

La clase Signer contiene un solo método público, llamado sign(), que computa el valor de firma. SecureEverydayMIDlet llamó de manera remota a este método sign() en el paso 6 del Listado 7, pasando el nombre de usuario, la contraseña y el dato que debe ser firmado junto con la llamada del método. La subsección que viene a continuación, explica cómo funciona el método Signer.sign().

Implementación del método sign()

El método sign() que aparece en el Listado 16) toma el dato de la aplicación como una matriz de bytes y computa el valor de firma en el dato de la aplicación, mediante los cinco pasos siguientes:

Paso 1

El método sign() verifica el estado de autenticación del cliente que realiza la solicitud, mediante una llamada al método AuthenticationService.isAuthenticated(), que comenté en Implementación de la clase AuthenticationService.

El método sign() pasa SecurityService.PRINCIPAL_CARDHOLDER como parámetro al método isAuthenticated(). Esto significa que el método sign() desea verificar si el cliente que realiza la solicitud es el titular de la tarjeta Java. Si isAuthenticated() devuelve false (falso) (lo que quiere decir que el cliente que realiza la solicitud no es el titular de la tarjeta Java), el método sign() no hace procesamiento alguno y arroja una excepción.

Si isAuthenticated() devuelve true (veradero) (lo que quiere decir que el cliente que realiza la solicitud es el titular de la tarjeta Java), el método sign() restablece el estado de autenticación mediante una llamada al método reset() de la clase AuthenticationService. Esto asegura que el cliente será autenticado cada vez que envíe una solicitud sign.

Paso 2

El método sign() crea una instancia de clase auxiliar llamada DSASignature. La clase DSASignature computa el valor de firma según un algoritmo de firma llamado DSA, un algoritmo de firma muy conocido, definido por el National Institute of Standards and Technology (Instituto Nacional de Estándares y Tecnología – NIST) (ver Recursos).

Nosotros implementamos el algoritmo de firma DSA en este tutorial, ajustando la implementación del algoritmo dentro de la clase DSASignature. Hacer esto tiene una ventaja de elasticidad. Si usted quiere implementar otros algoritmos de firma, puede implementar sus propias clases e integrarlas al método sign().

Implementaremos la clase DSASignature más adelante (en Implementación de DSASignature.getSignatureValue()). La clase DSASignature expone un método llamado getSignatureValue(), que toma dos parámetros:

  • El dato que debe ser firmado
  • La clave criptográfica que se usará cuando se compute el valor de firma

El método getSignatureValue() computa y devuelve el valor de firma.

Paso 3

El método sign() crea la estructura XML cuyo valor de firma usted quiere computar, que es el elemento SignedInfo del Listado 6 de la parte 1, que se reproduce aquí como Listado 17 para consulta rápida:

Listado 17. Elemento SignedInfo
<SignedInfo>
   <CanonicalizationMethod
      Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">
   </CanonicalizationMethod>
   <SignatureMethod
      Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1">
   </SignatureMethod>
   <Reference URI="//soap:Envelope/soap:Body/*">

      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
      <DigestValue>
         Xs4gwdg456sEsdaPf=
      </DigestValue>
   </Reference>
</SignedInfo>

Observe que usted debe firmar el elemento SignedInfo del Listado 17. El elemento inferior de DigestValue del elemento SignedInfo del Listado 17 ajusta el valor digest que usted computó antes en su dato XML en la sección Creación de la instancia miembro del dato digestValue de la Parte 2.

Por lo tanto, si usted firma la estructura completa de SignedInfo, indirectamente forma el dato XML cuyo valor digest está ajustado dentro del elemento SignedInfo. La firma del valor digest es computacionalmente menos cara, si se la compara con la firma del dato real.

No obstante, observe que el dato que debe ser firmado (enviado como tercer parámetro a la llamada del método sign()) no contiene toda la estructura de SignedInfo. En cambio, contiene únicamente el valor digest con el fin de minimizar el tamaño del dato intercambiado entre SecureEverydayMIDlet y JavaCardSignatureCalculator.

Por lo tanto, en el paso 3 del Listado 16 usted crea el elemento SignedInfo del Listado 17.

La autoría del elemento SignedInfo es muy sencilla. Tiene un solo componente dinámico (o sea, el valor digest) en la cadena textual, que representa a la estructura SignedInfo. El resto de la estructura no es dinámico y por lo tanto puede escribirse en el código fuente como se muestra en el método auxiliar privado authorSignedInfo() del Listado 18:

Listado 18. Método authorSignedInfo()
private byte[] authorSignedInfo(byte[] base64EncodedDigestValue){
   byte[] startSignedInfo = {(byte)0x3c, (byte)0x53, (byte)0x69,.. );
   byte[] endSignedInfo   = {(byte)0x65, (byte)0x74, (byte)0x68,.. );

   short length = 
      (short) (
         startSignedInfo.length + base64EncodedDigestValue.length + endSigndInfo.length);
      
   byte[] signedInfoStructure = new byte [length];
 
   for (short i = (short) 0; i < startSignedInfo.length ; i++)
      signedInfoStructure[i] = startSignedInfo [i];

   for (short j = (short) startSignedInfo.length ;  j < signedInfoStructure.length ; j++) 
   {
      if (j < (short) (startSignedInfo.length + base64EncodedDigestValue.length))
         signedInfoStructure[j] = 
            base64EncodedDigestValue [ (j - startSignedInfo.length)];
      else 
         signedInfoStructure[j] = 
            endSigndInfo [
               (j - (startSignedInfo.length + base64EncodedDigestValue.length))];
   }
      
   return signedInfoStructure;
}// authorSignedInfo()

Usted tiene ahora la estructura SignedInfo completa que debe ser firmada. La clase Signer ya tiene la clave criptográfica, que se pasó en el paso 3 del Listado 11.

Paso 4

Por lo que, en el paso 4, el método sign() llama al método DSASignature.getSignatureValue(), y pasa la estructura XML completa que debe ser firmada y la clave criptográfica que se usará para computar el valor de firma junto con la llamada del método getSignatureValue() El método getSignatureValue() (que explicaré más adelante (en Implementación de DSASignature.getSignatureValue()) devuelve una matriz de bytes que contiene el valor de firma requerido. El método sign() almacena la matriz de bytes devuelta en una variable llamada signatureValue.

Paso 5

Por último, el método sign() manda de vuelta signatureValue al servicio RMI, para que el servicio RMI pueda proceder con el paso 11 de la Figura 3 (o sea, enviar el valor de firma de vuelta a JCRE).

Implementación de DSASignature.getSignatureValue()

Ahora implementaremos el algoritmo de firma DSA en el método DSASignature.getSignatureValue().

Hemos tomado prestada la mayor parte de la lógica de programación del método DSASignature.getSignatureValue() de una implementación DSA de código abierto de The Legion of the Bouncy Castle (ver Recursos).

Bouncy Castle proporciona una implementación de código abierto de diversos algoritmos criptográficos conocidos (incluso DSA), tanto para J2SE como para J2ME. Es bastante sencillo adoptar el código J2ME de Bouncy para ejecutarlo en un applet Java Card. Sólo deberá asegurarse de no usar los siguientes tipos de dato J2ME en el código Java Card:

  • String
  • Float
  • Double
  • Long

Puede percibir que estos tipos de dato J2ME no están disponibles en las aplicaciones Java Card. La descarga de código fuente de este tutorial contiene una carpeta llamada JavaCardSignatureCalculator, que contiene todas las clases del applet JavaCardSignatureCalculator incluso aquellas que se toman prestadas de Bouncy Castle (ver Descargas).

Generación de la clave criptográfica

Consulte el Listado 12, en el que definió de manera predeterminada e inamovible la clave criptográfica del método install(). Ahora le mostraremos cómo generar esa clave que usted definió de manera predeterminada e inamovible en el Listado 12.

Puede usar una herramienta J2SE llamada keytool para generar una clave criptográfica DSA. El siguiente argumento de la línea de comandos le muestra cómo hacerlo:
keytool -genkey -keyalg DSA -dname "CN=Alice, OU=EmailService, O=EverydayServices, C=PK" -alias alice -keypass keyPass -keystore keystore -storepass storePass

Este comando genera una clave para el usuario llamada alice. Una contraseña para la clave será keyPass. La clave se almacenará en un almacén de claves llamado keyStore. La contraseña para el almacén de claves será storePass.

Aunque usted generó la clave, también deberá definirla de manera predeterminada e inamovible en su método install() (consulte el Listado 12). Entonces, usted necesita esta clave del tipo de una matriz de bytes.

Hemos escrito una aplicación J2SE sencilla llamada StoreKeyAsBytes, que se incluye en la descarga del código fuente de este tutorial (ver Descargas). Puede ejecutar StoreKeyAsBytes para traer su clave criptográfica del tipo de una matriz de bytes, desde el almacén de claves.

Hemos incluido la matriz de bytes en un archivo de texto llamado aliceKey.txt, que puede encontrar en la descarga del código fuente. Hemos usado la matriz de bytes del archivo aliceKey.txt para definir de manera predeterminada e inamovible el valor de clave en el Listado 12.

Ejecución de JavaCardSignatureCalculator

Ahora puede probar su applet JavaCardSignatureCalculator usando el método que presentamos antes (en Ejecución del código de instalación Java Card).

El primer paso es compilar todas las clases JavaCardSignatureCalculator como clases Java normales. Usted deberá tener los siguientes archivos JAR en su classpath:

  • api.jar
  • javacardframework.jar

Podrá encontrar estos archivos JAR en su instalación JCDK.

Tanto el código fuente como el formulario compilado de todas las clases JavaCardSignatureCalculator están en la descarga del código fuente (ver Descargas).

El segundo método requiere el uso de una herramienta llamada Java Card Workstation Development Environment (Entorno de desarrollo de la estación de trabajo Java Card – JCWDE). Para poder probar el applet JavaCardSignatureCalculator en JCWDE, deberá realizar los cuatro pasos siguientes:

  1. Copiar las clases compiladas de la aplicación JavaCardSignatureCalculator en la carpeta de clases del directorio de la instalación JCDK. Por ejemplo, si usted instaló JCDK en su disco X: , encontrará la carpeta de clases en X:\java_card_kit-2_2_1\samples\classes.
  2. Abrir un archivo de configuración llamado jcwde_rmi.app, que puede encontrar en el directorio X:\java_card_kit-2_2_1\samples\src\demo\jcwde. Deberá editar este archivo para incluir el AID de la aplicación JavaCardSignatureCalculator. Puede elegir el AID de sus applets Java Card. Un AID tiene una longitud de entre cinco y 16 bytes.

    Por ejemplo, nosotros elegimos 0xa0:0x0:0x0:0x0:0x62:0x3:0x1:0xc:0x8:0x2 como AID de JavaCardSignatureCalculator. El archivo jcwde_rmi.app editado está en la descarga del código fuente (ver Descargas).

  3. Establezca una variable de entorno llamada JC_HOME que apunte a la carpeta bin de su instalación JCDK. Por ejemplo, si usted instaló JCDK en el disco X: el valor de JC_HOME será X:\java_card_kit-2_2_1\bin.
  4. Ahora puede ejecutar JavaCardSignatureCalculator en JCWDE usando el siguiente argumento de la línea de comandos:
    jcwde jcwde_rmi.app

JavaCardSignatureCalculator ya está en producción y a la espera de solicitudes de firma desde SecureEverydayMIDlet. La subsección que viene a continuación explica cómo probar el último formulario de SecureEverydayMIDlet con JavaCardSignatureCalculator.

Ejecución de la aplicación de firma

Recuerde el mecanismo de pruebas que usó antes para probar SecureEverydayMIDlet (en Prueba del método getBase64EncodedValue). Usted tiene que ejecutar el mismo mecanismo con el último formulario de la clase SignatureCalculator.

La descarga del código fuente incluye todas las clases SecureEverydayMIDlet (código fuente como así también el formulario compilado) en una carpeta llamada Section5 (ver Descargas). La carpeta Section5 también incluye una aplicación Java llamada SignatureVerifier, que escucha mensajes SOAP firmados (por ejemplo, de clientes inalámbricos), procesa los mensajes SOAP, verifica los valores digest y de firma; e imprime los resultados de la verificación en la consola de salida.

Cuando usted ejecuta el mecanismo de prueba, se produce la siguiente secuencia de eventos:

  1. SecureEverydayMIDlet calcula el valor digest sobre su mensaje SOAP.
  2. SecureEverydayMIDlet le habla a JavaCardSignatureCalculator para traer el valor de firma del mensaje SOAP.
  3. SecureEverydayMIDlet crea el formulario firmado del mensaje SOAP y lo envía a SignatureVerifier.
  4. SignatureVerifier verifica el valor digest, e imprime el resultado de la verificación del digest en la consola de salida.
  5. SignatureVerifier verifica el valor de firma, e imprime el resultado de la verificación de firma en la consola de salida.

Su SecureEverydayMIDlet ahora está completo. Con esto finaliza nuestro comentario sobre protección de clases stub WSA.

La sección siguiente crea una herramienta mejoradora de stubs que puede realizar la mayor parte del difícil trabajo de programación que usted ha hecho en forma manual hasta ahora.


Implementación de una herramienta mejoradora de stubs

Pasos para la mejora de stubs

Usted hizo muchos pasos para mejorar clases stub. Ahora verá que, todos los pasos para la mejora de stubs que hizo manualmente, pueden implementarse automáticamente por medio de una Herramienta mejoradora de stubs.

Antes de comenzar a desarrollar la herramienta mejoradora de stubs, hacemos un breve resumen de los pasos para la mejora de stubs que usted hizo en forma manual:

  1. Usted mejoró la clase stub de servicios seguros (la clase SecureMobileEmailService_PortType_Stub en la sección Mejora de la clase stub de servicios seguros de la Parte 2. Al mejorar la clase stub de servicios seguros, usted mejoró su constructor, los miembros del dato, y los métodos de servicio.
  2. Usted mejoró las clases stub relacionadas con la firma en la sección Mejora de clases stub de firma de la Parte 2.
  3. Usted implementó las cuatro clases auxiliares llamadas CanonicalAuthor, SHA1DigestCalculator, Base64Encoder, y SignatureCalculator.

La herramienta mejoradora de stubs tomará la clase stub de servicios seguros generada por la herramienta generadora de stubs WSA y realizará todos estos pasos. Esto quiere decir que la herramienta mejoradora de stubs hará la mayor parte del difícil trabajo que usted tuvo que hacer para proteger a su cliente SOAP basado en J2ME.

Arquitectura de la herramienta mejoradora basada en stubs

La herramienta mejoradora de stubs contiene una clase llamada StubEnhancer con un método main() que se muestra en el el Listado 19:

Listado 19. Clase StubEnhancer
public class StubEnhancer {
 
    public static void main (String[] args) {
        /*** Step 1 ***/
        String secureServiceStubClassName = args[0];
        StubEnhancer enhancer = new StubEnhancer ();
        StringBuffer stringBuffer = 
            enhancer.readFileAsStringBufferObject (secureServiceStubClassName);

         /*** Step 2 ***/
        SecureServiceStubEnhancer secureServiceStub = 
            new SecureServiceStubEnhancer (stringBuffer);
         
        /*** Step 3 ***/
        secureServiceStub.writeFile();
         
        /*** Step 4 ***/
        String signatureClassName =  secureServiceStub.getSignatureClassName();
        String packageName        =  secureServiceStub.getPackageName();

        /*** Step 5 ***/
        stringBuffer = 
             enhancer.readFileAsStringBufferObject (signatureClassName);
            
        /*** Step 6 ***/
        SignatureRelatedEnhancer signatureEnhancer = 
            new SignatureRelatedEnhancer (
                stringBuffer,
                packageName,
                signatureClassName
            );

        /*** Step 7 ***/
        signatureEnhancer.writeFiles ();
        
        /*** Step 8 ***/
        HelperClassesGenerator helperClassesGenerator = 
            new HelperClassesGenerator (
                packageName,
                signatureEnhancer.getSignedInfoClassName()                    
        );  

        /*** Step 9 ***/        
        helperClassesGenerator.writeFiles();

     }//main() method

    /*** Other methods of StubEnhancer class will fit here.***/

}//StubEnhancer

Usted puede invocar al método main() de la clase StubEnhancer usando el siguiente argumento de la línea de comandos:
java StubEnhancer SecureMobileEmailService_PortType_Stub.java

A partir del argumento de la línea de comandos usted puede ver que la clase StubEnhancer necesita conocer el nombre del archivo del stub de servicios seguros. StubEnhancer sigue nueve pasos para producir el conjunto de clases stub mejoradas requerido.

La subsección que viene a continuación describirá los nueve pasos para la mejora de stubs.

Pasos para la mejora de stubs

El método main() del Listado 19 realiza los nueve pasos de mejora siguientes:

Paso 1

El método main() lee el nombre de la clase stub de servicios seguros desde la línea de comandos. Luego abre un archivo y lo lee dentro de un objeto StringBuffer.

Paso 2

El método main() crea una instancia de clase llamada SecureServiceStubEnhancer, pasando el objeto StringBuffer desde el paso 1 al constructor SecureServiceStubEnhancer La clase SecureServiceStubEnhancer procesa la clase stub de servicios seguros y crea su versión mejorada. El funcionamiento de la clase SecureServiceStubEnhancer está explicada enMejora de la clase stub de servicios seguros.

Paso 3

El método main() llama al método writeFile() de la clase SecureSeviceStubEnhancer, que guarda la versión mejorada del stub de servicios seguros con el nombre de archivo correcto en una carpeta llamada EnhancedStubClasses.

Paso 4

La clase SecureServiceStubEnhancer también lee el nombre de la clase de firma con calificación completa desde la clase stub de servicios seguros. Usted debe conocer el nombre de calificación completa para poder generar versiones mejoradas de clases stub relacionadas con firmas. La clase SecureServiceStubEnhancer expone los métodos getSignatureClassName() y getSignaturePackageName(), que el método main() usa para leer el nombre de calificación completa.

Paso 5

El método main() lee la clase de firma usando el nombre de clase desde el paso 4. Éste carga la clase de firma en un objeto StringBuffer.

Paso 6

El método main() crea una instancia de otra clase llamada SignatureRelatedEnhancer, pasando el nombre de calificación completa como así también la clase de firma al constructor SignatureRelatedEnhancer. La clase SignatureRelatedEnhancer genera versiones mejoradas de las clases relacionadas con firmas. La clase SignatureRelatedEnhancer está explicada en Generación de clases relacionadas con firmas y clases auxiliares.

Paso 7

El método main() llama al método writeFiles() de la clase SignatureRelatedEnhancer, que guarda todas las clases relacionadas con firmas con los nombres de archivo correctos en la carpeta EnhancedStubClasses.

Paso 8

El método main() crea una instancia de clase llamada HelperClassesGenerator. El HelperClassesGenerator (del que hablaremos más adelante en Generación de clases relacionadas con firmas y clases auxiliares) generará las cuatro clases auxiliares (CanonicalAuthor, SHA1DigestCalculator, Base64Encoder, y SignatureCalculator).

Paso 9

El método main() llama al método writeFIles() de la clase HelperClassesGenerator, que guarda las clases auxiliares.

Usted vio que la clase StubEnhancer usa tres clases: SecureServiceStubEnhancer, SignatureRelatedEnhancer, y HelperClassesGenerator. El resto de esta sección demostrará el funcionamiento de estas tres clases.

Mejora de la clase stub de servicios seguros

Usted realizó tres pasos manuales para mejorar la clase stub de servicios seguros en la Parte 2 de este tutorial:

  1. Usted mejoró el contructor de stubs en Cómo un MIDLet J2ME usará getSubjects().
  2. Usted mejoró miembros de datos de la clase stub en Mejora de la clase stub miembros de datos de una clase stub de servicios seguros.
  3. Usted mejoró métodos de servicios de la clase stub en Mejora de la implementación del método getSubjects().

La clase SecureServiceStubEnhancer tiene tres métodos: enhanceConstructor(), enhanceDataMembers(), y enhanceServiceMethods(). El constructor de SecureServiceStubEnhancer llama a estos tres métodos como se muestra en el Listado 20:

Listado 20. Constructor de SecureServiceStubEnhancer
public SecureServiceStubEnhancer (StringBuffer secureServiceStub2BEnhanced) 
{
    if (secureServiceStubData != null) 
    {
        StringBuffer secureServiceStubWithEnhancedConstructor = 
            enhanceConstructor (secureServiceStub2BEnhanced);

        StringBuffer secureServiceStubWithEnhancedDataMembers = 
            enhanceDataMembers (secureServiceStubWithEnhancedConstructor);

        this.secureServiceStubData = 
            enhanceServiceMethods (secureServiceStubWithEnhancedDataMembers);            
    }
    else 
        showErrorMessage();

}//SecureServiceStubEnhancer

Ahora demostraremos cómo funcionan los tres métodos de la clase SecureServiceStubEnhancer.

Mejora del constructor de stubs de servicios seguros

El Listado 21 muestra el formulario original del constructor de stubs de servicios seguros generado por la herramienta generadora de stubs WSA:

Listado 21. Formulario original del constructor de stubs de servicios seguros
public SecureMobileEmailService_PortType_Stub() 
{
    _propertyNames = new String[] {ENDPOINT_ADDRESS_PROPERTY};
    _propertyValues = new Object[] {"http://localhost:8090"};
}

Usted usó el formulario mejorado del constructor de stubs en el paso 1 del Listado 8 de la Parte 2. El formulario mejorado se parece al Listado 22:

Listado 22. Formulario mejorado del constructor de stubs de servicios seguros
public SecureMobileEmailService_PortType_Stub (
    String username, 
    String password) 
{
    this.username = username;
    this.password = password;
    _propertyNames = new String[] {ENDPOINT_ADDRESS_PROPERTY};
    _propertyValues = new Object[] {"http://localhost:8090"};
}

Puede comparar el Listado 21 con el Listado 22 y observar que sólo debe agregar los parámetros del nombre de usuario y de la contraseña al constructor.

El método enhanceConstructor() (que aparece en el Listado 23) de la clase SecureServiceStubEnhancer toma la clase stub de servicios seguros como un parámetro, mejora su constructor con el agregado de los parámetros de nombre de usuario y de contraseña, y devuelve el formulario mejorado de la clase.

Listado 23. Método enhanceConstructor()
private StringBuffer enhanceConstructor (StringBuffer secureServiceStub2BEnhanced) {
    
    String enhancedConstructorData = 
        createTwoParameterEnhancedConstructor(secureServiceStub2BEnhanced);

    int constructorIndex = 
        secureServiceStub2BEnhanced.indexOf("public "+stubClassName);
 
    return secureServiceStub2BEnhanced.insert(
        constructorIndex, 
        enhancedConstructorData);

}//enhanceConstructor

Mejora de los miembros de datos

Cuando mejoró los miembros de los datos en la subsección Enhancing data members of secure service stub class de la Parte 2, usted realizó dos tareas:

  1. Mejoró las definiciones del objeto QName.
  2. Agregó algunos objetos nuevos Element y ComplexType.

La clase SecureServiceStubEnhancer tiene un método llamado enhanceDataMembers() (que aparece en el Listado 24), que realiza estas dos tareas:

Listado 24. Método enhanceDataMembers()
private StringBuffer enhanceDataMembers (
    StringBuffer secureServiceStub2BEnhanced) 
{
    StringBuffer stubDataWithEnhancedQNameObjects = 
        fetchAndEnhanceQNameObjects (secureServiceStub2BEnhanced);

    return addNewComplexTypeAndElementObjects (stubDataWithEnhancedQNameObjects);

}//enhanceDataMembers

El método enhanceDataMembers() toma a la clase stub de servicios seguros completa como un objeto StringBuffer. Éste usa dos métodos auxiliares llamados fetchAndEnhanceQNameObjects() y addNewComplexTypeAndElementObjects()para mejorar la sección miembros de datos de la clase stub de servicios seguros.

El método fetchAndEnhanceQNameObjects() mejora las definiciones de objeto QName y el método addNewComplexTypeAndElementstObjects() agrega nuevas definiciones ComplexType y Element a la clase stub de servicios seguros.

Ahora explicaremos cómo funcionan los dos métodos auxiliares.

Mejora de las definiciones del objeto QName

La clase stub de servicios seguros contiene objetos QName con dos URIs de espacio de nombres diferentes. Los dos URIs de espacio de nombres son http://www.w3.org/2000/09/xmldsig# y http://www.everydaywebservices.com/secureemailservice. El primer URI representa al espacio de nombres de firma XML, y el segundo URI representa al servicio seguro que usted va a crear.

El formulario original de la clase stub de servicios seguros crea diversos objetos QName sin el uso de ninguna declaración de espacio de nombres, como se muestra en el siguiente extracto del Listado 9 de la Parte 2:

protected static final QName _qname_Signature =
                    new QName(] "", "Signature");

Al mejorar las definiciones QName, usted agregó la definición de espacio de nombres de la firma a los objetos QName relacionados con firmas. Eso se muestra en el siguiente extracto del Listado 10 de la Parte 2:

protected static final QName _qname_Signature =
                    new QName( "http://www.w3.org/2000/09/xmldsig#", vSignature", "ds");

Usted puede comparar el formulario original del objeto QName con su formulario mejorado. Verá que la única mejora en la definición del objeto QName es el agregado del URI del espacio de nombres de la firma (http://www.w3.org/2000/09/xmldsig#) y el prefijo (ds).

Del mismo modo, usted agregó también el URI de espacio de nombres para su servicio de correo electrónico seguro en los objetos QName. Por ejemplo, vea otro extracto del Listado 10 de la Parte 2:

 protected static final QName 
             _qname_Signature = new QName(]
                 "", 
                 "Signature");

El Listado 25 implementa el método fetchAndEnhanceQNameObjects(), que agrega las declaraciones de espacio de nombres a los objetos QName:

Listado 25. Método fetchAndEnhanceQNameObjects()
private StringBuffer fetchAndEnhanceQNameObjects (
    StringBuffer secureServiceStub2BEnhanced) 
{
    StringBuffer stubDataWithEnhancedSignatureRelatedQNameObjects =
        enhanceSignatureRelatedQNameObjects (secureServiceStub2BEnhanced);

    return enhanceServiceRelatedQNameObjects (
        stubDataWithEnhancedSignatureRelatedQNameObjects);

}//fetchAndEnhanceQNameObjects

El método fetchAndEnhanceQNameObjects() toma la clase stub de servicios seguros y usa dos métodos auxiliares llamados enhanceSignatureRelatedQNameObjects() y enhanceServiceRelatedQNameObjects(). El método enhanceSignatureRelatedQNameObjects() mejora las definiciones de los objetos QName.

Del mismo modo, el método enhanceServiceRelatedQNameObjects() mejora las definiciones de los objetos QName.

Incorporación de los objetos ComplexType y Element

Ahora veamos cómo el método addNewComplexTypeAndElementObjects() (al que ya llamó en elListado 24) agrega nuevos objetos ComplexType y Element a la sección miembros de datos.

El formulario original de la sección miembros de datos generado por la herramienta generadora de stubs WSA contiene un objeto ComplexType para cada método de servicio. Por ejemplo, vea el objeto ComplexType llamado _complexType_getSubjects, que aparece en el Listado 26:

Listado 26. Objeto ComplexType generado por el generador de stubs WSA
ComplexType _complexType_getSubjects;
_complexType_getSubjects = new ComplexType();
_complexType_getSubjects.elements = new Element[2];
_complexType_getSubjects.elements[0] = _type_senderEmailAddress;
_complexType_getSubjects.elements[1] = _type_Signature;
_type_getSubjects = new Element(_qname_getSubjects, _complexType_getSubjects);

El objeto _complexType_getSubjects del Listado 26 ajusta los elementos contenidos en la solicitud SOAP getSubjects tratada en el Listado 3 de la Parte 2.

Cuando mejoró el segmento miembros de datos en la sección Mejora de los miembros de datos de una clase stub de servicios seguros de la Parte 2, usted agregó otro objeto ComplexType llamado _complexType_getSubjects_enhanced al final del Listado 10 de la parte 2, reproducido aquí como Listado 27 para consulta rápida:

Listado 27. Objeto ComplexType nuevo agregado al final del Listado 10 de la Parte 2
ComplexType _complexType_getSubjects_enhanced;
_complexType_getSubjects_enhanced = new ComplexType();
_complexType_getSubjects_enhanced.elements = new Element[1];
_complexType_getSubjects_enhanced.elements[0] = _type_senderEmailAddress;

Puede ver que la única diferencia entre el objeto _complexType_getSubjects del Listado 26 y el objeto _complexType_getSubjects_enhanced del Listado 27 es que la representación del elemento Signature (que aparece en negrita en el Listado 26) no está en el Listado 27.

Usted necesita el objeto _complexType_getSubjects_enhanced (que no contiene el elemento Signature) porque no quiere incluir el elemento Signature en su dato XML mientras calcula el valor de firma. El elemento Signature sólo aparece en el mensaje SOAP una vez que usted calculó el valor de firma sobre el mensaje SOAP real.

También deberá ajustar el objeto _complexType_getSubjects_enhanced que está dentro del objeto Element. Puede encontrar un objeto Element llamado _type_getSubjects_enhanced al final del Listado 10 de la parte 2, reproducido aquí como Listado 28 para consulta rápida. Este objeto Elemento _type_getSubjects_enhanced ajusta al objeto ComplexType _complexType_getSubjects_enhanced.

Listado 28. Objeto Element _type_getSubjects_enhanced

Element _type_getSubjects_enhanced = new Element( _qname_getSubjects, _complexType_getSubjects_enhanced);

Esto significa que el método addNewComplexTypeAndElementObjects() debe realizar los siguientes pasos cuando agrega un par de objetos nuevos ComplexType y Element para cada método de servicio en la clase stub de servicios seguros:

  1. Extraer los miembros de datos de la clase stub de servicios seguros.
  2. Para cada método de servicio en la clase stub de servicios seguros, encuentre el formulario original del objeto ComplexType (por ejemplo, el objeto _complexType_getSubjects para el método getSubjects() ).
  3. Use la definición del objeto ComplexType desde el paso 2 para crear un nuevo objeto ComplexType (o sea, el objeto _complexType_getSubjects_enhanced del Listado 27), no contiene la representación del elemento Signature.
  4. Agregue el nuevo objeto ComplexType a la sección miembros de datos.
  5. Cree un nuevo objeto Element (o sea, el objeto _type_getSubjects_enhanced del Listado 28) que ajusta el nuevo objeto ComplexType del paso 3.
  6. Agregue el nuevo objeto Element a la sección miembros de datos.

El Listado 29 muestra una implementación de estos seis pasos en el método addNewComplexTypeAndElementObjects():

Listado 29. Método addNewComplexTypeAndElementObjects()
private StringBuffer addNewComplexTypeAndElementObjects (
      StringBuffer secureServiceStubData) 
{
    /*** Step 1 ***/
    StringBuffer dataMembers = 
        new StringBuffer(fetchDataMembers(secureServiceStub2BEnhanced)); 

    for (int i = 0; i < fetchMethodNames (secureServiceStub2BEnhanced).size(); i ++)
    {
        String methodName = (String)methodNamesVector.get(i);

        /*** Step 2 ***/
        StringBuffer existingComplexType = 
            fetchComplexTypeDefinition(dataMembers, methodName);

        /*** Step 3 ***/
        StringBuffer newComplexType = 
            authorEnhancedComplexTypeDefinition (
                existingComplexType, 
                methodName
            );

        /*** Step 4 ***/ 
       addComplexTypeToDataMembers (
            dataMembers,
            newComplexType
        );

        /*** Step 5 ***/
        StringBuffer newElement = 
            authorEnhancedElementDefinition (
                methodName
            );

        /*** Step 6 ***/            
        addElementToDataMembers (
            dataMembers,
            newElement
        );
    }

    int startIndex = 
        secureServiceStub2BEnhanced.indexOf ("protected static final Element _type_");
    return  secureServiceStub2BEnhanced.replace (
               startIndex, 
               secureServiceStub2BEnhanced.length(), 
               dataMembers.toString()
            );

}//addNewComplexTypeAndElementObjects

Con esto termina nuestro comentario sobre la mejora de miembros de datos. La subsección siguiente comenta cómo mejorar los métodos de servicio de la clase stub de servicios seguros.

Mejora de los métodos de servicio

Usted mejoró el método getSubjects() de la clase stub de servicios seguros en la subsección Mejora de la implementación del método getSubjects() de la Parte 2. Ahora verá cómo implementar las mismas mejoras en la clase SecureServiceStubEnhancer.

Vea enhanceServiceMethods() (que aparece en el Listado 30), que mejora todos los métodos de servicio de la clase stub de servicios seguros, (por ejemplo, el método getSubjects() que vio en el Listado 6 de la Parte 2).

Listado 30. Método enhanceServiceMethods()
private StringBuffer enhanceServiceMethods (
    StringBuffer secureServiceStub2BEnhanced) 
{
    StringBuffer enhancedStubData = 
        new StringBuffer(
            secureServiceStub2Benhanced.toString());

    for (int i = 0 ; i < fetchMethodNames(secureServiceStub2BEnhanced).size() ; i ++)
        enhancedStubData = 
            enhanceIndividualServiceMethod (
                (String)methodNamesVector.get(i), enhancedStubData)
           );

    return enhancedStubData;

}//enhanceServiceMethods

Puede ver a partir del Listado 30 que enhanceServiceMethods() trae los nombres de todos los métodos de servicio en un loop y después llama a un método denominado enhanceIndividualServiceMethod() para cada método. El método enhanceIndividualServiceMethod() mejora un método de servicio por vez.

La subsección que viene a continuación, explica cómo enhanceIndividualServiceMethod() mejora un método de servicio individual.

Mejora de un método de servicio individual

Usaremos el método getSubjects() como ejemplo de la mejora de métodos de servicio individuales.

Al mejorar el método getSubjects() de la subsección Mejora de la implementación del método getSubjects() de la Parte 2, usted realizó tres tareas:

  1. El formulario original del método getSubjects() tomaba tres parámetros, uno de los cuales era un objeto Signature. Esto se muestra en el Listado 31:
    Listado 31. Formulario original de la definición del método getSubjects()

    public java.lang.String[] getSubjects( java.lang.String senderEmailAddress secure.Signature signature ) throws java.rmi.RemoteException { // Method body }

    Usted editó la definición del método getSubjects() de manera tal que no tomara un objeto Signature como parámetro. El formulario corregido de la definición del método getSubjects() se muestra en el Listado 32:

    Listado 32. Formulario mejorado del método getSubjects() del Listado 11 de la Parte 2
    public java.lang.String[] getSubjects(
        java.lang.String senderEmailAddress) 
        throws java.rmi.RemoteException {
        //Method body
      }
  2. Usted creó una instancia del objeto Signature dentro del cuerpo del método getSubjects() como se muestra en la primera línea de mejora de código en el Listado 11 de la Parte 2, que se muestra aquí en el Listado 33 para consulta rápida:
    Listado 33. Creación de una instancia del objeto Signature en el cuerpo del método getSubjects()

    public java.lang.String[] getSubjects( java.lang.String senderEmailAddress) throws java.rmi.RemoteException { //First enhancement line of code secure.Signature signature = new secure.Signature ( _type_getSubjects_enhanced, username, password); //Rest of the getSubjects() method body }
  3. Usted pasó el dato de la aplicación al objeto Signaturemediante una llamada a su método setApplicationData() como se muestra en la segunda línea de mejora de código en el Listado 11 de la Parte 2, que se muestra aquí en el Listado 34 para consulta rápida:
    Listado 34. Pasaje del dato de la aplicación al objeto Signature

    public java.lang.String[] getSubjects( java.lang.String senderEmailAddress) throws java.rmi.RemoteException { //First enhancement line of code secure.Signature signature = new secure.Signature ( _type_getSubjects_enhanced, username, password); Object[] inputObject = new Object[2]; inputObject[0] = senderEmailAddress; //Second enhancement line of code signature.setApplicationData(inputObject); //Rest of the getSubjects() method body }// getSubjects()

Ahora vea el método enhanceIndividualServiceMethod() (que aparece en el Listado 35), que realiza simplemente estas tres tareas:

Listado 35. Método enhanceIndividualServiceMethod()
private StringBuffer enhanceIndividualServiceMethod (
    String methodName,
    StringBuffer secureServiceStub2BEnhanced) 
{
    
    //Tasks 1 and 2:
    StringBuffer enhancedStubData =
        enhanceMethodDefinitionAndAddSignatureObject (
             secureServiceStub2Benhanced, 
             methodName
        );

    //Task 3:
    return addSetApplicationDataMethodCall(
              enhancedStubData, 
              methodName
    );

}// enhanceIndividualServiceMethod

Usted vio los tres métodos (enhanceConstructor(), enhanceDataMembers(), y enhanceServiceMethods()) presentados antes en Mejora de la clase stub de servicios seguros. Con esto concluye nuestro comentario sobre la clase SecureServiceStubEnhancer. El código completo de la clase SecureServiceStubEnhancer está incluido en la descarga del código fuente de este tutorial (ver Descargas).

Recuerde que en Pasos para la mejora de stubs la herramienta mejoradora de stubs usa otras dos clases llamadas SignatureRelatedEnhancer y HelperClassesGenerator. La subsección que viene a continuación, comenta cómo funcionan las dos clases.

Generación de clases relacionadas con firmas y clases auxiliares

En la sección Mejora de las clases stub de firma de la parte 2, usted generó las clases mejoradas relacionadas con firmas (Signature, SignedInfo, y Reference) generadas por la herramienta mejoradora de stubs WSA. La mayor parte del código escrito en el formulario mejorado de clases relacionadas con firmas no depende del servicio web que usted va a proteger. Sólo unos pocos bits de código de las clases relacionadas con firmas son específicas de los servicios web.

Por ejemplo, puede consultar el Listado 14 de la Parte 2, donde se comentaba el formulario mejorado para la clase Signature. Ese listado se reproduce aquí como Listado 36:

Listado 36. Formulario corregido de la clase Signature

public class Signature { private Element inputElement; private Object applicationData; private String username; private String password; protected secure.GetSubjectsSignatureSignedInfo signedInfo; protected java.lang.String signatureValue; protected secure.GetSubjectsSignatureKeyInfo keyInfo; public Signature ( Element inputElement, String username, String password) { this.inputElement = inputElement; this.username = username; this.password = password; } public Signature ( Element inputElement, Object inputObject, String username, String password) { this.inputElement = inputElement; this.applicationData = inputObject; this.username = username; this.password = password; populateDataMembers(); } public void setApplicationData (Object inputObject) { this.applicationData = inputObject; populateDataMembers(); } private void populateDataMembers() { secure.GetSubjectsSignatureKeyInfo keyInfo = new secure.GetSubjectsSignatureKeyInfo (username); this.keyInfo = keyInfo; secure.GetSubjectsSignatureSignedInfo signedInfo = new secure.GetSubjectsSignatureSignedInfo ( inputElement, applicationData); this.signedInfo = signedInfo; SignatureCalculator signatureCalculator = new SignatureCalculator( signedInfo, username, password); byte[] signatureValueInBinaryForm = signatureCalculator.getSignatureValue(); Base64Encoder base64Encoder = new Base64Encoder(); this.signatureValue = base64Encoder.getBase64EncodedValue( signatureValueInBinaryForm); }// populateDataMembers public secure.GetSubjectsSignatureSignedInfo getSignedInfo() { return signedInfo; } public secure.GetSubjectsSignatureKeyInfo getKeyInfo() { return keyInfo; } public java.lang.String getSignatureValue() { return signatureValue; } }//Signature

Puede ver que algunas porciones del Listado 36 aparecen en negrita. Estas son las únicas porciones específicas para servicios web que usted debe generar dinámicamente durante la mejora del stub. El resto del código de la clase Signature será siempre igual (o sea, no cambia para los distintos servicios web).

Por lo tanto, una estrategia sencilla para mejorar las clases relacionadas con firmas, es comenzar a partir de una plantilla. La plantilla contiene todo el código mejorado para las clases relacionadas con firmas, con marcadores de posición para código específico de servicios web. La clase SignatureRelatedEnhancer simplemente llena los marcadores de posición con el código específico de servicios web.

Del mismo modo, usted puede usar la misma estrategia de la plantilla y el marcador de posición en la clase HelperClassesGenerator para generar las cuatro clases auxiliares (CanconicalAuthor, SHA1DigestCalculator, Base64Encoder, y SignatureCalculator).

Encontrará las clases SignatureRelatedEnhancer y HelperClassesGeneratoren la descarga del código fuente de este tutorial (ver Descargas).

Prueba de la herramienta mejoradora de stubs

Ahora llegó el momento de probar la herramienta mejoradora de stubs que acaba de crear. Hemos usado un servicio de hotelería presentado en la subsección Escenarios de aplicaciones de servicios web de muestra de la Parte 1, para probar la herramienta mejoradora de stubs.

La descarga del código fuente incluye el archivo WSDL para el servicio de hotelería. Hemos usado la herramienta de generación de stubs WSA para generar los formularios originales de las clases stub. Usted encontrará el formulario original de las clases stub en una carpeta llamada HotelServiceOriginalStub.

Después, usamos la herramienta mejoradora de stubs para generar el formulario de las clases stub para el servicio de hotelería. Encontrará el formulario mejorado de las clases stub en una carpeta llamada HotelServiceOriginalStub. Puede usar el sistema de prueba descripto en Ejecución de la aplicación de firma para probar el formulario mejorado de las clases stub para el servicio de hotelería.


Resumen

En esta serie de tutoriales usted aprendió cómo funciona WSA y cómo mejorar clases stub WSA para establecer el acceso inalámbrico a servicios web seguros. También desarrolló una aplicación Java Card y aprendió a usar SATSA para comunicarse con aplicaciones Java.

También vio cómo se implementan algoritmos relacionados a la seguridad en J2ME y a integrar algoritmos de seguridad en WSA. Este tutorial también demostró el uso de algunos sistemas de prueba que pueden ayudarlo a probar sus clientes de servicios web.

Y por último, usted desarrolló una herramienta mejoradora de stubs que puede realizar la mayor parte del difícil trabajo de programación requerido para mejorar las clases stub WSA para el acceso inalámbrico seguro.


Descargar

DescripciónNombretamañoMetodo de descarga
Source codeSource.zip259KBHTTP

Información sobre métodos de descarga

Recursos

Aprender

Obtener los productos y tecnologías

Comentar

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=SOA y servicios web
ArticleID=678274
ArticleTitle=Creación de un cliente SOAP seguro para J2ME, Parte 3: Clases stub de API de servicios web seguros
publish-date=04072014