Autenticación TLS/SSL de MQTT en Android

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.

Etiquetas: MQTT TLS SSL Android IoT

Publicado el 6-28 23:50