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..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 @@ -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.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 75eb88baa4..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 @@ -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.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 8d0574d209..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 @@ -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 06365f575e..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 @@ -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.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