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,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 |
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,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 | +} |
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 | +} |
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 | +} |
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,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 | ); |