Commit fd3e18f18b28ff5e91de08349fcaa2c416c2a08c
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 | 583 | bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" |
584 | 584 | bind_port: "${COAP_BIND_PORT:5683}" |
585 | 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 | 604 | swagger: |
588 | 605 | api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}" |
589 | 606 | security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api.*}" | ... | ... |
... | ... | @@ -45,6 +45,10 @@ |
45 | 45 | <artifactId>californium-core</artifactId> |
46 | 46 | </dependency> |
47 | 47 | <dependency> |
48 | + <groupId>org.eclipse.californium</groupId> | |
49 | + <artifactId>scandium</artifactId> | |
50 | + </dependency> | |
51 | + <dependency> | |
48 | 52 | <groupId>org.springframework</groupId> |
49 | 53 | <artifactId>spring-context-support</artifactId> |
50 | 54 | </dependency> | ... | ... |
... | ... | @@ -47,6 +47,10 @@ public class CoapTransportContext extends TransportContext { |
47 | 47 | private Long timeout; |
48 | 48 | |
49 | 49 | @Getter |
50 | + @Autowired(required = false) | |
51 | + private TbCoapDtlsSettings dtlsSettings; | |
52 | + | |
53 | + @Getter | |
50 | 54 | @Autowired |
51 | 55 | private JsonCoapAdaptor jsonCoapAdaptor; |
52 | 56 | ... | ... |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
... | ... | @@ -27,6 +27,7 @@ import org.eclipse.californium.core.observe.ObserveRelation; |
27 | 27 | import org.eclipse.californium.core.server.resources.CoapExchange; |
28 | 28 | import org.eclipse.californium.core.server.resources.Resource; |
29 | 29 | import org.eclipse.californium.core.server.resources.ResourceObserver; |
30 | +import org.springframework.util.StringUtils; | |
30 | 31 | import org.thingsboard.server.common.data.DataConstants; |
31 | 32 | import org.thingsboard.server.common.data.DeviceProfile; |
32 | 33 | import org.thingsboard.server.common.data.DeviceTransportType; |
... | ... | @@ -63,15 +64,22 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
63 | 64 | private static final int FEATURE_TYPE_POSITION = 4; |
64 | 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 | 71 | private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>(); |
67 | 72 | private final ConcurrentMap<String, AtomicInteger> tokenToNotificationCounterMap = new ConcurrentHashMap<>(); |
68 | 73 | private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet(); |
69 | 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 | 79 | super(coapTransportContext, name); |
73 | 80 | this.setObservable(true); // enable observing |
74 | 81 | this.addObserver(new CoapResourceObserver()); |
82 | + this.dtlsSessionIdMap = dtlsSessionIdMap; | |
75 | 83 | // this.setObservable(false); // disable observing |
76 | 84 | // this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs |
77 | 85 | // this.getAttributes().setObservable(); // mark observable in the Link-Format |
... | ... | @@ -187,107 +195,132 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
187 | 195 | Exchange advanced = exchange.advanced(); |
188 | 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 | 220 | Optional<DeviceTokenCredentials> credentials = decodeCredentials(request); |
191 | 221 | if (credentials.isEmpty()) { |
192 | - exchange.respond(CoAP.ResponseCode.BAD_REQUEST); | |
222 | + exchange.respond(CoAP.ResponseCode.UNAUTHORIZED); | |
193 | 223 | return; |
194 | 224 | } |
195 | - | |
196 | 225 | transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), |
197 | 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 | 324 | private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { |
292 | 325 | tokenToNotificationCounterMap.remove(token); |
293 | 326 | return tokenToSessionIdMap.remove(token); |
... | ... | @@ -310,7 +343,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
310 | 343 | |
311 | 344 | private Optional<DeviceTokenCredentials> decodeCredentials(Request request) { |
312 | 345 | List<String> uriPath = request.getOptions().getUriPath(); |
313 | - if (uriPath.size() >= ACCESS_TOKEN_POSITION) { | |
346 | + if (uriPath.size() > ACCESS_TOKEN_POSITION) { | |
314 | 347 | return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); |
315 | 348 | } else { |
316 | 349 | return Optional.empty(); |
... | ... | @@ -322,8 +355,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
322 | 355 | try { |
323 | 356 | if (uriPath.size() >= FEATURE_TYPE_POSITION) { |
324 | 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 | 364 | } catch (RuntimeException e) { |
329 | 365 | log.warn("Failed to decode feature type: {}", uriPath); |
... | ... | @@ -336,6 +372,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
336 | 372 | try { |
337 | 373 | if (uriPath.size() >= REQUEST_ID_POSITION) { |
338 | 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 | 378 | } catch (RuntimeException e) { |
341 | 379 | log.warn("Failed to decode feature type: {}", uriPath); | ... | ... |
... | ... | @@ -19,7 +19,10 @@ import lombok.extern.slf4j.Slf4j; |
19 | 19 | import org.eclipse.californium.core.CoapResource; |
20 | 20 | import org.eclipse.californium.core.CoapServer; |
21 | 21 | import org.eclipse.californium.core.network.CoapEndpoint; |
22 | +import org.eclipse.californium.core.network.config.NetworkConfig; | |
22 | 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 | 26 | import org.springframework.beans.factory.annotation.Autowired; |
24 | 27 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
25 | 28 | import org.springframework.stereotype.Service; |
... | ... | @@ -30,6 +33,11 @@ import javax.annotation.PreDestroy; |
30 | 33 | import java.net.InetAddress; |
31 | 34 | import java.net.InetSocketAddress; |
32 | 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 | 42 | @Service("CoapTransportService") |
35 | 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 | 52 | @Autowired |
45 | 53 | private CoapTransportContext coapTransportContext; |
46 | 54 | |
55 | + private TbCoapDtlsCertificateVerifier tbDtlsCertificateVerifier; | |
56 | + | |
47 | 57 | private CoapServer server; |
48 | 58 | |
59 | + private ScheduledExecutorService dtlsSessionsExecutor; | |
60 | + | |
49 | 61 | @PostConstruct |
50 | 62 | public void init() throws UnknownHostException { |
51 | 63 | log.info("Starting CoAP transport..."); |
52 | 64 | log.info("Starting CoAP transport server"); |
53 | 65 | |
54 | 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 | 90 | createResources(); |
56 | 91 | Resource root = this.server.getRoot(); |
57 | 92 | TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root); |
58 | 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 | 95 | server.start(); |
69 | 96 | log.info("CoAP transport started!"); |
70 | 97 | } |
71 | 98 | |
72 | 99 | private void createResources() { |
73 | 100 | CoapResource api = new CoapResource(API); |
74 | - api.add(new CoapTransportResource(coapTransportContext, V1)); | |
101 | + api.add(new CoapTransportResource(coapTransportContext, getDtlsSessionsMap(), V1)); | |
75 | 102 | |
76 | 103 | CoapResource efento = new CoapResource(EFENTO); |
77 | 104 | CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS); |
... | ... | @@ -81,8 +108,27 @@ public class CoapTransportService { |
81 | 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 | 127 | @PreDestroy |
85 | 128 | public void shutdown() { |
129 | + if (dtlsSessionsExecutor != null) { | |
130 | + dtlsSessionsExecutor.shutdownNow(); | |
131 | + } | |
86 | 132 | log.info("Stopping CoAP transport!"); |
87 | 133 | this.server.destroy(); |
88 | 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 | +} | |
\ No newline at end of file | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/TbCoapDtlsSessionInfo.java
0 → 100644
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 | +} | |
\ No newline at end of file | ... | ... |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/TbCoapDtlsSettings.java
0 → 100644
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 | +} | |
\ No newline at end of file | ... | ... |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/NoSecClient.java
0 → 100644
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 | 30 | import org.thingsboard.server.common.transport.TransportServiceCallback; |
31 | 31 | import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
32 | 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 | 35 | import javax.net.ssl.KeyManager; |
36 | 36 | import javax.net.ssl.KeyManagerFactory; |
... | ... | @@ -41,7 +41,6 @@ import javax.net.ssl.TrustManagerFactory; |
41 | 41 | import javax.net.ssl.X509TrustManager; |
42 | 42 | import java.io.File; |
43 | 43 | import java.io.FileInputStream; |
44 | -import java.io.IOException; | |
45 | 44 | import java.io.InputStream; |
46 | 45 | import java.net.URL; |
47 | 46 | import java.security.KeyStore; | ... | ... |
... | ... | @@ -66,7 +66,7 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; |
66 | 66 | import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; |
67 | 67 | import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler; |
68 | 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 | 71 | import javax.net.ssl.SSLPeerUnverifiedException; |
72 | 72 | import java.security.cert.Certificate; | ... | ... |
... | ... | @@ -25,7 +25,15 @@ import java.util.UUID; |
25 | 25 | public class SessionInfoCreator { |
26 | 26 | |
27 | 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 | 37 | .setSessionIdMSB(sessionId.getMostSignificantBits()) |
30 | 38 | .setSessionIdLSB(sessionId.getLeastSignificantBits()) |
31 | 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 | * See the License for the specific language governing permissions and |
14 | 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 | 18 | import lombok.extern.slf4j.Slf4j; |
19 | 19 | import org.springframework.util.Base64Utils; |
20 | 20 | import org.thingsboard.server.common.msg.EncryptionUtil; |
21 | 21 | |
22 | -import java.io.IOException; | |
23 | 22 | import java.security.cert.Certificate; |
24 | 23 | import java.security.cert.CertificateEncodingException; |
25 | 24 | ... | ... |
... | ... | @@ -1153,6 +1153,11 @@ |
1153 | 1153 | <version>${californium.version}</version> |
1154 | 1154 | </dependency> |
1155 | 1155 | <dependency> |
1156 | + <groupId>org.eclipse.californium</groupId> | |
1157 | + <artifactId>scandium</artifactId> | |
1158 | + <version>${californium.version}</version> | |
1159 | + </dependency> | |
1160 | + <dependency> | |
1156 | 1161 | <groupId>com.google.code.gson</groupId> |
1157 | 1162 | <artifactId>gson</artifactId> |
1158 | 1163 | <version>${gson.version}</version> | ... | ... |
... | ... | @@ -46,6 +46,24 @@ transport: |
46 | 46 | bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" |
47 | 47 | bind_port: "${COAP_BIND_PORT:5683}" |
48 | 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 | 67 | sessions: |
50 | 68 | inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" |
51 | 69 | report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" | ... | ... |
... | ... | @@ -518,7 +518,7 @@ export enum DeviceCredentialsType { |
518 | 518 | export const credentialTypeNames = new Map<DeviceCredentialsType, string>( |
519 | 519 | [ |
520 | 520 | [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'], |
521 | - [DeviceCredentialsType.X509_CERTIFICATE, 'MQTT X.509'], | |
521 | + [DeviceCredentialsType.X509_CERTIFICATE, 'X.509'], | |
522 | 522 | [DeviceCredentialsType.MQTT_BASIC, 'MQTT Basic'] |
523 | 523 | ] |
524 | 524 | ); | ... | ... |