2 changed files with 71 additions and 196 deletions
@ -1,196 +0,0 @@ |
|||||
/** |
|
||||
* 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.service.lwm2m; |
|
||||
|
|
||||
|
|
||||
import com.fasterxml.jackson.core.JsonProcessingException; |
|
||||
import com.fasterxml.jackson.databind.JsonNode; |
|
||||
import lombok.RequiredArgsConstructor; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.eclipse.leshan.core.SecurityMode; |
|
||||
import org.eclipse.leshan.core.util.Hex; |
|
||||
import org.eclipse.leshan.core.util.SecurityUtil; |
|
||||
import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; |
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
|
||||
import org.springframework.stereotype.Service; |
|
||||
import org.thingsboard.server.common.data.DeviceProfile; |
|
||||
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; |
|
||||
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; |
|
||||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|
||||
import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig; |
|
||||
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; |
|
||||
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; |
|
||||
|
|
||||
import java.io.IOException; |
|
||||
import java.security.GeneralSecurityException; |
|
||||
import java.security.KeyStore; |
|
||||
import java.security.PrivateKey; |
|
||||
import java.security.PublicKey; |
|
||||
import java.security.cert.Certificate; |
|
||||
import java.security.cert.X509Certificate; |
|
||||
import java.util.Map; |
|
||||
|
|
||||
@Slf4j |
|
||||
@Service |
|
||||
@RequiredArgsConstructor |
|
||||
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") |
|
||||
public class LwM2MServerSecurityInfoRepository { |
|
||||
|
|
||||
private final LwM2MTransportServerConfig serverConfig; |
|
||||
private final LwM2MTransportBootstrapConfig bootstrapConfig; |
|
||||
|
|
||||
public ServerSecurityConfig getServerSecurityInfo(boolean bootstrapServer) { |
|
||||
ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig); |
|
||||
result.setBootstrapServerIs(bootstrapServer); |
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig) { |
|
||||
ServerSecurityConfig bsServ = new ServerSecurityConfig(); |
|
||||
bsServ.setServerId(serverConfig.getId()); |
|
||||
bsServ.setHost(serverConfig.getHost()); |
|
||||
bsServ.setPort(serverConfig.getPort()); |
|
||||
bsServ.setSecurityHost(serverConfig.getSecureHost()); |
|
||||
bsServ.setSecurityPort(serverConfig.getSecurePort()); |
|
||||
bsServ.setServerPublicKey(getPublicKey(serverConfig)); |
|
||||
return bsServ; |
|
||||
} |
|
||||
|
|
||||
private String 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()); |
|
||||
} |
|
||||
} catch (Exception e) { |
|
||||
log.trace("Failed to fetch public key from key store!", e); |
|
||||
|
|
||||
} |
|
||||
return ""; |
|
||||
} |
|
||||
|
|
||||
public void verifySecurityKeyDevice(DeviceCredentials deviceCredentials) throws InvalidConfigurationException, JsonProcessingException { |
|
||||
JsonNode nodeCredentialsValue = deviceCredentials.getNodeCredentialsValue(); |
|
||||
checkClientKey (nodeCredentialsValue.get("client")); |
|
||||
checkServerKey (nodeCredentialsValue.get("bootstrap").get("bootstrapServer"), "Client`s by bootstrapServer"); |
|
||||
checkServerKey (nodeCredentialsValue.get("bootstrap").get("lwm2mServer"), "Client`s by lwm2mServer"); |
|
||||
} |
|
||||
|
|
||||
private void checkClientKey (JsonNode node) throws InvalidConfigurationException { |
|
||||
String modeName = node.get("securityConfigClientMode").asText(); |
|
||||
// checks security config
|
|
||||
|
|
||||
if (SecurityMode.RPK.name().equals(modeName)) { |
|
||||
String value = node.get("key").textValue(); |
|
||||
assertIf(decodeRfc7250PublicKey(Hex.decodeHex(((String) value).toCharArray())) == null, |
|
||||
"raw-public-key mode, Client`s public key or id must be RFC7250 encoded public key"); |
|
||||
} else if (SecurityMode.X509.name().equals(modeName)) { |
|
||||
String value = node.get("cert").textValue(); |
|
||||
if (value != null && !value.isEmpty()) { |
|
||||
assertIf(decodeCertificate(Hex.decodeHex(((String) value).toCharArray())) == null, |
|
||||
"x509 mode, Client`s public key must be DER encoded X.509 certificate"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
private void checkServerKey (JsonNode node, String serverType) throws InvalidConfigurationException { |
|
||||
String modeName = node.get("securityMode").asText(); |
|
||||
// checks security config
|
|
||||
if (SecurityMode.RPK.name().equals(modeName)) { |
|
||||
checkRPKServer(node, serverType); |
|
||||
} else if (SecurityMode.X509.name().equals(modeName)) { |
|
||||
checkX509Server(node, serverType); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected void checkRPKServer(JsonNode node, String serverType) throws InvalidConfigurationException { |
|
||||
String value = node.get("clientSecretKey").textValue(); |
|
||||
assertIf(decodeRfc5958PrivateKey(Hex.decodeHex(value.toCharArray())) == null, |
|
||||
"raw-public-key mode, " + serverType + " secret key must be RFC5958 encoded private key"); |
|
||||
value = node.get("clientPublicKeyOrId").textValue(); |
|
||||
assertIf(decodeRfc7250PublicKey(Hex.decodeHex(value.toCharArray())) == null, |
|
||||
"raw-public-key mode, " + serverType + " public key or id must be RFC7250 encoded public key"); |
|
||||
} |
|
||||
|
|
||||
protected void checkX509Server(JsonNode node, String serverType) throws InvalidConfigurationException { |
|
||||
String value = node.get("clientSecretKey").textValue(); |
|
||||
assertIf(decodeRfc5958PrivateKey(Hex.decodeHex(value.toCharArray())) == null, |
|
||||
"x509 mode " + serverType + " secret key must be RFC5958 encoded private key"); |
|
||||
value = node.get("clientPublicKeyOrId").textValue(); |
|
||||
assertIf(decodeCertificate(Hex.decodeHex(value.toCharArray())) == null, |
|
||||
"x509 mode " + serverType + " public key must be DER encoded X.509 certificate"); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public void verifySecurityKeyDeviceProfile(DeviceProfile deviceProfile) throws InvalidConfigurationException, JsonProcessingException { |
|
||||
Map serverBs = ((Lwm2mDeviceProfileTransportConfiguration)deviceProfile.getProfileData().getTransportConfiguration()).getBootstrap().getBootstrapServer(); |
|
||||
checkDeviceProfileServer (serverBs, "Servers: BootstrapServer`s"); |
|
||||
Map serverLwm2m = ((Lwm2mDeviceProfileTransportConfiguration)deviceProfile.getProfileData().getTransportConfiguration()).getBootstrap().getLwm2mServer(); |
|
||||
checkDeviceProfileServer (serverLwm2m, "Servers: Lwm2mServer`s"); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
protected void checkDeviceProfileServer (Map server, String serverType) throws InvalidConfigurationException{ |
|
||||
// checks security config
|
|
||||
String value = (String) server.get("serverPublicKey"); |
|
||||
if (SecurityMode.RPK.name().equals(server.get("securityMode"))) { |
|
||||
assertIf(decodeRfc7250PublicKey(Hex.decodeHex(value.toCharArray())) == null, |
|
||||
"raw-public-key mode, " + serverType + " public key or id must be RFC7250 encoded public key"); |
|
||||
} else if (SecurityMode.X509.name().equals(server.get("securityMode"))) { |
|
||||
assertIf(decodeCertificate(Hex.decodeHex(value.toCharArray())) == null, |
|
||||
"x509 mode, " + serverType + " public key must be DER encoded X.509 certificate"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected PrivateKey decodeRfc5958PrivateKey(byte[] encodedKey) throws InvalidConfigurationException { |
|
||||
try { |
|
||||
return SecurityUtil.privateKey.decode(encodedKey); |
|
||||
} catch (IOException | GeneralSecurityException e) { |
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected PublicKey decodeRfc7250PublicKey(byte[] encodedKey) throws InvalidConfigurationException { |
|
||||
try { |
|
||||
return SecurityUtil.publicKey.decode(encodedKey); |
|
||||
} catch (IOException | GeneralSecurityException e) { |
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected Certificate decodeCertificate(byte[] encodedCert) throws InvalidConfigurationException { |
|
||||
try { |
|
||||
return SecurityUtil.certificate.decode(encodedCert); |
|
||||
} catch (IOException | GeneralSecurityException e) { |
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected static void assertIf(boolean condition, String message) throws InvalidConfigurationException { |
|
||||
if (condition) { |
|
||||
throw new InvalidConfigurationException(message); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected static boolean isEmpty(byte[] array) { |
|
||||
return array == null || array.length == 0; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
} |
|
||||
|
|
||||
@ -0,0 +1,71 @@ |
|||||
|
/** |
||||
|
* 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.service.lwm2m; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.eclipse.leshan.core.util.Hex; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; |
||||
|
import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig; |
||||
|
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; |
||||
|
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; |
||||
|
import java.security.KeyStore; |
||||
|
import java.security.cert.X509Certificate; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Service |
||||
|
@RequiredArgsConstructor |
||||
|
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") |
||||
|
public class LwM2MServiceImpl implements LwM2MService { |
||||
|
|
||||
|
private final LwM2MTransportServerConfig serverConfig; |
||||
|
private final LwM2MTransportBootstrapConfig bootstrapConfig; |
||||
|
|
||||
|
@Override |
||||
|
public ServerSecurityConfig getServerSecurityInfo(boolean bootstrapServer) { |
||||
|
ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig); |
||||
|
result.setBootstrapServerIs(bootstrapServer); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig) { |
||||
|
ServerSecurityConfig bsServ = new ServerSecurityConfig(); |
||||
|
bsServ.setServerId(serverConfig.getId()); |
||||
|
bsServ.setHost(serverConfig.getHost()); |
||||
|
bsServ.setPort(serverConfig.getPort()); |
||||
|
bsServ.setSecurityHost(serverConfig.getSecureHost()); |
||||
|
bsServ.setSecurityPort(serverConfig.getSecurePort()); |
||||
|
bsServ.setServerPublicKey(getPublicKey(serverConfig)); |
||||
|
return bsServ; |
||||
|
} |
||||
|
|
||||
|
private String 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()); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.trace("Failed to fetch public key from key store!", e); |
||||
|
|
||||
|
} |
||||
|
return ""; |
||||
|
} |
||||
|
} |
||||
|
|
||||
Loading…
Reference in new issue