Paho Java で MQTT ブローカに TLS 接続する


今回の例では、mosquittoのテストサーバに接続します。

## 証明書を取得する

mosquittoの証明書は公開されていので取得します。

```
$ wget http://test.mosquitto.org/ssl/mosquitto.org.crt
```

keytool（Java付属の鍵・証明書管理用ツール）で証明書の内容を確認できます。

```
$ keytool -printcert -file mosquitto.org.crt
keytool -printcert -file mosquitto.org.crt
所有者: EMAILADDRESS=roger@atchoo.org, CN=mosquitto.org, OU=CA, O=Mosquitto, L=Derby, ST=United Kingdom, C=GB
発行者: EMAILADDRESS=roger@atchoo.org, CN=mosquitto.org, OU=CA, O=Mosquitto, L=Derby, ST=United Kingdom, C=GB
シリアル番号: e0fadcf9578c98bc
有効期間の開始日: Sat Jun 30 07:11:59 JST 2012終了日: Tue Jun 28 07:11:59 JST 2022
証明書のフィンガプリント:
	 MD5:  AB:F2:AF:A0:94:9E:08:64:F7:1D:5F:9B:EE:88:D1:8F
	 SHA1: A2:79:92:D3:42:0C:89:F2:93:D3:51:37:8B:A5:F5:67:5F:74:FE:3C
	 SHA256: D1:56:59:4D:32:A9:9D:0C:72:66:5B:5F:94:3C:30:34:D0:7C:DB:A3:60:51:A4:62:56:13:7F:63:1D:2D:F4:E6
	 署名アルゴリズム名: SHA1withRSA
	 バージョン: 3
(省略)
```

## 証明書をキーストアにインポートする

Javaで秘密鍵や証明書を扱う場合、キーストアという鍵や証明書を管理するデータベースに入れる必要があります。


```
$ keytool \
  -import \
  -alias rootCA \
  -trustcacerts \
  -keystore ./crt.jks \
  -storetype jks \
  -storepass testtest \
  -file ./mosquitto.org.crt
```


ここでは以下を行っています。

* インポートする証明書としてmosquitto.org.crtを指定。
* 上記証明書にrootCAというエイリアスをつける。
* インポート先のキーストア名はcrt.jks
* キーストアの形式はjks（Java Key Store）
* キーストアのパスワードはtesttest

以下でキーストアに証明書がインポートされているか確認できます。

```
$ keytool \
  -list \
  -keystore ./crt.jks \
  -alias rootCA \
  -storepass testtest
rootCA,2016/11/30, trustedCertEntry, 
証明書のフィンガプリント(SHA1): A2:79:92:D3:42:0C:89:F2:93:D3:51:37:8B:A5:F5:67:5F:74:FE:3C
```

## Paho JavaでMQTTS（MQTT over TLS）接続する

通常のTCP接続のサンプルは以下のURLを参照ください。

https://eclipse.org/paho/clients/java/

以下がTLS接続版のサンプルコードです。

```
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

import java.util.Properties;

public class MqttPublishSample {
    void run () {
        String topic        = "MQTT Examples";
        String content      = "Message from MqttPublishSample";
        int qos             = 2;
        String broker       = "ssl://test.mosquitto.org:8883";
        String clientId     = "JavaSample";
        MemoryPersistence persistence = new MemoryPersistence();
        String trustStore   = this.getClass().getResource("crt.jks").getPath();

        try {
            MqttClient sampleClient = new MqttClient(broker, clientId, persistence);
            MqttConnectOptions connOpts = new MqttConnectOptions();
            connOpts.setCleanSession(true);
            Properties prop = new Properties();
            prop.setProperty("com.ibm.ssl.trustStore", trustStore);
            connOpts.setSSLProperties(prop);
            System.out.println("Connecting to broker: "+broker);
            sampleClient.connect(connOpts);
            System.out.println("Connected");
            System.out.println("Publishing message: "+content);
            MqttMessage message = new MqttMessage(content.getBytes());
            message.setQos(qos);
            sampleClient.publish(topic, message);
            System.out.println("Message published");
            sampleClient.disconnect();
            System.out.println("Disconnected");
            System.exit(0);
        } catch(MqttException me) {
            System.out.println("reason "+me.getReasonCode());
            System.out.println("msg "+me.getMessage());
            System.out.println("loc "+me.getLocalizedMessage());
            System.out.println("cause "+me.getCause());
            System.out.println("excep "+me);
            me.printStackTrace();
        }
    }

    public static void main(String[] args) {
        MqttPublishSample sample = new MqttPublishSample();
        sample.run();
    }
}
```

TCP接続の場合との違いが2点あります。

1つ目はブローカのURI。
* スキームとしてsslを指定します。
* デフォルトポートは8883です（これはブローカの設定によります）。

```
String broker       = "ssl://test.mosquitto.org:8883";
```

2つ目はSSL設定を行っていること。
* Propertiesを作成し、MqttConnectOptions.setSslPropertiesで設定します。
* 最低限、リモートホストの証明書（あるいは証明書チェーン）を含むキーストアのファイルパスを"com.ibm.ssl.trustStore"で指定します。
  * なお、keytoolでキーストアを扱う場合は完全性の維持のため、パスワードを指定する必要があります。ただし、Java API で扱う証明書を扱う場合はパスワードの指定が不要のようです。

```
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(true);
Properties prop = new Properties();
prop.setProperty("com.ibm.ssl.trustStore", trustStore);
connOpts.setSSLProperties(prop);
```

実際に実行するとうまく接続できました。

```
Connecting to broker: ssl://test.mosquitto.org:8883
Connected
Publishing message: Message from MqttPublishSample
Message published
Disconnected
```