Commit fd3e18f18b28ff5e91de08349fcaa2c416c2a08c

Authored by ShvaykaD
Committed by GitHub
1 parent 68a73caa

CoAP DTLS support (#4316)

* dtls init commit

* added fixes after review

* fix typo

* changed translation for DeviceCredentialsType.X509_CERTIFICATE
Showing 19 changed files with 1050 additions and 113 deletions
@@ -583,7 +583,24 @@ transport: @@ -583,7 +583,24 @@ transport:
583 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" 583 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
584 bind_port: "${COAP_BIND_PORT:5683}" 584 bind_port: "${COAP_BIND_PORT:5683}"
585 timeout: "${COAP_TIMEOUT:10000}" 585 timeout: "${COAP_TIMEOUT:10000}"
586 - 586 + dtls:
  587 + # Enable/disable DTLS 1.2 support
  588 + enabled: "${COAP_DTLS_ENABLED:false}"
  589 + # Secure mode. Allowed values: NO_AUTH, X509
  590 + mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}"
  591 + # Path to the key store that holds the certificate
  592 + key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
  593 + # Password used to access the key store
  594 + key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
  595 + # Password used to access the key
  596 + key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
  597 + # Key alias
  598 + key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
  599 + # Skip certificate validity check for client certificates.
  600 + skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
  601 + x509:
  602 + dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
  603 + dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
587 swagger: 604 swagger:
588 api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}" 605 api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}"
589 security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api.*}" 606 security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api.*}"
@@ -45,6 +45,10 @@ @@ -45,6 +45,10 @@
45 <artifactId>californium-core</artifactId> 45 <artifactId>californium-core</artifactId>
46 </dependency> 46 </dependency>
47 <dependency> 47 <dependency>
  48 + <groupId>org.eclipse.californium</groupId>
  49 + <artifactId>scandium</artifactId>
  50 + </dependency>
  51 + <dependency>
48 <groupId>org.springframework</groupId> 52 <groupId>org.springframework</groupId>
49 <artifactId>spring-context-support</artifactId> 53 <artifactId>spring-context-support</artifactId>
50 </dependency> 54 </dependency>
@@ -47,6 +47,10 @@ public class CoapTransportContext extends TransportContext { @@ -47,6 +47,10 @@ public class CoapTransportContext extends TransportContext {
47 private Long timeout; 47 private Long timeout;
48 48
49 @Getter 49 @Getter
  50 + @Autowired(required = false)
  51 + private TbCoapDtlsSettings dtlsSettings;
  52 +
  53 + @Getter
50 @Autowired 54 @Autowired
51 private JsonCoapAdaptor jsonCoapAdaptor; 55 private JsonCoapAdaptor jsonCoapAdaptor;
52 56
@@ -27,6 +27,7 @@ import org.eclipse.californium.core.observe.ObserveRelation; @@ -27,6 +27,7 @@ import org.eclipse.californium.core.observe.ObserveRelation;
27 import org.eclipse.californium.core.server.resources.CoapExchange; 27 import org.eclipse.californium.core.server.resources.CoapExchange;
28 import org.eclipse.californium.core.server.resources.Resource; 28 import org.eclipse.californium.core.server.resources.Resource;
29 import org.eclipse.californium.core.server.resources.ResourceObserver; 29 import org.eclipse.californium.core.server.resources.ResourceObserver;
  30 +import org.springframework.util.StringUtils;
30 import org.thingsboard.server.common.data.DataConstants; 31 import org.thingsboard.server.common.data.DataConstants;
31 import org.thingsboard.server.common.data.DeviceProfile; 32 import org.thingsboard.server.common.data.DeviceProfile;
32 import org.thingsboard.server.common.data.DeviceTransportType; 33 import org.thingsboard.server.common.data.DeviceTransportType;
@@ -63,15 +64,22 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -63,15 +64,22 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
63 private static final int FEATURE_TYPE_POSITION = 4; 64 private static final int FEATURE_TYPE_POSITION = 4;
64 private static final int REQUEST_ID_POSITION = 5; 65 private static final int REQUEST_ID_POSITION = 5;
65 66
  67 + private static final int FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST = 3;
  68 + private static final int REQUEST_ID_POSITION_CERTIFICATE_REQUEST = 4;
  69 + private static final String DTLS_SESSION_ID_KEY = "DTLS_SESSION_ID";
  70 +
66 private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>(); 71 private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>();
67 private final ConcurrentMap<String, AtomicInteger> tokenToNotificationCounterMap = new ConcurrentHashMap<>(); 72 private final ConcurrentMap<String, AtomicInteger> tokenToNotificationCounterMap = new ConcurrentHashMap<>();
68 private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet(); 73 private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet();
69 private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet(); 74 private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet();
70 75
71 - public CoapTransportResource(CoapTransportContext coapTransportContext, String name) { 76 + private ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap;
  77 +
  78 + public CoapTransportResource(CoapTransportContext coapTransportContext, ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap, String name) {
72 super(coapTransportContext, name); 79 super(coapTransportContext, name);
73 this.setObservable(true); // enable observing 80 this.setObservable(true); // enable observing
74 this.addObserver(new CoapResourceObserver()); 81 this.addObserver(new CoapResourceObserver());
  82 + this.dtlsSessionIdMap = dtlsSessionIdMap;
75 // this.setObservable(false); // disable observing 83 // this.setObservable(false); // disable observing
76 // this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs 84 // this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs
77 // this.getAttributes().setObservable(); // mark observable in the Link-Format 85 // this.getAttributes().setObservable(); // mark observable in the Link-Format
@@ -187,107 +195,132 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -187,107 +195,132 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
187 Exchange advanced = exchange.advanced(); 195 Exchange advanced = exchange.advanced();
188 Request request = advanced.getRequest(); 196 Request request = advanced.getRequest();
189 197
  198 + String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY);
  199 + if (!StringUtils.isEmpty(dtlsSessionIdStr)) {
  200 + if (dtlsSessionIdMap != null) {
  201 + TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap
  202 + .computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> {
  203 + dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis());
  204 + return dtlsSessionInfo;
  205 + });
  206 + if (tbCoapDtlsSessionInfo != null) {
  207 + processRequest(exchange, type, request, tbCoapDtlsSessionInfo.getSessionInfoProto(), tbCoapDtlsSessionInfo.getDeviceProfile());
  208 + } else {
  209 + exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
  210 + }
  211 + } else {
  212 + processAccessTokenRequest(exchange, type, request);
  213 + }
  214 + } else {
  215 + processAccessTokenRequest(exchange, type, request);
  216 + }
  217 + }
  218 +
  219 + private void processAccessTokenRequest(CoapExchange exchange, SessionMsgType type, Request request) {
190 Optional<DeviceTokenCredentials> credentials = decodeCredentials(request); 220 Optional<DeviceTokenCredentials> credentials = decodeCredentials(request);
191 if (credentials.isEmpty()) { 221 if (credentials.isEmpty()) {
192 - exchange.respond(CoAP.ResponseCode.BAD_REQUEST); 222 + exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
193 return; 223 return;
194 } 224 }
195 -  
196 transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), 225 transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(),
197 new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> { 226 new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> {
198 - UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());  
199 - try {  
200 - TransportConfigurationContainer transportConfigurationContainer = getTransportConfigurationContainer(deviceProfile);  
201 - CoapTransportAdaptor coapTransportAdaptor = getCoapTransportAdaptor(transportConfigurationContainer.isJsonPayload());  
202 - switch (type) {  
203 - case POST_ATTRIBUTES_REQUEST:  
204 - transportService.process(sessionInfo,  
205 - coapTransportAdaptor.convertToPostAttributes(sessionId, request,  
206 - transportConfigurationContainer.getAttributesMsgDescriptor()),  
207 - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
208 - reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));  
209 - break;  
210 - case POST_TELEMETRY_REQUEST:  
211 - transportService.process(sessionInfo,  
212 - coapTransportAdaptor.convertToPostTelemetry(sessionId, request,  
213 - transportConfigurationContainer.getTelemetryMsgDescriptor()),  
214 - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
215 - reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));  
216 - break;  
217 - case CLAIM_REQUEST:  
218 - transportService.process(sessionInfo,  
219 - coapTransportAdaptor.convertToClaimDevice(sessionId, request, sessionInfo),  
220 - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
221 - break;  
222 - case SUBSCRIBE_ATTRIBUTES_REQUEST:  
223 - TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionIdMap.get(getTokenFromRequest(request));  
224 - if (currentAttrSession == null) {  
225 - attributeSubscriptions.add(sessionId);  
226 - registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));  
227 - transportService.process(sessionInfo,  
228 - TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new CoapNoOpCallback(exchange));  
229 - }  
230 - break;  
231 - case UNSUBSCRIBE_ATTRIBUTES_REQUEST:  
232 - TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request));  
233 - if (attrSession != null) {  
234 - UUID attrSessionId = new UUID(attrSession.getSessionIdMSB(), attrSession.getSessionIdLSB());  
235 - attributeSubscriptions.remove(attrSessionId);  
236 - transportService.process(attrSession,  
237 - TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(),  
238 - new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
239 - closeAndDeregister(sessionInfo, sessionId);  
240 - }  
241 - break;  
242 - case SUBSCRIBE_RPC_COMMANDS_REQUEST:  
243 - TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionIdMap.get(getTokenFromRequest(request));  
244 - if (currentRpcSession == null) {  
245 - rpcSubscriptions.add(sessionId);  
246 - registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));  
247 - transportService.process(sessionInfo,  
248 - TransportProtos.SubscribeToRPCMsg.getDefaultInstance(),  
249 - new CoapNoOpCallback(exchange));  
250 - } else {  
251 - UUID rpcSessionId = new UUID(currentRpcSession.getSessionIdMSB(), currentRpcSession.getSessionIdLSB());  
252 - reportActivity(currentRpcSession, attributeSubscriptions.contains(rpcSessionId), rpcSubscriptions.contains(rpcSessionId));  
253 - }  
254 - break;  
255 - case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:  
256 - TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request));  
257 - if (rpcSession != null) {  
258 - UUID rpcSessionId = new UUID(rpcSession.getSessionIdMSB(), rpcSession.getSessionIdLSB());  
259 - rpcSubscriptions.remove(rpcSessionId);  
260 - transportService.process(rpcSession,  
261 - TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(),  
262 - new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
263 - closeAndDeregister(sessionInfo, sessionId);  
264 - }  
265 - break;  
266 - case TO_DEVICE_RPC_RESPONSE:  
267 - transportService.process(sessionInfo,  
268 - coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request),  
269 - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));  
270 - break;  
271 - case TO_SERVER_RPC_REQUEST:  
272 - transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());  
273 - transportService.process(sessionInfo,  
274 - coapTransportAdaptor.convertToServerRpcRequest(sessionId, request),  
275 - new CoapNoOpCallback(exchange));  
276 - break;  
277 - case GET_ATTRIBUTES_REQUEST:  
278 - transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());  
279 - transportService.process(sessionInfo,  
280 - coapTransportAdaptor.convertToGetAttributes(sessionId, request),  
281 - new CoapNoOpCallback(exchange));  
282 - break;  
283 - }  
284 - } catch (AdaptorException e) {  
285 - log.trace("[{}] Failed to decode message: ", sessionId, e);  
286 - exchange.respond(CoAP.ResponseCode.BAD_REQUEST);  
287 - } 227 + processRequest(exchange, type, request, sessionInfo, deviceProfile);
288 })); 228 }));
289 } 229 }
290 230
  231 + private void processRequest(CoapExchange exchange, SessionMsgType type, Request request, TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
  232 + UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
  233 + try {
  234 + TransportConfigurationContainer transportConfigurationContainer = getTransportConfigurationContainer(deviceProfile);
  235 + CoapTransportAdaptor coapTransportAdaptor = getCoapTransportAdaptor(transportConfigurationContainer.isJsonPayload());
  236 + switch (type) {
  237 + case POST_ATTRIBUTES_REQUEST:
  238 + transportService.process(sessionInfo,
  239 + coapTransportAdaptor.convertToPostAttributes(sessionId, request,
  240 + transportConfigurationContainer.getAttributesMsgDescriptor()),
  241 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  242 + reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
  243 + break;
  244 + case POST_TELEMETRY_REQUEST:
  245 + transportService.process(sessionInfo,
  246 + coapTransportAdaptor.convertToPostTelemetry(sessionId, request,
  247 + transportConfigurationContainer.getTelemetryMsgDescriptor()),
  248 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  249 + reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
  250 + break;
  251 + case CLAIM_REQUEST:
  252 + transportService.process(sessionInfo,
  253 + coapTransportAdaptor.convertToClaimDevice(sessionId, request, sessionInfo),
  254 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  255 + break;
  256 + case SUBSCRIBE_ATTRIBUTES_REQUEST:
  257 + TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
  258 + if (currentAttrSession == null) {
  259 + attributeSubscriptions.add(sessionId);
  260 + registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
  261 + transportService.process(sessionInfo,
  262 + TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new CoapNoOpCallback(exchange));
  263 + }
  264 + break;
  265 + case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
  266 + TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
  267 + if (attrSession != null) {
  268 + UUID attrSessionId = new UUID(attrSession.getSessionIdMSB(), attrSession.getSessionIdLSB());
  269 + attributeSubscriptions.remove(attrSessionId);
  270 + transportService.process(attrSession,
  271 + TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(),
  272 + new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  273 + closeAndDeregister(sessionInfo, sessionId);
  274 + }
  275 + break;
  276 + case SUBSCRIBE_RPC_COMMANDS_REQUEST:
  277 + TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
  278 + if (currentRpcSession == null) {
  279 + rpcSubscriptions.add(sessionId);
  280 + registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
  281 + transportService.process(sessionInfo,
  282 + TransportProtos.SubscribeToRPCMsg.getDefaultInstance(),
  283 + new CoapNoOpCallback(exchange));
  284 + } else {
  285 + UUID rpcSessionId = new UUID(currentRpcSession.getSessionIdMSB(), currentRpcSession.getSessionIdLSB());
  286 + reportActivity(currentRpcSession, attributeSubscriptions.contains(rpcSessionId), rpcSubscriptions.contains(rpcSessionId));
  287 + }
  288 + break;
  289 + case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
  290 + TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
  291 + if (rpcSession != null) {
  292 + UUID rpcSessionId = new UUID(rpcSession.getSessionIdMSB(), rpcSession.getSessionIdLSB());
  293 + rpcSubscriptions.remove(rpcSessionId);
  294 + transportService.process(rpcSession,
  295 + TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(),
  296 + new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  297 + closeAndDeregister(sessionInfo, sessionId);
  298 + }
  299 + break;
  300 + case TO_DEVICE_RPC_RESPONSE:
  301 + transportService.process(sessionInfo,
  302 + coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request),
  303 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  304 + break;
  305 + case TO_SERVER_RPC_REQUEST:
  306 + transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());
  307 + transportService.process(sessionInfo,
  308 + coapTransportAdaptor.convertToServerRpcRequest(sessionId, request),
  309 + new CoapNoOpCallback(exchange));
  310 + break;
  311 + case GET_ATTRIBUTES_REQUEST:
  312 + transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());
  313 + transportService.process(sessionInfo,
  314 + coapTransportAdaptor.convertToGetAttributes(sessionId, request),
  315 + new CoapNoOpCallback(exchange));
  316 + break;
  317 + }
  318 + } catch (AdaptorException e) {
  319 + log.trace("[{}] Failed to decode message: ", sessionId, e);
  320 + exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
  321 + }
  322 + }
  323 +
291 private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { 324 private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) {
292 tokenToNotificationCounterMap.remove(token); 325 tokenToNotificationCounterMap.remove(token);
293 return tokenToSessionIdMap.remove(token); 326 return tokenToSessionIdMap.remove(token);
@@ -310,7 +343,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -310,7 +343,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
310 343
311 private Optional<DeviceTokenCredentials> decodeCredentials(Request request) { 344 private Optional<DeviceTokenCredentials> decodeCredentials(Request request) {
312 List<String> uriPath = request.getOptions().getUriPath(); 345 List<String> uriPath = request.getOptions().getUriPath();
313 - if (uriPath.size() >= ACCESS_TOKEN_POSITION) { 346 + if (uriPath.size() > ACCESS_TOKEN_POSITION) {
314 return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); 347 return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1)));
315 } else { 348 } else {
316 return Optional.empty(); 349 return Optional.empty();
@@ -322,8 +355,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -322,8 +355,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
322 try { 355 try {
323 if (uriPath.size() >= FEATURE_TYPE_POSITION) { 356 if (uriPath.size() >= FEATURE_TYPE_POSITION) {
324 return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); 357 return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase()));
325 - } else if (uriPath.size() == 3 && uriPath.contains(DataConstants.PROVISION)) {  
326 - return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase())); 358 + } else if (uriPath.size() >= FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) {
  359 + if (uriPath.contains(DataConstants.PROVISION)) {
  360 + return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase()));
  361 + }
  362 + return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST - 1).toUpperCase()));
327 } 363 }
328 } catch (RuntimeException e) { 364 } catch (RuntimeException e) {
329 log.warn("Failed to decode feature type: {}", uriPath); 365 log.warn("Failed to decode feature type: {}", uriPath);
@@ -336,6 +372,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -336,6 +372,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
336 try { 372 try {
337 if (uriPath.size() >= REQUEST_ID_POSITION) { 373 if (uriPath.size() >= REQUEST_ID_POSITION) {
338 return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1))); 374 return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1)));
  375 + } else {
  376 + return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION_CERTIFICATE_REQUEST - 1)));
339 } 377 }
340 } catch (RuntimeException e) { 378 } catch (RuntimeException e) {
341 log.warn("Failed to decode feature type: {}", uriPath); 379 log.warn("Failed to decode feature type: {}", uriPath);
@@ -19,7 +19,10 @@ import lombok.extern.slf4j.Slf4j; @@ -19,7 +19,10 @@ import lombok.extern.slf4j.Slf4j;
19 import org.eclipse.californium.core.CoapResource; 19 import org.eclipse.californium.core.CoapResource;
20 import org.eclipse.californium.core.CoapServer; 20 import org.eclipse.californium.core.CoapServer;
21 import org.eclipse.californium.core.network.CoapEndpoint; 21 import org.eclipse.californium.core.network.CoapEndpoint;
  22 +import org.eclipse.californium.core.network.config.NetworkConfig;
22 import org.eclipse.californium.core.server.resources.Resource; 23 import org.eclipse.californium.core.server.resources.Resource;
  24 +import org.eclipse.californium.scandium.DTLSConnector;
  25 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
23 import org.springframework.beans.factory.annotation.Autowired; 26 import org.springframework.beans.factory.annotation.Autowired;
24 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 27 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
25 import org.springframework.stereotype.Service; 28 import org.springframework.stereotype.Service;
@@ -30,6 +33,11 @@ import javax.annotation.PreDestroy; @@ -30,6 +33,11 @@ import javax.annotation.PreDestroy;
30 import java.net.InetAddress; 33 import java.net.InetAddress;
31 import java.net.InetSocketAddress; 34 import java.net.InetSocketAddress;
32 import java.net.UnknownHostException; 35 import java.net.UnknownHostException;
  36 +import java.util.Random;
  37 +import java.util.concurrent.ConcurrentMap;
  38 +import java.util.concurrent.Executors;
  39 +import java.util.concurrent.ScheduledExecutorService;
  40 +import java.util.concurrent.TimeUnit;
33 41
34 @Service("CoapTransportService") 42 @Service("CoapTransportService")
35 @ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") 43 @ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
@@ -44,34 +52,53 @@ public class CoapTransportService { @@ -44,34 +52,53 @@ public class CoapTransportService {
44 @Autowired 52 @Autowired
45 private CoapTransportContext coapTransportContext; 53 private CoapTransportContext coapTransportContext;
46 54
  55 + private TbCoapDtlsCertificateVerifier tbDtlsCertificateVerifier;
  56 +
47 private CoapServer server; 57 private CoapServer server;
48 58
  59 + private ScheduledExecutorService dtlsSessionsExecutor;
  60 +
49 @PostConstruct 61 @PostConstruct
50 public void init() throws UnknownHostException { 62 public void init() throws UnknownHostException {
51 log.info("Starting CoAP transport..."); 63 log.info("Starting CoAP transport...");
52 log.info("Starting CoAP transport server"); 64 log.info("Starting CoAP transport server");
53 65
54 this.server = new CoapServer(); 66 this.server = new CoapServer();
  67 +
  68 + CoapEndpoint.Builder capEndpointBuilder = new CoapEndpoint.Builder();
  69 +
  70 + if (isDtlsEnabled()) {
  71 + TbCoapDtlsSettings dtlsSettings = coapTransportContext.getDtlsSettings();
  72 + DtlsConnectorConfig dtlsConnectorConfig = dtlsSettings.dtlsConnectorConfig();
  73 + DTLSConnector connector = new DTLSConnector(dtlsConnectorConfig);
  74 + capEndpointBuilder.setConnector(connector);
  75 + if (dtlsConnectorConfig.isClientAuthenticationRequired()) {
  76 + tbDtlsCertificateVerifier = (TbCoapDtlsCertificateVerifier) dtlsConnectorConfig.getAdvancedCertificateVerifier();
  77 + dtlsSessionsExecutor = Executors.newSingleThreadScheduledExecutor();
  78 + dtlsSessionsExecutor.scheduleAtFixedRate(this::evictTimeoutSessions, new Random().nextInt((int) getDtlsSessionReportTimeout()), getDtlsSessionReportTimeout(), TimeUnit.MILLISECONDS);
  79 + }
  80 + } else {
  81 + InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());
  82 + InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());
  83 + capEndpointBuilder.setInetSocketAddress(sockAddr);
  84 + capEndpointBuilder.setNetworkConfig(NetworkConfig.getStandard());
  85 + }
  86 + CoapEndpoint coapEndpoint = capEndpointBuilder.build();
  87 +
  88 + server.addEndpoint(coapEndpoint);
  89 +
55 createResources(); 90 createResources();
56 Resource root = this.server.getRoot(); 91 Resource root = this.server.getRoot();
57 TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root); 92 TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root);
58 this.server.setMessageDeliverer(messageDeliverer); 93 this.server.setMessageDeliverer(messageDeliverer);
59 94
60 - InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());  
61 - InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());  
62 -  
63 - CoapEndpoint.Builder coapEndpoitBuilder = new CoapEndpoint.Builder();  
64 - coapEndpoitBuilder.setInetSocketAddress(sockAddr);  
65 - CoapEndpoint coapEndpoint = coapEndpoitBuilder.build();  
66 -  
67 - server.addEndpoint(coapEndpoint);  
68 server.start(); 95 server.start();
69 log.info("CoAP transport started!"); 96 log.info("CoAP transport started!");
70 } 97 }
71 98
72 private void createResources() { 99 private void createResources() {
73 CoapResource api = new CoapResource(API); 100 CoapResource api = new CoapResource(API);
74 - api.add(new CoapTransportResource(coapTransportContext, V1)); 101 + api.add(new CoapTransportResource(coapTransportContext, getDtlsSessionsMap(), V1));
75 102
76 CoapResource efento = new CoapResource(EFENTO); 103 CoapResource efento = new CoapResource(EFENTO);
77 CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS); 104 CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS);
@@ -81,8 +108,27 @@ public class CoapTransportService { @@ -81,8 +108,27 @@ public class CoapTransportService {
81 server.add(efento); 108 server.add(efento);
82 } 109 }
83 110
  111 + private boolean isDtlsEnabled() {
  112 + return coapTransportContext.getDtlsSettings() != null;
  113 + }
  114 +
  115 + private ConcurrentMap<String, TbCoapDtlsSessionInfo> getDtlsSessionsMap() {
  116 + return tbDtlsCertificateVerifier != null ? tbDtlsCertificateVerifier.getTbCoapDtlsSessionIdsMap() : null;
  117 + }
  118 +
  119 + private void evictTimeoutSessions() {
  120 + tbDtlsCertificateVerifier.evictTimeoutSessions();
  121 + }
  122 +
  123 + private long getDtlsSessionReportTimeout() {
  124 + return tbDtlsCertificateVerifier.getDtlsSessionReportTimeout();
  125 + }
  126 +
84 @PreDestroy 127 @PreDestroy
85 public void shutdown() { 128 public void shutdown() {
  129 + if (dtlsSessionsExecutor != null) {
  130 + dtlsSessionsExecutor.shutdownNow();
  131 + }
86 log.info("Stopping CoAP transport!"); 132 log.info("Stopping CoAP transport!");
87 this.server.destroy(); 133 this.server.destroy();
88 log.info("CoAP transport stopped!"); 134 log.info("CoAP transport stopped!");
  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.coap;
  17 +
  18 +import lombok.Data;
  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.util.ServerNames;
  31 +import org.springframework.util.StringUtils;
  32 +import org.thingsboard.server.common.data.DeviceProfile;
  33 +import org.thingsboard.server.common.data.DeviceTransportType;
  34 +import org.thingsboard.server.common.msg.EncryptionUtil;
  35 +import org.thingsboard.server.common.transport.TransportService;
  36 +import org.thingsboard.server.common.transport.TransportServiceCallback;
  37 +import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
  38 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
  39 +import org.thingsboard.server.common.transport.util.SslUtil;
  40 +import org.thingsboard.server.gen.transport.TransportProtos;
  41 +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
  42 +
  43 +import javax.security.auth.x500.X500Principal;
  44 +import java.security.cert.CertPath;
  45 +import java.security.cert.CertificateEncodingException;
  46 +import java.security.cert.CertificateExpiredException;
  47 +import java.security.cert.CertificateNotYetValidException;
  48 +import java.security.cert.X509Certificate;
  49 +import java.util.Collections;
  50 +import java.util.List;
  51 +import java.util.UUID;
  52 +import java.util.concurrent.ConcurrentMap;
  53 +import java.util.concurrent.CountDownLatch;
  54 +import java.util.concurrent.TimeUnit;
  55 +
  56 +@Slf4j
  57 +@Data
  58 +public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVerifier {
  59 +
  60 + private final TbCoapDtlsSessionInMemoryStorage tbCoapDtlsSessionInMemoryStorage;
  61 +
  62 + private TransportService transportService;
  63 + private TbServiceInfoProvider serviceInfoProvider;
  64 + private boolean skipValidityCheckForClientCert;
  65 +
  66 + public TbCoapDtlsCertificateVerifier(TransportService transportService, TbServiceInfoProvider serviceInfoProvider, long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout, boolean skipValidityCheckForClientCert) {
  67 + this.transportService = transportService;
  68 + this.serviceInfoProvider = serviceInfoProvider;
  69 + this.skipValidityCheckForClientCert = skipValidityCheckForClientCert;
  70 + this.tbCoapDtlsSessionInMemoryStorage = new TbCoapDtlsSessionInMemoryStorage(dtlsSessionInactivityTimeout, dtlsSessionReportTimeout);
  71 + }
  72 +
  73 + @Override
  74 + public List<CertificateType> getSupportedCertificateType() {
  75 + return Collections.singletonList(CertificateType.X_509);
  76 + }
  77 +
  78 + @Override
  79 + public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) {
  80 + try {
  81 + String credentialsBody = null;
  82 + CertPath certpath = message.getCertificateChain();
  83 + X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]);
  84 + for (X509Certificate cert : chain) {
  85 + try {
  86 + if (!skipValidityCheckForClientCert) {
  87 + cert.checkValidity();
  88 + }
  89 + String strCert = SslUtil.getCertificateString(cert);
  90 + String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
  91 + final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1];
  92 + CountDownLatch latch = new CountDownLatch(1);
  93 + transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
  94 + new TransportServiceCallback<>() {
  95 + @Override
  96 + public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  97 + if (!StringUtils.isEmpty(msg.getCredentials())) {
  98 + deviceCredentialsResponse[0] = msg;
  99 + }
  100 + latch.countDown();
  101 + }
  102 +
  103 + @Override
  104 + public void onError(Throwable e) {
  105 + log.error(e.getMessage(), e);
  106 + latch.countDown();
  107 + }
  108 + });
  109 + latch.await(10, TimeUnit.SECONDS);
  110 + ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0];
  111 + if (msg != null && strCert.equals(msg.getCredentials())) {
  112 + credentialsBody = msg.getCredentials();
  113 + DeviceProfile deviceProfile = msg.getDeviceProfile();
  114 + if (msg.hasDeviceInfo() && deviceProfile != null) {
  115 + TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, serviceInfoProvider.getServiceId(), UUID.randomUUID());
  116 + tbCoapDtlsSessionInMemoryStorage.put(session.getSessionIdentifier().toString(), new TbCoapDtlsSessionInfo(sessionInfoProto, deviceProfile));
  117 + }
  118 + break;
  119 + }
  120 + } catch (InterruptedException |
  121 + CertificateEncodingException |
  122 + CertificateExpiredException |
  123 + CertificateNotYetValidException e) {
  124 + log.error(e.getMessage(), e);
  125 + }
  126 + }
  127 + if (credentialsBody == null) {
  128 + AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE,
  129 + session.getPeer());
  130 + throw new HandshakeException("Certificate chain could not be validated", alert);
  131 + } else {
  132 + return new CertificateVerificationResult(cid, certpath, null);
  133 + }
  134 + } catch (HandshakeException e) {
  135 + log.trace("Certificate validation failed!", e);
  136 + return new CertificateVerificationResult(cid, e, null);
  137 + }
  138 + }
  139 +
  140 + @Override
  141 + public List<X500Principal> getAcceptedIssuers() {
  142 + return CertPathUtil.toSubjects(null);
  143 + }
  144 +
  145 + @Override
  146 + public void setResultHandler(HandshakeResultHandler resultHandler) {
  147 + // empty implementation
  148 + }
  149 +
  150 + public ConcurrentMap<String, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionIdsMap() {
  151 + return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionIdMap();
  152 + }
  153 +
  154 + public void evictTimeoutSessions() {
  155 + tbCoapDtlsSessionInMemoryStorage.evictTimeoutSessions();
  156 + }
  157 +
  158 + public long getDtlsSessionReportTimeout() {
  159 + return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout();
  160 + }
  161 +}
  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.coap;
  17 +
  18 +import lombok.Data;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +
  21 +import java.util.concurrent.ConcurrentHashMap;
  22 +import java.util.concurrent.ConcurrentMap;
  23 +
  24 +@Slf4j
  25 +@Data
  26 +public class TbCoapDtlsSessionInMemoryStorage {
  27 +
  28 + private final ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap = new ConcurrentHashMap<>();
  29 + private long dtlsSessionInactivityTimeout;
  30 + private long dtlsSessionReportTimeout;
  31 +
  32 +
  33 + public TbCoapDtlsSessionInMemoryStorage(long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout) {
  34 + this.dtlsSessionInactivityTimeout = dtlsSessionInactivityTimeout;
  35 + this.dtlsSessionReportTimeout = dtlsSessionReportTimeout;
  36 + }
  37 +
  38 + public void put(String dtlsSessionId, TbCoapDtlsSessionInfo dtlsSessionInfo) {
  39 + log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", dtlsSessionId, dtlsSessionInfo.getLastActivityTime());
  40 + dtlsSessionIdMap.putIfAbsent(dtlsSessionId, dtlsSessionInfo);
  41 + }
  42 +
  43 + public void evictTimeoutSessions() {
  44 + long expTime = System.currentTimeMillis() - dtlsSessionInactivityTimeout;
  45 + dtlsSessionIdMap.entrySet().removeIf(entry -> {
  46 + if (entry.getValue().getLastActivityTime() < expTime) {
  47 + log.trace("DTLS session was removed from in-memory store: [{}]", entry.getKey());
  48 + return true;
  49 + } else {
  50 + return false;
  51 + }
  52 + });
  53 + }
  54 +
  55 +}
  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.coap;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.DeviceProfile;
  20 +import org.thingsboard.server.gen.transport.TransportProtos;
  21 +
  22 +@Data
  23 +public class TbCoapDtlsSessionInfo {
  24 +
  25 + private TransportProtos.SessionInfoProto sessionInfoProto;
  26 + private DeviceProfile deviceProfile;
  27 + private long lastActivityTime;
  28 +
  29 +
  30 + public TbCoapDtlsSessionInfo(TransportProtos.SessionInfoProto sessionInfoProto, DeviceProfile deviceProfile) {
  31 + this.sessionInfoProto = sessionInfoProto;
  32 + this.deviceProfile = deviceProfile;
  33 + this.lastActivityTime = System.currentTimeMillis();
  34 + }
  35 +}
  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.coap;
  17 +
  18 +import com.google.common.io.Resources;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.elements.util.SslContextUtil;
  21 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  22 +import org.eclipse.californium.scandium.dtls.CertificateType;
  23 +import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
  24 +import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.springframework.beans.factory.annotation.Value;
  26 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  27 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  28 +import org.springframework.stereotype.Component;
  29 +import org.thingsboard.server.common.transport.TransportService;
  30 +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
  31 +
  32 +import java.io.IOException;
  33 +import java.net.InetAddress;
  34 +import java.net.InetSocketAddress;
  35 +import java.net.UnknownHostException;
  36 +import java.security.GeneralSecurityException;
  37 +import java.security.cert.Certificate;
  38 +import java.util.Collections;
  39 +import java.util.Optional;
  40 +
  41 +@Slf4j
  42 +@ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false)
  43 +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')")
  44 +@Component
  45 +public class TbCoapDtlsSettings {
  46 +
  47 + @Value("${transport.coap.bind_address}")
  48 + private String host;
  49 +
  50 + @Value("${transport.coap.bind_port}")
  51 + private Integer port;
  52 +
  53 + @Value("${transport.coap.dtls.mode}")
  54 + private String mode;
  55 +
  56 + @Value("${transport.coap.dtls.key_store}")
  57 + private String keyStoreFile;
  58 +
  59 + @Value("${transport.coap.dtls.key_store_password}")
  60 + private String keyStorePassword;
  61 +
  62 + @Value("${transport.coap.dtls.key_password}")
  63 + private String keyPassword;
  64 +
  65 + @Value("${transport.coap.dtls.key_alias}")
  66 + private String keyAlias;
  67 +
  68 + @Value("${transport.coap.dtls.skip_validity_check_for_client_cert}")
  69 + private boolean skipValidityCheckForClientCert;
  70 +
  71 + @Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout}")
  72 + private long dtlsSessionInactivityTimeout;
  73 +
  74 + @Value("${transport.coap.dtls.x509.dtls_session_report_timeout}")
  75 + private long dtlsSessionReportTimeout;
  76 +
  77 + @Autowired
  78 + private TransportService transportService;
  79 +
  80 + @Autowired
  81 + private TbServiceInfoProvider serviceInfoProvider;
  82 +
  83 + public DtlsConnectorConfig dtlsConnectorConfig() throws UnknownHostException {
  84 + Optional<SecurityMode> securityModeOpt = SecurityMode.parse(mode);
  85 + if (securityModeOpt.isEmpty()) {
  86 + log.warn("Incorrect configuration of securityMode {}", mode);
  87 + throw new RuntimeException("Failed to parse mode property: " + mode + "!");
  88 + } else {
  89 + DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder();
  90 + configBuilder.setAddress(getInetSocketAddress());
  91 + String keyStoreFilePath = Resources.getResource(keyStoreFile).getPath();
  92 + SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath);
  93 + SecurityMode securityMode = securityModeOpt.get();
  94 + if (securityMode.equals(SecurityMode.NO_AUTH)) {
  95 + configBuilder.setClientAuthenticationRequired(false);
  96 + configBuilder.setServerOnly(true);
  97 + } else {
  98 + configBuilder.setAdvancedCertificateVerifier(
  99 + new TbCoapDtlsCertificateVerifier(
  100 + transportService,
  101 + serviceInfoProvider,
  102 + dtlsSessionInactivityTimeout,
  103 + dtlsSessionReportTimeout,
  104 + skipValidityCheckForClientCert
  105 + )
  106 + );
  107 + }
  108 + configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(),
  109 + Collections.singletonList(CertificateType.X_509));
  110 + return configBuilder.build();
  111 + }
  112 + }
  113 +
  114 + private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) {
  115 + try {
  116 + return SslContextUtil.loadCredentials(keyStoreFilePath, keyAlias, keyStorePassword.toCharArray(),
  117 + keyPassword.toCharArray());
  118 + } catch (GeneralSecurityException | IOException e) {
  119 + throw new RuntimeException("Failed to load serverCredentials due to: ", e);
  120 + }
  121 + }
  122 +
  123 + private void loadTrustedCertificates(DtlsConnectorConfig.Builder config, String keyStoreFilePath) {
  124 + StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
  125 + try {
  126 + Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
  127 + keyStoreFilePath, keyAlias,
  128 + keyStorePassword.toCharArray());
  129 + trustBuilder.setTrustedCertificates(trustedCertificates);
  130 + if (trustBuilder.hasTrusts()) {
  131 + config.setAdvancedCertificateVerifier(trustBuilder.build());
  132 + }
  133 + } catch (GeneralSecurityException | IOException e) {
  134 + throw new RuntimeException("Failed to load trusted certificates due to: ", e);
  135 + }
  136 + }
  137 +
  138 + private InetSocketAddress getInetSocketAddress() throws UnknownHostException {
  139 + InetAddress addr = InetAddress.getByName(host);
  140 + return new InetSocketAddress(addr, port);
  141 + }
  142 +
  143 + private enum SecurityMode {
  144 + X509,
  145 + NO_AUTH;
  146 +
  147 + static Optional<SecurityMode> parse(String name) {
  148 + SecurityMode mode = null;
  149 + if (name != null) {
  150 + for (SecurityMode securityMode : SecurityMode.values()) {
  151 + if (securityMode.name().equalsIgnoreCase(name)) {
  152 + mode = securityMode;
  153 + break;
  154 + }
  155 + }
  156 + }
  157 + return Optional.ofNullable(mode);
  158 + }
  159 +
  160 + }
  161 +
  162 +}
  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.coap.client;
  17 +
  18 +import org.eclipse.californium.core.CoapClient;
  19 +import org.eclipse.californium.core.CoapResponse;
  20 +import org.eclipse.californium.core.Utils;
  21 +import org.eclipse.californium.elements.DtlsEndpointContext;
  22 +import org.eclipse.californium.elements.EndpointContext;
  23 +import org.eclipse.californium.elements.exception.ConnectorException;
  24 +
  25 +import java.io.IOException;
  26 +import java.net.URI;
  27 +import java.net.URISyntaxException;
  28 +import java.security.Principal;
  29 +import java.util.concurrent.ExecutorService;
  30 +import java.util.concurrent.Executors;
  31 +
  32 +public class NoSecClient {
  33 +
  34 + private ExecutorService executor = Executors.newFixedThreadPool(1);
  35 + private CoapClient coapClient;
  36 +
  37 + public NoSecClient(String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
  38 + URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys));
  39 + this.coapClient = new CoapClient(uri);
  40 + }
  41 +
  42 + public void test() {
  43 + executor.submit(() -> {
  44 + try {
  45 + while (!Thread.interrupted()) {
  46 + CoapResponse response = null;
  47 + try {
  48 + response = coapClient.get();
  49 + } catch (ConnectorException | IOException e) {
  50 + System.err.println("Error occurred while sending request: " + e);
  51 + System.exit(-1);
  52 + }
  53 + if (response != null) {
  54 +
  55 + System.out.println(response.getCode() + " - " + response.getCode().name());
  56 + System.out.println(response.getOptions());
  57 + System.out.println(response.getResponseText());
  58 + System.out.println();
  59 + System.out.println("ADVANCED:");
  60 + EndpointContext context = response.advanced().getSourceContext();
  61 + Principal identity = context.getPeerIdentity();
  62 + if (identity != null) {
  63 + System.out.println(context.getPeerIdentity());
  64 + } else {
  65 + System.out.println("anonymous");
  66 + }
  67 + System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
  68 + System.out.println(Utils.prettyPrint(response));
  69 + } else {
  70 + System.out.println("No response received.");
  71 + }
  72 + Thread.sleep(5000);
  73 + }
  74 + } catch (Exception e) {
  75 + System.out.println("Error occurred while sending COAP requests.");
  76 + }
  77 + });
  78 + }
  79 +
  80 + private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) {
  81 + return "coap://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
  82 + }
  83 +
  84 + public static void main(String[] args) throws URISyntaxException {
  85 + System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.NoSecClient " +
  86 + "host port accessToken clientKeys sharedKeys");
  87 +
  88 + String host = args[0];
  89 + int port = Integer.parseInt(args[1]);
  90 + String accessToken = args[2];
  91 + String clientKeys = args[3];
  92 + String sharedKeys = args[4];
  93 +
  94 + NoSecClient client = new NoSecClient(host, port, accessToken, clientKeys, sharedKeys);
  95 + client.test();
  96 + }
  97 +}
  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.coap.client;
  17 +
  18 +import org.eclipse.californium.core.CoapClient;
  19 +import org.eclipse.californium.core.CoapResponse;
  20 +import org.eclipse.californium.core.Utils;
  21 +import org.eclipse.californium.core.network.CoapEndpoint;
  22 +import org.eclipse.californium.elements.DtlsEndpointContext;
  23 +import org.eclipse.californium.elements.EndpointContext;
  24 +import org.eclipse.californium.elements.exception.ConnectorException;
  25 +import org.eclipse.californium.elements.util.SslContextUtil;
  26 +import org.eclipse.californium.scandium.DTLSConnector;
  27 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  28 +import org.eclipse.californium.scandium.dtls.CertificateType;
  29 +import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
  30 +
  31 +import java.io.IOException;
  32 +import java.net.URI;
  33 +import java.net.URISyntaxException;
  34 +import java.security.GeneralSecurityException;
  35 +import java.security.Principal;
  36 +import java.security.cert.Certificate;
  37 +import java.util.Collections;
  38 +import java.util.concurrent.ExecutorService;
  39 +import java.util.concurrent.Executors;
  40 +
  41 +public class SecureClientNoAuth {
  42 +
  43 + private final DTLSConnector dtlsConnector;
  44 + private ExecutorService executor = Executors.newFixedThreadPool(1);
  45 + private CoapClient coapClient;
  46 +
  47 + public SecureClientNoAuth(DTLSConnector dtlsConnector, String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
  48 + this.dtlsConnector = dtlsConnector;
  49 + this.coapClient = getCoapClient(host, port, accessToken, clientKeys, sharedKeys);
  50 + }
  51 +
  52 + public void test() {
  53 + executor.submit(() -> {
  54 + try {
  55 + while (!Thread.interrupted()) {
  56 + CoapResponse response = null;
  57 + try {
  58 + response = coapClient.get();
  59 + } catch (ConnectorException | IOException e) {
  60 + System.err.println("Error occurred while sending request: " + e);
  61 + System.exit(-1);
  62 + }
  63 + if (response != null) {
  64 +
  65 + System.out.println(response.getCode() + " - " + response.getCode().name());
  66 + System.out.println(response.getOptions());
  67 + System.out.println(response.getResponseText());
  68 + System.out.println();
  69 + System.out.println("ADVANCED:");
  70 + EndpointContext context = response.advanced().getSourceContext();
  71 + Principal identity = context.getPeerIdentity();
  72 + if (identity != null) {
  73 + System.out.println(context.getPeerIdentity());
  74 + } else {
  75 + System.out.println("anonymous");
  76 + }
  77 + System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
  78 + System.out.println(Utils.prettyPrint(response));
  79 + } else {
  80 + System.out.println("No response received.");
  81 + }
  82 + Thread.sleep(5000);
  83 + }
  84 + } catch (Exception e) {
  85 + System.out.println("Error occurred while sending COAP requests.");
  86 + }
  87 + });
  88 + }
  89 +
  90 + private CoapClient getCoapClient(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
  91 + URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys));
  92 + CoapClient client = new CoapClient(uri);
  93 + CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
  94 + builder.setConnector(dtlsConnector);
  95 +
  96 + client.setEndpoint(builder.build());
  97 + return client;
  98 + }
  99 +
  100 + private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) {
  101 + return "coaps://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
  102 + }
  103 +
  104 + public static void main(String[] args) throws URISyntaxException {
  105 + System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientNoAuth " +
  106 + "host port accessToken keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys");
  107 +
  108 + String host = args[0];
  109 + int port = Integer.parseInt(args[1]);
  110 + String accessToken = args[2];
  111 + String clientKeys = args[7];
  112 + String sharedKeys = args[8];
  113 +
  114 + String keyStoreUriPath = args[3];
  115 + String keyStoreAlias = args[4];
  116 + String trustedAliasPattern = args[5];
  117 + String keyStorePassword = args[6];
  118 +
  119 +
  120 + DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
  121 + setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword);
  122 + DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
  123 + SecureClientNoAuth client = new SecureClientNoAuth(dtlsConnector, host, port, accessToken, clientKeys, sharedKeys);
  124 + client.test();
  125 + }
  126 +
  127 + private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) {
  128 + StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
  129 + try {
  130 + SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials(
  131 + keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray());
  132 + Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
  133 + keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray());
  134 + trustBuilder.setTrustedCertificates(trustedCertificates);
  135 + config.setAdvancedCertificateVerifier(trustBuilder.build());
  136 + config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509));
  137 + } catch (GeneralSecurityException e) {
  138 + System.err.println("certificates are invalid!");
  139 + throw new IllegalArgumentException(e.getMessage());
  140 + } catch (IOException e) {
  141 + System.err.println("certificates are missing!");
  142 + throw new IllegalArgumentException(e.getMessage());
  143 + }
  144 + }
  145 +}
  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.coap.client;
  17 +
  18 +import org.eclipse.californium.core.CoapClient;
  19 +import org.eclipse.californium.core.CoapResponse;
  20 +import org.eclipse.californium.core.Utils;
  21 +import org.eclipse.californium.core.network.CoapEndpoint;
  22 +import org.eclipse.californium.elements.DtlsEndpointContext;
  23 +import org.eclipse.californium.elements.EndpointContext;
  24 +import org.eclipse.californium.elements.exception.ConnectorException;
  25 +import org.eclipse.californium.elements.util.SslContextUtil;
  26 +import org.eclipse.californium.scandium.DTLSConnector;
  27 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  28 +import org.eclipse.californium.scandium.dtls.CertificateType;
  29 +import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
  30 +
  31 +import java.io.IOException;
  32 +import java.net.URI;
  33 +import java.net.URISyntaxException;
  34 +import java.security.GeneralSecurityException;
  35 +import java.security.Principal;
  36 +import java.security.cert.Certificate;
  37 +import java.util.Collections;
  38 +import java.util.concurrent.ExecutorService;
  39 +import java.util.concurrent.Executors;
  40 +
  41 +public class SecureClientX509 {
  42 +
  43 + private final DTLSConnector dtlsConnector;
  44 + private ExecutorService executor = Executors.newFixedThreadPool(1);
  45 + private CoapClient coapClient;
  46 +
  47 + public SecureClientX509(DTLSConnector dtlsConnector, String host, int port, String clientKeys, String sharedKeys) throws URISyntaxException {
  48 + this.dtlsConnector = dtlsConnector;
  49 + this.coapClient = getCoapClient(host, port, clientKeys, sharedKeys);
  50 + }
  51 +
  52 + public void test() {
  53 + executor.submit(() -> {
  54 + try {
  55 + while (!Thread.interrupted()) {
  56 + CoapResponse response = null;
  57 + try {
  58 + response = coapClient.get();
  59 + } catch (ConnectorException | IOException e) {
  60 + System.err.println("Error occurred while sending request: " + e);
  61 + System.exit(-1);
  62 + }
  63 + if (response != null) {
  64 +
  65 + System.out.println(response.getCode() + " - " + response.getCode().name());
  66 + System.out.println(response.getOptions());
  67 + System.out.println(response.getResponseText());
  68 + System.out.println();
  69 + System.out.println("ADVANCED:");
  70 + EndpointContext context = response.advanced().getSourceContext();
  71 + Principal identity = context.getPeerIdentity();
  72 + if (identity != null) {
  73 + System.out.println(context.getPeerIdentity());
  74 + } else {
  75 + System.out.println("anonymous");
  76 + }
  77 + System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
  78 + System.out.println(Utils.prettyPrint(response));
  79 + } else {
  80 + System.out.println("No response received.");
  81 + }
  82 + Thread.sleep(5000);
  83 + }
  84 + } catch (Exception e) {
  85 + System.out.println("Error occurred while sending COAP requests.");
  86 + }
  87 + });
  88 + }
  89 +
  90 + private CoapClient getCoapClient(String host, Integer port, String clientKeys, String sharedKeys) throws URISyntaxException {
  91 + URI uri = new URI(getFutureUrl(host, port, clientKeys, sharedKeys));
  92 + CoapClient client = new CoapClient(uri);
  93 + CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
  94 + builder.setConnector(dtlsConnector);
  95 +
  96 + client.setEndpoint(builder.build());
  97 + return client;
  98 + }
  99 +
  100 + private String getFutureUrl(String host, Integer port, String clientKeys, String sharedKeys) {
  101 + return "coaps://" + host + ":" + port + "/api/v1/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
  102 + }
  103 +
  104 + public static void main(String[] args) throws URISyntaxException {
  105 + System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientX509 " +
  106 + "host port keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys");
  107 +
  108 + String host = args[0];
  109 + int port = Integer.parseInt(args[1]);
  110 + String clientKeys = args[6];
  111 + String sharedKeys = args[7];
  112 +
  113 + String keyStoreUriPath = args[2];
  114 + String keyStoreAlias = args[3];
  115 + String trustedAliasPattern = args[4];
  116 + String keyStorePassword = args[5];
  117 +
  118 +
  119 + DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
  120 + setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword);
  121 + DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
  122 + SecureClientX509 client = new SecureClientX509(dtlsConnector, host, port, clientKeys, sharedKeys);
  123 + client.test();
  124 + }
  125 +
  126 + private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) {
  127 + StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
  128 + try {
  129 + SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials(
  130 + keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray());
  131 + Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
  132 + keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray());
  133 + trustBuilder.setTrustedCertificates(trustedCertificates);
  134 + config.setAdvancedCertificateVerifier(trustBuilder.build());
  135 + config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509));
  136 + } catch (GeneralSecurityException e) {
  137 + System.err.println("certificates are invalid!");
  138 + throw new IllegalArgumentException(e.getMessage());
  139 + } catch (IOException e) {
  140 + System.err.println("certificates are missing!");
  141 + throw new IllegalArgumentException(e.getMessage());
  142 + }
  143 + }
  144 +}
@@ -30,7 +30,7 @@ import org.thingsboard.server.common.transport.TransportService; @@ -30,7 +30,7 @@ import org.thingsboard.server.common.transport.TransportService;
30 import org.thingsboard.server.common.transport.TransportServiceCallback; 30 import org.thingsboard.server.common.transport.TransportServiceCallback;
31 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; 31 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
32 import org.thingsboard.server.gen.transport.TransportProtos; 32 import org.thingsboard.server.gen.transport.TransportProtos;
33 -import org.thingsboard.server.transport.mqtt.util.SslUtil; 33 +import org.thingsboard.server.common.transport.util.SslUtil;
34 34
35 import javax.net.ssl.KeyManager; 35 import javax.net.ssl.KeyManager;
36 import javax.net.ssl.KeyManagerFactory; 36 import javax.net.ssl.KeyManagerFactory;
@@ -41,7 +41,6 @@ import javax.net.ssl.TrustManagerFactory; @@ -41,7 +41,6 @@ import javax.net.ssl.TrustManagerFactory;
41 import javax.net.ssl.X509TrustManager; 41 import javax.net.ssl.X509TrustManager;
42 import java.io.File; 42 import java.io.File;
43 import java.io.FileInputStream; 43 import java.io.FileInputStream;
44 -import java.io.IOException;  
45 import java.io.InputStream; 44 import java.io.InputStream;
46 import java.net.URL; 45 import java.net.URL;
47 import java.security.KeyStore; 46 import java.security.KeyStore;
@@ -66,7 +66,7 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; @@ -66,7 +66,7 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
66 import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; 66 import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
67 import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler; 67 import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler;
68 import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher; 68 import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher;
69 -import org.thingsboard.server.transport.mqtt.util.SslUtil; 69 +import org.thingsboard.server.common.transport.util.SslUtil;
70 70
71 import javax.net.ssl.SSLPeerUnverifiedException; 71 import javax.net.ssl.SSLPeerUnverifiedException;
72 import java.security.cert.Certificate; 72 import java.security.cert.Certificate;
@@ -25,7 +25,15 @@ import java.util.UUID; @@ -25,7 +25,15 @@ import java.util.UUID;
25 public class SessionInfoCreator { 25 public class SessionInfoCreator {
26 26
27 public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, TransportContext context, UUID sessionId) { 27 public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, TransportContext context, UUID sessionId) {
28 - return TransportProtos.SessionInfoProto.newBuilder().setNodeId(context.getNodeId()) 28 + return getSessionInfoProto(msg, context.getNodeId(), sessionId);
  29 + }
  30 +
  31 + public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, String nodeId, UUID sessionId) {
  32 + return getSessionInfoProto(msg, nodeId, sessionId);
  33 + }
  34 +
  35 + private static TransportProtos.SessionInfoProto getSessionInfoProto(ValidateDeviceCredentialsResponse msg, String nodeId, UUID sessionId) {
  36 + return TransportProtos.SessionInfoProto.newBuilder().setNodeId(nodeId)
29 .setSessionIdMSB(sessionId.getMostSignificantBits()) 37 .setSessionIdMSB(sessionId.getMostSignificantBits())
30 .setSessionIdLSB(sessionId.getLeastSignificantBits()) 38 .setSessionIdLSB(sessionId.getLeastSignificantBits())
31 .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits()) 39 .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits())
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java renamed from common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java
@@ -13,13 +13,12 @@ @@ -13,13 +13,12 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.transport.mqtt.util; 16 +package org.thingsboard.server.common.transport.util;
17 17
18 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
19 import org.springframework.util.Base64Utils; 19 import org.springframework.util.Base64Utils;
20 import org.thingsboard.server.common.msg.EncryptionUtil; 20 import org.thingsboard.server.common.msg.EncryptionUtil;
21 21
22 -import java.io.IOException;  
23 import java.security.cert.Certificate; 22 import java.security.cert.Certificate;
24 import java.security.cert.CertificateEncodingException; 23 import java.security.cert.CertificateEncodingException;
25 24
@@ -1153,6 +1153,11 @@ @@ -1153,6 +1153,11 @@
1153 <version>${californium.version}</version> 1153 <version>${californium.version}</version>
1154 </dependency> 1154 </dependency>
1155 <dependency> 1155 <dependency>
  1156 + <groupId>org.eclipse.californium</groupId>
  1157 + <artifactId>scandium</artifactId>
  1158 + <version>${californium.version}</version>
  1159 + </dependency>
  1160 + <dependency>
1156 <groupId>com.google.code.gson</groupId> 1161 <groupId>com.google.code.gson</groupId>
1157 <artifactId>gson</artifactId> 1162 <artifactId>gson</artifactId>
1158 <version>${gson.version}</version> 1163 <version>${gson.version}</version>
@@ -46,6 +46,24 @@ transport: @@ -46,6 +46,24 @@ transport:
46 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" 46 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
47 bind_port: "${COAP_BIND_PORT:5683}" 47 bind_port: "${COAP_BIND_PORT:5683}"
48 timeout: "${COAP_TIMEOUT:10000}" 48 timeout: "${COAP_TIMEOUT:10000}"
  49 + dtls:
  50 + # Enable/disable DTLS 1.2 support
  51 + enabled: "${COAP_DTLS_ENABLED:false}"
  52 + # Secure mode. Allowed values: NO_AUTH, X509
  53 + mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}"
  54 + # Path to the key store that holds the certificate
  55 + key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
  56 + # Password used to access the key store
  57 + key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
  58 + # Password used to access the key
  59 + key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
  60 + # Key alias
  61 + key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
  62 + # Skip certificate validity check for client certificates.
  63 + skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
  64 + x509:
  65 + dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
  66 + dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
49 sessions: 67 sessions:
50 inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" 68 inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
51 report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" 69 report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"
@@ -518,7 +518,7 @@ export enum DeviceCredentialsType { @@ -518,7 +518,7 @@ export enum DeviceCredentialsType {
518 export const credentialTypeNames = new Map<DeviceCredentialsType, string>( 518 export const credentialTypeNames = new Map<DeviceCredentialsType, string>(
519 [ 519 [
520 [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'], 520 [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'],
521 - [DeviceCredentialsType.X509_CERTIFICATE, 'MQTT X.509'], 521 + [DeviceCredentialsType.X509_CERTIFICATE, 'X.509'],
522 [DeviceCredentialsType.MQTT_BASIC, 'MQTT Basic'] 522 [DeviceCredentialsType.MQTT_BASIC, 'MQTT Basic']
523 ] 523 ]
524 ); 524 );