View Javadoc
1   package com.guinetik.rr.auth;
2   
3   import com.guinetik.rr.RocketRestConfig;
4   import com.guinetik.rr.http.RocketClient;
5   import org.slf4j.Logger;
6   import org.slf4j.LoggerFactory;
7   
8   import javax.net.ssl.KeyManagerFactory;
9   import javax.net.ssl.SSLContext;
10  import javax.net.ssl.TrustManager;
11  import javax.net.ssl.X509TrustManager;
12  import java.io.InputStream;
13  import java.nio.file.Files;
14  import java.nio.file.Paths;
15  import java.security.KeyStore;
16  import java.security.NoSuchAlgorithmException;
17  import java.security.cert.X509Certificate;
18  import java.util.HashMap;
19  import java.util.Map;
20  
21  /**
22   * Utility class for handling SSL (TLS 1.2 and 1.3) certificates and contexts.
23   * Used for setting up secure connections with custom certificates.
24   */
25  public class RocketSSL {
26  
27      private static final Logger logger = LoggerFactory.getLogger(RocketSSL.class);
28  
29      /**
30       * Interface for objects that can provide certificate information.
31       */
32      public interface SSLConfig {
33          String getCustomCertificateFilename();
34  
35          String getCustomCertificatePassword();
36  
37          boolean isCustomCertificateEnabled();
38      }
39  
40      /**
41       * Interface for authentication strategies that can be configured with an SSL context.
42       * This interface is implemented by strategies that need to make HTTPS requests,
43       * such as OAuth2 strategies that need to get tokens from a server.
44       */
45      public interface SSLAware {
46  
47          /**
48           * Sets the SSL context to be used for HTTPS requests made by the strategy.
49           *
50           * @param sslContext the SSL context to use
51           */
52          void configureSsl(SSLContext sslContext);
53      }
54  
55      /**
56       * Class representing certificate information.
57       */
58      public static class SSLCertificate {
59          private String certificateFilename;
60          private String certificatePassword;
61  
62          public SSLCertificate(String certificateFilename, String certificatePassword) {
63              this.certificateFilename = certificateFilename;
64              this.certificatePassword = certificatePassword;
65          }
66  
67          public SSLCertificate() {
68          }
69  
70          public String getCertificateFilename() {
71              return certificateFilename;
72          }
73  
74          public void setCertificateFilename(String certificateFilename) {
75              this.certificateFilename = certificateFilename;
76          }
77  
78          public String getCertificatePassword() {
79              return certificatePassword;
80          }
81  
82          public void setCertificatePassword(String certificatePassword) {
83              this.certificatePassword = certificatePassword;
84          }
85      }
86  
87      private Map<String, SSLContext> sslContexts = new HashMap<>();
88  
89      /**
90       * Gets the SSL context for the given certificate information.
91       *
92       * @param SSLCertificate the certificate information
93       * @return the SSL context, or null if an error occurred
94       */
95      public synchronized SSLContext getSSLContext(SSLCertificate SSLCertificate) {
96          return getSSLContext(SSLCertificate.getCertificateFilename(), SSLCertificate.getCertificatePassword());
97      }
98  
99      /**
100      * Gets the SSL context for the given certificate file and password.
101      *
102      * @param fileName the certificate file path
103      * @param certPass the certificate password
104      * @return the SSL context, or null if an error occurred
105      */
106     public synchronized SSLContext getSSLContext(String fileName, String certPass) {
107         SSLContext sc;
108         if (sslContexts.get(fileName) != null)
109             return sslContexts.get(fileName);
110         try {
111             TrustManager tm = new X509TrustManager() {
112                 public void checkClientTrusted(X509Certificate[] chain, String authType) {
113                 }
114 
115                 public void checkServerTrusted(X509Certificate[] chain, String authType) {
116                 }
117 
118                 public X509Certificate[] getAcceptedIssuers() {
119                     return null;
120                 }
121             };
122 
123             if (fileName == null || certPass == null) {
124                 throw new IllegalArgumentException("Security certificate file name or password not found.");
125             }
126             KeyManagerFactory kmf;
127 
128             kmf = KeyManagerFactory.getInstance("SunX509");
129 
130             InputStream fis = null;
131             try {
132                 KeyStore ks = KeyStore.getInstance("PKCS12");
133                 fis = Files.newInputStream(Paths.get(fileName));
134                 ks.load(fis, certPass.toCharArray());
135                 kmf.init(ks, certPass.toCharArray());
136             } finally {
137                 if (fis != null) {
138                     fis.close();
139                 }
140             }
141             try {
142                 // Try TLS 1.3 first
143                 sc = SSLContext.getInstance("TLSv1.3");
144             } catch (NoSuchAlgorithmException e) {
145                 // Fall back to TLS 1.2 if 1.3 is not available
146                 logger.info("TLS 1.3 not available, falling back to TLS 1.2");
147                 sc = SSLContext.getInstance("TLSv1.2");
148             }
149             sc.init(kmf.getKeyManagers(), new TrustManager[]{tm}, null);
150             sslContexts.put(fileName, sc);
151         } catch (Exception e) {
152             logger.error("Exception occurred getting SSL context: {}", e.getMessage());
153             return null;
154         }
155 
156         return sc;
157     }
158 
159     /**
160      * Configures SSL for a client if the provided config implements SSLAware and has a custom
161      * certificate enabled.
162      *
163      * @param client The client to configure
164      * @param config The configuration that might contain SSL settings
165      * @return true if SSL was successfully configured, false otherwise
166      */
167     public static boolean configureSsl(RocketClient client, RocketRestConfig config) {
168         if (!(config instanceof SSLConfig)) {
169             return false;
170         }
171 
172         SSLConfig certConfig = (SSLConfig) config;
173 
174         if (!certConfig.isCustomCertificateEnabled()) {
175             return false;
176         }
177 
178         String certFile = certConfig.getCustomCertificateFilename();
179         String certPass = certConfig.getCustomCertificatePassword();
180 
181         if (certFile == null || certPass == null) {
182             logger.warn("Certificate file or password is null");
183             return false;
184         }
185 
186         logger.info("Configuring SSL with custom certificate: {}", certFile);
187 
188         SSLCertificate cert = new SSLCertificate(certFile, certPass);
189         RocketSSL ssl = new RocketSSL();
190         SSLContext sslContext = ssl.getSSLContext(cert);
191 
192         if (sslContext == null) {
193             logger.error("Failed to configure SSL context with certificate: {}", certFile);
194             return false;
195         }
196 
197         // Configure HTTP client
198         client.configureSsl(sslContext);
199 
200         // Configure auth strategy if it supports SSL
201         if (config.getAuthStrategy() instanceof SSLAware) {
202             ((SSLAware) config.getAuthStrategy()).configureSsl(sslContext);
203             logger.info("SSL context configured for authentication strategy");
204         }
205 
206         logger.info("SSL context configured successfully");
207         return true;
208     }
209 }