thingsboard-aplcache

Changes

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

docker/.env 3(+2 -1)

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 70277a1..18cdb58 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/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 976f688..2889148 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -76,14 +76,10 @@ mqtt:
   adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}"
   timeout: "${MQTT_TIMEOUT:10000}"
 # Uncomment the following lines to enable ssl for MQTT
-#  ssl:
-#    key-store: keystore/mqttserver.jks
-#    key-store-password: password
-#    keyStoreType: JKS
-# TrustStore can be the same as KeyStore
-#    trust-store: keystore/mqttserver.jks
-#    trust-store-password: password
-#    trustStoreType: JKS
+  ssl:
+    key_store: keystore/mqttserver.jks
+    key_store_password: password
+    key_store_type: JKS
 
 # CoAP server parameters
 coap:
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 a340b1b..e37bc0d 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 6448d34..78805ff 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..a7f23a8
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceX509Credentials.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016-2017 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 465fa09..199da11 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 6a4e0cb..fa98b86 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 1471f7c..10e329a 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,12 +23,12 @@ 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;
 
-import java.util.Optional;
-
 import static org.thingsboard.server.dao.DaoUtil.getData;
 import static org.thingsboard.server.dao.service.Validator.validateId;
 import static org.thingsboard.server.dao.service.Validator.validateString;
@@ -70,11 +70,21 @@ public class DeviceCredentialsServiceImpl implements DeviceCredentialsService {
     }
 
     private DeviceCredentials saveOrUpdare(DeviceCredentials deviceCredentials) {
+        if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) {
+            formatCertData(deviceCredentials);
+        }
         log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials);
         credentialsValidator.validate(deviceCredentials);
         return getData(deviceCredentialsDao.save(deviceCredentials));
     }
 
+    private void formatCertData(DeviceCredentials deviceCredentials) {
+        String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue());
+        String sha3Hash = EncryptionUtil.getSha3Hash(cert);
+        deviceCredentials.setCredentialsId(sha3Hash);
+        deviceCredentials.setCredentialsValue(cert);
+    }
+
     @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..0ce5ac2
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/EncryptionUtil.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright © 2016-2017 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 trimNewLines(String input) {
+        return input.replaceAll("\n","").replaceAll("\r","");
+    }
+
+    public static String getSha3Hash(String data) {
+        String trimmedData = trimNewLines(data);
+        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;
+    }
+}

docker/.env 3(+2 -1)

diff --git a/docker/.env b/docker/.env
index ca7f2b0..534d4e8 100644
--- a/docker/.env
+++ b/docker/.env
@@ -1 +1,2 @@
-CASSANDRA_DATA_DIR=/home/docker/cassandra_volume
+CASSANDRA_DATA_DIR=/Users/admin/data_dir
+#CASSANDRA_DATA_DIR=/home/docker/cassandra_volume

pom.xml 11(+11 -0)

diff --git a/pom.xml b/pom.xml
index 442caef..909e255 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..08d6816 100644
--- a/tools/src/main/shell/keygen.properties
+++ b/tools/src/main/shell/keygen.properties
@@ -1,7 +1,25 @@
-HOSTNAME="$(hostname)"
+#
+# Copyright © 2016-2017 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.
+#
+
+DOMAIN_SUFFIX="$(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 d7cb75a..1572d89 100755
--- a/tools/src/main/shell/keygen.sh
+++ b/tools/src/main/shell/keygen.sh
@@ -15,14 +15,57 @@
 # limitations under the License.
 #
 
+usage() {
+    echo "This script generates thingsboard server's ssl certificate"
+    echo "and optionally copies it to the server's resource directory."
+    echo "usage: ./keygen.sh [-c flag] [-d directory]"
+    echo "    -c | --copy flag                  Set if copy keystore to server directory needed. Default value is true"
+    echo "    -d | --dir directory              Server keystore directory, where the generated keystore file will be copied."
+    echo "                                      Default value is SERVER_KEYSTORE_DIR property from properties file"
+    echo "    -p | --props | --properties file  Properties file. default value is ./keygen.properties"
+	echo "    -h | --help | ?                   Show this message"
+}
 
-. keygen.properties
+COPY=true;
+COPY_DIR=
+PROPERTIES_FILE=keygen.properties
+
+while true; do
+  case "$1" in
+    -c | --copy)                 COPY=$2 ;
+                                 shift
+                                 ;;
+    -d | --dir | --directory)    COPY_DIR=$2 ;
+                                 shift
+                                 ;;
+    -p | --props | --properties) PROPERTIES_FILE=$2 ;
+                                shift
+                                ;;
+    -h | --help | ?)            usage
+                                exit 0
+                                ;;
+    -- ) shift;
+         break
+         ;;
+    * ) break
+         ;;
+  esac
+  shift
+done
+
+if [[ "$COPY" != true ]] && [[ "$COPY" != false ]]; then
+   usage
+fi
+
+echo "copy: $COPY; copy_dir: $COPY_DIR; PROPERTIES_FILE=$PROPERTIES_FILE";
+
+. $PROPERTIES_FILE
 
 echo "Generating SSL Key Pair..."
 
 keytool -genkeypair -v \
   -alias $SERVER_KEY_ALIAS \
-  -dname "CN=$HOSTNAME, OU=Thingsboard, O=Thingsboard, L=Piscataway, ST=NJ, C=US" \
+  -dname "CN=$DOMAIN_SUFFIX, OU=Thingsboard, O=Thingsboard, L=Piscataway, ST=NJ, C=US" \
   -keystore $SERVER_FILE_PREFIX.jks \
   -keypass $PASSWORD \
   -storepass $PASSWORD \
@@ -30,28 +73,46 @@ keytool -genkeypair -v \
   -keysize 2048 \
   -validity 9999
 
+status=$?
+if [[ $status != 0 ]]; then
+    exit $status;
+fi
+
 keytool -export \
   -alias $SERVER_KEY_ALIAS \
   -keystore $SERVER_FILE_PREFIX.jks \
   -file $CLIENT_TRUSTSTORE -rfc \
   -storepass $PASSWORD
 
-read -p  "Do you want to copy $SERVER_FILE_PREFIX.jks to server directory? " yn
-    case $yn in
-        [Yy]) echo "Please, specify destination dir: "
-             read -p "(Default: $SERVER_KEYSTORE_DIR): " dir
-             if [[ !  -z  $dir  ]]; then
-                DESTINATION=$dir;
-             else
-                DESTINATION=$SERVER_KEYSTORE_DIR
-             fi;
-             cp $SERVER_FILE_PREFIX.jks $DESTINATION
-             if [ $? -ne 0 ]; then
-                echo "Failed to copy keystore file."
-             else
-                echo "File copied successfully."
-             fi
-             break;;
-        * ) ;;
-    esac
-echo "Done."
+status=$?
+if [[ $status != 0 ]]; then
+    exit $status;
+fi
+
+
+if [[ $COPY = true ]]; then
+    if [[ -z "$COPY_DIR" ]]; then
+        read -p  "Do you want to copy $SERVER_FILE_PREFIX.jks to server directory? " yn
+            case $yn in
+                [Yy]) echo "Please, specify destination dir: "
+                     read -p "(Default: $SERVER_KEYSTORE_DIR): " dir
+                     if [[ !  -z  $dir  ]]; then
+                        DESTINATION=$dir;
+                     else
+                        DESTINATION=$SERVER_KEYSTORE_DIR
+                     fi;
+                     break;;
+                * ) ;;
+            esac
+    else
+        DESTINATION=$COPY_DIR
+    fi
+    mkdir -p $DESTINATION
+    cp $SERVER_FILE_PREFIX.jks $DESTINATION
+    if [ $? -ne 0 ]; then
+        echo "Failed to copy keystore file."
+    else
+        echo "File copied successfully."
+    fi
+fi
+echo "Done."
\ No newline at end of file
diff --git a/tools/src/main/shell/onewaysslmqttclient.py b/tools/src/main/shell/onewaysslmqttclient.py
new file mode 100644
index 0000000..b0824e6
--- /dev/null
+++ b/tools/src/main/shell/onewaysslmqttclient.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © 2016-2017 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..4d986cd
--- /dev/null
+++ b/tools/src/main/shell/securemqttclient.keygen.sh
@@ -0,0 +1,89 @@
+#!/bin/sh
+#
+# Copyright © 2016-2017 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.
+#
+
+usage() {
+    echo "This script generates client public/private rey pair, extracts them to a no-password RSA pem file,"
+    echo "and also imports server public key to client trust store"
+    echo "usage: ./securemqttclient.keygen.sh [-p file]"
+    echo "    -p | --props | --properties file  Properties file. default value is ./keygen.properties"
+	echo "    -h | --help  | ?                  Show this message"
+}
+
+PROPERTIES_FILE=keygen.properties
+
+while true; do
+  case "$1" in
+    -p | --props | --properties) PROPERTIES_FILE=$2 ;
+                                shift
+                                ;;
+    -h | --help | ?)            usage
+                                exit 0
+                                ;;
+    -- ) shift;
+         break
+         ;;
+    * )  break
+         ;;
+  esac
+  shift
+done
+
+. $PROPERTIES_FILE
+
+echo "Generating SSL Key Pair..."
+
+keytool -genkeypair -v \
+  -alias $CLIENT_KEY_ALIAS \
+  -dname "CN=$DOMAIN_SUFFIX, 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/tools/src/main/shell/simplemqttclient.py b/tools/src/main/shell/simplemqttclient.py
index 91b3e34..9ec3250 100644
--- a/tools/src/main/shell/simplemqttclient.py
+++ b/tools/src/main/shell/simplemqttclient.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 #
 # Copyright © 2016-2017 The Thingsboard Authors
 #
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 1fc9e1d..da478eb 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.
@@ -37,31 +45,27 @@ import java.security.KeyStore;
 public class MqttSslHandlerProvider {
 
     public static final String TLS = "TLS";
-    @Value("${mqtt.ssl.key-store}")
+    @Value("${mqtt.ssl.key_store}")
     private String keyStoreFile;
-    @Value("${mqtt.ssl.key-store-password}")
+    @Value("${mqtt.ssl.key_store_password}")
     private String keyStorePassword;
-    @Value("${mqtt.ssl.keyStoreType}")
+    @Value("${mqtt.ssl.key_store_type}")
     private String keyStoreType;
 
-    @Value("${mqtt.ssl.trust-store}")
-    private String trustStoreFile;
-    @Value("${mqtt.ssl.trust-store-password}")
-    private String trustStorePassword;
-    @Value("${mqtt.ssl.trustStoreType}")
-    private String trustStoreType;
+    @Autowired
+    private DeviceCredentialsService deviceCredentialsService;
 
 
     public SslHandler getSslHandler() {
         try {
             URL ksUrl = Resources.getResource(keyStoreFile);
             File ksFile = new File(ksUrl.toURI());
-            URL tsUrl = Resources.getResource(trustStoreFile);
+            URL tsUrl = Resources.getResource(keyStoreFile);
             File tsFile = new File(tsUrl.toURI());
 
             TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-            KeyStore trustStore = KeyStore.getInstance(trustStoreType);
-            trustStore.load(new FileInputStream(tsFile), trustStorePassword.toCharArray());
+            KeyStore trustStore = KeyStore.getInstance(keyStoreType);
+            trustStore.load(new FileInputStream(tsFile), keyStorePassword.toCharArray());
             tmFactory.init(trustStore);
 
             KeyStore ks = KeyStore.getInstance(keyStoreType);
@@ -71,13 +75,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 +93,57 @@ 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;
+            }
+        }
+        return new ThingsboardMqttX509TrustManager(x509Tm, deviceCredentialsService);
+    }
+
+    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 {
+            DeviceCredentials deviceCredentials = null;
+            for (X509Certificate cert : chain) {
+                try {
+                    String strCert = SslUtil.getX509CertificateString(cert);
+                    String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
+                    deviceCredentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(sha3Hash);
+                    if (deviceCredentials != null && strCert.equals(deviceCredentials.getCredentialsValue())) {
+                        break;
+                    }
+                } catch (IOException e) {
+                    log.error(e.getMessage(), e);
+                }
+            }
+            if (deviceCredentials == null) {
+                throw new CertificateException("Invalid Device Certificate");
+            }
+        }
+    }
 }
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 a10b216..30b37e3 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 0aa2874..323ed1e 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..d1ea59b
--- /dev/null
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright © 2016-2017 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 org.thingsboard.server.dao.EncryptionUtil;
+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 EncryptionUtil.trimNewLines(new String(out.toByteArray(), "UTF-8"));
+    }
+
+    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 EncryptionUtil.trimNewLines(new String(out.toByteArray(), "UTF-8"));
+    }
+}
diff --git a/transport/pom.xml b/transport/pom.xml
index 27d0afa..4d4a705 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 c89ab49..537df5d 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,8 +51,17 @@ export default function ManageDeviceCredentialsController(deviceService, $scope,
 
     function valid() {
         return vm.deviceCredentials &&
-               vm.deviceCredentials.credentialsType === 'ACCESS_TOKEN' &&
-               vm.deviceCredentials.credentialsId && vm.deviceCredentials.credentialsId.length > 0;
+               (vm.deviceCredentials.credentialsType === 'ACCESS_TOKEN'
+                   && vm.deviceCredentials.credentialsId
+                   && vm.deviceCredentials.credentialsId.length > 0
+                   || vm.deviceCredentials.credentialsType === 'X509_CERTIFICATE'
+                   && vm.deviceCredentials.credentialsValue
+                   && vm.deviceCredentials.credentialsValue.length > 0);
+    }
+
+    function clear() {
+        vm.deviceCredentials.credentialsId = null;
+        vm.deviceCredentials.credentialsValue = null;
     }
 
     function save() {
diff --git a/ui/src/app/device/device-credentials.tpl.html b/ui/src/app/device/device-credentials.tpl.html
index cc1ead0..9dd4553 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.credentialsValue"
+							  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 ccbc3e7..f042993 100644
--- a/ui/src/locale/en_US.json
+++ b/ui/src/locale/en_US.json
@@ -308,6 +308,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",