0

I have using below code for Mqtt client-:certificate based authentication for MQTT broker wihich is running in VMware. I am running Mqtt client through code which is in windows system.

public class TestClient {

public static void main(String[] args) {
        String serverUrl = "ssl://192.168.5.12:8883";
        String caFilePath = "rootca.pem";
        String clientCrtFilePath = "Client.pem";
        String clientKeyFilePath = "Client.key";
        String mqttUserName = "guest";
        String mqttPassword = "123123";
        
        MqttClient client;
        try {
            client = new MqttClient(serverUrl, "2");
            MqttConnectOptions options = new MqttConnectOptions();
//          options.setUserName(mqttUserName);
//          options.setPassword(mqttPassword.toCharArray());
//          options.setSSLProperties(props);
            options.setConnectionTimeout(0);
            options.setKeepAliveInterval(300);
            options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1);

            SSLSocketFactory socketFactory = getSocketFactory(caFilePath,
                    clientCrtFilePath, clientKeyFilePath, "");
            options.setSocketFactory(socketFactory);

            System.out.println("starting connect the server...");
            client.connect(options);
            System.out.println("connected!");
            Thread.sleep(1000);

            client.subscribe(
                    "/u/56ca327d17531d08e76bddd4a215e37f5fd6082f7442151c4d3f1d100a0ffd4e",
                    0);
            client.disconnect();
            System.out.println("disconnected!");
        } catch (MqttException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static SSLSocketFactory getSocketFactory(final String caCrtFile,
            final String crtFile, final String keyFile, final String password)
            throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        // load CA certificate
        X509Certificate caCert = null;
        FileInputStream fis = new FileInputStream(caCrtFile);
        BufferedInputStream bis = new BufferedInputStream(fis);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        while (bis.available() > 0) {
            caCert = (X509Certificate) cf.generateCertificate(bis);
            // System.out.println(caCert.toString());
        }
        // load client certificate
        bis = new BufferedInputStream(new FileInputStream(crtFile));
        X509Certificate cert = null;
        while (bis.available() > 0) {
            cert = (X509Certificate) cf.generateCertificate(bis);
            // System.out.println(caCert.toString());
        }
        // load client private key
        PEMParser pemParser = new PEMParser(new FileReader(keyFile));
        Object object = pemParser.readObject();
        PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder()
                .build(password.toCharArray());
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter()
                .setProvider("BC");
        KeyPair key;
        if (object instanceof PEMEncryptedKeyPair) {
            System.out.println("Encrypted key - we will use provided password");
            key = converter.getKeyPair(((PEMEncryptedKeyPair) object)
                    .decryptKeyPair(decProv));
        } else {
            System.out.println("Unencrypted key - no password needed");
            key = converter.getKeyPair((PEMKeyPair) object);
        }
        pemParser.close();

        // CA certificate is used to authenticate server
        KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
        caKs.load(null, null);
        caKs.setCertificateEntry("ca-certificate", caCert);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
        tmf.init(caKs);

        // client key and certificates are sent to server so it can authenticate
        // us
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(null, null);
        ks.setCertificateEntry("certificate", cert);
        ks.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(),
                new java.security.cert.Certificate[] { cert });
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory
                .getDefaultAlgorithm());
        kmf.init(ks, password.toCharArray());

        // finally, create SSL socket factory
        SSLContext context = SSLContext.getInstance("TLSv1.2");
        context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        return context.getSocketFactory();
    }
  }

I am getting below error IP address no subject alternative name match

Unencrypted key - no password needed
starting connect the server...
MqttException (0) - javax.net.ssl.SSLHandshakeException: No subject alternative names matching IP address 192.168.5.12 found
    

I searched a lot i am not able to fix it please help Thanks

Anup
  • 67
  • 1
  • 1
  • 11

2 Answers2

5

You have 3 choices here.

  1. Change the certificate that the broker is presenting, it needs to include a SAN (Subject Alternative Name) entries for the IP address.
  2. Change the broker URL you are using in the client to use a use what ever is already in the SAN or CN field of the broker.
  3. Tell your app not to validate the remote brokers certificate. This is done by modifying the SocketFactory code you already have to include a custom TrustManager that won't check if the hostname/IP match the certificate.

Options 1/2 are probably the easiest (assuming you created the certificate the broker is using), option 3 is dangerous as you don't want this code to slip out into production as it would make it easier for somebody to do a Man in the Middle attack

hardillb
  • 54,545
  • 11
  • 67
  • 105
1

It looks like the ip is missing in the SAN field of your server certificate. If you recreate your server certificate with the ip in the SAN field it should work. See here for the similar question/answer with explanation: Certificate for doesn't match any of the subject alternative names Maybe you can call the server from the hostname instead of the ip, but than the hostname should be available as a DNS property within the SAN field

Hakan54
  • 3,121
  • 1
  • 23
  • 37