Commit 7322afac0be4c15be9e47aae8e054367e5a17410

Authored by Andrii Shvaika
1 parent a8dd25a7

Implementation draft

Showing 15 changed files with 326 additions and 108 deletions
... ... @@ -643,6 +643,7 @@ transport:
643 643 private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
644 644 # Only Certificate_x509:
645 645 alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}"
  646 + skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
646 647 bootstrap:
647 648 enable: "${LWM2M_ENABLED_BS:true}"
648 649 id: "${LWM2M_SERVER_ID_BS:111}"
... ...
... ... @@ -145,7 +145,6 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
145 145
146 146 @Override
147 147 public void setResultHandler(HandshakeResultHandler resultHandler) {
148   - // empty implementation
149 148 }
150 149
151 150 public ConcurrentMap<String, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionIdsMap() {
... ...
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java renamed from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java
... ... @@ -65,7 +65,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g
65 65 @Component
66 66 @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')")
67 67 @RequiredArgsConstructor
68   -public class LwM2MTransportBootstrapServerConfiguration {
  68 +//TODO: @ybondarenko please refactor this to be similar to DefaultLwM2mTransportService
  69 +public class LwM2MTransportBootstrapService {
69 70 private PublicKey publicKey;
70 71 private PrivateKey privateKey;
71 72 private boolean pskMode = false;
... ...
... ... @@ -103,7 +103,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
103 103 BootstrapConfig bsConfig = store.getBootstrapConfig();
104 104 if (bsConfig.security != null) {
105 105 try {
106   - bootstrapConfigStore.add(store.getEndPoint(), bsConfig);
  106 + bootstrapConfigStore.add(store.getEndpoint(), bsConfig);
107 107 } catch (InvalidConfigurationException e) {
108 108 log.error("", e);
109 109 }
... ... @@ -121,22 +121,22 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
121 121 switch (SecurityMode.valueOf(lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode())) {
122 122 /* Use RPK only */
123 123 case PSK:
124   - store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndPoint(),
  124 + store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndpoint(),
125 125 lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId(),
126 126 Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientSecretKey().toCharArray())));
127 127 store.setSecurityMode(SecurityMode.PSK.code);
128 128 break;
129 129 case RPK:
130 130 try {
131   - store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndPoint(),
  131 + store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndpoint(),
132 132 SecurityUtil.publicKey.decode(Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId().toCharArray()))));
133 133 store.setSecurityMode(SecurityMode.RPK.code);
134 134 break;
135 135 } catch (IOException | GeneralSecurityException e) {
136   - log.error("Unable to decode Client public key for [{}] [{}]", store.getEndPoint(), e.getMessage());
  136 + log.error("Unable to decode Client public key for [{}] [{}]", store.getEndpoint(), e.getMessage());
137 137 }
138 138 case X509:
139   - store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndPoint()));
  139 + store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndpoint()));
140 140 store.setSecurityMode(SecurityMode.X509.code);
141 141 break;
142 142 case NO_SEC:
... ... @@ -166,22 +166,22 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
166 166 if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) {
167 167 lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
168 168 lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer);
169   - String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndPoint());
  169 + String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndpoint());
170 170 helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo);
171 171 return lwM2MBootstrapConfig;
172 172 } else {
173   - log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint());
174   - log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint());
175   - String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint());
  173 + log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint());
  174 + log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint());
  175 + String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint());
176 176 helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo);
177 177 return null;
178 178 }
179 179 }
180 180 } catch (JsonProcessingException e) {
181   - log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndPoint(), e.getMessage());
  181 + log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndpoint(), e.getMessage());
182 182 return null;
183 183 }
184   - log.error("Unable to decode Json or Certificate for [{}]", store.getEndPoint());
  184 + log.error("Unable to decode Json or Certificate for [{}]", store.getEndpoint());
185 185 return null;
186 186 }
187 187
... ...
... ... @@ -148,9 +148,7 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
148 148 keyStoreValue = KeyStore.getInstance(keyStoreType);
149 149 keyStoreValue.load(inKeyStore, keyStorePassword == null ? null : keyStorePassword.toCharArray());
150 150 } catch (Exception e) {
151   - log.warn("Unable to lookup LwM2M keystore. Reason: {}, {}" , uri, e.getMessage());
152   -// Absence of the key store should not block user from using plain LwM2M
153   -// throw new RuntimeException("Failed to lookup LwM2M keystore: " + (uri != null ? uri.toString() : ""), e);
  151 + log.info("Unable to lookup LwM2M keystore. Reason: {}, {}" , uri, e.getMessage());
154 152 }
155 153 }
156 154 }
... ...
... ... @@ -20,19 +20,20 @@ import lombok.Data;
20 20 import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
21 21 import org.eclipse.leshan.server.security.SecurityInfo;
22 22 import org.thingsboard.server.common.data.DeviceProfile;
  23 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
23 24 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
24 25
25 26 import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.DEFAULT_MODE;
26 27
27 28 @Data
28 29 public class EndpointSecurityInfo {
29   - private ValidateDeviceCredentialsResponseMsg msg;
  30 + private ValidateDeviceCredentialsResponse msg;
30 31 private SecurityInfo securityInfo;
31 32 private int securityMode = DEFAULT_MODE.code;
32 33
33 34 /** bootstrap */
34 35 private DeviceProfile deviceProfile;
35 36 private JsonObject bootstrapJsonCredential;
36   - private String endPoint;
  37 + private String endpoint;
37 38 private BootstrapConfig bootstrapConfig;
38 39 }
... ...
... ... @@ -24,6 +24,7 @@ import org.eclipse.leshan.server.security.SecurityInfo;
24 24 import org.springframework.stereotype.Component;
25 25 import org.thingsboard.server.common.data.DeviceProfile;
26 26 import org.thingsboard.server.common.transport.TransportServiceCallback;
  27 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
27 28 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
28 29 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg;
29 30 import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
... ... @@ -59,12 +60,11 @@ public class LwM2mCredentialsSecurityInfoValidator {
59 60 context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(),
60 61 new TransportServiceCallback<>() {
61 62 @Override
62   - public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) {
63   - String credentialsBody = msg.getCredentialsBody();
  63 + public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  64 + String credentialsBody = msg.getCredentials();
64 65 resultSecurityStore[0] = createSecurityInfo(endpoint, credentialsBody, keyValue);
65 66 resultSecurityStore[0].setMsg(msg);
66   - Optional<DeviceProfile> deviceProfileOpt = LwM2mTransportUtil.decode(msg.getProfileBody().toByteArray());
67   - deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile));
  67 + resultSecurityStore[0].setDeviceProfile(msg.getDeviceProfile());
68 68 latch.countDown();
69 69 }
70 70
... ... @@ -105,7 +105,7 @@ public class LwM2mCredentialsSecurityInfoValidator {
105 105 if (object != null && !object.isJsonNull()) {
106 106 if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) {
107 107 result.setBootstrapJsonCredential(object);
108   - result.setEndPoint(endpoint);
  108 + result.setEndpoint(endpoint);
109 109 result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code);
110 110 } else {
111 111 LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase());
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.secure;
  17 +
  18 +import org.eclipse.leshan.core.request.Identity;
  19 +import org.eclipse.leshan.core.request.UplinkRequest;
  20 +import org.eclipse.leshan.server.registration.Registration;
  21 +import org.eclipse.leshan.server.security.Authorizer;
  22 +
  23 +public class TbLwM2MAuthorizer implements Authorizer {
  24 + @Override
  25 + public Registration isAuthorized(UplinkRequest<?> request, Registration registration, Identity senderIdentity) {
  26 + return null;
  27 + }
  28 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.secure;
  17 +
  18 +import lombok.RequiredArgsConstructor;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.elements.util.CertPathUtil;
  21 +import org.eclipse.californium.scandium.dtls.AlertMessage;
  22 +import org.eclipse.californium.scandium.dtls.CertificateMessage;
  23 +import org.eclipse.californium.scandium.dtls.CertificateType;
  24 +import org.eclipse.californium.scandium.dtls.CertificateVerificationResult;
  25 +import org.eclipse.californium.scandium.dtls.ConnectionId;
  26 +import org.eclipse.californium.scandium.dtls.DTLSSession;
  27 +import org.eclipse.californium.scandium.dtls.HandshakeException;
  28 +import org.eclipse.californium.scandium.dtls.HandshakeResultHandler;
  29 +import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier;
  30 +import org.eclipse.californium.scandium.dtls.x509.StaticCertificateVerifier;
  31 +import org.eclipse.californium.scandium.util.ServerNames;
  32 +import org.springframework.beans.factory.annotation.Value;
  33 +import org.springframework.stereotype.Component;
  34 +import org.springframework.util.StringUtils;
  35 +import org.thingsboard.server.common.data.DeviceProfile;
  36 +import org.thingsboard.server.common.msg.EncryptionUtil;
  37 +import org.thingsboard.server.common.transport.TransportService;
  38 +import org.thingsboard.server.common.transport.TransportServiceCallback;
  39 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
  40 +import org.thingsboard.server.common.transport.util.SslUtil;
  41 +import org.thingsboard.server.gen.transport.TransportProtos;
  42 +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
  43 +
  44 +import javax.annotation.PostConstruct;
  45 +import javax.security.auth.x500.X500Principal;
  46 +import java.security.PublicKey;
  47 +import java.security.cert.CertPath;
  48 +import java.security.cert.CertificateEncodingException;
  49 +import java.security.cert.CertificateExpiredException;
  50 +import java.security.cert.CertificateNotYetValidException;
  51 +import java.security.cert.X509Certificate;
  52 +import java.util.Arrays;
  53 +import java.util.List;
  54 +import java.util.concurrent.CountDownLatch;
  55 +import java.util.concurrent.TimeUnit;
  56 +
  57 +@Slf4j
  58 +@Component
  59 +@RequiredArgsConstructor
  60 +public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVerifier {
  61 +
  62 + private final TransportService transportService;
  63 + private final TbLwM2MDtlsSessionStorage sessionStorage;
  64 + private final LwM2MTransportServerConfig config;
  65 +
  66 + @SuppressWarnings("deprecation")
  67 + private StaticCertificateVerifier staticCertificateVerifier;
  68 +
  69 + @Value("${transport.lwm2m.server.security.skip_validity_check_for_client_cert:false}")
  70 + private boolean skipValidityCheckForClientCert;
  71 +
  72 + @Override
  73 + public List<CertificateType> getSupportedCertificateType() {
  74 + return Arrays.asList(CertificateType.X_509, CertificateType.RAW_PUBLIC_KEY);
  75 + }
  76 +
  77 + @PostConstruct
  78 + public void init() {
  79 + try {
  80 + /* by default trust all */
  81 + X509Certificate[] trustedCertificates = new X509Certificate[0];
  82 + if (config.getKeyStoreValue() != null) {
  83 + X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias());
  84 + if (rootCAX509Cert != null) {
  85 + trustedCertificates = new X509Certificate[1];
  86 + trustedCertificates[0] = rootCAX509Cert;
  87 + }
  88 + }
  89 + staticCertificateVerifier = new StaticCertificateVerifier(trustedCertificates);
  90 + } catch (Exception e) {
  91 + log.info("Failed to initialize the ");
  92 + }
  93 + }
  94 +
  95 + @Override
  96 + public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) {
  97 + CertPath certChain = message.getCertificateChain();
  98 + if (certChain == null) {
  99 + //We trust all RPK on this layer, and use TbLwM2MAuthorizer
  100 + PublicKey publicKey = message.getPublicKey();
  101 + return new CertificateVerificationResult(cid, publicKey, null);
  102 + } else {
  103 + try {
  104 + String credentialsBody = null;
  105 + CertPath certpath = message.getCertificateChain();
  106 + X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]);
  107 + for (X509Certificate cert : chain) {
  108 + try {
  109 + if (!skipValidityCheckForClientCert) {
  110 + cert.checkValidity();
  111 + }
  112 +
  113 + String strCert = SslUtil.getCertificateString(cert);
  114 + String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
  115 + final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1];
  116 + CountDownLatch latch = new CountDownLatch(1);
  117 + transportService.process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(sha3Hash).build(),
  118 + new TransportServiceCallback<>() {
  119 + @Override
  120 + public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  121 + if (!StringUtils.isEmpty(msg.getCredentials())) {
  122 + deviceCredentialsResponse[0] = msg;
  123 + }
  124 + latch.countDown();
  125 + }
  126 +
  127 + @Override
  128 + public void onError(Throwable e) {
  129 + log.error(e.getMessage(), e);
  130 + latch.countDown();
  131 + }
  132 + });
  133 + latch.await(10, TimeUnit.SECONDS);
  134 + ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0];
  135 + if (msg != null && strCert.equals(msg.getCredentials())) {
  136 + credentialsBody = msg.getCredentials();
  137 + DeviceProfile deviceProfile = msg.getDeviceProfile();
  138 + if (msg.hasDeviceInfo() && deviceProfile != null) {
  139 + String endpoint = sha3Hash; //TODO: extract endpoint from credentials body and push to storage
  140 + sessionStorage.put(endpoint, msg);
  141 + }
  142 + break;
  143 + }
  144 + } catch (InterruptedException |
  145 + CertificateEncodingException |
  146 + CertificateExpiredException |
  147 + CertificateNotYetValidException e) {
  148 + log.error(e.getMessage(), e);
  149 + }
  150 + }
  151 + if (credentialsBody == null) {
  152 + if (staticCertificateVerifier != null) {
  153 + staticCertificateVerifier.verifyCertificate(message, session);
  154 + } else {
  155 + AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.INTERNAL_ERROR,
  156 + session.getPeer());
  157 + throw new HandshakeException("x509 verification not enabled!", alert);
  158 + }
  159 + }
  160 + return new CertificateVerificationResult(cid, certpath, null);
  161 + } catch (HandshakeException e) {
  162 + log.trace("Certificate validation failed!", e);
  163 + return new CertificateVerificationResult(cid, e, null);
  164 + }
  165 + }
  166 + }
  167 +
  168 + @Override
  169 + public List<X500Principal> getAcceptedIssuers() {
  170 + return CertPathUtil.toSubjects(null);
  171 + }
  172 +
  173 + @Override
  174 + public void setResultHandler(HandshakeResultHandler resultHandler) {
  175 +
  176 + }
  177 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.secure;
  17 +
  18 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
  19 +
  20 +public interface TbLwM2MDtlsSessionStorage {
  21 +
  22 + void put(String endpoint, ValidateDeviceCredentialsResponse msg);
  23 +
  24 + ValidateDeviceCredentialsResponse get(String endpoint);
  25 +
  26 +}
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server;
18 18 import lombok.RequiredArgsConstructor;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  21 +import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
21 22 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
22 23 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
23 24 import org.eclipse.leshan.core.util.Hex;
... ... @@ -25,14 +26,14 @@ import org.eclipse.leshan.server.californium.LeshanServer;
25 26 import org.eclipse.leshan.server.californium.LeshanServerBuilder;
26 27 import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
27 28 import org.eclipse.leshan.server.model.LwM2mModelProvider;
28   -import org.eclipse.leshan.server.security.DefaultAuthorizer;
29 29 import org.eclipse.leshan.server.security.EditableSecurityStore;
30   -import org.eclipse.leshan.server.security.SecurityChecker;
31 30 import org.springframework.stereotype.Component;
32 31 import org.thingsboard.server.common.data.StringUtils;
33 32 import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
34 33 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
35 34 import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC;
  35 +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer;
  36 +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier;
36 37 import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
37 38 import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
38 39
... ... @@ -41,7 +42,6 @@ import javax.annotation.PreDestroy;
41 42 import java.math.BigInteger;
42 43 import java.security.AlgorithmParameters;
43 44 import java.security.KeyFactory;
44   -import java.security.KeyStoreException;
45 45 import java.security.NoSuchAlgorithmException;
46 46 import java.security.PrivateKey;
47 47 import java.security.PublicKey;
... ... @@ -70,9 +70,10 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g
70 70 @RequiredArgsConstructor
71 71 public class DefaultLwM2mTransportService implements LwM2MTransportService {
72 72
  73 + public static final CipherSuite[] RPK_OR_X509_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256};
  74 + public static final CipherSuite[] PSK_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256};
73 75 private PublicKey publicKey;
74 76 private PrivateKey privateKey;
75   - private boolean pskMode = false;
76 77
77 78 private final LwM2mTransportContext context;
78 79 private final LwM2MTransportServerConfig config;
... ... @@ -81,6 +82,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
81 82 private final CaliforniumRegistrationStore registrationStore;
82 83 private final EditableSecurityStore securityStore;
83 84 private final LwM2mClientContext lwM2mClientContext;
  85 + private final TbLwM2MDtlsCertificateVerifier certificateVerifier;
  86 + private final TbLwM2MAuthorizer authorizer;
84 87
85 88 private LeshanServer server;
86 89
... ... @@ -128,9 +131,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
128 131 config.setModelProvider(modelProvider);
129 132 builder.setObjectModelProvider(modelProvider);
130 133
131   - /* Create credentials */
132   - this.setServerWithCredentials(builder);
133   -
134 134 /* Set securityStore with new registrationStore */
135 135 builder.setSecurityStore(securityStore);
136 136 builder.setRegistrationStore(registrationStore);
... ... @@ -142,18 +142,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
142 142 dtlsConfig.setServerOnly(true);
143 143 dtlsConfig.setRecommendedSupportedGroupsOnly(config.isRecommendedSupportedGroups());
144 144 dtlsConfig.setRecommendedCipherSuitesOnly(config.isRecommendedCiphers());
145   - if (this.pskMode) {
146   - dtlsConfig.setSupportedCipherSuites(
147   - TLS_PSK_WITH_AES_128_CCM_8,
148   - TLS_PSK_WITH_AES_128_CBC_SHA256);
149   - } else {
150   - dtlsConfig.setSupportedCipherSuites(
151   - TLS_PSK_WITH_AES_128_CCM_8,
152   - TLS_PSK_WITH_AES_128_CBC_SHA256,
153   - TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
154   - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256);
155   - }
156   -
  145 + /* Create credentials */
  146 + this.setServerWithCredentials(builder, dtlsConfig);
157 147
158 148 /* Set DTLS Config */
159 149 builder.setDtlsConfig(dtlsConfig);
... ... @@ -162,40 +152,21 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
162 152 return builder.build();
163 153 }
164 154
165   - private void setServerWithCredentials(LeshanServerBuilder builder) {
166   - try {
167   - if (config.getKeyStoreValue() != null) {
168   - if (this.setBuilderX509(builder)) {
169   - X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias());
170   - if (rootCAX509Cert != null) {
171   - X509Certificate[] trustedCertificates = new X509Certificate[1];
172   - trustedCertificates[0] = rootCAX509Cert;
173   - builder.setTrustedCertificates(trustedCertificates);
174   - } else {
175   - /* by default trust all */
176   - builder.setTrustedCertificates(new X509Certificate[0]);
177   - }
178   - /* Set securityStore with registrationStore*/
179   - builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() {
180   - @Override
181   - protected boolean matchX509Identity(String endpoint, String receivedX509CommonName,
182   - String expectedX509CommonName) {
183   - return endpoint.startsWith(expectedX509CommonName);
184   - }
185   - }));
186   - }
187   - } else if (this.setServerRPK(builder)) {
188   - this.infoPramsUri("RPK");
189   - this.infoParamsServerKey(this.publicKey, this.privateKey);
190   - } else {
191   - /* by default trust all */
192   - builder.setTrustedCertificates(new X509Certificate[0]);
193   - log.info("Unable to load X509 files for LWM2MServer");
194   - this.pskMode = true;
195   - this.infoPramsUri("PSK");
196   - }
197   - } catch (KeyStoreException ex) {
198   - log.error("[{}] Unable to load X509 files server", ex.getMessage());
  155 + private void setServerWithCredentials(LeshanServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) {
  156 + if (config.getKeyStoreValue() != null && this.setBuilderX509(builder)) {
  157 + dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier);
  158 + builder.setAuthorizer(authorizer);
  159 + dtlsConfig.setSupportedCipherSuites(RPK_OR_X509_CIPHER_SUITES);
  160 + } else if (this.setServerRPK(builder)) {
  161 + this.infoPramsUri("RPK");
  162 + this.infoParamsServerKey(this.publicKey, this.privateKey);
  163 + dtlsConfig.setSupportedCipherSuites(RPK_OR_X509_CIPHER_SUITES);
  164 + } else {
  165 + /* by default trust all */
  166 + builder.setTrustedCertificates(new X509Certificate[0]);
  167 + log.info("Unable to load X509 files for LWM2MServer");
  168 + dtlsConfig.setSupportedCipherSuites(PSK_CIPHER_SUITES);
  169 + this.infoPramsUri("PSK");
199 170 }
200 171 }
201 172
... ... @@ -241,7 +212,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
241 212
242 213 private boolean setServerRPK(LeshanServerBuilder builder) {
243 214 try {
244   - this.generateKeyForRPK();
  215 + this.loadOrGenerateRPKKeys();
245 216 if (this.publicKey != null && this.publicKey.getEncoded().length > 0 &&
246 217 this.privateKey != null && this.privateKey.getEncoded().length > 0) {
247 218 builder.setPublicKey(this.publicKey);
... ... @@ -254,7 +225,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
254 225 return false;
255 226 }
256 227
257   - private void generateKeyForRPK() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException {
  228 + private void loadOrGenerateRPKKeys() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException {
258 229 /* Get Elliptic Curve Parameter spec for secp256r1 */
259 230 AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
260 231 algoParameters.init(new ECGenParameterSpec("secp256r1"));
... ...
... ... @@ -40,6 +40,7 @@ import org.eclipse.leshan.core.model.ResourceModel;
40 40 import org.eclipse.leshan.core.node.codec.CodecException;
41 41 import org.springframework.stereotype.Component;
42 42 import org.thingsboard.server.common.transport.TransportServiceCallback;
  43 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
43 44 import org.thingsboard.server.gen.transport.TransportProtos;
44 45 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
45 46 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
... ... @@ -106,21 +107,21 @@ public class LwM2mTransportServerHelper {
106 107 /**
107 108 * @return - sessionInfo after access connect client
108 109 */
109   - public SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) {
  110 + public SessionInfoProto getValidateSessionInfo(ValidateDeviceCredentialsResponse msg, long mostSignificantBits, long leastSignificantBits) {
110 111 return SessionInfoProto.newBuilder()
111 112 .setNodeId(context.getNodeId())
112 113 .setSessionIdMSB(mostSignificantBits)
113 114 .setSessionIdLSB(leastSignificantBits)
114   - .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
115   - .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB())
116   - .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB())
117   - .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB())
118   - .setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB())
119   - .setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB())
  115 + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits())
  116 + .setDeviceIdLSB(msg.getDeviceInfo().getDeviceId().getId().getLeastSignificantBits())
  117 + .setTenantIdMSB(msg.getDeviceInfo().getTenantId().getId().getMostSignificantBits())
  118 + .setTenantIdLSB(msg.getDeviceInfo().getTenantId().getId().getLeastSignificantBits())
  119 + .setCustomerIdMSB(msg.getDeviceInfo().getCustomerId().getId().getMostSignificantBits())
  120 + .setCustomerIdLSB(msg.getDeviceInfo().getCustomerId().getId().getLeastSignificantBits())
120 121 .setDeviceName(msg.getDeviceInfo().getDeviceName())
121 122 .setDeviceType(msg.getDeviceInfo().getDeviceType())
122   - .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB())
123   - .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB())
  123 + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits())
  124 + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileId().getId().getLeastSignificantBits())
124 125 .build();
125 126 }
126 127
... ...
... ... @@ -27,6 +27,7 @@ import org.eclipse.leshan.server.registration.Registration;
27 27 import org.eclipse.leshan.server.security.SecurityInfo;
28 28 import org.thingsboard.server.common.data.Device;
29 29 import org.thingsboard.server.common.data.DeviceProfile;
  30 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
30 31 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
31 32 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
32 33 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
... ... @@ -79,7 +80,7 @@ public class LwM2mClient implements Cloneable {
79 80 @Setter
80 81 private Registration registration;
81 82
82   - private ValidateDeviceCredentialsResponseMsg credentialsResponse;
  83 + private ValidateDeviceCredentialsResponse credentials;
83 84 @Getter
84 85 private final Map<String, ResourceValue> resources;
85 86 @Getter
... ... @@ -98,11 +99,11 @@ public class LwM2mClient implements Cloneable {
98 99 return super.clone();
99 100 }
100 101
101   - public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponseMsg credentialsResponse, UUID profileId, UUID sessionId) {
  102 + public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponse credentials, UUID profileId, UUID sessionId) {
102 103 this.endpoint = endpoint;
103 104 this.identity = identity;
104 105 this.securityInfo = securityInfo;
105   - this.credentialsResponse = credentialsResponse;
  106 + this.credentials = credentials;
106 107 this.delayedRequests = new ConcurrentHashMap<>();
107 108 this.pendingReadRequests = new CopyOnWriteArrayList<>();
108 109 this.resources = new ConcurrentHashMap<>();
... ... @@ -112,8 +113,8 @@ public class LwM2mClient implements Cloneable {
112 113 this.updateFw = false;
113 114 this.queuedRequests = new ConcurrentLinkedQueue<>();
114 115 this.frUpdate = new LwM2mFirmwareUpdate();
115   - if (this.credentialsResponse != null && this.credentialsResponse.hasDeviceInfo()) {
116   - this.session = createSession(nodeId, sessionId, credentialsResponse);
  116 + if (this.credentials != null && this.credentials.hasDeviceInfo()) {
  117 + this.session = createSession(nodeId, sessionId, credentials);
117 118 this.deviceId = new UUID(session.getDeviceIdMSB(), session.getDeviceIdLSB());
118 119 this.profileId = new UUID(session.getDeviceProfileIdMSB(), session.getDeviceProfileIdLSB());
119 120 this.deviceName = session.getDeviceName();
... ... @@ -146,21 +147,21 @@ public class LwM2mClient implements Cloneable {
146 147 builder.setDeviceType(this.deviceProfileName);
147 148 }
148 149
149   - private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponseMsg msg) {
  150 + private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponse msg) {
150 151 return SessionInfoProto.newBuilder()
151 152 .setNodeId(nodeId)
152 153 .setSessionIdMSB(sessionId.getMostSignificantBits())
153 154 .setSessionIdLSB(sessionId.getLeastSignificantBits())
154   - .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
155   - .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB())
156   - .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB())
157   - .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB())
158   - .setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB())
159   - .setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB())
  155 + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits())
  156 + .setDeviceIdLSB(msg.getDeviceInfo().getDeviceId().getId().getLeastSignificantBits())
  157 + .setTenantIdMSB(msg.getDeviceInfo().getTenantId().getId().getMostSignificantBits())
  158 + .setTenantIdLSB(msg.getDeviceInfo().getTenantId().getId().getLeastSignificantBits())
  159 + .setCustomerIdMSB(msg.getDeviceInfo().getCustomerId().getId().getMostSignificantBits())
  160 + .setCustomerIdLSB(msg.getDeviceInfo().getCustomerId().getId().getLeastSignificantBits())
160 161 .setDeviceName(msg.getDeviceInfo().getDeviceName())
161 162 .setDeviceType(msg.getDeviceInfo().getDeviceType())
162   - .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB())
163   - .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB())
  163 + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits())
  164 + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileId().getId().getLeastSignificantBits())
164 165 .build();
165 166 }
166 167
... ...
... ... @@ -79,7 +79,7 @@ public interface TransportService {
79 79 TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
80 80
81 81 void process(ValidateDeviceLwM2MCredentialsRequestMsg msg,
82   - TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback);
  82 + TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
83 83
84 84 void process(GetOrCreateDeviceFromGatewayRequestMsg msg,
85 85 TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback);
... ...
... ... @@ -348,11 +348,25 @@ public class DefaultTransportService implements TransportService {
348 348 }
349 349
350 350 @Override
351   - public void process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg msg, TransportServiceCallback<TransportProtos.ValidateDeviceCredentialsResponseMsg> callback) {
352   - log.trace("Processing msg: {}", msg);
353   - TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateDeviceLwM2MCredentialsRequestMsg(msg).build());
354   - AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg),
355   - response -> callback.onSuccess(response.getValue().getValidateCredResponseMsg()), callback::onError, transportCallbackExecutor);
  351 + public void process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg requestMsg, TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) {
  352 + log.trace("Processing msg: {}", requestMsg);
  353 + TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateDeviceLwM2MCredentialsRequestMsg(requestMsg).build());
  354 + ListenableFuture<ValidateDeviceCredentialsResponse> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> {
  355 + TransportProtos.ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg();
  356 + ValidateDeviceCredentialsResponse.ValidateDeviceCredentialsResponseBuilder result = ValidateDeviceCredentialsResponse.builder();
  357 + if (msg.hasDeviceInfo()) {
  358 + result.credentials(msg.getCredentialsBody());
  359 + TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo());
  360 + result.deviceInfo(tdi);
  361 + ByteString profileBody = msg.getProfileBody();
  362 + if (!profileBody.isEmpty()) {
  363 + DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody);
  364 + result.deviceProfile(profile);
  365 + }
  366 + }
  367 + return result.build();
  368 + }, MoreExecutors.directExecutor());
  369 + AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
356 370 }
357 371
358 372 @Override
... ... @@ -372,7 +386,7 @@ public class DefaultTransportService implements TransportService {
372 386 TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo());
373 387 result.deviceInfo(tdi);
374 388 ByteString profileBody = msg.getProfileBody();
375   - if (profileBody != null && !profileBody.isEmpty()) {
  389 + if (!profileBody.isEmpty()) {
376 390 DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody);
377 391 if (transportType != DeviceTransportType.DEFAULT
378 392 && profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) {
... ...