From 0afc61a64470fd89b1dd488476602bcf243868cc Mon Sep 17 00:00:00 2001 From: thanicz Date: Wed, 6 May 2026 06:36:57 +0200 Subject: [PATCH 1/2] KNOX-3315: Adds BCFKS as an option cert export --- .../org/apache/knox/gateway/util/KnoxCLI.java | 7 ++-- .../metadata/KnoxMetadataResource.java | 15 ++++++++ .../gateway/util/X509CertificateUtil.java | 2 +- .../gateway/util/X509CertificateUtilTest.java | 35 +++++++++++++++++++ 4 files changed, 56 insertions(+), 3 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 a64b743ff0..91c0e6c4a7 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 String getUsage() { 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.`" + "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 -keystore gateway-client-trust.`"; private GatewayConfig getGatewayConfig() { @@ -808,6 +808,9 @@ public void execute() throws Exception { } 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.writeCertificatesToKeyStore(new Certificate[] { cert }, new File(keyStoreDir + "gateway-client-trust.bcfks"), "bcfks", null); + 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 75eb88baa4..1a28cc1472 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 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 Response getPublicCertification(@QueryParam("type") @DefaultValue("pem") } 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 @@ private void generateCertificateJks(Certificate[] certificateChain, GatewayConfi } } + private void generateCertificateBcfks(Certificate[] certificateChain, GatewayConfig gatewayConfig) { + try { + if (bcfksFilePath == null || !bcfksFilePath.toFile().exists()) { + bcfksFilePath = Paths.get(gatewayConfig.getGatewaySecurityDir(), "gateway-client-trust.bcfks"); + X509CertificateUtil.writeCertificatesToKeyStore(certificateChain, bcfksFilePath.toFile(), "bcfks", 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 8d0574d209..70a34a24be 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 @@ -172,7 +172,7 @@ private static void writeCertificateToKeyStore(Certificate cert, final File file /* * Writes an arbitrary number of certificates into the given keystore file protected by the given password */ - private static void writeCertificatesToKeyStore(Certificate[] certs, final File file, String type, String keystorePassword) + public static void writeCertificatesToKeyStore(Certificate[] certs, final File file, String type, String keystorePassword) throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException { if (certs != null) { KeyStore ks = KeyStore.getInstance(type); 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 06365f575e..54c15f4c2f 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 @@ 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.writeCertificatesToKeyStore(new Certificate[]{cert}, bcfksFile, "BCFKS", "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 From ffa0211406d1897754c275b3f75d08d78cfa89f8 Mon Sep 17 00:00:00 2001 From: thanicz Date: Wed, 6 May 2026 12:37:18 +0200 Subject: [PATCH 2/2] KNOX-3315: Address review comments --- .../java/org/apache/knox/gateway/util/KnoxCLI.java | 2 +- .../service/metadata/KnoxMetadataResource.java | 2 +- .../knox/gateway/util/X509CertificateUtil.java | 12 +++++++++++- .../knox/gateway/util/X509CertificateUtilTest.java | 2 +- 4 files changed, 14 insertions(+), 4 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 91c0e6c4a7..141f2e8160 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 @@ -809,7 +809,7 @@ public void execute() throws Exception { 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.writeCertificatesToKeyStore(new Certificate[] { cert }, new File(keyStoreDir + "gateway-client-trust.bcfks"), "bcfks", null); + 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 1a28cc1472..ba77c0e5df 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 @@ -216,7 +216,7 @@ private void generateCertificateBcfks(Certificate[] certificateChain, GatewayCon try { if (bcfksFilePath == null || !bcfksFilePath.toFile().exists()) { bcfksFilePath = Paths.get(gatewayConfig.getGatewaySecurityDir(), "gateway-client-trust.bcfks"); - X509CertificateUtil.writeCertificatesToKeyStore(certificateChain, bcfksFilePath.toFile(), "bcfks", null); + X509CertificateUtil.writeCertificatesToBcfks(certificateChain, bcfksFilePath.toFile(), null); } } catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) { LOG.failedToGeneratePublicCert("BCFKS", e.getMessage(), e); 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 70a34a24be..3bf07d8caa 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 @@ -172,7 +172,7 @@ private static void writeCertificateToKeyStore(Certificate cert, final File file /* * Writes an arbitrary number of certificates into the given keystore file protected by the given password */ - public static void writeCertificatesToKeyStore(Certificate[] certs, final File file, String type, String keystorePassword) + private static void writeCertificatesToKeyStore(Certificate[] certs, final File file, String type, String keystorePassword) throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException { if (certs != null) { KeyStore ks = KeyStore.getInstance(type); @@ -215,6 +215,16 @@ public static void writeCertificateToPkcs12(Certificate cert, final File file) 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. *

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 54c15f4c2f..506bf34121 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 @@ -60,7 +60,7 @@ public void testWriteCertificatesToKeyStoreBCFKS() throws Exception { // 2. Write it to a BCFKS keystore File bcfksFile = temporaryFolder.newFile("test.bcfks"); - X509CertificateUtil.writeCertificatesToKeyStore(new Certificate[]{cert}, bcfksFile, "BCFKS", "password"); + X509CertificateUtil.writeCertificatesToBcfks(new Certificate[]{cert}, bcfksFile, "password"); // 3. Verify the keystore KeyStore keyStore = KeyStore.getInstance("BCFKS");