thingsboard-aplcache

Changes

dao/pom.xml 4(+4 -0)

pom.xml 11(+11 -0)

Details

diff --git a/application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java b/application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java
index cef73fe..dbc1920 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java
@@ -51,6 +51,8 @@ public class DefaultDeviceAuthService implements DeviceAuthService {
                         // Credentials ID matches Credentials value in this
                         // primitive case;
                         return DeviceAuthResult.of(credentials.getDeviceId());
+                    case X509_CERTIFICATE:
+                        return DeviceAuthResult.of(credentials.getDeviceId());
                     default:
                         return DeviceAuthResult.of("Credentials Type is not supported yet!");
                 }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java
index 3daa1e4..d388348 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.security;
 
 public enum DeviceCredentialsType {
 
-    ACCESS_TOKEN
+    ACCESS_TOKEN,
+    X509_CERTIFICATE
 
 }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceTokenCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceTokenCredentials.java
index 8ce9f00..3e9001b 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceTokenCredentials.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceTokenCredentials.java
@@ -20,7 +20,6 @@ public class DeviceTokenCredentials implements DeviceCredentialsFilter {
     private final String token;
 
     public DeviceTokenCredentials(String token) {
-        super();
         this.token = token;
     }
 
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceX509Credentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceX509Credentials.java
new file mode 100644
index 0000000..f2be4c1
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceX509Credentials.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.common.data.security;
+
+/**
+ * @author Valerii Sosliuk
+ */
+public class DeviceX509Credentials implements DeviceCredentialsFilter {
+
+    private final String sha3Hash;
+
+    public DeviceX509Credentials(String sha3Hash) {
+        this.sha3Hash = sha3Hash;
+    }
+
+    @Override
+    public String getCredentialsId() { return sha3Hash; }
+
+    @Override
+    public DeviceCredentialsType getCredentialsType() { return DeviceCredentialsType.X509_CERTIFICATE; }
+
+    @Override
+    public String toString() {
+        return "DeviceX509Credentials [SHA3=" + sha3Hash + "]";
+    }
+}

dao/pom.xml 4(+4 -0)

diff --git a/dao/pom.xml b/dao/pom.xml
index a2edc7a..3b25c42 100644
--- a/dao/pom.xml
+++ b/dao/pom.xml
@@ -146,6 +146,10 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-autoconfigure</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
index da1a42f..f258122 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
@@ -18,9 +18,7 @@ package org.thingsboard.server.dao;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 import java.util.UUID;
 
 import org.thingsboard.server.common.data.id.UUIDBased;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
index 19ad2d1..cc0e642 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
@@ -23,6 +23,8 @@ import org.springframework.util.StringUtils;
 import org.thingsboard.server.common.data.Device;
 import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+import org.thingsboard.server.dao.EncryptionUtil;
 import org.thingsboard.server.dao.exception.DataValidationException;
 import org.thingsboard.server.dao.model.DeviceCredentialsEntity;
 import org.thingsboard.server.dao.service.DataValidator;
@@ -70,11 +72,19 @@ public class DeviceCredentialsServiceImpl implements DeviceCredentialsService {
     }
 
     private DeviceCredentials saveOrUpdare(DeviceCredentials deviceCredentials) {
+        if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) {
+            encryptDeviceId(deviceCredentials);
+        }
         log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials);
         credentialsValidator.validate(deviceCredentials);
         return getData(deviceCredentialsDao.save(deviceCredentials));
     }
 
+    private void encryptDeviceId(DeviceCredentials deviceCredentials) {
+        String sha3Hash = EncryptionUtil.getSha3Hash(deviceCredentials.getCredentialsId());
+        deviceCredentials.setCredentialsId(sha3Hash);
+    }
+
     @Override
     public void deleteDeviceCredentials(DeviceCredentials deviceCredentials) {
         log.trace("Executing deleteDeviceCredentials [{}]", deviceCredentials);
@@ -121,6 +131,10 @@ public class DeviceCredentialsServiceImpl implements DeviceCredentialsService {
                                 throw new DataValidationException("Incorrect access token length [" + deviceCredentials.getCredentialsId().length() + "]!");
                             }
                             break;
+                        case X509_CERTIFICATE:
+                            if (deviceCredentials.getCredentialsId().length() == 0) {
+                                throw new DataValidationException("X509 Certificate Cannot be empty!");
+                            }
                         default:
                             break;
                     }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/EncryptionUtil.java b/dao/src/main/java/org/thingsboard/server/dao/EncryptionUtil.java
new file mode 100644
index 0000000..df456c8
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/EncryptionUtil.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao;
+
+import lombok.extern.slf4j.Slf4j;
+import org.bouncycastle.crypto.digests.SHA3Digest;
+import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
+/**
+ * @author Valerii Sosliuk
+ */
+@Slf4j
+public class EncryptionUtil {
+
+    private EncryptionUtil() {
+    }
+
+    public static String getSha3Hash(String data) {
+        String trimmedData = data.replaceAll("\n","").replaceAll("\r","");
+        byte[] dataBytes = trimmedData.getBytes();
+        SHA3Digest md = new SHA3Digest(256);
+        md.reset();
+        md.update(dataBytes, 0, dataBytes.length);
+        byte[] hashedBytes = new byte[256 / 8];
+        md.doFinal(hashedBytes, 0);
+        String sha3Hash = ByteUtils.toHexString(hashedBytes);
+        return sha3Hash;
+    }
+}

pom.xml 11(+11 -0)

diff --git a/pom.xml b/pom.xml
index b68bee7..226f915 100755
--- a/pom.xml
+++ b/pom.xml
@@ -69,6 +69,7 @@
         <surfire.version>2.19.1</surfire.version>
         <jar-plugin.version>3.0.2</jar-plugin.version>
         <springfox-swagger.version>2.6.1</springfox-swagger.version>
+        <bouncycastle.version>1.56</bouncycastle.version>
     </properties>
 
     <modules>
@@ -689,6 +690,16 @@
                 <artifactId>springfox-swagger2</artifactId>
                 <version>${springfox-swagger.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcprov-jdk15on</artifactId>
+                <version>${bouncycastle.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcpkix-jdk15on</artifactId>
+                <version>${bouncycastle.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
diff --git a/tools/src/main/shell/keygen.properties b/tools/src/main/shell/keygen.properties
index 6016bda..d27e0f5 100644
--- a/tools/src/main/shell/keygen.properties
+++ b/tools/src/main/shell/keygen.properties
@@ -1,7 +1,9 @@
 HOSTNAME="$(hostname)"
 PASSWORD="password"
 
-CLIENT_TRUSTSTORE="client_truststore.crt"
+CLIENT_TRUSTSTORE="client_truststore.pem"
+CLIENT_KEY_ALIAS="clientalias"
+CLIENT_FILE_PREFIX="mqttclient"
 
 SERVER_KEY_ALIAS="serveralias"
 SERVER_FILE_PREFIX="mqttserver"
diff --git a/tools/src/main/shell/keygen.sh b/tools/src/main/shell/keygen.sh
index 25b3157..46e670d 100755
--- a/tools/src/main/shell/keygen.sh
+++ b/tools/src/main/shell/keygen.sh
@@ -45,6 +45,7 @@ read -p  "Do you want to copy $SERVER_FILE_PREFIX.jks to server directory? " yn
              else
                 DESTINATION=$SERVER_KEYSTORE_DIR
              fi;
+             mkdir -p $SERVER_KEYSTORE_DIR
              cp $SERVER_FILE_PREFIX.jks $DESTINATION
              if [ $? -ne 0 ]; then
                 echo "Failed to copy keystore file."
diff --git a/tools/src/main/shell/onewaysslmqttclient.py b/tools/src/main/shell/onewaysslmqttclient.py
new file mode 100644
index 0000000..a6efbd3
--- /dev/null
+++ b/tools/src/main/shell/onewaysslmqttclient.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © 2016 The Thingsboard Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import paho.mqtt.client as mqtt
+import ssl, socket
+
+# The callback for when the client receives a CONNACK response from the server.
+def on_connect(client, userdata, rc):
+   print('Connected with result code '+str(rc))
+   # Subscribing in on_connect() means that if we lose the connection and
+   # reconnect then subscriptions will be renewed.
+   client.subscribe('v1/devices/me/attributes')
+   client.subscribe('v1/devices/me/attributes/response/+')
+   client.subscribe('v1/devices/me/rpc/request/+')
+
+
+# The callback for when a PUBLISH message is received from the server.
+def on_message(client, userdata, msg):
+   print 'Topic: ' + msg.topic + '\nMessage: ' + str(msg.payload)
+   if msg.topic.startswith( 'v1/devices/me/rpc/request/'):
+       requestId = msg.topic[len('v1/devices/me/rpc/request/'):len(msg.topic)]
+       print 'This is a RPC call. RequestID: ' + requestId + '. Going to reply now!'
+       client.publish('v1/devices/me/rpc/response/' + requestId, "{\"value1\":\"A\", \"value2\":\"B\"}", 1)
+
+
+client = mqtt.Client()
+client.on_connect = on_connect
+client.on_message = on_message
+client.publish('v1/devices/me/attributes/request/1', "{\"clientKeys\":\"model\"}", 1)
+
+#client.tls_set(ca_certs="client_truststore.pem", certfile="mqttclient.nopass.pem", keyfile=None, cert_reqs=ssl.CERT_REQUIRED,
+#               tls_version=ssl.PROTOCOL_TLSv1, ciphers=None);
+client.tls_set(ca_certs="client_truststore.pem", certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED,
+                       tls_version=ssl.PROTOCOL_TLSv1, ciphers=None);
+
+client.username_pw_set("B1_TEST_TOKEN")
+client.tls_insecure_set(False)
+client.connect(socket.gethostname(), 1883, 1)
+
+
+# Blocking call that processes network traffic, dispatches callbacks and
+# handles reconnecting.
+# Other loop*() functions are available that give a threaded interface and a
+# manual interface.
+client.loop_forever()
diff --git a/tools/src/main/shell/securemqttclient.keygen.sh b/tools/src/main/shell/securemqttclient.keygen.sh
new file mode 100755
index 0000000..1d6752c
--- /dev/null
+++ b/tools/src/main/shell/securemqttclient.keygen.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+# Copyright © 2016 The Thingsboard Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+. keygen.properties
+
+echo "Generating SSL Key Pair..."
+
+keytool -genkeypair -v \
+  -alias $CLIENT_KEY_ALIAS \
+  -dname "CN=$HOSTNAME, OU=Thingsboard, O=Thingsboard, L=Piscataway, ST=NJ, C=US" \
+  -keystore $CLIENT_FILE_PREFIX.jks \
+  -keypass $PASSWORD \
+  -storepass $PASSWORD \
+  -keyalg RSA \
+  -keysize 2048 \
+  -validity 9999
+echo "Converting keystore to pkcs12"
+keytool -importkeystore  \
+  -srckeystore $CLIENT_FILE_PREFIX.jks \
+  -destkeystore $CLIENT_FILE_PREFIX.p12 \
+  -srcalias $CLIENT_KEY_ALIAS \
+  -srcstoretype jks \
+  -deststoretype pkcs12 \
+  -keypass $PASSWORD \
+  -srcstorepass $PASSWORD \
+  -deststorepass $PASSWORD \
+  -srckeypass $PASSWORD \
+  -destkeypass $PASSWORD
+
+echo "Converting pkcs12 to pem"
+openssl pkcs12 -in $CLIENT_FILE_PREFIX.p12 \
+  -out $CLIENT_FILE_PREFIX.pem \
+  -passin pass:$PASSWORD \
+  -passout pass:$PASSWORD \
+
+echo "Importing server public key..."
+keytool -export \
+  -alias $SERVER_KEY_ALIAS \
+  -keystore $SERVER_KEYSTORE_DIR/$SERVER_FILE_PREFIX.jks \
+  -file $CLIENT_TRUSTSTORE -rfc \
+  -storepass $PASSWORD
+
+echo "Exporting no-password pem certificate"
+openssl rsa -in $CLIENT_FILE_PREFIX.pem -out $CLIENT_FILE_PREFIX.nopass.pem -passin pass:$PASSWORD
+tail -n +$(($(grep -m1 -n -e '-----BEGIN CERTIFICATE' $CLIENT_FILE_PREFIX.pem | cut -d: -f1) )) \
+  $CLIENT_FILE_PREFIX.pem >> $CLIENT_FILE_PREFIX.nopass.pem
+
+echo "Done."
\ No newline at end of file
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
index f7b38d0..6872c03 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
@@ -18,15 +18,23 @@ package org.thingsboard.server.transport.mqtt;
 import com.google.common.io.Resources;
 import io.netty.handler.ssl.SslHandler;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.dao.EncryptionUtil;
+import org.thingsboard.server.dao.device.DeviceCredentialsService;
+import org.thingsboard.server.transport.mqtt.util.SslUtil;
 
 import javax.net.ssl.*;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.IOException;
 import java.net.URL;
 import java.security.KeyStore;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
 
 /**
  * Created by valerii.sosliuk on 11/6/16.
@@ -51,6 +59,9 @@ public class MqttSslHandlerProvider {
     @Value("${mqtt.ssl.trustStoreType}")
     private String trustStoreType;
 
+    @Autowired
+    private DeviceCredentialsService deviceCredentialsService;
+
 
     public SslHandler getSslHandler() {
         try {
@@ -71,13 +82,14 @@ public class MqttSslHandlerProvider {
             kmf.init(ks, keyStorePassword.toCharArray());
 
             KeyManager[] km = kmf.getKeyManagers();
-            TrustManager[] tm = tmFactory.getTrustManagers();
+            TrustManager x509wrapped = getX509TrustManager(tmFactory);
+            TrustManager[] tm = {x509wrapped};
             SSLContext sslContext = SSLContext.getInstance(TLS);
             sslContext.init(km, tm, null);
             SSLEngine sslEngine = sslContext.createSSLEngine();
             sslEngine.setUseClientMode(false);
             sslEngine.setNeedClientAuth(false);
-            sslEngine.setWantClientAuth(false);
+            sslEngine.setWantClientAuth(true);
             sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols());
             sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());
             sslEngine.setEnableSessionCreation(true);
@@ -88,4 +100,54 @@ public class MqttSslHandlerProvider {
         }
     }
 
+    private TrustManager getX509TrustManager(TrustManagerFactory tmf) throws Exception {
+        X509TrustManager x509Tm = null;
+        for (TrustManager tm : tmf.getTrustManagers()) {
+            if (tm instanceof X509TrustManager) {
+                x509Tm = (X509TrustManager) tm;
+                break;
+            }
+        }
+        X509TrustManager x509TmWrapper = new ThingsboardMqttX509TrustManager(x509Tm, deviceCredentialsService);
+        return x509TmWrapper;
+    }
+
+    static class ThingsboardMqttX509TrustManager implements X509TrustManager {
+
+        private final X509TrustManager trustManager;
+        private DeviceCredentialsService deviceCredentialsService;
+
+        ThingsboardMqttX509TrustManager(X509TrustManager trustManager, DeviceCredentialsService deviceCredentialsService) {
+            this.trustManager = trustManager;
+            this.deviceCredentialsService = deviceCredentialsService;
+        }
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            return trustManager.getAcceptedIssuers();
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain,
+                                       String authType) throws CertificateException {
+            trustManager.checkServerTrusted(chain, authType);
+        }
+
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain,
+                                       String authType) throws CertificateException {
+            for (X509Certificate cert : chain) {
+                try {
+                    String strCert = SslUtil.getX509CertificateString(cert);
+                    String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
+                    DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(sha3Hash);
+                    if (deviceCredentials == null) {
+                        throw new CertificateException("Invalid Device Certificate");
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
 }
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
index b4d8108..437ce03 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
@@ -18,11 +18,13 @@ package org.thingsboard.server.transport.mqtt;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.handler.codec.mqtt.*;
+import io.netty.handler.ssl.SslHandler;
 import io.netty.util.concurrent.Future;
 import io.netty.util.concurrent.GenericFutureListener;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.util.StringUtils;
 import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
+import org.thingsboard.server.common.data.security.DeviceX509Credentials;
 import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg;
 import org.thingsboard.server.common.msg.session.BasicToDeviceActorSessionMsg;
 import org.thingsboard.server.common.msg.session.MsgType;
@@ -30,9 +32,13 @@ import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
 import org.thingsboard.server.common.transport.SessionMsgProcessor;
 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
+import org.thingsboard.server.dao.EncryptionUtil;
 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
 import org.thingsboard.server.transport.mqtt.session.MqttSessionCtx;
+import org.thingsboard.server.transport.mqtt.util.SslUtil;
 
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -57,12 +63,15 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
     private final String sessionId;
     private final MqttTransportAdaptor adaptor;
     private final SessionMsgProcessor processor;
+    private final SslHandler sslHandler;
 
-    public MqttTransportHandler(SessionMsgProcessor processor, DeviceAuthService authService, MqttTransportAdaptor adaptor) {
+    public MqttTransportHandler(SessionMsgProcessor processor, DeviceAuthService authService,
+                                MqttTransportAdaptor adaptor, SslHandler sslHandler) {
         this.processor = processor;
         this.adaptor = adaptor;
         this.sessionCtx = new MqttSessionCtx(processor, authService, adaptor);
         this.sessionId = sessionCtx.getSessionId().toUidStr();
+        this.sslHandler = sslHandler;
     }
 
     @Override
@@ -197,6 +206,15 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 
     private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) {
         log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier());
+        X509Certificate cert;
+        if (sslHandler != null && (cert = getX509Certificate()) != null) {
+            processX509CertConnect(ctx, cert);
+        } else {
+            processAuthTokenConnect(ctx, msg);
+        }
+    }
+
+    private void processAuthTokenConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) {
         String userName = msg.payload().userName();
         if (StringUtils.isEmpty(userName)) {
             ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD));
@@ -209,6 +227,35 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
         }
     }
 
+    private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert) {
+        try {
+            String strCert = SslUtil.getX509CertificateString(cert);
+            String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
+            if (sessionCtx.login(new DeviceX509Credentials(sha3Hash))) {
+                ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_ACCEPTED));
+            } else {
+                ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED));
+                ctx.close();
+            }
+        } catch (Exception e) {
+            ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED));
+            ctx.close();
+        }
+    }
+
+    private X509Certificate getX509Certificate() {
+        try {
+            X509Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificateChain();
+            if (certChain.length > 0) {
+                return certChain[0];
+            }
+        } catch (SSLPeerUnverifiedException e) {
+            log.warn(e.getMessage());
+            return null;
+        }
+        return null;
+    }
+
     private void processDisconnect(ChannelHandlerContext ctx) {
         ctx.close();
     }
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
index 0c60309..7eeb955 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
@@ -55,13 +55,15 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
     @Override
     public void initChannel(SocketChannel ch) {
         ChannelPipeline pipeline = ch.pipeline();
+        SslHandler sslHandler = null;
         if (sslHandlerProvider != null) {
-            pipeline.addLast(sslHandlerProvider.getSslHandler());
+            sslHandler = sslHandlerProvider.getSslHandler();
+            pipeline.addLast(sslHandler);
         }
         pipeline.addLast("decoder", new MqttDecoder());
         pipeline.addLast("encoder", MqttEncoder.INSTANCE);
 
-        MqttTransportHandler handler = new MqttTransportHandler(processor, authService, adaptor);
+        MqttTransportHandler handler = new MqttTransportHandler(processor, authService, adaptor, sslHandler);
         pipeline.addLast(handler);
         ch.closeFuture().addListener(handler);
     }
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java
new file mode 100644
index 0000000..8df1681
--- /dev/null
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.transport.mqtt.util;
+
+import lombok.extern.slf4j.Slf4j;
+import sun.misc.BASE64Encoder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+/**
+ * @author Valerii Sosliuk
+ */
+@Slf4j
+public class SslUtil {
+
+    private SslUtil() {
+    }
+
+    public static String getX509CertificateString(X509Certificate cert) throws CertificateEncodingException, IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        BASE64Encoder encoder = new BASE64Encoder();
+        encoder.encodeBuffer(cert.getEncoded(), out);
+        return new String(out.toByteArray(), "UTF-8").trim();
+    }
+
+    public static String getX509CertificateString(javax.security.cert.X509Certificate cert)
+            throws javax.security.cert.CertificateEncodingException, IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        BASE64Encoder encoder = new BASE64Encoder();
+        encoder.encodeBuffer(cert.getEncoded(), out);
+        return new String(out.toByteArray(), "UTF-8").trim();
+    }
+}
diff --git a/transport/pom.xml b/transport/pom.xml
index 4b7c8ab..dd71478 100644
--- a/transport/pom.xml
+++ b/transport/pom.xml
@@ -42,9 +42,17 @@
 
     <dependencies>
         <dependency>
+            <groupId>org.thingsboard</groupId>
+            <artifactId>dao</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-autoconfigure</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/ui/src/app/device/device-credentials.controller.js b/ui/src/app/device/device-credentials.controller.js
index 42b6696..acc1106 100644
--- a/ui/src/app/device/device-credentials.controller.js
+++ b/ui/src/app/device/device-credentials.controller.js
@@ -24,7 +24,7 @@ export default function ManageDeviceCredentialsController(deviceService, $scope,
             value: 'ACCESS_TOKEN'
         },
         {
-            name: 'X.509 Certificate (Coming soon)',
+            name: 'X.509 Certificate',
             value: 'X509_CERTIFICATE'
         }
     ];
@@ -35,6 +35,7 @@ export default function ManageDeviceCredentialsController(deviceService, $scope,
     vm.valid = valid;
     vm.cancel = cancel;
     vm.save = save;
+    vm.clear = clear;
 
     loadDeviceCredentials();
 
@@ -50,10 +51,16 @@ export default function ManageDeviceCredentialsController(deviceService, $scope,
 
     function valid() {
         return vm.deviceCredentials &&
-               vm.deviceCredentials.credentialsType === 'ACCESS_TOKEN' &&
+               (vm.deviceCredentials.credentialsType === 'ACCESS_TOKEN'
+                  || vm.deviceCredentials.credentialsType === 'X509_CERTIFICATE')
+               &&
                vm.deviceCredentials.credentialsId && vm.deviceCredentials.credentialsId.length > 0;
     }
 
+    function clear() {
+        vm.deviceCredentials.credentialsId = null;
+    }
+
     function save() {
         deviceService.saveDeviceCredentials(vm.deviceCredentials).then(function success(deviceCredentials) {
             vm.deviceCredentials = deviceCredentials;
diff --git a/ui/src/app/device/device-credentials.tpl.html b/ui/src/app/device/device-credentials.tpl.html
index 5bfc2c0..ddd9def 100644
--- a/ui/src/app/device/device-credentials.tpl.html
+++ b/ui/src/app/device/device-credentials.tpl.html
@@ -33,7 +33,8 @@
 	        <fieldset ng-disabled="loading || vm.isReadOnly">
 				<md-input-container class="md-block">
 					<label translate>device.credentials-type</label>
-		            <md-select ng-disabled="loading || vm.isReadOnly" ng-model="vm.deviceCredentials.credentialsType">
+		            <md-select ng-disabled="loading || vm.isReadOnly" ng-model="vm.deviceCredentials.credentialsType"
+					    ng-change="vm.clear()">
 		              	<md-option ng-repeat="credentialsType in vm.credentialsTypes" value="{{credentialsType.value}}">
 		                	{{credentialsType.name}}
 		             	</md-option>
@@ -48,6 +49,14 @@
 							<div translate ng-message="pattern">device.access-token-invalid</div>
        				</div>
 				</md-input-container>
+				<md-input-container class="md-block" ng-if="vm.deviceCredentials.credentialsType === 'X509_CERTIFICATE'">
+					<label translate>device.rsa-key</label>
+					<textarea required name="rsaKey" ng-model="vm.deviceCredentials.credentialsId"
+							  cols="15" rows="5" />
+					<div ng-messages="theForm.rsaKey.$error">
+						<div translate ng-message="required">device.rsa-key-required</div>
+					</div>
+				</md-input-container>
 			</fieldset>
 	      </div>
 	    </md-dialog-content>
diff --git a/ui/src/locale/en_US.json b/ui/src/locale/en_US.json
index 5de8938..832a6ed 100644
--- a/ui/src/locale/en_US.json
+++ b/ui/src/locale/en_US.json
@@ -294,6 +294,8 @@
     "access-token": "Access token",
     "access-token-required": "Access token is required.",
     "access-token-invalid": "Access token length must be from 1 to 20 characters.",
+    "rsa-key": "RSA public key",
+    "access-token-required": "RSA public key is required.",
     "secret": "Secret",
     "secret-required": "Secret is required.",
     "name": "Name",