diff --git a/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql b/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql index a182bc8117..8daa1b1e1e 100644 --- a/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql +++ b/application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql @@ -14,6 +14,7 @@ -- limitations under the License. -- + CREATE OR REPLACE PROCEDURE update_profile_bootstrap() LANGUAGE plpgsql AS $$ @@ -25,9 +26,11 @@ BEGIN profile_data, '{transportConfiguration}', get_bootstrap( - profile_data::jsonb #> '{transportConfiguration}', - subquery.publickey_bs, - subquery.publickey_lw), + profile_data::jsonb #> '{transportConfiguration}', + subquery.publickey_bs, + subquery.publickey_lw, + profile_data::json #>> '{transportConfiguration, bootstrap, bootstrapServer, securityMode}', + profile_data::json #>> '{transportConfiguration, bootstrap, lwm2mServer, securityMode}'), true) FROM ( SELECT id, @@ -48,7 +51,8 @@ END; $$; CREATE OR REPLACE FUNCTION get_bootstrap(transport_configuration_in jsonb, publickey_bs text, - publickey_lw text) RETURNS jsonb AS + publickey_lw text, security_mode_bs text, + security_mode_lw text) RETURNS jsonb AS $$ DECLARE @@ -56,10 +60,19 @@ DECLARE bootstrap_in jsonb; BEGIN + + IF security_mode_lw IS NULL THEN + security_mode_lw := 'NO_SEC'; + END IF; + + IF security_mode_bs IS NULL THEN + security_mode_bs := 'NO_SEC'; + END IF; + bootstrap_in := transport_configuration_in::jsonb #> '{bootstrap}'; bootstrap_new := json_build_array( json_build_object('shortServerId', bootstrap_in::json #> '{bootstrapServer}' -> 'serverId', - 'securityMode', bootstrap_in::json #> '{bootstrapServer}' ->> 'securityMode', + 'securityMode', security_mode_bs, 'binding', bootstrap_in::json #> '{servers}' ->> 'binding', 'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime', 'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled', @@ -73,7 +86,7 @@ BEGIN bootstrap_in::json #> '{bootstrapServer}' -> 'bootstrapServerAccountTimeout' ), json_build_object('shortServerId', bootstrap_in::json #> '{lwm2mServer}' -> 'serverId', - 'securityMode', bootstrap_in::json #> '{lwm2mServer}' ->> 'securityMode', + 'securityMode', security_mode_lw, 'binding', bootstrap_in::json #> '{servers}' ->> 'binding', 'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime', 'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled', @@ -93,7 +106,7 @@ BEGIN bootstrap_new, true) || '{"bootstrapServerUpdateEnable": true}'; -END ; +END; $$ LANGUAGE plpgsql; CREATE OR REPLACE PROCEDURE update_device_credentials_to_base64_and_bootstrap() @@ -102,9 +115,9 @@ $$ BEGIN -UPDATE device_credentials -SET credentials_value = get_device_and_bootstrap(credentials_value::text) -WHERE credentials_type = 'LWM2M_CREDENTIALS'; + UPDATE device_credentials + SET credentials_value = get_device_and_bootstrap(credentials_value::text) + WHERE credentials_type = 'LWM2M_CREDENTIALS'; END; $$; @@ -112,7 +125,7 @@ CREATE OR REPLACE FUNCTION get_device_and_bootstrap(IN credentials_value text, O LANGUAGE plpgsql AS $$ DECLARE -client_secret_key text; + client_secret_key text; client_public_key_or_id text; client_key_value_object jsonb; client_bootstrap_server_value_object jsonb; @@ -130,7 +143,7 @@ BEGIN 'key', client_public_key_or_id); credentials_value_new := credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb; -END IF; + END IF; IF credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode' = 'X509' AND NULLIF((credentials_value::jsonb #> '{client}' ->> 'cert' ~ '^[0-9a-fA-F]+$')::text, 'false') = 'true' THEN client_public_key_or_id := @@ -141,8 +154,8 @@ END IF; 'cert', client_public_key_or_id); credentials_value_new := credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb; -END IF; - + END IF; + IF credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'RPK' OR credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'X509' THEN IF NULLIF((credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientSecretKey' ~ '^[0-9a-fA-F]+$')::text, @@ -165,9 +178,9 @@ END IF; client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb; credentials_value_new := jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb; -END IF; -END IF; - + END IF; + END IF; + IF credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'RPK' OR credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'X509' THEN IF NULLIF( @@ -193,8 +206,8 @@ END IF; client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb; credentials_value_new := jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb; -END IF; -END IF; + END IF; + END IF; END; $$; \ No newline at end of file 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 b5ec5cc654..7d0c623f96 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 @@ -63,6 +63,12 @@ public class LwM2MServiceImpl implements LwM2MService { } else { bsServ.setServerPublicKey(Base64.encodeBase64String(publicKeyBase64)); } + byte[] certificateBase64 = getCertificate(bsServerConfig); + if (certificateBase64 == null) { + bsServ.setServerCertificate(""); + } else { + bsServ.setServerCertificate(Base64.encodeBase64String(certificateBase64)); + } return bsServ; } @@ -77,5 +83,17 @@ public class LwM2MServiceImpl implements LwM2MService { } return null; } + + private byte[] getCertificate(LwM2MSecureServerConfig config) { + try { + SslCredentials sslCredentials = config.getSslCredentials(); + if (sslCredentials != null) { + return sslCredentials.getCertificateChain()[0].getEncoded(); + } + } catch (Exception e) { + log.trace("Failed to fetch certificate from key store!", e); + } + return null; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java index 12dd9c5a64..11bf049621 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java @@ -42,17 +42,23 @@ public class LwM2MServerSecurityConfig { @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) 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) + @ApiModelProperty(position = 9, value = "Server Public Key for 'Security' mode (DTLS): X509. Format: base64 encoded", example = "MMIICODCCAd6gAwIBAgIUI88U1zowOdrxDK/dOV+36gJxI2MwCgYIKoZIzj0EAwIwejELMAkGA1UEBhMCVUs\n" + + "xEjAQBgNVBAgTCUt5aXYgY2l0eTENMAsGA1UEBxMES3lpdjEUMBIGA1UEChMLVGhpbmdzYm9hcmQxFzAVBgNVBAsMDkRFVkVMT1BFUl9URVNUMRkwFwYDVQQDDBBpbnRlcm1lZGlhdGVfY2EwMB4XDTIyMDEwOTEzMDMwMFoXDTI3MDEwODEzMDMwMFowFDESMBAGA1UEAxM\n" + + "JbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUO3vBo/JTv0eooY7XHiKAIVDoWKFqtrU7C6q8AIKqpLcqhCdW+haFeBOH3PjY6EwaWkY04Bir4oanU0s7tz2uKOBpzCBpDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/\n" + + "BAIwADAdBgNVHQ4EFgQUEjc3Q4a0TxzP/3x3EV4fHxYUg0YwHwYDVR0jBBgwFoAUuSquGycMU6Q0SYNcbtSkSD3TfH0wLwYDVR0RBCgwJoIVbG9jYWxob3N0LmxvY2FsZG9tYWlugglsb2NhbGhvc3SCAiAtMAoGCCqGSM49BAMCA0gAMEUCIQD7dbZObyUaoDiNbX+9fUNp\n" + + "AWrD7N7XuJUwZ9FcN75R3gIgb2RNjDkHoyUyF1YajwkBk+7XmIXNClmizNJigj908mw=", readOnly = true) + protected String serverCertificate; + @ApiModelProperty(position = 10, 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; /** Config -> ObjectId = 1 'LwM2M Server' */ - @ApiModelProperty(position = 10, value = "Specify the lifetime of the registration in seconds.", example = "300", readOnly = true) + @ApiModelProperty(position = 11, value = "Specify the lifetime of the registration in seconds.", example = "300", readOnly = true) private Integer lifetime = 300; - @ApiModelProperty(position = 11, value = "The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation. " + + @ApiModelProperty(position = 12, value = "The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation. " + "If this Resource doesn’t exist, the default value is 0.", example = "1", readOnly = true) private Integer defaultMinPeriod = 1; /** ResourceID=6 'Notification Storing When Disabled or Offline' */ - @ApiModelProperty(position = 12, value = "If true, the LwM2M Client stores “Notify” operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored “Notify” operations to the Server. " + + @ApiModelProperty(position = 13, value = "If true, the LwM2M Client stores “Notify” operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored “Notify” operations to the Server. " + "If false, the LwM2M Client discards all the “Notify” operations or temporarily disables the Observe function while the LwM2M Server is disabled or the LwM2M Client is offline. " + "The default value is true.", example = "true", readOnly = true) private boolean notifIfDisabled = true; 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 18a8a1ecde..b685fc7aa8 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 @@ -765,15 +765,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D X509LwM2MBootstrapServerCredential x509ServerCredentials = (X509LwM2MBootstrapServerCredential) bootstrapServerConfig; server = x509ServerCredentials.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server"; if (StringUtils.isEmpty(x509ServerCredentials.getServerPublicKey())) { - throw new DeviceCredentialsValidationException(server + " X509 public key must be specified!"); + throw new DeviceCredentialsValidationException(server + " X509 certificate must be specified!"); } try { String certServer = EncryptionUtil.certTrimNewLines(x509ServerCredentials.getServerPublicKey()); x509ServerCredentials.setServerPublicKey(certServer); - SecurityUtil.publicKey.decode(x509ServerCredentials.getDecodedCServerPublicKey()); + SecurityUtil.certificate.decode(x509ServerCredentials.getDecodedCServerPublicKey()); } catch (Exception e) { - throw new DeviceCredentialsValidationException(server + " X509 public key must be in standard [RFC7250] and then encoded to Base64 format!"); + throw new DeviceCredentialsValidationException(server + " X509 certificate must be in DER-encoded X509v3 format and support only EC algorithm and then encoded to Base64 format!"); } break; } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts index fa6d31153c..de75ea416b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts @@ -112,6 +112,9 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc this.changeSecurityHostPortFields(serverSecurityConfig); } this.serverFormGroup.patchValue(serverSecurityConfig, {emitEvent: false}); + if (this.currentSecurityMode === Lwm2mSecurityType.X509) { + this.serverFormGroup.get('serverPublicKey').patchValue(serverSecurityConfig.serverCertificate, {emitEvent: false}); + } }); this.serverFormGroup.valueChanges.pipe( takeUntil(this.destroy$) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts index 56ae00c5a9..ce2824fb12 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts @@ -129,6 +129,7 @@ export interface ServerSecurityConfig { securityHost?: string; securityPort?: number; serverPublicKey?: string; + serverCertificate?: string; clientHoldOffTime?: number; shortServerId?: number; bootstrapServerAccountTimeout: number;