Mostrando entradas con la etiqueta Java. Mostrar todas las entradas
Mostrando entradas con la etiqueta Java. Mostrar todas las entradas

Agregando certificados a Java

 

Cuando comenzamos a preocuparnos de la seguridad en nuestros desarrollos, sin duda alguna, una de las primeras cosas con las que nos tropezaremos serán las conexiones seguras, y los certificados que necesitamos para las mismas.

Comencemos desde el principio: 


¿Qué es HTTP?

HTTP (por las siglas de Hypertext Transfer Protocol) es un protocolo de comunicación que permite transmitir mensajes y documentos.  Fue pensado para la comunicación entre servidores web y navegadores web, pero puede ser usado también para otros propósitos.  En otras palabras, es la forma estándar en la que tu navegador se comunica con otras máquinas cada vez que buscas algo (como este artículo!).


¿Qué es HTTPS?

HTTPS (por las siglas de HTTP Secure) es el hermano responsable de HTTP.  Mientras que HTTP solo se encarga de definir la forma en la que el mensaje se envía desde el origen y se recibe en el destino, HTTPS se encarga además de garantizar que en dicha comunicación participen solamente dichos origen y destino, evitando que la comunicación sea interceptada y las identidades suplantadas.

Básicamente, este protocolo permite que el mensaje sea encriptado usando SSL (Secure Sockets Layer), su hijo más actualizado TLS (Transport Layer Security), o el protocolo de encriptación que sea el apropiado.  De esta forma, solamente los participantes adecuados, el origen y el destino, pueden participar en una conversación dada.


¿Qué es un Certificado?

Un certificado no es otra cosa, ni más ni menos, que un documento que prueba que uno de los participantes de una conversación es quien dice ser.  Podemos tener certificados tanto suministrados por el servidor para el cliente, como por el cliente para el servidor, pero lo más común siempre es el primer caso.

Para compartirles un ejemplo resumido, si yo tengo un servidor que publica cierta información, y quiero que uno de ustedes tenga acceso seguro (usando HTTPS/TLS), sería mi responsabilidad compartirles mi certificado (normalmente un archivo .cer), y responsabilidad de ustedes instalarlo para que su navegador pueda usarlo.  De esa forma, cuando mi servidor les mandara información, ustedes podrían saber si en efecto la estoy enviando yo.

Normalmente, para empresas "públicas" (Google, Amazon, y casi cualquier otro site que consigas en Internet) a las que accedemos usando nuestros navegadores, el intercambio de certificados es bastante transparente para nosotros como usuarios: existen entidades responsables de validar los certificados (las CAs o Certificate Authorities), de tal forma que nuestro navegador tenga contra qué comparar que ese certificado de Google que le está llegando es realmente de Google.  Sin embargo, para las comunicaciones "privadas" que normalmente manejamos como desarrolladores, esa responsabilidad recae en nosotros.

Noten que un certificado puede tener fecha de caducidad, según lo que decida su dueño, por lo que puede ser necesario reimportarlo de cuando en cuando.


¿Qué es un Java KeyStore?

Un Java KeyStore (JKS, o simplemente KeyStore) es un repositorio de certificados, sean públicos o privados, y de cualquier clave privada que sea necesaria para su uso.  Básicamente, es el lugar donde debemos, como desarrolladores, importar el certificado de la otra máquina con la que queremos ser capaces de comunicarnos vía comunicación segura.

Si nuestro programa o herramienta usa la comunicación TLS o similar, debemos añadir un certificado al almacén de claves JKS en cada una de las máquinas (servidores, estaciones de trabajo, etc) con las que queramos comunicarnos.  Así de sencillo.


Y al fin, ¿cómo importo un certificado .cer a mi KeyStore Java?

Pues simplemente usaremos el comando keytool de Java para importar el certificado al almacén de claves.  Podemos ejecutarlo vía línea de comandos, o vía algún programa o interfaz con la que contemos.  Si usamos la línea de comandos, tendríamos algo como esto:

keytool -importcert -file <certificate.cer> -keystore <keystore.jks>

Donde:

  • <certificate.cer> es la ubicación y nombre del certificado que nos ha compartido el servidor.
  • <keystore.jks> es la ubicación y nombre del KeyStore dentro de la instalación Java del servidor o cliente que estamos usando.

Además de estos parámetros podemos usar muchos otros (para asignarle una clave a un KeyStore y que no cualquiera pueda revisarlo, por ejemplo, o asignarle un alias a un certificado importado).  Pueden ver el detalle del comando aquí.

Noten que el mismo comando puede ser usado no solo para la importación de un certificado (que es lo que hemos cubierto en este artículo), sino para todo lo que tenga que ver con un KeyStore, incluyendo su creación! No teman en pasear por la documentación.


Como siempre, ¡Quedo a la orden ante cualquier duda!

-- Gorka Siverio


Instalando Java

 

Cada vez que estoy enseñando Java a alguien que no ha tenido contacto previo con dicho lenguaje, siempre me queda la sensación de que lo más complicado no es enseñar el lenguaje en sí (al menos, a nivel básico), sino preparar el entorno de trabajo para poder usarlo.  ¿Qué necesito tener para poder usar Java?  ¿Cómo lo instalo?  ¿De dónde lo descargo?

Vamos a dar un breve paseo por el proceso de instalación que requerimos para poder usar dicho lenguaje.  Estas instrucciones están enfocadas en Windows, pues es el sistema operativo que hoy estoy usando: en general la base del proceso es la misma para otros sistemas operativos, pero los pasos particulares pueden variar.  Trataré de agregar los comentarios pertinentes en cada caso.


¿Qué necesito?

Para poder programar en Java necesitas únicamente dos componentes:

  • El lenguaje de programación, que es el set de reglas que usarás para codificar.
  • El entorno de programación, que es la herramienta que usarás para codificar.

Si quieres entender un poco mejor qué es cada cosa, digamos que el lenguaje de programación es el idioma que hablarás con la computadora para que te entienda (castellano, inglés, élfico, etc), mientras que el entorno de programación es la herramienta que usarás para llevar a cabo la conversación con ella (teléfono, e-mail, señales de humo, etc).

El lenguaje, pues, define un set de reglas para que nos podamos entender, mientras que el entorno es lo que hace posible que apliquemos dichas reglas.  Ahora, vayamos un poco más en detalle sobre ambos elementos.


El lenguaje de programación: Instalación y configuración de Java

Pues aquí comenzamos como con cualquier otra cosa en la vida: googleamos "download Java", y esto nos llevará, como no, a la página de descargas de Java, que tiene la decencia de mostrarte de una vez la versión correcta para tu sistema operativo.  Sin embargo, si nos descargamos lo que nos indican en esa página estaremos bajándonos solo el JRE (que no es lo que queremos), y no el JDK (que sí es lo que queremos).  

Y pues... benditos acrónimos.  ¿Qué es cada cosa, y cuál quiero?

El JRE (por las siglas de "Java Runtime Environment", algo así como "Entorno de Ejecución Java") es una instalación de Java que te permite ejecutar programas hechos en dicho lenguaje.  Para lograr su misión de code once, run anywhere, los programas en Java no se casan con un sistema operativo, sino que corren sobre una capa extra, la Máquina Virtual (JVM, por las siglas de Java Virtual Machine).  Por ende, en general necesitas tener Java en tu máquina para correr programas hechos en Java.

El JDK (por las siglas de "Java Development Kit", que viene siendo algo como "Herramientas de Desarrollo en Java") es una instalación de Java un poco más extensa que el JRE, pues permite no solo ejecutar programas Java, sino que además incluye librerías y herramientas para poder crear dichos programas.  Si eres un desarrollador, este es el que quieres.

En resumen:

  • JRE para ejecutar programas Java.  Dirigido al público en general.
  • JDK para desarrollar y ejecutar programas Java.  Dirigido a desarrolladores.

Por ende, queremos descargar el JDK.  Pues no pasa nada, googleamos "download java jdk" y esto nos llevará a la página de descarga del JDK Java.  Y ahí procedemos a descargar el del sistema operativo que estemos usando (en mi caso, descargué el Windows x64 Installer).  Y siendo Windows lo que es, pues doble click al archivo .exe descargado, algunos "next" dejando las opciones por defecto, y close al finalizar.  ¡Felicidades, ya tienes Java instalado en tu máquina!

Dependiendo del sistema operativo quizás tengas que configurar un par de cosillas más para que tu sistema reconozca el JDK.  Lo más común es tener que indicar en algunas propiedades del sistema las rutas de configuración (las más necesarias tienden a ser JAVA_HOME, PATH y CLASSPATH), pero con la instalación de Windows ya todo eso se configura de forma automática.  Si quieres ver si se instaló correctamente, simplemente abre un Editor de Línea de Comandos (Command Prompt) y escribe "java --version", lo que te mostrará información de la versión de Java instalada.


C:\Users\gsive>java --version
java 15.0.2 2021-01-19
Java(TM) SE Runtime Environment (build 15.0.2+7-27)
Java HotSpot(TM) 64-Bit Server VM (build 15.0.2+7-27, mixed mode, sharing)


El entorno de programación: Definición, elección e instalación del IDE

Ahora que ya sabemos hablar Java solo tenemos que ver qué herramienta usar para hablarlo.  En general cualquier editor de texto servirá para ello (incluso el Notepad que Windows trae por defecto), pero si te quieres (aunque sea un poquito) buscarás un editor que reconozca la sintaxis de Java (es decir, que te ponga de colores el código) y te brinde herramientas más avanzadas.



¡I know Kung Fu... digo, Java!


Hay multitud de editores compatibles con Java (mi preferido es Notepad++), pero mi recomendación es que instales alguno de los IDEs disponibles para Java.

Y seguimos con acrónimos... A ver, ¿qué es un IDE?

Un IDE (por las siglas de "Integrated Development Environment", que traduciré como "Entorno de Desarrollo Integrado") son editores de texto más poderosos de lo normal, que brindan herramientas y otras facilidades para desarrollar en uno o más lenguajes de programación (sintaxis, depuradores, compiladores, control de versiones, etc.  Si no sabes qué es todo esto, tranquilo, que más adelante lo sabrás.  Por ahora, confía en que son cosas que quieres mucho :D).

En el mercado hay muchos IDEs, gratuitos o pagos, que cumplen excelentemente su función.  Para Java, seguramente el más usado sea Eclipse, aunque hay muchos otros conocidos, como NetBeans o Visual Studio.  En mi caso, mi preferido es IntelliJ IDEA.  Siéntete libre de probarlos (varios son gratis, o al menos tienen una versión que lo es) y elegir la herramienta que más te guste: todos cumplen la misma función.

El proceso de instalación del IDE variará según el que elijas, pero en Windows son similares todos: googlea y ve a la página de descarga, doble click en el archivo .exe y sigue las instrucciones en pantalla.  Al finalizar la instalación quizás tendrás que indicarle al IDE dónde instalaste Java... y eso sería todo.  Estarás listo ya para poder comenzar a desarrollar en Java.

¿Que cómo desarrollas algún programa?  Bueno, querido lector, eso es tema para otro artículo. :D


Ojalá este artículo les ayude a solventar este primer escollo como desarrolladores.  ¡Quedo a la orden ante cualquier duda!

-- Gorka Siverio

Errores básicos usando log4j


Log4j es una librería open source desarrollada en Java por la Apache Software Foundation. La librería permite a los desarrolladores de software elegir la salida y el nivel de granularidad de los logs a tiempo de ejecución, y no a tiempo de compilación como es comúnmente realizado.

Dicha configuración se hace en tiempo de ejecución mediante el uso de archivos de configuración externos (normalmente, el famoso log4j.properties). Log4J ha sido implementado en otros lenguajes como: C, C++, C#, Perl, Python, Ruby y Eiffel, si bien este artículo se enfoca principalmente en su uso en Java.

Su uso básico es realmente sencillo. Sin profundizar mucho aquí -ya lo haremos en otro post-, sencillamente se agrega la librería al proyecto donde se desee usar, se configura el archivo de configuración para indicar las salidas y el nivel de detalle de los logs deseados, y se modifica en el código los usos de System.out.print y similares por las llamadas apropiadas al logger de log4j (normalmente, log.debug, log.error, etc).

Las trazas en log4j manejan niveles de prioridad. A saber, ALL, TRACE, DEBUG, INFO, WARNING, ERROR, FATAL y OFF. Cada una es "mayor" o "menor" que la anterior o posterior, lo que nos permite configurar "niveles" de granularidad, detalle, o como quieran llamarlo. Por ejemplo, si en mi archivo de configuración de log4j indico que tengo el nivel de trazas seteado en INFO, eso quiere decir que todo lo superior e igual a INFO se mostrará (INFO, WARNING, ERROR y FATAL), mientras que lo inferior (TRACE, DEBUG) no se logueará. Si seteamos el nivel de trazas a ALL se mostrarán todas, y si lo colocamos en OFF no se verá ninguna.

Esto nos permite configurar el nivel de logueo de acuerdo a las necesidades de cada ambiente en particular. Por ejemplo, para nuestro ambiente de Desarrollo o incluso de Prueba, el archivo de configuración de log4j debería estar en nivel DEBUG, TRACE o incluso en ALL, para que podamos ver todas las trazas. Para el ambiente del cliente (Producción), se podría setear INFO, para que les lleguen los mensajes sin información de debugging, o incluso WARNING o ERROR, por cuestiones de performance (no me interesa que me digan que la operación fué un éxito, sólo me interesa enterarme de que falló y por qué).

Para que esto funcione apropiadamente, debemos asegurarnos de usar la prioridad apropiada en cada traza que coloquemos en el sistema. En general, al colocar una traza para información de debug o desarrollo, deberíamos usar el nivel de debug o el de trace:

log.debug("esta es mi traza");

Para colocar una traza con información de un error, deberíamos usar el nivel de error. Es decir, en todos los catch de la aplicación sólo deberíamos loguear usando el nivel error o fatal, y pasarle la excepción al mismo para poder controlar las trazas completamente (no llamar a e.printStackTrace()):

log.error("error haciendo algo", e);

Los demás niveles de logueo son un poco menos usados:

- log.info(...) se usa cuando queremos mostrar información de éxito en la ejecución de un programa. Como comenté arriba, normalmente cuando estamos en producción no nos interesa que nos aparezcan trazas diciendo "todo va como debería", por lo que yo tengo como regla no usar trazas de este nivel.

- log.warn(...) debe usarse cuando se encuentre una situación potencialmente peligrosa. Si no sabes qué es esto, eso quiere decir que no debes usar el nivel warn, por lo que tampoco acostumbro a usarlo. Normalmente, esto refleja una situación en la que algo falló, pero que el sistema pudo manejar de alguna forma, como asumiendo valores por defecto.

(De ahora en adelante, por comodidad y para ahorrar bits, todos los ejemplos del documento serán dados usando el nivel de debug/DEBUG, dado que este es el caso más común. Sin embargo, debemos notar que en general, todos aplican a los demás niveles indicados arriba (cuando hablamos de log.debug aplica también a log.info y log.error; cuando hablamos de isDebugEnabled() hablamos también de isInfoEnabled(), etc).

Validación del nivel de la traza:


A veces hemos visto que se acostumbra en las trazas validar a mano si el nivel de trazas usado permite o no que se muestre la traza en cuestión. Ejemplo:

if (log.isDebugEnabled())
log.debug("Traza del objeto ", this);

Si revisamos la implementación del método isDebugEnabled() vemos que lo que revisa es que las trazas del nivel indicado (en el caso del ejemplo, DEBUG) estén permitidas, y que el nivel de traza configurado acepte el nivel indicado:

public boolean isDebugEnabled() {
if(repository.isDisabled( Level.DEBUG_INT))
return false;
return Level.DEBUG.isGreaterOrEqual(
this.getEffectiveLevel());
}

Si revisamos la implementación del método debug(...) vemos que hace exactamente las mismas validaciones antes de imprimir la traza indicada:

public void debug(Object message) {
if(repository.isDisabled(Level.DEBUG_INT))
return;
if(Level.DEBUG.isGreaterOrEqual(
this.getEffectiveLevel())) {
forcedLog(FQCN, Level.DEBUG, message, null);
}
}

Por ende, llamando al método isDebugEnabled() no solo agregamos líneas de más a la clase, haciéndola más ilegible, sino que además agregamos procesamiento innecesario, causando mayores tiempos de respuesta sin necesidad (sí, es sólo un if, pero es un if en cada llamada al log del sistema). Después de todo, es precisamente de ello de lo que se encarga la librería log4j!

No se me ocurren muchas utilidades para que tengamos que llamar alguna vez al método isDebugEnabled(), aunque estoy seguro de que alguna habrá (de hecho, hay una justo más abajo. Sigan leyendo). Como todo método, se puede usar, pero normalmente su uso será redundante, por lo que no lo recomendamos.

Performance


log4j nos da la facilidad de "apagar" las trazas como creamos necesario, de acuerdo a lo hablado en la primera parte de este documento. Sin embargo, esto no es del todo cierto, pues las concatenaciones necesarias para formar el mensaje a mostrar aún se realizan, por lo que -si las trazas están apagadas- forzamos al sistema a realizar operaciones de más, que afectan el performance general de la misma, para nada. Ver, por ejemplo, lo siguiente:

log.debug("Se cambio la propiedad '" + entry.name +
"', del valor anterior '" + prevValue +
"' al nuevo valor '" + entry.value + "'.");

Es deseable pensar en usar, en lugar de los métodos de logueo que reciben como parámetro un único String formado por la concatenación de varios, una solución alterna, como StringBuffers, o una lista o arreglo con todos los parámetros a imprimir, y que sea el método de logueo en nuestra clase logger (que hereda del Logger de log4j) el que -luego de validar que el nivel de traza es el apropiado para que la misma aparezca- concatene el mensaje. Una implementación sencilla de la solución con arreglos podría ser algo como:

public String buildMessage(String[] message) {
// Deberíamos usar un StringBuffer o alguna
// otra solución aquí...
String result = "";
for (int ii = 0; ii < message.length; ii++) {
result += message[ii];
}
return result;
}

Que luego podría ser usado por los métodos de logueo en nuestra clase logger:

public void debug(String[] message) {
if (repository.isDisabled(Level.DEBUG_INT)) {
return;
}
if (Level.DEBUG.isGreaterOrEqual(
this.getEffectiveLevel())) {
forcedLog(FQCN, Level.DEBUG,
buildMessage(message), null);
}
}

Este es un cambio seguro y fácil de realizar, pero no rápido, pues habría que cambiar todas las llamadas a métodos de logueo (debug(...), error(...), etc) ya existentes en nuestras aplicaciones. Es una prueba interesante, para analizar su uso en los desarrollos venideros, hacer una prueba sencilla para validar el performance de dicho cambio.

Para realizar dicho cambio habría que cambiar las llamadas actuales al log:

log.debug("Se cambio la propiedad '" + entry.name +
"', del valor anterior '" + prevValue +
"' al nuevo valor '" + entry.value + "'.");

Por:

log.debug(new String[] {"Se cambio la propiedad '",
entry.name,
"', del valor anterior '",
prevValue,
"' al nuevo valor '",
entry.value, "'."});

O:

String[] message = {"Se cambio la propiedad '",
entry.name,
"', del valor anterior '",
prevValue,
"' al nuevo valor '",
entry.value, "'."}
log.debug(message);

La mejora en el performance es notoria, pero la aplicación pierde algo de legibilidad, por lo que este punto debe analizarse con cuidado. Siempre queda la opción de usar unos métodos de logueo en las aplicaciones donde el performance no es significativo, y usar otros cuando sí lo sea.