This is an automated email from the ASF dual-hosted git repository.

hanicz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new df175163e KNOX-3315: Adds BCFKS as an option cert export (#1221)
df175163e is described below

commit df175163e76403ec984ee9740429c9e4fd9222c8
Author: hanicz <[email protected]>
AuthorDate: Wed May 6 13:51:16 2026 +0200

    KNOX-3315: Adds BCFKS as an option cert export (#1221)
    
    * KNOX-3315: Adds BCFKS as an option cert export
    
    * KNOX-3315: Address review comments
---
 .../java/org/apache/knox/gateway/util/KnoxCLI.java |  7 +++--
 .../service/metadata/KnoxMetadataResource.java     | 15 ++++++++++
 .../knox/gateway/util/X509CertificateUtil.java     | 10 +++++++
 .../knox/gateway/util/X509CertificateUtilTest.java | 35 ++++++++++++++++++++++
 4 files changed, 65 insertions(+), 2 deletions(-)

diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/util/KnoxCLI.java 
b/gateway-server/src/main/java/org/apache/knox/gateway/util/KnoxCLI.java
index a64b743ff..141f2e816 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/util/KnoxCLI.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/util/KnoxCLI.java
@@ -756,12 +756,12 @@ public class KnoxCLI extends Configured implements Tool {
 
   public class CertExportCommand extends Command {
 
-    public static final String USAGE = "export-cert [--type 
PEM|JKS|JCEKS|PKCS12]";
+    public static final String USAGE = "export-cert [--type 
PEM|JKS|JCEKS|PKCS12|BCFKS]";
     public static final String DESC = "The export-cert command exports the 
public certificate\n" +
                                       "from the a gateway.jks keystore with 
the alias of gateway-identity.\n" +
                                       "It will be exported to 
`{GATEWAY_HOME}/data/security/keystores/` with a name of 
`gateway-client-trust.<type>`" +
                                       "Using the --type option you can specify 
which keystore type you need (default: PEM)\n" +
-                                      "NOTE: The password for the JKS, JCEKS 
and PKCS12 types is `changeit`.\n" +
+                                      "NOTE: The password for the JKS, JCEKS, 
PKCS12 and BCFKS types is `changeit`.\n" +
                                       "It can be changed using: `keytool 
-storepasswd -storetype <type> -keystore gateway-client-trust.<type>`";
 
     private GatewayConfig getGatewayConfig() {
@@ -808,6 +808,9 @@ public class KnoxCLI extends Configured implements Tool {
           } else if ("PKCS12".equalsIgnoreCase(type)) {
             X509CertificateUtil.writeCertificateToPkcs12(cert, new 
File(keyStoreDir + "gateway-client-trust.pkcs12"));
             out.println("Certificate gateway-identity has been successfully 
exported to: " + keyStoreDir + "gateway-client-trust.pkcs12");
+          } else if ("BCFKS".equalsIgnoreCase(type)) {
+            X509CertificateUtil.writeCertificateToBcfks(cert, new 
File(keyStoreDir + "gateway-client-trust.bcfks"));
+            out.println("Certificate gateway-identity has been successfully 
exported to: " + keyStoreDir + "gateway-client-trust.bcfks");
           } else {
             out.println("Invalid type for export file provided. Export has not 
been done. Please use: [PEM|JKS|JCEKS|PKCS12] default value is PEM.");
           }
diff --git 
a/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/KnoxMetadataResource.java
 
b/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/KnoxMetadataResource.java
index 75eb88baa..ba77c0e5d 100644
--- 
a/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/KnoxMetadataResource.java
+++ 
b/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/KnoxMetadataResource.java
@@ -88,6 +88,7 @@ public class KnoxMetadataResource {
   private Set<String> pinnedTopologies;
   private java.nio.file.Path pemFilePath;
   private java.nio.file.Path jksFilePath;
+  private java.nio.file.Path bcfksFilePath;
 
   @Context
   private HttpServletRequest request;
@@ -147,6 +148,9 @@ public class KnoxMetadataResource {
       } else if ("jks".equals(certType)) {
         generateCertificateJks(certificateChain, config);
         return generateSuccessFileDownloadResponse(jksFilePath);
+      } else if ("bcfks".equals(certType)) {
+        generateCertificateBcfks(certificateChain, config);
+        return generateSuccessFileDownloadResponse(bcfksFilePath);
       } else {
         return generateFailureFileDownloadResponse(Status.BAD_REQUEST, 
"Invalid certification type provided!");
       }
@@ -208,6 +212,17 @@ public class KnoxMetadataResource {
     }
   }
 
+  private void generateCertificateBcfks(Certificate[] certificateChain, 
GatewayConfig gatewayConfig) {
+    try {
+      if (bcfksFilePath == null || !bcfksFilePath.toFile().exists()) {
+        bcfksFilePath = Paths.get(gatewayConfig.getGatewaySecurityDir(), 
"gateway-client-trust.bcfks");
+        X509CertificateUtil.writeCertificatesToBcfks(certificateChain, 
bcfksFilePath.toFile(), null);
+      }
+    } catch (IOException | KeyStoreException | NoSuchAlgorithmException | 
CertificateException e) {
+      LOG.failedToGeneratePublicCert("BCFKS", e.getMessage(), e);
+    }
+  }
+
   private String getBaseGatewayUrl(GatewayConfig config) {
     return request.getRequestURL().substring(0, 
request.getRequestURL().length() - request.getRequestURI().length()) + "/" + 
config.getGatewayPath();
   }
diff --git 
a/gateway-util-common/src/main/java/org/apache/knox/gateway/util/X509CertificateUtil.java
 
b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/X509CertificateUtil.java
index 8d0574d20..3bf07d8ca 100644
--- 
a/gateway-util-common/src/main/java/org/apache/knox/gateway/util/X509CertificateUtil.java
+++ 
b/gateway-util-common/src/main/java/org/apache/knox/gateway/util/X509CertificateUtil.java
@@ -215,6 +215,16 @@ public class X509CertificateUtil {
     writeCertificateToKeyStore(cert, file, "pkcs12");
   }
 
+  public static void writeCertificateToBcfks(Certificate cert, final File file)
+          throws IOException, KeyStoreException, NoSuchAlgorithmException, 
CertificateException {
+    writeCertificateToKeyStore(cert, file, "bcfks");
+  }
+
+  public static void writeCertificatesToBcfks(Certificate[] certs, final File 
file, String keystorePassword)
+          throws IOException, KeyStoreException, NoSuchAlgorithmException, 
CertificateException {
+    writeCertificatesToKeyStore(certs, file, "bcfks", keystorePassword);
+  }
+
   /**
    * Tests the X509 certificate to see if it was self-signed.
    * <p>
diff --git 
a/gateway-util-common/src/test/java/org/apache/knox/gateway/util/X509CertificateUtilTest.java
 
b/gateway-util-common/src/test/java/org/apache/knox/gateway/util/X509CertificateUtilTest.java
index 06365f575..506bf3412 100644
--- 
a/gateway-util-common/src/test/java/org/apache/knox/gateway/util/X509CertificateUtilTest.java
+++ 
b/gateway-util-common/src/test/java/org/apache/knox/gateway/util/X509CertificateUtilTest.java
@@ -17,19 +17,25 @@
  */
 package org.apache.knox.gateway.util;
 
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLServerSocket;
 import javax.net.ssl.SSLServerSocketFactory;
 import javax.net.ssl.SSLSocket;
+import java.io.File;
 import java.io.IOException;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.KeyStore;
 import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
@@ -39,6 +45,35 @@ import java.util.concurrent.TimeUnit;
 
 public class X509CertificateUtilTest {
 
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Test
+  public void testWriteCertificatesToKeyStoreBCFKS() throws Exception {
+    Security.addProvider(new BouncyCastleProvider());
+    try {
+      // 1. Generate a self-signed certificate
+      KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+      keyPairGenerator.initialize(2048);
+      KeyPair keyPair = keyPairGenerator.generateKeyPair();
+      X509Certificate cert = 
X509CertificateUtil.generateCertificate("CN=localhost", keyPair, 30, 
"SHA256withRSA");
+
+      // 2. Write it to a BCFKS keystore
+      File bcfksFile = temporaryFolder.newFile("test.bcfks");
+      X509CertificateUtil.writeCertificatesToBcfks(new Certificate[]{cert}, 
bcfksFile, "password");
+
+      // 3. Verify the keystore
+      KeyStore keyStore = KeyStore.getInstance("BCFKS");
+      keyStore.load(java.nio.file.Files.newInputStream(bcfksFile.toPath()), 
"password".toCharArray());
+      Assert.assertTrue(keyStore.containsAlias("gateway-identity1"));
+      Certificate loadedCert = keyStore.getCertificate("gateway-identity1");
+      Assert.assertNotNull(loadedCert);
+      Assert.assertEquals(cert, loadedCert);
+    } finally {
+      Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+    }
+  }
+
   @Test
   public void testFetchPublicCertsFromServer() throws Exception {
     // 1. Generate a self-signed certificate

Reply via email to