Back

EMQ X server SSL/TLS secure connection configuration guide

2019-08-21

Foreword

EMQ X has built-in support for TLS/DTLS, including support for multiple authentications, such as one-way or two-way authentication and X.509 certificate, and LB Proxy Protocol V1/2. You can enable TLS/DTLS for all protocols supported by EMQ X, or configure the HTTP API provided by EMQ X to use TLS. This article describes how to enable TLS for MQTT in EMQ X by means of self-signed certificate.

Self-sign CA and issue certificate

Create a certificate

1.Preparation

$ docker pull centos:centos7
$ docker run -it --name centos7 centos:centos7 /bin/sh
$ yum install openssl
$ yum install vim
$ mkdir /opt/ssl
$ cd /opt/ssl/
$ cp /etc/pki/tls/openssl.cnf ./
$ rm -rf /etc/pki/CA/*.old
## Generate certificate index library database file
$ touch /etc/pki/CA/index.txt
## Specify the serial number of the first certificate issued
$ echo 01 > /etc/pki/CA/serial
$ rm -rf certs;mkdir certs

2.Generate a CA self-signed certificate

$ openssl genrsa -out certs/root-ca.key 2048
$ openssl req -new -x509 -days 365 -config ./openssl.cnf -key certs/root-ca.key -out certs/root-cacert.pem -subj "/C=CN/ST=hangzhou/O=EMQ/CN=RootCA"

View the certificate

$ openssl x509 -in certs/root-cacert.pem -noout -text
...
            X509v3 Basic Constraints:
                CA:TRUE
...

3.Issue a client certificate

$ openssl genrsa -out certs/client.key 2048
$ openssl req -new -days 365 -key certs/client.key -out certs/client-cert.csr -subj "/C=CN/ST=hangzhou/O=EMQ/CN=Client"
$ openssl ca -config ./openssl.cnf -extensions v3_req -days 365 -in certs/client-cert.csr -out certs/client-cert.pem -cert certs/root-cacert.pem -keyfile certs/root-ca.key

View the certificate

$ openssl x509 -in certs/client-cert.pem -noout -text
...
            X509v3 Basic Constraints:
                CA:FALSE
...

4.Issue a server certificate

$ openssl genrsa -out certs/server.key 2048
$ openssl req -new -days 365 -key certs/server.key -out certs/server-cert.csr -subj "/C=CN/ST=hangzhou/O=EMQ/CN=Server"
$ openssl ca -config ./openssl.cnf -extensions v3_req -days 365 -in certs/server-cert.csr -out certs/server-cert.pem -cert certs/root-cacert.pem -keyfile certs/root-ca.key

5.Verification

$ openssl verify -CAfile certs/root-cacert.pem certs/server-cert.pem

One-way authentication test

$ openssl s_server -accept 2009 -key certs/server.key -cert certs/server-cert.pem
$ openssl s_client -connect localhost:2009 -CAfile certs/root-cacert.pem -showcerts
Verify return code: 0 (ok)

Two-way authentication test

$ openssl s_server -accept 2009 -key certs/server.key -cert certs/server-cert.pem -CAfile certs/root-cacert.pem -Verify 1
$ openssl s_client -connect localhost:2009 -key certs/client.key -cert certs/client-cert.pem -CAfile certs/root-cacert.pem -showcerts
Verify return code: 0 (ok)

Self-sign secondary CA and issue certificate

Create a certificate

1.Create Root CA

The steps for creating the Root CA self-signed certificate are the same as above and will not be repeated.

$ ls certs
root-ca.key  root-cacert.pem

2.Create a secondary CA

$ openssl genrsa -out certs/second-ca.key 2048
$ openssl req -new -days 365 -key certs/second-ca.key -out certs/second-cacert.csr -subj "/C=CN/ST=hangzhou/O=EMQ/CN=SecondCA"
$ openssl ca -config ./openssl.cnf -extensions v3_ca -days 365 -in certs/second-cacert.csr -out certs/second-cacert.pem -cert certs/root-cacert.pem -keyfile certs/root-ca.key

3.Issue a client certificate and a server certificate

Same as above, except that it is required to replace the information of Root CA with that of Second CA. Finally we will get the following files:

$ ls -l certs
total 48
-rw-r--r-- 1 root root  948 Aug  6 05:58 client-cert.csr
-rw-r--r-- 1 root root 3973 Aug  6 05:59 client-cert.pem
-rw-r--r-- 1 root root 1679 Aug  6 05:58 client.key
-rw-r--r-- 1 root root 1675 Aug  6 05:53 root-ca.key
-rw-r--r-- 1 root root 1212 Aug  6 05:53 root-cacert.pem
-rw-r--r-- 1 root root 1679 Aug  6 05:54 second-ca.key
-rw-r--r-- 1 root root  952 Aug  6 05:54 second-cacert.csr
-rw-r--r-- 1 root root 4194 Aug  6 05:54 second-cacert.pem
-rw-r--r-- 1 root root  948 Aug  6 05:59 server-cert.csr
-rw-r--r-- 1 root root 3973 Aug  6 05:59 server-cert.pem
-rw-r--r-- 1 root root 1675 Aug  6 05:59 server.key

4.Verification

$ openssl verify -CAfile certs/root-cacert.pem -untrusted certs/second-cacert.pem certs/server-cert.pem
certs/server-cert.pem: OK
$ openssl verify -CAfile certs/root-cacert.pem -untrusted certs/second-cacert.pem certs/client-cert.pem
certs/client-cert.pem: OK
$ cat certs/root-cacert.pem > certs/cacert.pem;cat certs/second-cacert.pem >> certs/cacert.pem
$ openssl verify -CAfile certs/cacert.pem certs/client-cert.pem
certs/client-cert.pem: OK

One-way authentication test

1.OpenSSL ACTS as Server and Client

$ openssl s_server -accept 2009 -key certs/server.key -cert certs/server-cert.pem
$ openssl s_client -connect localhost:2009 -CAfile certs/cacert.pem -showcerts
Verify return code: 0 (ok)

Note: Client can either use cacert.pem by the combination of root-cacert.pem and second-cacert.pem or second-cacert.pem.

2.OpenSSL as client, EMQ X as server

Assuming you have successfully installed EMQ X, we will copy the previously generated certificate to the emqx/etc/certs directory:

$ cp certs/* emqx/etc/certs/

Then, we will modify the emqx.conf configuration as follows:

listener.ssl.external.keyfile = etc/certs/server.key
listener.ssl.external.certfile = etc/certs/server-cert.pem

Start EMQ X and change the log level to Debug.

$ ./emqx/bin/emqx start
$ ./emqx/bin/emqx_ctl log set-level debug

Connect EMQ X with OpenSSL s_client and send a MQTT Connect packet with Client ID of "a".

$ echo -en "\x10\x0d\x00\x04MQTT\x04\x00\x00\x00\x00\x01a" | openssl s_client -connect localhost:8883 -CAfile certs/cacert.pem -showcerts
Verify return code: 0 (ok)

If you see the following log in emqx/log/erlang.log.1, the SSL certificate is successful.

2019-08-06 15:13:30.748 [debug] 127.0.0.1:60737 [Protocol] RECV CONNECT(Q0, R0, D0, ClientId=a, ProtoName=MQTT, ProtoVsn=4, CleanStart=false, KeepAlive=0, Username=undefined, Password=undefined)

3.emqtt as Client,EMQ X as Server

EMQ X continues to run, compile and launch emqtt.

$ git clone -b v1.0.1 https://github.com/emqx/emqtt.git
$ cd emqtt
$ make
$ erl -pa _build/default/lib/*/ebin
## connect to broker
1> {ok, ConnPid} = emqtt:start_link([{client_id, <<"my_client">>}, {ssl, true}, {ssl_opts, [{cacertfile,"../certs/cacert.pem"}]}, {port, 8883}]).
{ok,<0.80.0>}
2> {ok, _Props} = emqtt:connect(ConnPid).
{ok,undefined}
## subscribe
3> {ok, _Props, _ReasonCodes} = emqtt:subscribe(ConnPid, {<<"hello">>, 0}).
{ok,undefined,[0]}
## publish
4> ok = emqtt:publish(ConnPid, <<"hello">>, <<"Hello World!">>, 0).
ok
## receive message
5> receive
    {publish, Message} ->
        io:format("Message: ~p~n", [Message])
after
    1000 ->
        io:format("Error: receive timeout!~n")
end.
Message: #{client_pid => <0.80.0>,dup => false,packet_id => undefined,
           payload => <<"Hello World!">>,properties => undefined,qos => 0,
           retain => false,topic => <<"hello">>}
ok
## disconnect from broker
6> ok = emqtt:disconnect(ConnPid).

The connection is successfully established and the subscription/publish can be done normally. The SSL one-way authentication test is passed.

4.mqtt.fx as Client, EMQ X as Server

EMQ X continues to run, start mqtt.fx and complete the configuration as the figure shown below:

image20190808150338975.png

**Note: **Only second-cacert.pem can be used here as a CA Certificate.

Click the Connect button, the connection is successful, and the subscription/publish can be done normally. SSL two-way authentication is passed. 1.png

Two-way authentication test

1.OpenSSL as Server and Client

$ openssl s_server -accept 2009 -key certs/server.key -cert certs/server-cert.pem -CAfile certs/cacert.pem -Verify 1
## Use root CA
$ openssl s_client -connect localhost:2009 -key certs/client.key -cert certs/client-cert.pem -CAfile certs/cacert.pem -showcerts
Verify return code: 0 (ok)
## Use second CA
$ openssl s_client -connect localhost:2009 -key certs/client.key -cert certs/client-cert.pem -CAfile certs/second-cacert.pem -showcerts
Verify return code: 19 (self signed certificate in certificate chain)

2.OpenSSL as Client, EMQ X as Server

Modify the emqx.conf configuration as follows:

listener.ssl.external.keyfile = etc/certs/server.key
listener.ssl.external.certfile = etc/certs/server-cert.pem
listener.ssl.external.cacertfile = etc/certs/cacert.pem
## Enable two-way authentication
listener.ssl.external.verify = verify_peer
## prohibit one-way authentication
listener.ssl.external.fail_if_no_peer_cert = true

Start EMQ X and change the log level to Debug.

$ ./emqx/bin/emqx start
$ ./emqx/bin/emqx_ctl log set-level debug

Connect EMQ X with openssl s_client and send a MQTT Connect packet with Client ID of "a".

## Use root CA
$ echo -en "\x10\x0d\x00\x04MQTT\x04\x00\x00\x00\x00\x01a" | openssl s_client -connect localhost:8883 -CAfile certs/cacert.pem -cert certs/client-cert.pem -key certs/client.key -showcerts
Verify return code: 0 (ok)
## Use second CA
$ echo -en "\x10\x0d\x00\x04MQTT\x04\x00\x00\x00\x00\x01a" | openssl s_client -connect localhost:8883 -CAfile certs/second-cacert.pem -cert certs/client-cert.pem -key certs/client.key -showcerts
Verify return code: 19 (self signed certificate in certificate chain)

If you see the following log in emqx/log/erlang.log.1, the SSL certificate is successful.

2019-08-06 15:47:03.925 [debug] 127.0.0.1:61343 [Protocol] RECV CONNECT(Q0, R0, D0, ClientId=a, ProtoName=MQTT, ProtoVsn=4, CleanStart=false, KeepAlive=0, Username=undefined, Password=undefined)

3.emqtt as Client, EMQ X as Server

EMQ X continues to run,start emqtt.

$ erl -pa _build/default/lib/*/ebin
## connect to broker
1> {ok, ConnPid} = emqtt:start_link([{client_id, <<"my_client">>}, {ssl, true}, {ssl_opts, [{certfile,"../certs/client-cert.pem"},{keyfile,"../certs/client.key"}, {cacertfile,"../certs/cacert.pem"}]}, {port, 8883}]).
{ok,<0.182.0>}
2> {ok, _Props} = emqtt:connect(ConnPid).
{ok,undefined}
## subscribe
3> {ok, _Props, _ReasonCodes} = emqtt:subscribe(ConnPid, {<<"hello">>, 0}).
{ok,undefined,[0]}
## publish
4> ok = emqtt:publish(ConnPid, <<"hello">>, <<"Hello World!">>, 0).
ok
## receive message
5> receive
    {publish, Message} ->
        io:format("Message: ~p~n", [Message])
after
    1000 ->
        io:format("Error: receive timeout!~n")
end.
Message: #{client_pid => <0.182.0>,dup => false,packet_id => undefined,
           payload => <<"Hello World!">>,properties => undefined,qos => 0,
           retain => false,topic => <<"hello">>}
ok
## disconnect from broker
6> ok = emqtt:disconnect(ConnPid).

The connection is successfully established and the subscription/publish can be done normally. The SSL two-way authentication test is passed.

4.mqtt.fx as Client, EMQ X as Server

EMQ X continues to run, start mqtt.fx and complete the configuration as the figure shown below:

image20190808144846677.png

Note: CA File can either use cacert.pem by the combination of root-cacert.pem and second-cacert.pem or second-cacert.pem.

Click the Connect button, the connection is successful, and the subscription/publish can be done normally. SSL two-way authentication is passed.

2.png


Welcome to our open source project github.com/emqx/emqx. Please visit the official documentation for details.