Browse Source

X509 certificate support implemented

pull/32/head
Valerii Sosliuk 10 years ago
parent
commit
75f39693ff
  1. 2
      application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java
  2. 3
      common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java
  3. 1
      common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceTokenCredentials.java
  4. 39
      common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceX509Credentials.java
  5. 4
      dao/pom.xml
  6. 2
      dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
  7. 41
      dao/src/main/java/org/thingsboard/server/dao/EncryptionUtil.java
  8. 14
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
  9. 11
      pom.xml
  10. 4
      tools/src/main/shell/keygen.properties
  11. 1
      tools/src/main/shell/keygen.sh
  12. 59
      tools/src/main/shell/onewaysslmqttclient.py
  13. 63
      tools/src/main/shell/securemqttclient.keygen.sh
  14. 6
      tools/src/main/shell/twowaysslmqttclient.py
  15. 66
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
  16. 49
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  17. 6
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
  18. 49
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java
  19. 8
      transport/pom.xml
  20. 11
      ui/src/app/device/device-credentials.controller.js
  21. 11
      ui/src/app/device/device-credentials.tpl.html
  22. 2
      ui/src/locale/en_US.json

2
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!");
}

3
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
}

1
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;
}

39
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 + "]";
}
}

4
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>

2
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;

41
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;
}
}

14
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;
}

11
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>

4
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"

1
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."

59
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()

63
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."

6
tools/src/main/shell/securemqttclient.py → tools/src/main/shell/twowaysslmqttclient.py

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2016 The Thingsboard Authors
#
@ -41,8 +42,9 @@ 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.crt", certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED,
tls_version=ssl.PROTOCOL_TLSv1, ciphers=None);
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.username_pw_set("TEST_TOKEN")
client.tls_insecure_set(False)
client.connect(socket.gethostname(), 1883, 1)

66
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();
}
}
}
}
}

49
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();
}

6
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);
}

49
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();
}
}

8
transport/pom.xml

@ -41,10 +41,18 @@
</modules>
<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>

11
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;

11
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>

2
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",

Loading…
Cancel
Save