diff --git a/hawkbit-sdk/hawkbit-sdk-commons/pom.xml b/hawkbit-sdk/hawkbit-sdk-commons/pom.xml
index 1d0c2c503..f08a6cb69 100644
--- a/hawkbit-sdk/hawkbit-sdk-commons/pom.xml
+++ b/hawkbit-sdk/hawkbit-sdk-commons/pom.xml
@@ -40,5 +40,11 @@
org.springframework.boot
spring-boot-starter-hateoas
+
+
+ org.bouncycastle
+ bcpkix-jdk18on
+ ${bouncycastle.version}
+
diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Certificate.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Certificate.java
new file mode 100644
index 000000000..c3655c97d
--- /dev/null
+++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Certificate.java
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.sdk;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Objects;
+
+import lombok.Data;
+
+@Data
+public class Certificate {
+
+ private final KeyPair keyPair;
+ private final X509Certificate[] certificateChain;
+
+ // create holder for the certificate - key pair and the certificate chain
+ public Certificate(final KeyPair keyPair, final X509Certificate[] certificateChain) {
+ Objects.requireNonNull(keyPair, "keyPair must not be null");
+ Objects.requireNonNull(certificateChain, "certificateChain must not be null");
+ if (certificateChain.length == 0) {
+ throw new IllegalArgumentException("certificateChain must not be empty");
+ }
+ this.keyPair = keyPair;
+ this.certificateChain = certificateChain;
+ }
+
+ public KeyStore toKeyStore(final String pass) throws KeyStoreException {
+ try {
+ final KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ keyStore.load(null, null); // init
+ keyStore.setKeyEntry("alias", keyPair.getPrivate(), pass.toCharArray(), certificateChain);
+ return keyStore;
+ } catch (final NoSuchAlgorithmException | CertificateException | IOException e) {
+ throw new KeyStoreException(e);
+ }
+ }
+
+ public void writeToOS(final String pass, final OutputStream os) throws KeyStoreException {
+ try {
+ toKeyStore(pass).store(os, pass.toCharArray());
+ } catch (final NoSuchAlgorithmException | CertificateException | IOException e) {
+ throw new KeyStoreException(e);
+ }
+ }
+
+ public StringBuilder printPem(final StringBuilder sb) throws CertificateException {
+ for (final X509Certificate certificate : certificateChain) {
+ sb.append("-----BEGIN CERTIFICATE-----\n");
+ sb.append(toPem(certificate.getEncoded()));
+ sb.append("-----END CERTIFICATE-----\n");
+ }
+ sb.append('\n');
+
+ sb.append("-----BEGIN PRIVATE KEY-----\n");
+ sb.append(toPem(keyPair.getPrivate().getEncoded()));
+ sb.append("-----END PRIVATE KEY-----\n");
+
+ return sb;
+ }
+
+ private static String toPem(final byte[] ba) {
+ final String base64 = Base64.getEncoder().encodeToString(ba);
+ final StringBuilder formatted = new StringBuilder();
+ for (int off = 0, end; (end = Math.min(off + 64, base64.length())) >= 0; off = end) {
+ formatted.append(base64, off, end).append("\n");
+ }
+ return formatted.toString();
+ }
+}
\ No newline at end of file
diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Controller.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Controller.java
index d63ef2095..50f23a029 100644
--- a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Controller.java
+++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Controller.java
@@ -24,4 +24,6 @@ public class Controller {
// (target) security token
@Nullable
private String securityToken;
+ @Nullable
+ private Certificate certificate;
}
diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java
index 9c3b41dca..e883dbc31 100644
--- a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java
+++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java
@@ -21,15 +21,28 @@ import java.lang.reflect.Proxy;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Random;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Client;
import feign.Contract;
@@ -58,29 +71,22 @@ public class HawkbitClient {
private static final String AUTHORIZATION = "Authorization";
public static final BiFunction DEFAULT_REQUEST_INTERCEPTOR_FN =
- (tenant, controller) ->
- controller == null
- ? template ->
- template.header(
- AUTHORIZATION,
-
- "Basic " +
- Base64.getEncoder()
- .encodeToString(
- (Objects.requireNonNull(tenant.getUsername(), "User is null!") +
- ":" +
- Objects.requireNonNull(tenant.getPassword(),
- "Password is not available!"))
- .getBytes(StandardCharsets.ISO_8859_1)))
- : template -> {
- if (ObjectUtils.isEmpty(tenant.getGatewayToken())) {
- if (!ObjectUtils.isEmpty(controller.getSecurityToken())) {
- template.header(AUTHORIZATION, "TargetToken " + controller.getSecurityToken());
- } // else do not send authentication
- } else {
- template.header(AUTHORIZATION, "GatewayToken " + tenant.getGatewayToken());
- }
- };
+ (tenant, controller) -> controller == null
+ ? template ->
+ template.header(
+ AUTHORIZATION,
+ "Basic " + Base64.getEncoder().encodeToString(
+ (Objects.requireNonNull(tenant.getUsername(), "User is null!") +
+ ":" +
+ Objects.requireNonNull(tenant.getPassword(),"Password is not available!"))
+ .getBytes(StandardCharsets.ISO_8859_1)))
+ : template -> {
+ if (!ObjectUtils.isEmpty(tenant.getGatewayToken())) {
+ template.header(AUTHORIZATION, "GatewayToken " + tenant.getGatewayToken());
+ } else if (!ObjectUtils.isEmpty(controller.getSecurityToken())) {
+ template.header(AUTHORIZATION, "TargetToken " + controller.getSecurityToken());
+ } // else do not send authentication, no auth or certificate based
+ };
private static final ErrorDecoder DEFAULT_ERROR_DECODER_0 = new ErrorDecoder.Default();
public static final ErrorDecoder DEFAULT_ERROR_DECODER = (methodKey, response) -> {
final Exception e = DEFAULT_ERROR_DECODER_0.decode(methodKey, response);
@@ -144,6 +150,19 @@ public class HawkbitClient {
}
private T service0(final Class serviceType, final Tenant tenant, final Controller controller) {
+ final Client client;
+ if (controller != null && controller.getCertificate() != null && hawkBitServer.getDdiUrl().startsWith("https://")) {
+ // mTLS could be requested
+ try {
+ client = mTlsClient(controller.getCertificate(), tenant);
+ } catch (final RuntimeException | Error e) {
+ throw e;
+ } catch (final Exception e) {
+ throw new IllegalStateException("Failed to create mTLS client", e);
+ }
+ } else {
+ client = this.client;
+ }
return Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
@@ -312,4 +331,33 @@ public class HawkbitClient {
}
return null;
}
+
+ private static final String KEYSTORE_PASSWORD;
+ static {
+ final Random random = new SecureRandom();
+ final byte[] bytes = new byte[16];
+ random.nextBytes(bytes);
+ KEYSTORE_PASSWORD = Base64.getEncoder().encodeToString(bytes);
+ }
+ private static Client mTlsClient(final Certificate certificate, final Tenant tenant)
+ throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, CertificateException, IOException,
+ KeyManagementException {
+ final KeyStore clientKeyStore = certificate.toKeyStore(KEYSTORE_PASSWORD);
+ final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ keyManagerFactory.init(clientKeyStore, KEYSTORE_PASSWORD.toCharArray());
+ // Truststore
+ final TrustManagerFactory trustManagerFactory;
+ if (tenant.getDdiCertificate() == null) {
+ trustManagerFactory = null;
+ } else {
+ final KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(null, null);
+ trustStore.setEntry("alias", new KeyStore.TrustedCertificateEntry(tenant.getDdiCertificate()), null);
+ trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(trustStore);
+ }
+ final SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory == null ? null : trustManagerFactory.getTrustManagers(), null);
+ return new Client.Default(sslContext.getSocketFactory(), null);
+ }
}
\ No newline at end of file
diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Tenant.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Tenant.java
index 6fd3542d1..550480a65 100644
--- a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Tenant.java
+++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Tenant.java
@@ -9,8 +9,11 @@
*/
package org.eclipse.hawkbit.sdk;
+import java.security.cert.Certificate;
+
import lombok.Data;
import lombok.ToString;
+import org.eclipse.hawkbit.sdk.ca.CA;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
@@ -33,6 +36,16 @@ public class Tenant {
// gateway token
@Nullable
private String gatewayToken;
+ // gateway token
+ @Nullable
+ private String[] certificateFingerprints;
+
+ // the tenant DDI server certificate - it shall be trusted by controllers connecting via HTTPS
+ @Nullable
+ private Certificate ddiCertificate;
+ // Certificate Authority for the tenant that is used to sign the target certificates. It shall be trusted by the DDI server
+ @Nullable
+ private CA ddiCA;
// amqp settings (if DMF is used)
@Nullable
diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/ca/CA.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/ca/CA.java
new file mode 100644
index 000000000..57fadcc0a
--- /dev/null
+++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/ca/CA.java
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.sdk.ca;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Objects;
+
+import javax.security.auth.x500.X500Principal;
+
+import lombok.Data;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.eclipse.hawkbit.sdk.Certificate;
+
+@Data
+public class CA {
+
+ public static final String DEFAULT_CA_DN = "CN=CA, O=hawkBit, L=Sofia, C=BG";
+ public static final long DEFAULT_NOT_BEFORE_DAYS_OFFSET = 1;
+ public static final long DEFAULT_NOT_AFTER_DAYS_OFFSET = 30;
+
+ private static final String SHA_256_WITH_RSA_ENCRYPTION = "SHA256WithRSAEncryption";
+
+ private final Certificate certificate;
+ private long nextSerial = System.currentTimeMillis();
+
+ // creates a self-signed CA with defaults
+ public CA() throws CertificateException {
+ this(null, null, null);
+ }
+
+ // creates a self-signed CA
+ public CA(final String caDN, final Date notBefore, final Date notAfter) throws CertificateException {
+ this(selfSign(caDN, notBefore, notAfter));
+ }
+
+ // create a CA with a key and certificate chain
+ public CA(final Certificate certificate) {
+ this(certificate, 0);
+ }
+
+ // create a CA with a key and certificate chain
+ public CA(final Certificate certificate, final long nextSerial) {
+ this.certificate = certificate;
+ this.nextSerial = nextSerial;
+ }
+
+ // generate key and issue a certificate with defaults
+ public Certificate issue(final String subject) throws CertificateException {
+ return issue(subject, null, null);
+ }
+
+ // generate key and issue a certificate
+ public Certificate issue(final String subject, final Date notBefore, final Date notAfter) throws CertificateException {
+ Objects.requireNonNull(subject);
+ try {
+ final KeyPair keyPair = genKey();
+ final X509Certificate[] certificateChain = certificate.getCertificateChain();
+ final ContentSigner signer = new JcaContentSignerBuilder(SHA_256_WITH_RSA_ENCRYPTION).build(certificate.getKeyPair().getPrivate());
+ final X509v3CertificateBuilder caCertBuilder = new JcaX509v3CertificateBuilder(
+ certificateChain[0].getSubjectX500Principal(),
+ BigInteger.valueOf(nextSerial++), notBefore(notBefore), notAfter(notAfter), new X500Principal(subject),
+ keyPair.getPublic());
+ final X509Certificate[] subjectCertificateChain = new X509Certificate[certificateChain.length + 1];
+ certificateChain[0] = new JcaX509CertificateConverter().getCertificate(caCertBuilder.build(signer));
+ System.arraycopy(certificateChain, 0, subjectCertificateChain, 1, certificateChain.length);
+ return new Certificate(keyPair, subjectCertificateChain);
+ } catch (final NoSuchAlgorithmException | OperatorCreationException e) {
+ throw new CertificateException(e);
+ }
+ }
+
+ public String getFingerprint() {
+ try {
+ final X509Certificate[] certificateChain = certificate.getCertificateChain();
+ return toHex(MessageDigest.getInstance("SHA-256").digest(certificateChain[certificateChain.length - 1].getEncoded()));
+ } catch (final NoSuchAlgorithmException | CertificateEncodingException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private static String toHex(final byte[] bytes) {
+ final StringBuilder sb = new StringBuilder();
+ for (final byte b : bytes) {
+ sb.append(String.format("%02x", b)).append(':');
+ }
+ sb.deleteCharAt(sb.length() - 1);
+ return sb.toString();
+ }
+
+ private static Certificate selfSign(final String caDN, final Date notBefore, final Date notAfter) throws CertificateException {
+ try {
+ final KeyPair keyPair = genKey();
+ final X500Principal caPrincipal = new X500Principal(caDN == null ? DEFAULT_CA_DN : caDN);
+ final ContentSigner selfSigner = new JcaContentSignerBuilder(SHA_256_WITH_RSA_ENCRYPTION).build(keyPair.getPrivate());
+ final X509v3CertificateBuilder caCertBuilder = new JcaX509v3CertificateBuilder(
+ caPrincipal, BigInteger.valueOf(0L), notBefore(notBefore), notAfter(notAfter), caPrincipal, keyPair.getPublic());
+ return new Certificate(keyPair, new X509Certificate[] { new JcaX509CertificateConverter().getCertificate(caCertBuilder.build(selfSigner)) });
+ } catch (final NoSuchAlgorithmException | OperatorCreationException e) {
+ throw new CertificateException(e);
+ }
+ }
+
+ private static KeyPair genKey() throws NoSuchAlgorithmException {
+ final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(2048);
+ return keyPairGenerator.generateKeyPair();
+ }
+
+ private static Date notBefore(final Date notBefore) {
+ return notBefore == null ? new Date(System.currentTimeMillis() - DEFAULT_NOT_BEFORE_DAYS_OFFSET * 24 * 3600_000L) : notBefore;
+ }
+
+ private static Date notAfter(final Date notAfter) {
+ return notAfter == null ? new Date(System.currentTimeMillis() + DEFAULT_NOT_AFTER_DAYS_OFFSET * 24 * 3600_000L) : notAfter;
+ }
+}
\ No newline at end of file
diff --git a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java
index 62bddd7b7..65f3d8b14 100644
--- a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java
+++ b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java
@@ -24,7 +24,7 @@ import org.eclipse.hawkbit.sdk.Tenant;
import org.eclipse.hawkbit.sdk.device.DdiController;
import org.eclipse.hawkbit.sdk.device.DdiTenant;
import org.eclipse.hawkbit.sdk.device.UpdateHandler;
-import org.eclipse.hawkbit.sdk.mgmt.MgmtApi;
+import org.eclipse.hawkbit.sdk.mgmt.AuthenticationSetupHelper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@@ -57,8 +57,8 @@ public class DeviceApp {
}
@Bean
- MgmtApi mgmtApi(final Tenant tenant, final HawkbitClient hawkbitClient) {
- return new MgmtApi(tenant, hawkbitClient);
+ AuthenticationSetupHelper mgmtApi(final Tenant tenant, final HawkbitClient hawkbitClient) {
+ return new AuthenticationSetupHelper(tenant, hawkbitClient);
}
@ShellComponent
@@ -67,10 +67,10 @@ public class DeviceApp {
private final DdiTenant ddiTenant;
private final DdiController device;
- private final MgmtApi mgmtApi;
+ private final AuthenticationSetupHelper mgmtApi;
@SuppressWarnings("java:S3358")
- Shell(final DdiTenant ddiTenant, final MgmtApi mgmtApi, final Optional updateHandler) {
+ Shell(final DdiTenant ddiTenant, final AuthenticationSetupHelper mgmtApi, final Optional updateHandler) {
this.ddiTenant = ddiTenant;
this.mgmtApi = mgmtApi;
String controllerId = System.getProperty("demo.controller.id");
@@ -79,7 +79,7 @@ public class DeviceApp {
this.device = this.ddiTenant.createController(Controller.builder()
.controllerId(controllerId)
.securityToken(ObjectUtils.isEmpty(securityToken) ?
- (ObjectUtils.isEmpty(ddiTenant.getTenant().getGatewayToken()) ? MgmtApi.randomToken() : securityToken) :
+ (ObjectUtils.isEmpty(ddiTenant.getTenant().getGatewayToken()) ? AuthenticationSetupHelper.randomToken() : securityToken) :
securityToken)
.build(),
updateHandler.orElse(null)).setOverridePollMillis(10_000);
diff --git a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java
index d1a958595..b4d7567b8 100644
--- a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java
+++ b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java
@@ -24,7 +24,7 @@ import org.eclipse.hawkbit.sdk.Tenant;
import org.eclipse.hawkbit.sdk.device.DdiController;
import org.eclipse.hawkbit.sdk.device.DdiTenant;
import org.eclipse.hawkbit.sdk.device.UpdateHandler;
-import org.eclipse.hawkbit.sdk.mgmt.MgmtApi;
+import org.eclipse.hawkbit.sdk.mgmt.AuthenticationSetupHelper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@@ -57,20 +57,20 @@ public class MultiDeviceApp {
}
@Bean
- MgmtApi mgmtApi(final Tenant defaultTenant, final HawkbitClient hawkbitClient) {
- return new MgmtApi(defaultTenant, hawkbitClient);
+ AuthenticationSetupHelper mgmtApi(final Tenant defaultTenant, final HawkbitClient hawkbitClient) {
+ return new AuthenticationSetupHelper(defaultTenant, hawkbitClient);
}
@ShellComponent
public static class Shell {
private final DdiTenant ddiTenant;
- private final MgmtApi mgmtApi;
+ private final AuthenticationSetupHelper mgmtApi;
private final UpdateHandler updateHandler;
private boolean setup;
- Shell(final DdiTenant ddiTenant, final MgmtApi mgmtApi, final Optional updateHandler) {
+ Shell(final DdiTenant ddiTenant, final AuthenticationSetupHelper mgmtApi, final Optional updateHandler) {
this.ddiTenant = ddiTenant;
this.mgmtApi = mgmtApi;
this.updateHandler = updateHandler.orElse(null);
diff --git a/hawkbit-sdk/hawkbit-sdk-device/pom.xml b/hawkbit-sdk/hawkbit-sdk-device/pom.xml
index 8362ac3a5..5feceb1bb 100644
--- a/hawkbit-sdk/hawkbit-sdk-device/pom.xml
+++ b/hawkbit-sdk/hawkbit-sdk-device/pom.xml
@@ -36,6 +36,5 @@
hawkbit-ddi-api
${project.version}
-
\ No newline at end of file
diff --git a/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiController.java b/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiController.java
index 606f3fcc8..ea52dc17a 100644
--- a/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiController.java
+++ b/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiController.java
@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.ddi.json.model.DdiDeployment;
import org.eclipse.hawkbit.ddi.json.model.DdiDeploymentBase;
import org.eclipse.hawkbit.ddi.json.model.DdiUpdateMode;
import org.eclipse.hawkbit.ddi.rest.api.DdiRootControllerRestApi;
+import org.eclipse.hawkbit.sdk.Certificate;
import org.eclipse.hawkbit.sdk.Controller;
import org.eclipse.hawkbit.sdk.HawkbitClient;
import org.eclipse.hawkbit.sdk.Tenant;
@@ -64,6 +65,8 @@ public class DdiController {
private final boolean downloadAuthenticationEnabled;
private final String gatewayToken;
private final String targetSecurityToken;
+ private final Certificate certificate;
+
@Setter
@Accessors(chain = true)
private long overridePollMillis = -1; // -1 means disabled
@@ -90,6 +93,7 @@ public class DdiController {
downloadAuthenticationEnabled = tenant.isDownloadAuthenticationEnabled();
this.controllerId = controller.getControllerId();
this.targetSecurityToken = controller.getSecurityToken();
+ this.certificate = controller.getCertificate();
this.updateHandler = updateHandler == null ? UpdateHandler.SKIP : updateHandler;
ddiApi = hawkbitClient.ddiService(DdiRootControllerRestApi.class, tenant, controller);
}
diff --git a/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiTenant.java b/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiTenant.java
index 28387b4fe..bbb89ab1e 100644
--- a/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiTenant.java
+++ b/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiTenant.java
@@ -50,5 +50,4 @@ public class DdiTenant {
public Optional getController(final String controllerId) {
return Optional.ofNullable(controllers.get(controllerId));
}
-
}
\ No newline at end of file
diff --git a/hawkbit-sdk/hawkbit-sdk-mgmt/src/main/java/org/eclipse/hawkbit/sdk/mgmt/MgmtApi.java b/hawkbit-sdk/hawkbit-sdk-mgmt/src/main/java/org/eclipse/hawkbit/sdk/mgmt/AuthenticationSetupHelper.java
similarity index 62%
rename from hawkbit-sdk/hawkbit-sdk-mgmt/src/main/java/org/eclipse/hawkbit/sdk/mgmt/MgmtApi.java
rename to hawkbit-sdk/hawkbit-sdk-mgmt/src/main/java/org/eclipse/hawkbit/sdk/mgmt/AuthenticationSetupHelper.java
index 4afaf7f6c..8be4252b0 100644
--- a/hawkbit-sdk/hawkbit-sdk-mgmt/src/main/java/org/eclipse/hawkbit/sdk/mgmt/MgmtApi.java
+++ b/hawkbit-sdk/hawkbit-sdk-mgmt/src/main/java/org/eclipse/hawkbit/sdk/mgmt/AuthenticationSetupHelper.java
@@ -26,19 +26,24 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTenantManagementRestApi;
import org.eclipse.hawkbit.sdk.HawkbitClient;
import org.eclipse.hawkbit.sdk.Tenant;
+import org.eclipse.hawkbit.sdk.ca.CA;
import org.springframework.util.ObjectUtils;
/**
- * Management Api Interface
+ * Helper for authentication setup
*/
@Slf4j
@AllArgsConstructor
-public class MgmtApi {
+public class AuthenticationSetupHelper {
private static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY = "authentication.gatewaytoken.key";
private static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED = "authentication.gatewaytoken.enabled";
private static final String AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED = "authentication.targettoken.enabled";
+ private static final String AUTHENTICATION_MODE_HEADER_ENABLED = "authentication.header.enabled";
+ private static final String AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME = "authentication.header.authority";
+
private static final Random RND = new SecureRandom();
+
@NonNull
private final Tenant tenant;
@NonNull
@@ -50,42 +55,47 @@ public class MgmtApi {
return Base64.getEncoder().encodeToString(rnd);
}
- // if gateway toke is configured then the gateway auth is enabled key is set
+ // if gateway token is configured then the gateway auth is enabled key is set
// so all devices use gateway token authentication
- // otherwise target token authentication is enabled. Then all devices shall be registerd
+ // otherwise target token authentication is enabled. Then all devices shall be registered
// and the target token shall be set to the one from the DDI controller instance
public void setupTargetAuthentication() {
- final MgmtTenantManagementRestApi mgmtTenantManagementRestApi =
- hawkbitClient.mgmtService(MgmtTenantManagementRestApi.class, tenant);
+ final MgmtTenantManagementRestApi mgmtTenantManagementRestApi = hawkbitClient.mgmtService(MgmtTenantManagementRestApi.class, tenant);
final String gatewayToken = tenant.getGatewayToken();
if (ObjectUtils.isEmpty(gatewayToken)) {
if (!(Boolean.TRUE.equals(Objects.requireNonNull(mgmtTenantManagementRestApi
.getTenantConfigurationValue(AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED)
.getBody()).getValue()))) {
- mgmtTenantManagementRestApi.updateTenantConfiguration(
- Map.of(AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, true)
- );
+ mgmtTenantManagementRestApi.updateTenantConfiguration(Map.of(AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, true));
}
} else {
if (!(Boolean.TRUE.equals(Objects.requireNonNull(mgmtTenantManagementRestApi
.getTenantConfigurationValue(AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED)
.getBody()).getValue()))) {
- mgmtTenantManagementRestApi.updateTenantConfiguration(
- Map.of(AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, true)
- );
+ mgmtTenantManagementRestApi.updateTenantConfiguration(Map.of(AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, true));
}
if (!gatewayToken.equals(
Objects.requireNonNull(mgmtTenantManagementRestApi
.getTenantConfigurationValue(AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY)
.getBody()).getValue())) {
- mgmtTenantManagementRestApi.updateTenantConfiguration(
- Map.of(AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, gatewayToken)
- );
+ mgmtTenantManagementRestApi.updateTenantConfiguration(Map.of(AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, gatewayToken));
}
}
}
- // returns target token
+ // set gateway token authentication (generate and sets gateway token to tenant, if not set up)
+ // return the gateway token
+ public String setupGatewayToken() {
+ String gatewayToken = tenant.getGatewayToken();
+ if (ObjectUtils.isEmpty(gatewayToken)) {
+ gatewayToken = randomToken();
+ tenant.setGatewayToken(gatewayToken);
+ }
+ setupTargetAuthentication();
+ return gatewayToken;
+ }
+
+ // sets up a target token and returns it
public String setupTargetToken(final String controllerId, String securityTargetToken) {
if (ObjectUtils.isEmpty(tenant.getGatewayToken())) {
final MgmtTargetRestApi mgmtTargetRestApi = hawkbitClient.mgmtService(MgmtTargetRestApi.class, tenant);
@@ -96,15 +106,13 @@ public class MgmtApi {
if (ObjectUtils.isEmpty(target.getSecurityToken())) {
// generate random to set to tha existing target without configured security token
securityTargetToken = randomToken();
- mgmtTargetRestApi.updateTarget(controllerId,
- new MgmtTargetRequestBody().setSecurityToken(securityTargetToken));
+ mgmtTargetRestApi.updateTarget(controllerId, new MgmtTargetRequestBody().setSecurityToken(securityTargetToken));
} else {
securityTargetToken = target.getSecurityToken();
}
} else if (!securityTargetToken.equals(target.getSecurityToken())) {
// update target's with the security token (since it doesn't match)
- mgmtTargetRestApi.updateTarget(controllerId,
- new MgmtTargetRequestBody().setSecurityToken(securityTargetToken));
+ mgmtTargetRestApi.updateTarget(controllerId, new MgmtTargetRequestBody().setSecurityToken(securityTargetToken));
}
} catch (final FeignException.NotFound e) {
if (ObjectUtils.isEmpty(securityTargetToken)) {
@@ -112,16 +120,35 @@ public class MgmtApi {
}
// create target with the security token
mgmtTargetRestApi.createTargets(List.of(
- new MgmtTargetRequestBody()
- .setControllerId(controllerId)
- .setSecurityToken(securityTargetToken)));
+ new MgmtTargetRequestBody().setControllerId(controllerId).setSecurityToken(securityTargetToken)));
}
}
return securityTargetToken;
}
- public void deleteController(final String controllerId) {
- hawkbitClient.mgmtService(MgmtTargetRestApi.class, tenant).deleteTarget(controllerId);
+ // sets up a target token and returns it
+ public void setupCertificateFingerprint() {
+ final MgmtTenantManagementRestApi mgmtTenantManagementRestApi = hawkbitClient.mgmtService(MgmtTenantManagementRestApi.class, tenant);
+ final CA ddiCA = tenant.getDdiCA();
+ final Object enabled = Objects.requireNonNull(mgmtTenantManagementRestApi
+ .getTenantConfigurationValue(AUTHENTICATION_MODE_HEADER_ENABLED)
+ .getBody()).getValue();
+ if (ddiCA == null) {
+ if (Boolean.TRUE.equals(enabled)) {
+ mgmtTenantManagementRestApi.updateTenantConfiguration(Map.of(AUTHENTICATION_MODE_HEADER_ENABLED, false));
+ }
+ } else {
+ if (!Boolean.TRUE.equals(enabled)) {
+ mgmtTenantManagementRestApi.updateTenantConfiguration(Map.of(AUTHENTICATION_MODE_HEADER_ENABLED, true));
+ }
+ final String fingerprint = ddiCA.getFingerprint();
+ if (!fingerprint.equals(
+ Objects.requireNonNull(mgmtTenantManagementRestApi
+ .getTenantConfigurationValue(AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME)
+ .getBody()).getValue())) {
+ mgmtTenantManagementRestApi.updateTenantConfiguration(Map.of(AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, fingerprint));
+ }
+ }
}
}
\ No newline at end of file
diff --git a/hawkbit-sdk/pom.xml b/hawkbit-sdk/pom.xml
index f9444a84f..ee2fbdeb4 100644
--- a/hawkbit-sdk/pom.xml
+++ b/hawkbit-sdk/pom.xml
@@ -26,6 +26,7 @@
4.2.0
13.5
+ 1.78.1