Tema 8: Seguridad en BEA WebLogic

En este tema revisaremos diversos ejemplos de seguridad proporcionados por WebLogic. Concretamente revisaremos aquí los conceptos de SSL y JAAS en el contexto de un servidor de aplicaciones real.

8.1. Ejemplos de SSL en WebLogic

8.1.1. Variables de entorno

En primer lugar, abriremos un nuevo shell y definiremos las siguientes variables de entorno, suponiendo que $HOME=/home/sco siendo sco el nombre de usuario:

$ export HOME_BEA=$HOME/bea

$ export SAMPLES_HOME=$HOME_BEA/weblogic700/samples

$ export CONFIG_HOME=$SAMPLES_HOME/server/config/examples

$ export EXAMPLES_HOME=$SAMPLES_HOME/server/src/examples

Concretamente, $CONFIG_HOME es el directorio de configuración de ejemplos desde donde lanzaremos el servidor de ejemplos, y $EXAMPLES_HOME es el directorio donde están todos los fuentes de ejemplos. Concretamente en el subdirectorio security se encuentran los ejemplos de seguridad. Los fuentes de los ejemplos relacionados con SSL se encuentran en el sub-subdirectorio sslclient y forman el package examples.security.sslclient

8.1.2. Compilando los ejemplos con ant

Primero accedemos al directorio donde están los fuentes y seguidamente copiamos el script que inicializa las variables de entorno para el ejemplo (entre ellas el acceso a la implementación Java del servidor en weblogic.jar).

$ cd $EXAMPLES_HOME/security/sslclient

$ cp $CONFIG_HOME/setExamplesEnv.sh .

A continuación añadimos la línea ant al final del fichero setExamplesEnv.sh y seguidamente invocamos este script que construye los ejemplos relacionados con SSL a partir del fichero de directivas build.xml. La construcción de los ejemplos implica compilar todas las clases, almacenar los *.class en otros directorios, copiar los JSPs necesarios a sus directorios de destino. Pero este proceso también conlleva la copia del certificado digital para identificar el servidor CertGenCA.der y su clave privada CertGenCAKey.der. Ambos han sido generados por la herramienta CertGen y firmados por una CA de demostración, y por lo tanto deberán sustituirse por certificados firmados por una CA real cuando estemos en producción. Asímismo, ant tambien crea un certificado y clave para identificar al cliente caso de que se requiera: clientcer.der y clientkey.der. Los certificados y claves se traducen a formato pem Finalmente se crea el almacén mykeystore para el cliente.

8.1.3. Servidor de Ejemplos

Todos los ejemplos que veremos en esta sección dedicada a SSL necesitan que el Servidor de Ejemplos de BEA WebLogic esté arrancado ya que en él se encuentran algunos JSPs y servlets a los que nos intentaremos conectar desde los clientes java, ya sea desde línea de comando o desde el navegador. Para arrancar este servidor abriremos un nuevo shell, re-exportaremos las variables de entorno y haremos lo siguiente:

$ cd $CONFIG_HOME

$ ./StartExamplesServer.sh

Una vez lanzado (RUNNING mode) podremos acceder a la consola de administración abriendo un navegador (Netscape/Mozilla) con: http://localhost:7001/console y entraremos poniendo como login y password la palabra weblogic.

8.1.4. Ejemplo SSLClient

En este ejemplo se muestra como crear un cliente que establezca una conexión HTTP y otra HTTPS con un JSP servido por el Servidor de Ejemplos de WebLogic. En el código fuente (ver SSLClient.java) cabe la posibilidad de utilizar la implementación JSSE de Sun o bien la de WebLogic (cambiar los comentarios del código). Si utilizamos la de WebLogic, en el método wlsURLConnect() se construye un objeto URL y a continuación se toma como argumento para crear un objeto weblogic.net.http.HttpURLConnection y pasáreselo al método SSLClient.tryConnection() que intenta realizar la conexión ya sea en HTTP o en HTTPS:

try {
  wlsUrl = new URL("http", host, Integer.valueOf(port).intValue(), query);
  weblogic.net.http.HttpURLConnection connection =
     new weblogic.net.http.HttpURLConnection(wlsUrl);
  tryConnection(connection, out);
} catch (Exception e) {
  printOut(e.getMessage(), out);
  e.printStackTrace();
  printSecurityProviders(System.out);
  System.out.println("----");
}

El caso sencillo, y más general, es que no se pida la autentificación del cliente sino tan solo la del servidor (autentificación one-way o unidireccional). Para lanzar este ejemplo solamente tenemos que sustituir la última línea del setExamplesEnv.sh (donde pone ant) por ant run_sslclient. La salida generada será la siguiente:

1. Package que implementa el protocolo SSL.

2. Proveedores de seguridad definidos en el fichero java.security.

3. Resultado de la conexión con HTTP (al puerto 7001).

4. Resultado de la conexión con HTTPS (al puerto 7002).

run_sslclient:
     [java] ----
     [java]  JDK Protocol Handlers and Security Providers:
     [java]    java.protocol.handler.pkgs - weblogic.net
     [java]    provider[0] - SUN - SUN (DSA key/parameter generation; DSA signing;
	  SHA-1, MD5 digests; SecureRandom; X.509 certificates; JKS keystore)
     [java]    provider[1] - SunRsaSign - SUN's provider for RSA signatures
     [java]
     [java]  Trying a new HTTP connection using WLS client classes -
     [java]     http://localhost:7001/examplesWebApp/SnoopServlet.jsp
     [java]             200 -- OK
     [java]             weblogic.net.http.KeepAliveStream
     [java]  Trying a new HTTPS connection using WLS client classes -
     [java]     https://localhost:7002/examplesWebApp/SnoopServlet.jsp
     [java]             200 -- OK
     [java]             weblogic.net.http.KeepAliveStream
     [java] ----

BUILD SUCCESSFUL

Total time: 7 seconds

8.1.5. Ejemplo SSLSocketClient

En este ejemplo se demuestra el uso de los sockets SSL para realizar conexiones seguras (ver el constructor del código fuente en SSLSocketClient.java). Como ya vimos en su día se siguen los siguientes pasos:

1. Inicializar un SSLContext. En WebLogic, esto implica obtener la clave privada del cliente y su certificado que están guardados en el almacén mykeystore y asignarlas al contexto con loadLocalIdentity(). Posteriormente se define un TrustManagerJSSE y se asigna al contexto.

...
SSLContext sslCtx = SSLContext.getInstance("https");
...

KeyStore ks = KeyStore.getInstance("jks");
ks.load(new FileInputStream("mykeystore"), null);
PrivateKey key = (PrivateKey)ks.getKey("mykey", "testkey".toCharArray());
Certificate [] certChain = ks.getCertificateChain("mykey");
sslCtx.loadLocalIdentity(certChain, key);
...

TrustManagerJSSE tManager = new NulledTrustManager();
sslCtx.setTrustManagerJSSE(tManager);
...

2. Definir una SSLSocketFactory a partir del contexto, y a partir de ella crear un SSLSocket.

SSLSocketFactory sslSF = (SSLSocketFactory) sslCtx.getSocketFactoryJSSE();
System.out.println(" Creating and opening new SSLSocket with SSLSocketFactory");
// using createSocket(String hostname, int port)
SSLSocket sslSock = (SSLSocket) sslSF.createSocket(argv[0],
                                         new Integer(argv[1]).intValue());

A partir de ahí lo que queda es enviar la petición y mostrar la respuesta por pantalla:

String req = "GET /examplesWebApp/ShowDate.jsp HTTP/1.0\r\n\r\n";
      out.write(req.getBytes());

Para lanzar este ejemplo sustituiremos la última línea del setExamplesEnv.sh (donde pone ant run_sslclient) por ant run_sslsocketclient. La salida generada por el script será la siguiente

run_sslsocketclient:
     [java]
     [java] https://localhost:7002
     [java]  Creating the SSLContext
     [java]  Initializing the SSLContext with client
     [java]   identity (certificates and private key),
     [java]   HostnameVerifierJSSE, AND NulledTrustManager
     [java]  Creating new SSLSocketFactory with SSLContext
     [java]  Creating and opening new SSLSocket with SSLSocketFactory
     [java]  SSLSocket created
     [java]  --- Do Not Use In Production ---
     [java]   By using this NulledTrustManager, the trust in the server's identity
	  is completely lost.
     [java]  --------------------------------
     [java]  certificate 0 -- Serial number: 33
     [java] Issuer:C=US, ST=California, L=San Francisco, O=BEA WebLogic, OU=Security,
	  CN=Demo Certificate Authority, EMAIL=support@bea.com
     [java] Subject:C=US, ST=California, L=San Francisco, O=BEA WebLogic, CN=weblogic.bea.com,
	  EMAIL=support@bea.com
     [java] Not Valid Before:Tue May 30 21:38:01 EDT 2000
     [java] Not Valid After:Thu May 13 21:38:01 EDT 2004
     [java] Signature Algorithm:MD5withRSA
     [java]
     [java]  certificate 1 -- Serial number: 0
     [java] Issuer:C=US, ST=California, L=San Francisco, O=BEA WebLogic, OU=Security,
	  CN=Demo Certificate Authority, EMAIL=support@bea.com
     [java] Subject:C=US, ST=California, L=San Francisco, O=BEA WebLogic, OU=Security,
	  CN=Demo Certificate Authority, EMAIL=support@bea.com
     [java] Not Valid Before:Tue May 30 21:37:44 EDT 2000
     [java] Not Valid After:Fri May 14 21:37:44 EDT 2004
     [java] Signature Algorithm:MD5withRSA
     [java]
     [java] Handshake Completed with peer 127.0.0.1:7002
     [java]    cipher: TLS_RSA_WITH_RC4_128_MD5
     [java]    peer certificates:
     [java]       certs[0]: Serial number: 33
     [java] Issuer:C=US, ST=California, L=San Francisco, O=BEA WebLogic, OU=Security,
	  CN=Demo Certificate Authority, EMAIL=support@bea.com
     [java] Subject:C=US, ST=California, L=San Francisco, O=BEA WebLogic, CN=weblogic.bea.com,
	  EMAIL=support@bea.com
     [java] Not Valid Before:Tue May 30 21:38:01 EDT 2000
     [java] Not Valid After:Thu May 13 21:38:01 EDT 2004
     [java] Signature Algorithm:MD5withRSA
     [java]
     [java] HTTP/1.0 200 OK
     [java] Date: Fri, 08 Mar 2002 19:31:47 GMT
     [java] Server: WebLogic WebLogic Server 7.0 beta Thu Mar 7 22:34:51 PST 2002 170092
     [java] Content-Length: 494
     [java] Content-Type: text/html
     [java] Refresh: 5
     [java] Set-Cookie: JSESSIONID=8JRk2lZLbcNVSqqMXI5r1WYl7DxjbwG8eEr3IUDuozkAIx5yS1Wq!1675777731; path=/
     [java] Cache-control: no-cache="set-cookie"
     [java] Connection: Close
     [java]
     \\
     \\The output of the ShowDate.jsp is shown here. The text is formatted in HTML for browser display.
     \\See your output for actual text.
     \\
     [java]  SSLSocket closed

BUILD SUCCESSFUL

Total time: 6 seconds

en donde la primera parte de la respuesta tiene que ver con el proceso de creación del socket, incluyendo los datos del certificado del cliente, mientras que la segunda tiene que ver con la salida HTML generada por el JSP.

8.1.6. Ejemplo SSLClientServlet

Finalmente, este ejemplo cuyo código se encuentra en SSLClientServlet.java, es un servlet que envuelve el código SSLClient. Este ejemplo, como el anterior, ya estará construido así que antes de lanzarlo solamente tenemos que editar el fichero: $CONFIG_HOME/startExamplesServer.sh y poner la siguiente línea:

JAVA_OPTIONS ="
	-Dweblogic.security.SSL.hostnameVerifier=
	   examples.security.sslclient.NulledHostnameVerifier
	-Dweblogic.security.SSL.trustedCAKeyStore=
	   /home/sco/bea/weblogic700/server/lib/cacerts"

donde la propiedad trustedCAKeyStore indica el almacén de claves que contiene las CAs en cuyos certificados confía el servidor. Estamos diciéndole al servidor que confíe en aquellos certificados contenidos en /home/sco/bea/weblogic700/server/lib/cacerts, entre ellos los generados por CertGen. Si no hacemos esto, el servidor solamente confía en las CAs registradas en el fichero $JAVA_HOME/lib/security/cacerts en donde están incluidas autoridades reales como Verisign.

Una vez activado el servidor de ejemplos consultaríamos la siguente URL desde el navegador:

http://localhost:7001/examplesWebApp/SSLClientServlet

y la salida que deberíamos observar sería:

wls ssl client classes

java SSLClient wls localhost 7001 7002 /examplesWebApp/SnoopServlet.jsp

 JDK Protocol Handlers and Security Providers:
   java.protocol.handler.pkgs - weblogic.utils|weblogic.utils|weblogic.net|weblogic.management
   provider[0] - SUN - SUN (DSA key/parameter generation; DSA signing; SHA-1, MD5 digests; SecureRandom; X.509 certificates; JKS keystore)
   provider[1] - SunRsaSign - SUN's provider for RSA signatures

 Trying a new HTTP connection using WLS client classes -
        http://localhost:7001/examplesWebApp/SnoopServlet.jsp
                200 -- OK
                weblogic.net.http.KeepAliveStream
 Trying a new HTTPS connection using WLS client classes -
        https://localhost:7002/examplesWebApp/SnoopServlet.jsp
                200 -- OK
                weblogic.net.http.KeepAliveStream

8.2. Ejemplos de JAAS en WebLogic

En el ejemplo del paquete examples.security.jaas se muestra la implementación de un cliente (ver código SampleClient.java) que implementa una autentificación por login-password, y si ésta tiene éxito se llama a un EJB del servidor WebLogic. Para ello se siguen los siguientes pasos:

1. Instanciar un LoginContext con una implementación de CallbackHandler (vér código SampleCallbackHandler.java) que se encargará de pedir el nombre de usuario y el password caso de que éstos no se suministren en la línea de comandos.

2.Intentar la autentificación llamando al método LoginContext.login() que se define en una implementación de la interfaz LoginModule. Como es habitual, esta implementación se referencia en un fichero de configuración. En este caso dicho fichero es sample_jaas.config y su contenido es el siguiente:

Sample {
   weblogic.security.auth.login.UsernamePasswordLoginModule required debug=false;
};

dondeUsernamePasswordLoginModule está definido en la distribución de WebLogic, es decir está incluido en $HOME_BEA/weblogic700/server/lib/weblogic.jar aunque su código fuente está impreso en el documento Programming WebLogic Security. Concretamente, la autentificación se realiza en el método login(), concretamente en la llamada al método weblogic.security.auth.Authenticate.authenticate:

...
if (url != null) {
  Envirnoment env = new Environment();
  env.setProviderUrl(url);
  env.setSecurityPrincipal(username);
  env.setSecurityCredentials(password);

  try {
     Authenticate.authenticate(env, subject);
  }
  catch(RemoteException re) { ... }

  catch(IOException ioe) { ... }

  catch(LoginException le) { ... }
...
}

3. Obtener los datos del Subject autentificado, y definir una acción a realizar. Dicha acción es una clase SampleAction.java que implementa la interfaz PrivilegedAction. Concretamente contiene un método run() que hace una llamada a un EJB el cual realiza algunas compras y ventas.

public class SampleAction implements PrivilegedAction
{
  private static final String JNDI_NAME = "ejb20-statelessSession-TraderHome";
  private String url;

  public SampleAction(String url)
  {
    this.url = url;
  }

  public Object run()
  {
    Object obj = null;

    try {
      callTraderEJB();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
    return obj;
  }

  /**
   * Call Trader EJB.
   */
  public void callTraderEJB()
    throws NamingException, CreateException, RemoteException, RemoveException
  {
    TraderHome home = lookupTraderHome();

    // create a Trader
    ExampleUtils.log("Creating a trader");
    Trader trader = (Trader)ExampleUtils.narrow(home.create(), Trader.class);

    String [] stocks = {"BEAS", "MSFT", "AMZN", "HWP" };

      // execute some buys
    for (int i=0; i<stocks.length; i++) {
      int shares = (i+1) * 100;
      ExampleUtils.log("Buying "+shares+" shares of "+stocks[i]+".");
      trader.buy(stocks[i], shares);
    }

    // execute some sells
    for (int i=0; i<stocks.length; i++) {
      int shares = (i+1) * 100;
      ExampleUtils.log("Selling "+shares+" shares of "+stocks[i]+".");
      trader.sell(stocks[i], shares);
    }

    // remove the Trader
    ExampleUtils.log("Removing the trader");
    trader.remove();
  }

  /**
   * Look up the bean's home interface using JNDI.
   */
  private TraderHome lookupTraderHome()
    throws NamingException
  {
    Context ctx = ExampleUtils.getInitialContext(url);
    Object home = (TraderHome)ctx.lookup(JNDI_NAME);
    return (TraderHome)ExampleUtils.narrow(home, TraderHome.class);
  }

}

Este método se ejecuta al llamar al método weblogic.security.Security.runAs() desde el cliente.

Para probar este ejemplo, suponiendo que el servidor de ejemplos esté arrancado, tenemos que hacer lo siguiente:

$ cd $EXAMPLES_HOME/security/jaas

$ cp $CONFIG_HOME/setExamplesEnv.sh .

Añadiendo la línea ant al final y ejecutando el shell conseguimos construir el ejemplo, lo cual consiste en compilar las clases (includo el EJB) y alojarlas en sus directorios.

Después se ha de copiar el fichero sample_jaas.config en el directorio $JAVA_HOME/jre/lib/security (donde JAVA_HOME=$HOME/bea/jdk121_03) y añadimos la siguiente línea a $JAVA_HOME/jre/lib/security/java.security

login.config.url.1=file:${java.home}/lib/security/sample_jaas.config

A continuación añadimos al final el contenido del fichero run.sh:

java -classpath $CLASSPATH:$CLIENT_CLASSES/ejb20_basic_statelessSession_client.jar:
               $CLIENT_CLASSES/utils_common.jar  examples.security.jaas.SampleClient
	       t3://localhost:7001 weblogic weblogic

y ejecutamos el shell. Las dos últimas palabras (weblogic, weblogic) son el número de usuario y password esperados (si omitimos estas dos palabras nos pide nombre de usuario y password). Así, si la autentificación tiene éxito se observará la siguiente salida:

username: weblogic
password: weblogic
URL: t3://localhost:7001
Creating a trader
Buying 100 shares of BEAS.
Buying 200 shares of MSFT.
Buying 300 shares of AMZN.
Buying 400 shares of HWP.
Selling 100 shares of BEAS.
Selling 200 shares of MSFT.
Selling 300 shares of AMZN.
Selling 400 shares of HWP.
Removing the trader

Si ponemos un nombre de usuario y un password que no corresponden a ningún usuario creado desde la consola entonces la autentificación fallará y con ello el acceso al EJB. Para crear un nuevo usuario hay que lanzar la consola de administración y activar la siguiente cadena de subdirectorios en el applet de la izquierda: Security -> Realms -> myRealm -> Users y luego en el javascript de la derecha pinchar en Configure a new user.... Seguidamente nos pedirá nombre y password (mínino 8 caracteres). Tras pulsar en Apply ya podemos lanzar de nuevo el shell para acceder al EJB. En cuanto a la configuración de grupos no hay que seleccionar ninguna opción