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

pzampino 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 de3c78cbc KNOX-3085: Metadata API should return the certificate chain 
(if any) instead of only the configured Knox instance certificate (#988)
de3c78cbc is described below

commit de3c78cbc44098d273f520b0510cad9519843849
Author: Phil Zampino <[email protected]>
AuthorDate: Thu Jan 23 10:53:10 2025 -0500

    KNOX-3085: Metadata API should return the certificate chain (if any) 
instead of only the configured Knox instance certificate (#988)
    
    Co-authored-by: Sandor Molnar <[email protected]>
---
 .../service/metadata/KnoxMetadataResource.java     | 42 +++++------
 .../java/org/apache/knox/gateway/shell/KnoxSh.java | 63 +---------------
 .../knox/gateway/util/X509CertificateUtil.java     | 84 +++++++++++++++++++++-
 3 files changed, 100 insertions(+), 89 deletions(-)

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 7af07fc0b..03846150e 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
@@ -63,8 +63,6 @@ import org.apache.knox.gateway.services.ServiceType;
 import org.apache.knox.gateway.services.registry.ServiceDefinitionRegistry;
 import org.apache.knox.gateway.services.security.AliasService;
 import org.apache.knox.gateway.services.security.AliasServiceException;
-import org.apache.knox.gateway.services.security.KeystoreService;
-import org.apache.knox.gateway.services.security.KeystoreServiceException;
 import org.apache.knox.gateway.services.security.token.impl.TokenMAC;
 import org.apache.knox.gateway.services.topology.TopologyService;
 import org.apache.knox.gateway.topology.Service;
@@ -138,20 +136,17 @@ public class KnoxMetadataResource {
   @Produces(APPLICATION_OCTET_STREAM)
   @Path("publicCert")
   public Response getPublicCertification(@QueryParam("type") 
@DefaultValue("pem") String certType) {
-    final GatewayServices gatewayServices = (GatewayServices) 
request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
-    if (gatewayServices != null) {
-      final GatewayConfig config = (GatewayConfig) 
request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
-      final Certificate certificate = getPublicCertificate(gatewayServices, 
config);
-      if (certificate != null) {
-        if ("pem".equals(certType)) {
-          generateCertificatePem(certificate, config);
-          return generateSuccessFileDownloadResponse(pemFilePath);
-        } else if ("jks".equals(certType)) {
-          generateCertificateJks(certificate, config);
-          return generateSuccessFileDownloadResponse(jksFilePath);
-        } else {
-          return generateFailureFileDownloadResponse(Status.BAD_REQUEST, 
"Invalid certification type provided!");
-        }
+    final GatewayConfig config = (GatewayConfig) 
request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
+    final Certificate[] certificateChain = getPublicCertificates();
+    if (certificateChain != null) {
+      if ("pem".equals(certType)) {
+        generateCertificatePem(certificateChain, config);
+        return generateSuccessFileDownloadResponse(pemFilePath);
+      } else if ("jks".equals(certType)) {
+        generateCertificateJks(certificateChain, config);
+        return generateSuccessFileDownloadResponse(jksFilePath);
+      } else {
+        return generateFailureFileDownloadResponse(Status.BAD_REQUEST, 
"Invalid certification type provided!");
       }
     }
     return generateFailureFileDownloadResponse(Status.SERVICE_UNAVAILABLE, 
"Could not generate public certificate");
@@ -169,32 +164,31 @@ public class KnoxMetadataResource {
     return responseBuilder.build();
   }
 
-  private Certificate getPublicCertificate(GatewayServices gatewayServices, 
GatewayConfig config) {
+  private Certificate[] getPublicCertificates() {
     try {
-      final KeystoreService keystoreService = 
gatewayServices.getService(ServiceType.KEYSTORE_SERVICE);
-      return 
keystoreService.getKeystoreForGateway().getCertificate(config.getIdentityKeyAlias());
-    } catch (KeyStoreException | KeystoreServiceException e) {
+      return 
X509CertificateUtil.fetchPublicCertsFromServer(request.getRequestURL().toString(),
 true, null);
+    } catch (Exception e) {
       LOG.failedToFetchPublicCert(e.getMessage(), e);
       return null;
     }
   }
 
-  private void generateCertificatePem(Certificate certificate, GatewayConfig 
gatewayConfig) {
+  private void generateCertificatePem(Certificate[] certificateChain, 
GatewayConfig gatewayConfig) {
     try {
       if (pemFilePath == null || !pemFilePath.toFile().exists()) {
         pemFilePath = Paths.get(gatewayConfig.getGatewaySecurityDir(), 
"gateway-client-trust.pem");
-        X509CertificateUtil.writeCertificateToFile(certificate, 
pemFilePath.toFile());
+        X509CertificateUtil.writeCertificatesToFile(certificateChain, 
pemFilePath.toFile());
       }
     } catch (CertificateEncodingException | IOException e) {
       LOG.failedToGeneratePublicCert("PEM", e.getMessage(), e);
     }
   }
 
-  private void generateCertificateJks(Certificate certificate, GatewayConfig 
gatewayConfig) {
+  private void generateCertificateJks(Certificate[] certificateChain, 
GatewayConfig gatewayConfig) {
     try {
       if (jksFilePath == null || !jksFilePath.toFile().exists()) {
         jksFilePath = Paths.get(gatewayConfig.getGatewaySecurityDir(), 
"gateway-client-trust.jks");
-        X509CertificateUtil.writeCertificateToJks(certificate, 
jksFilePath.toFile());
+        X509CertificateUtil.writeCertificatesToJks(certificateChain, 
jksFilePath.toFile(), null);
       }
     } catch (IOException | KeyStoreException | NoSuchAlgorithmException | 
CertificateException e) {
       LOG.failedToGeneratePublicCert("JKS", e.getMessage(), e);
diff --git 
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSh.java 
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSh.java
index 205b80058..ec169b51d 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSh.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSh.java
@@ -31,14 +31,10 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.PrintStream;
-import java.net.Socket;
-import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.nio.file.attribute.PosixFilePermission;
-import java.security.KeyStore;
-import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
@@ -51,13 +47,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
 
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
-
 public class KnoxSh {
 
   private static final String USAGE_PREFIX = "KnoxSh {cmd} [options]";
@@ -192,7 +181,7 @@ public class KnoxSh {
     public void execute() throws Exception {
       String result = GATEWAY_CERT_NOT_EXPORTED;
       try {
-        final X509Certificate[] gatewayServerPublicCerts = 
fetchPublicCertsFromGatewayServer();
+        final X509Certificate[] gatewayServerPublicCerts = 
X509CertificateUtil.fetchPublicCertsFromServer(gateway, false, out);
         if (gatewayServerPublicCerts != null) {
           final File trustStoreFile = 
ClientTrustStoreHelper.getClientTrustStoreFile();
           final String trustStorePassword = 
ClientTrustStoreHelper.getClientTrustStoreFilePassword();
@@ -205,60 +194,10 @@ public class KnoxSh {
       out.println(result);
     }
 
-    private X509Certificate[] fetchPublicCertsFromGatewayServer() throws 
Exception {
-      final TrustManagerFactory trustManagerFactory = 
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-      trustManagerFactory.init((KeyStore) null);
-      final X509TrustManager defaultTrustManager = (X509TrustManager) 
trustManagerFactory.getTrustManagers()[0];
-      final CertificateChainAwareTrustManager trustManagerWithCertificateChain 
= new CertificateChainAwareTrustManager(defaultTrustManager);
-      final SSLContext sslContext = SSLContext.getInstance("TLS");
-      sslContext.init(null, new TrustManager[] { 
trustManagerWithCertificateChain }, null);
-
-      final URL url = new URL(gateway);
-      final int port = url.getPort() == -1 ? url.getDefaultPort() : 
url.getPort();
-      out.println("Opening connection to " + url.getHost() + ":" + port + 
"...");
-      try (Socket socket = 
sslContext.getSocketFactory().createSocket(url.getHost(), port)) {
-        socket.setSoTimeout(10000);
-        out.println("Starting SSL handshake...");
-        ((SSLSocket) socket).startHandshake();
-        out.println("No errors, certificate is already trusted");
-        return null; //we already trust the given site's certs; it does not 
make sense to build a new truststore
-      } catch (SSLException e) {
-        // NOP; this is expected in case the gateway server's certificate is 
not in the
-        // trust store the JVM uses
-      }
-
-      return trustManagerWithCertificateChain.serverCertificateChain;
-    }
-
     @Override
     public String getUsage() {
       return USAGE + ":\n\n" + DESC;
     }
-
-    private class CertificateChainAwareTrustManager implements 
X509TrustManager {
-      private final X509TrustManager defaultTrustManager;
-      private X509Certificate[] serverCertificateChain;
-
-      CertificateChainAwareTrustManager(X509TrustManager tm) {
-        this.defaultTrustManager = tm;
-      }
-
-      @Override
-      public X509Certificate[] getAcceptedIssuers() {
-        return defaultTrustManager.getAcceptedIssuers();
-      }
-
-      @Override
-      public void checkClientTrusted(X509Certificate[] chain, String authType) 
throws CertificateException {
-        defaultTrustManager.checkClientTrusted(chain, authType);
-      }
-
-      @Override
-      public void checkServerTrusted(X509Certificate[] chain, String authType) 
throws CertificateException {
-        this.serverCertificateChain = chain;
-        defaultTrustManager.checkServerTrusted(chain, authType);
-      }
-    }
   }
 
   private class KnoxInit extends Command {
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 741511bd4..e0ddaa303 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
@@ -20,12 +20,15 @@ package org.apache.knox.gateway.util;
 import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.PrintStream;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.math.BigInteger;
 import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.security.KeyPair;
@@ -41,6 +44,13 @@ import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.Date;
 
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
 import org.apache.commons.codec.binary.Base64;
 import org.apache.knox.gateway.i18n.GatewayUtilCommonMessages;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
@@ -383,14 +393,24 @@ public class X509CertificateUtil {
   }
 
   public static void writeCertificateToFile(Certificate cert, final File file)
+          throws CertificateEncodingException, IOException {
+      writeCertificatesToFile(new Certificate[] {cert}, file);
+  }
+
+  public static void writeCertificatesToFile(Certificate[] certs, final File 
file)
       throws CertificateEncodingException, IOException {
-    byte[] bytes = cert.getEncoded();
-    Base64 encoder = new Base64( 76, "\n".getBytes( StandardCharsets.US_ASCII 
) );
+    final Base64 encoder = new Base64( 76, "\n".getBytes( 
StandardCharsets.US_ASCII ) );
     try(OutputStream out = Files.newOutputStream(file.toPath()) ) {
+        for (Certificate cert : certs) {
+            saveCertificate(out, cert.getEncoded(), encoder);
+        }
+    }
+  }
+
+  private static void saveCertificate(OutputStream out, byte[] bytes, Base64 
encoder) throws IOException {
       out.write( "-----BEGIN CERTIFICATE-----\n".getBytes( 
StandardCharsets.US_ASCII ) );
       out.write( encoder.encodeToString( bytes ).getBytes( 
StandardCharsets.US_ASCII ) );
       out.write( "-----END CERTIFICATE-----\n".getBytes( 
StandardCharsets.US_ASCII ) );
-    }
   }
 
   /*
@@ -471,4 +491,62 @@ public class X509CertificateUtil {
       return false;
     }
   }
+
+  public static X509Certificate[] fetchPublicCertsFromServer(String serverUrl, 
boolean forceReturnCert, PrintStream out) throws Exception {
+      final TrustManagerFactory trustManagerFactory = 
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+      trustManagerFactory.init((KeyStore) null);
+      final X509TrustManager defaultTrustManager = (X509TrustManager) 
trustManagerFactory.getTrustManagers()[0];
+      final CertificateChainAwareTrustManager trustManagerWithCertificateChain 
= new CertificateChainAwareTrustManager(defaultTrustManager);
+      final SSLContext sslContext = SSLContext.getInstance("TLS");
+      sslContext.init(null, new TrustManager[] { 
trustManagerWithCertificateChain }, null);
+
+      final URL url = new URL(serverUrl);
+      final int port = url.getPort() == -1 ? url.getDefaultPort() : 
url.getPort();
+      logOutput(out, "Opening connection to " + url.getHost() + ":" + port + 
"...");
+      try (Socket socket = 
sslContext.getSocketFactory().createSocket(url.getHost(), port)) {
+        socket.setSoTimeout(10000);
+        logOutput(out, "Starting SSL handshake...");
+        ((SSLSocket) socket).startHandshake();
+        logOutput(out, "No errors, certificate is already trusted");
+        if (!forceReturnCert) {
+            return null; //we already trust the given site's certs; it does 
not make sense to build a new truststore
+        }
+      } catch (SSLException e) {
+        // NOP; this is expected in case the gateway server's certificate is 
not in the
+        // trust store the JVM uses
+      }
+
+      return trustManagerWithCertificateChain.serverCertificateChain;
+  }
+
+  private static void logOutput(PrintStream out, String message) {
+      if (out != null) {
+          out.println(message);
+      }
+  }
+
+  private static class CertificateChainAwareTrustManager implements 
X509TrustManager {
+      private final X509TrustManager defaultTrustManager;
+      private X509Certificate[] serverCertificateChain;
+
+      CertificateChainAwareTrustManager(X509TrustManager tm) {
+        this.defaultTrustManager = tm;
+      }
+
+      @Override
+      public X509Certificate[] getAcceptedIssuers() {
+        return defaultTrustManager.getAcceptedIssuers();
+      }
+
+      @Override
+      public void checkClientTrusted(X509Certificate[] chain, String authType) 
throws CertificateException {
+        defaultTrustManager.checkClientTrusted(chain, authType);
+      }
+
+      @Override
+      public void checkServerTrusted(X509Certificate[] chain, String authType) 
throws CertificateException {
+        this.serverCertificateChain = chain;
+        defaultTrustManager.checkServerTrusted(chain, authType);
+      }
+    }
 }

Reply via email to