MQTT es un protocolo ligero y felxible para el intercmabio de mensajes y transferencia de datos en el Internet de las Cosas, diseñado para equilibrar la flexibilidad con los recursos de hardware y red disponibles. Para garantizar la seguridad de las comunicaciones, se utiliza comúnmente TLS/SSL para el cifrado de la transmisión de datos.
Este artículo explica cómo implementar la autenticación unidireccional y bidireccional mediante TLS/SSL entre Android y MQTT.
Preparación
En este artículo utilizaremos Eclipse Paho Android Service y BouncyCastle. Añadimos las dependencias necesarias:
dependencies {
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.59'
}
A continuación se presenta el código fundamental para establecer conexiones TLS/SSL en Android:
MqttConnectOptions opciones = new MqttConnectOptions();
SSLSocketFactory factorySSL = ...
opciones.setSocketFactory(factorySSL);
El aspecto fundamental radica en obtener el SSLSocketFactory. A continuación se detalla cómo hacerlo para cada tipo de autenticación.
Autenticación Unidireccional
La autenticación unidireccional se refiere a la validación del servidor hacia el cliente. El código principal es el siguiente:
public static SSLSocketFactory crearSocketFactoryUnidireccional(InputStream flujoCertificadoCA) throws Exception {
Security.addProvider(new BouncyCastleProvider());
X509Certificate certificadoCA = null;
BufferedInputStream bufferEntrada = new BufferedInputStream(flujoCertificadoCA);
CertificateFactory generadorCertificados = CertificateFactory.getInstance("X.509");
while (bufferEntrada.available() > 0) {
certificadoCA = (X509Certificate) generadorCertificados.generateCertificate(bufferEntrada);
}
KeyStore almacenCA = KeyStore.getInstance(KeyStore.getDefaultType());
almacenCA.load(null, null);
almacenCA.setCertificateEntry("certificado-ca", certificadoCA);
TrustManagerFactory fabricaTM = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
fabricaTM.init(almacenCA);
SSLContext contextoSSL = SSLContext.getInstance("TLSv1.2");
contextoSSL.init(null, fabricaTM.getTrustManagers(), null);
return contextoSSL.getSocketFactory();
}
Colocamos el archivo ca.crt en el directorio res/raw y lo invocamos de la siguiente manera:
try {
InputStream flujoCA = contexto.getResources().openRawResource(R.raw.ca);
opciones.setSocketFactory(crearSocketFactoryUnidireccional(flujoCA));
} catch (Exception e) {
e.printStackTrace();
}
Autenticación Bidireccional
La autenticación bidireccional implica la validación mutua entre el servidor y el cliente. El código esencial es:
public static SSLSocketFactory crearSocketFactoryBidireccional(InputStream flujoCA, InputStream flujoCert, InputStream flujoClave,
String clavePassword) throws Exception {
Security.addProvider(new BouncyCastleProvider());
// Cargar certificado de la autoridad certificadora
X509Certificate certCA = null;
BufferedInputStream buffer = new BufferedInputStream(flujoCA);
CertificateFactory fabricaCerts = CertificateFactory.getInstance("X.509");
while (buffer.available() > 0) {
certCA = (X509Certificate) fabricaCerts.generateCertificate(buffer);
}
// Cargar certificado del cliente
buffer = new BufferedInputStream(flujoCert);
X509Certificate certificadoCliente = null;
while (buffer.available() > 0) {
certificadoCliente = (X509Certificate) fabricaCerts.generateCertificate(buffer);
}
// Cargar clave privada del cliente
PEMParser parserPEM = new PEMParser(new InputStreamReader(flujoClave));
Object objeto = parserPEM.readObject();
JcaPEMKeyConverter conversor = new JcaPEMKeyConverter().setProvider("BC");
KeyPair parClaves = conversor.getKeyPair((PEMKeyPair) objeto);
KeyStore almacenCA = KeyStore.getInstance(KeyStore.getDefaultType());
almacenCA.load(null, null);
almacenCA.setCertificateEntry("ca-certificate", certCA);
TrustManagerFactory fabricaTM = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
fabricaTM.init(almacenCA);
KeyStore almacenCliente = KeyStore.getInstance(KeyStore.getDefaultType());
almacenCliente.load(null, null);
almacenCliente.setCertificateEntry("client-certificate", certificadoCliente);
almacenCliente.setKeyEntry("clave-privada", parClaves.getPrivate(), clavePassword.toCharArray(),
new java.security.cert.Certificate[]{certificadoCliente});
KeyManagerFactory fabricaKM = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
fabricaKM.init(almacenCliente, clavePassword.toCharArray());
SSLContext contexto = SSLContext.getInstance("TLSv1.2");
contexto.init(fabricaKM.getKeyManagers(), fabricaTM.getTrustManagers(), null);
return contexto.getSocketFactory();
}
Es necesario disponer del certificado del servidor, el certificado del cliente y la clave privada, colocándolos en res/raw. Luego se invoca de esta forma, utilizando una cadena vacía como contraseña:
try {
InputStream flujoCA = contexto.getResources().openRawResource(R.raw.ca);
InputStream flujoCert = contexto.getResources().openRawResource(R.raw.cert);
InputStream flujoClave = contexto.getResources().openRawResource(R.raw.key);
opciones.setSocketFactory(crearSocketFactoryBidireccional(flujoCA, flujoCert, flujoClave, ""));
} catch (Exception e) {
e.printStackTrace();
}
Esto describe el proceso para implementar la autenticación unidireccional y bidireccional mediante TLS/SSL con MQTT en Android.