diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java index 3a85a94f2d..34f8c54300 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.lwm2m; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; import org.eclipse.leshan.core.util.Hex; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; @@ -50,22 +51,26 @@ public class LwM2MServiceImpl implements LwM2MService { bsServ.setPort(serverConfig.getPort()); bsServ.setSecurityHost(serverConfig.getSecureHost()); bsServ.setSecurityPort(serverConfig.getSecurePort()); - bsServ.setServerPublicKey(getPublicKey(serverConfig)); + byte[] publicKeyBase64 = getPublicKey(serverConfig); + if (publicKeyBase64 == null) { + bsServ.setServerPublicKey(""); + } else { + bsServ.setServerPublicKey(Base64.encodeBase64String(getPublicKey(serverConfig))); + } return bsServ; } - private String getPublicKey(LwM2MSecureServerConfig config) { + private byte[] getPublicKey(LwM2MSecureServerConfig config) { try { KeyStore keyStore = serverConfig.getKeyStoreValue(); if (keyStore != null) { X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(config.getCertificateAlias()); - return Hex.encodeHexString(serverCertificate.getPublicKey().getEncoded()); + return serverCertificate.getPublicKey().getEncoded(); } } catch (Exception e) { log.trace("Failed to fetch public key from key store!", e); - } - return ""; + return null; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/BootstrapConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/BootstrapConfiguration.java index 66bdfd1f4c..4b3901d655 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/BootstrapConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/BootstrapConfiguration.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.device.data.lwm2m; import lombok.Data; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.ServerCredentials; import java.util.Map; @@ -24,7 +25,7 @@ public class BootstrapConfiguration { //TODO: define the objects; private Map servers; - private Map lwm2mServer; - private Map bootstrapServer; + private ServerCredentials lwm2mServer; + private ServerCredentials bootstrapServer; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/AbstractServerCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/AbstractServerCredentials.java new file mode 100644 index 0000000000..030b40946f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/AbstractServerCredentials.java @@ -0,0 +1,38 @@ +/** + * 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.device.profile.lwm2m.bootstrap; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import org.apache.commons.codec.binary.Base64; +import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; + +@Getter +@Setter +public abstract class AbstractServerCredentials extends ServerSecurityConfig implements ServerCredentials { + + @JsonIgnore + public byte[] getDecodedCServerPublicKey() { + return getDecoded(serverPublicKey); + } + + @SneakyThrows + private static byte[] getDecoded(String key) { + return Base64.decodeBase64(key.getBytes()); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecServerCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecServerCredentials.java new file mode 100644 index 0000000000..432066b1ff --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecServerCredentials.java @@ -0,0 +1,25 @@ +/** + * 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.device.profile.lwm2m.bootstrap; + +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; + +public class NoSecServerCredentials extends AbstractServerCredentials{ + @Override + public LwM2MSecurityMode getSecurityMode() { + return LwM2MSecurityMode.NO_SEC; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKServerCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKServerCredentials.java new file mode 100644 index 0000000000..2e21e34145 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKServerCredentials.java @@ -0,0 +1,25 @@ +/** + * 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.device.profile.lwm2m.bootstrap; + +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; + +public class PSKServerCredentials extends AbstractServerCredentials{ + @Override + public LwM2MSecurityMode getSecurityMode() { + return LwM2MSecurityMode.PSK; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKServerCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKServerCredentials.java new file mode 100644 index 0000000000..42d7ab2981 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKServerCredentials.java @@ -0,0 +1,25 @@ +/** + * 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.device.profile.lwm2m.bootstrap; + +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; + +public class RPKServerCredentials extends AbstractServerCredentials{ + @Override + public LwM2MSecurityMode getSecurityMode() { + return LwM2MSecurityMode.RPK; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/ServerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/ServerConfig.java new file mode 100644 index 0000000000..ab05fb6b75 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/ServerConfig.java @@ -0,0 +1,27 @@ +/** + * 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.device.profile.lwm2m.bootstrap; + +import lombok.Data; + +@Data +public class ServerConfig { + private Integer shortId = 123; + private Integer lifetime = 300; + private Integer defaultMinPeriod = 1; + private boolean notifIfDisabled = true; + private String binding = "U"; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/ServerCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/ServerCredentials.java new file mode 100644 index 0000000000..146e2cf125 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/ServerCredentials.java @@ -0,0 +1,37 @@ +/** + * 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.device.profile.lwm2m.bootstrap; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "securityMode") +@JsonSubTypes({ + @JsonSubTypes.Type(value = NoSecServerCredentials.class, name = "NO_SEC"), + @JsonSubTypes.Type(value = PSKServerCredentials.class, name = "PSK"), + @JsonSubTypes.Type(value = RPKServerCredentials.class, name = "RPK"), + @JsonSubTypes.Type(value = X509ServerCredentials.class, name = "X509") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +public interface ServerCredentials { + @JsonIgnore + LwM2MSecurityMode getSecurityMode(); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509ServerCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509ServerCredentials.java new file mode 100644 index 0000000000..f56cb12e80 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509ServerCredentials.java @@ -0,0 +1,25 @@ +/** + * 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.device.profile.lwm2m.bootstrap; + +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; + +public class X509ServerCredentials extends AbstractServerCredentials{ + @Override + public LwM2MSecurityMode getSecurityMode() { + return LwM2MSecurityMode.X509; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java index 79b3ef40b6..83b56a2661 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java @@ -22,23 +22,23 @@ import lombok.Data; @ApiModel @Data public class ServerSecurityConfig { - @ApiModelProperty(position = 1, value = "Is Bootstrap Server", example = "true", readOnly = true) - boolean bootstrapServerIs = true; + @ApiModelProperty(position = 1, value = "Is Bootstrap Server or Lwm2m Server", example = "true or false", readOnly = true) + protected boolean bootstrapServerIs = true; @ApiModelProperty(position = 2, value = "Host for 'No Security' mode", example = "0.0.0.0", readOnly = true) - String host; - @ApiModelProperty(position = 3, value = "Port for 'No Security' mode", example = "5687", readOnly = true) - Integer port; + protected String host; + @ApiModelProperty(position = 3, value = "Port for Lwm2m Server: 'No Security' mode: Lwm2m Server or Bootstrap Server", example = "'5685' or '5687'", readOnly = true) + protected Integer port; @ApiModelProperty(position = 4, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", readOnly = true) - String securityHost; - @ApiModelProperty(position = 5, value = "Port for 'Security' mode (DTLS)", example = "5688", readOnly = true) - Integer securityPort; - @ApiModelProperty(position = 5, value = "Server short Id", example = "111", readOnly = true) - Integer serverId = 111; - @ApiModelProperty(position = 7, value = "Client Hold Off Time", example = "1", readOnly = true) - Integer clientHoldOffTime = 1; - @ApiModelProperty(position = 8, value = "Server Public Key (base64 encoded)", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" + + protected String securityHost; + @ApiModelProperty(position = 5, value = "Port for 'Security' mode (DTLS): Lwm2m Server or Bootstrap Server", example = "5686 or 5688", readOnly = true) + protected Integer securityPort; + @ApiModelProperty(position = 6, value = "Server short Id", example = "111", readOnly = true) + protected Integer serverId = 111; + @ApiModelProperty(position = 7, value = "Client Hold Off Time. The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode. (This information is relevant for use with a Bootstrap-Server only.)", example = "1", readOnly = true) + protected Integer clientHoldOffTime = 1; + @ApiModelProperty(position = 8, value = "Server Public Key for 'Security' mode (DTLS): RPK or X509. Format: base64 encoded", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" + "+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", readOnly = true) - String serverPublicKey; - @ApiModelProperty(position = 9, value = "Bootstrap Server Account Timeout", example = "0", readOnly = true) + protected String serverPublicKey; + @ApiModelProperty(position = 9, value = "Bootstrap Server Account Timeout (If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.)", example = "0", readOnly = true) Integer bootstrapServerAccountTimeout = 0; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index f3e2f6599f..c7ebbb11bc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -28,6 +28,7 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoParser; import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.util.SecurityUtil; import org.thingsboard.server.common.data.StringUtils; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; @@ -58,16 +59,21 @@ import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTrans import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.RPKServerCredentials; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.ServerCredentials; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.X509ServerCredentials; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; @@ -695,6 +701,42 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } + private void validateLwm2mServersCredentialOfBootstrapForClient(ServerCredentials bootstrapServerConfig, String server) { + switch (bootstrapServerConfig.getSecurityMode()) { + case NO_SEC: + case PSK: + break; + case RPK: + RPKServerCredentials rpkServerCredentials = (RPKServerCredentials) bootstrapServerConfig; + if (StringUtils.isEmpty(rpkServerCredentials.getServerPublicKey())) { + throw new DeviceCredentialsValidationException(server + " RPK public key must be specified!"); + } + try { + String pubkRpkSever = EncryptionUtil.pubkTrimNewLines(rpkServerCredentials.getServerPublicKey()); + rpkServerCredentials.setServerPublicKey(pubkRpkSever); + SecurityUtil.publicKey.decode(rpkServerCredentials.getDecodedCServerPublicKey()); + } catch (Exception e) { + throw new DeviceCredentialsValidationException(server + " RPK public key must be in standard [RFC7250] and then encoded to Base64 format!"); + } + break; + case X509: + X509ServerCredentials x509ServerCredentials = (X509ServerCredentials) bootstrapServerConfig; +// X509BootstrapServerCredentials x509ServerCredentials = (X509BootstrapServerCredentials) bootstrapServerConfig; + if (StringUtils.isEmpty(x509ServerCredentials.getServerPublicKey())) { + throw new DeviceCredentialsValidationException(server + " X509 public key must be specified!"); + } + + try { + String certServer = EncryptionUtil.certTrimNewLines(x509ServerCredentials.getServerPublicKey()); + x509ServerCredentials.setServerPublicKey(certServer); + SecurityUtil.publicKey.decode(x509ServerCredentials.getDecodedCServerPublicKey()); + } catch (Exception e) { + throw new DeviceCredentialsValidationException(server + " X509 public key must be in standard [RFC7250] and then encoded to Base64 format!"); + } + break; + } + } + private PaginatedRemover tenantDeviceProfilesRemover = new PaginatedRemover() {