3838import java .nio .file .Files ;
3939import java .nio .file .Paths ;
4040import java .security .InvalidAlgorithmParameterException ;
41+ import java .security .KeyFactory ;
4142import java .security .KeyManagementException ;
4243import java .security .KeyStore ;
4344import java .security .KeyStoreException ;
4445import java .security .MessageDigest ;
4546import java .security .NoSuchAlgorithmException ;
47+ import java .security .PrivateKey ;
4648import java .security .UnrecoverableKeyException ;
4749import java .security .cert .CRLException ;
50+ import java .security .cert .Certificate ;
4851import java .security .cert .CertificateException ;
52+ import java .security .cert .CertificateFactory ;
53+ import java .security .spec .InvalidKeySpecException ;
54+ import java .security .spec .PKCS8EncodedKeySpec ;
4955import java .util .Arrays ;
56+ import java .util .Base64 ;
5057import java .util .HashMap ;
5158import java .util .Map ;
5259import java .util .concurrent .atomic .AtomicReference ;
@@ -59,6 +66,8 @@ public class ReloadingCertificateHandler implements CertificateHandler
5966 private final AtomicReference <Context > currentContext = new AtomicReference <>();
6067 private final Supplier <CqlTLSConfig > myCqlTLSConfigSupplier ;
6168
69+ private static final String DEFAULT_STORE_TYPE_JKS = "JKS" ;
70+
6271 public ReloadingCertificateHandler (final Supplier <CqlTLSConfig > cqlTLSConfigSupplier )
6372 {
6473 this .myCqlTLSConfigSupplier = cqlTLSConfigSupplier ;
@@ -208,54 +217,136 @@ protected static SslContext createSSLContext(final CqlTLSConfig tlsConfig) throw
208217 CertificateException ,
209218 UnrecoverableKeyException
210219 {
211-
212220 SslContextBuilder builder = SslContextBuilder .forClient ();
221+ String storeType = tlsConfig .getStoreType ().orElse (DEFAULT_STORE_TYPE_JKS );
222+
213223 if (tlsConfig .isCertificateConfigured ())
214224 {
225+ LOG .info ("PEM certificates configured for CQL connections, using internal store type {}" , storeType );
226+
227+ // Get certificate and key files from config
215228 File certificateFile = new File (tlsConfig .getCertificatePath ().get ());
216229 File certificatePrivateKeyFile = new File (tlsConfig .getCertificatePrivateKeyPath ().get ());
217230 File trustCertificateFile = new File (tlsConfig .getTrustCertificatePath ().get ());
218231
219- builder .keyManager (certificateFile , certificatePrivateKeyFile );
220- builder .trustManager (trustCertificateFile );
232+ KeyStore keyStore = KeyStore .getInstance (storeType );
233+ keyStore .load (null , null );
234+
235+ // Setup client certificates and its private key into a keystore/truststore
236+ CertificateFactory cf = CertificateFactory .getInstance ("X.509" );
237+ Certificate clientCert = cf .generateCertificate (new FileInputStream (certificateFile ));
238+ Certificate trustCert = cf .generateCertificate (new FileInputStream (trustCertificateFile ));
239+ PrivateKey privateKey = loadPrivateKey (certificatePrivateKeyFile );
240+
241+ LOG .info ("Private key algorithm: {}" , privateKey .getAlgorithm ());
242+
243+ // Create the certificate chain and add it to the keystore
244+ Certificate [] certChain = new Certificate []{clientCert };
245+ keyStore .setKeyEntry ("client-key" , privateKey , "" .toCharArray (), certChain );
246+
247+ // Create KeyManagerFactory
248+ KeyManagerFactory kmf = KeyManagerFactory .getInstance (KeyManagerFactory .getDefaultAlgorithm ());
249+ kmf .init (keyStore , "" .toCharArray ());
250+
251+ // Create TrustManagerFactory and load the trusted certificate into it
252+ TrustManagerFactory tmf = TrustManagerFactory .getInstance (TrustManagerFactory .getDefaultAlgorithm ());
253+ KeyStore trustStore = KeyStore .getInstance (storeType );
254+ trustStore .load (null , null );
255+ trustStore .setCertificateEntry ("trust-cert" , trustCert );
256+ tmf .init (trustStore );
257+
258+ // Finally, set the managers in the builder
259+ builder .keyManager (kmf );
260+ setTrustManagers (builder , tlsConfig , tmf );
221261 }
222262 else
223263 {
264+ LOG .info ("Keystore/truststore configured for CQL connections, expecting store type {}" , storeType );
265+
224266 KeyManagerFactory keyManagerFactory = getKeyManagerFactory (tlsConfig );
225267 builder .keyManager (keyManagerFactory );
226- TrustManagerFactory trustManagerFactory = getTrustManagerFactory (tlsConfig );
268+ setTrustManagers (builder , tlsConfig , getTrustManagerFactory (tlsConfig ));
269+ }
270+ if (tlsConfig .getCipherSuites ().isPresent ())
271+ {
272+ builder .ciphers (Arrays .asList (tlsConfig .getCipherSuites ().get ()));
273+ }
274+ return builder .protocols (tlsConfig .getProtocols ()).build ();
275+ }
227276
228- if (tlsConfig .getCRLConfig ().getEnabled ())
277+ protected static void setTrustManagers (final SslContextBuilder builder ,
278+ final CqlTLSConfig config ,
279+ final TrustManagerFactory tmf )
280+ {
281+ if (config .getCRLConfig ().getEnabled ())
282+ {
283+ // If CRL is enabled, use the CustomX509TrustManager (CRL checking is added to the SSL context)
284+ LOG .info ("CRL enabled using strict mode: {}" , config .getCRLConfig ().getStrict ());
285+ TrustManager [] trustManagers = tmf .getTrustManagers ();
286+ for (int i = 0 ; i < trustManagers .length ; i ++)
229287 {
230- // If CRL is enabled, use the CustomX509TrustManager (CRL checking is added to the SSL context)
231- LOG .debug ("CRL enabled using strict mode: {}" , tlsConfig .getCRLConfig ().getStrict ());
232- TrustManager [] trustManagers = trustManagerFactory .getTrustManagers ();
233- for (int i = 0 ; i < trustManagers .length ; i ++)
288+ // Replace X509TrustManager with our custom one
289+ if (trustManagers [i ] instanceof X509TrustManager )
234290 {
235- if (trustManagers [i ] instanceof X509TrustManager )
236- {
237- CustomCRLValidator validator = new CustomCRLValidator (tlsConfig .getCRLConfig ());
238- trustManagers [i ] = new CustomX509TrustManager (
239- (X509TrustManager ) trustManagers [i ],
240- validator
241- );
242- // Add customized TrustManager
243- builder .trustManager (trustManagers [i ]);
244- }
291+ CustomCRLValidator validator = new CustomCRLValidator (config .getCRLConfig ());
292+ trustManagers [i ] = new CustomX509TrustManager (
293+ (X509TrustManager ) trustManagers [i ],
294+ validator
295+ );
296+ builder .trustManager (trustManagers [i ]);
245297 }
246298 }
247- else
299+ }
300+ else
301+ {
302+ // No CRL, use TrustManagerFactory as-is
303+ LOG .info ("CRL not enabled" );
304+ builder .trustManager (tmf );
305+ }
306+ }
307+
308+ @ SuppressWarnings ("PMD.PreserveStackTrace" )
309+ protected static PrivateKey loadPrivateKey (final File file ) throws IOException
310+ {
311+ try
312+ {
313+ // Supported key types are EC and RSA (DSA is legacy and should not be used)
314+
315+ // Read key file and remove the PEM header and any whitespaces
316+ String key = Files .readString (file .toPath ());
317+ String privateKeyPEM = key
318+ .replaceAll ("-----BEGIN .*PRIVATE KEY-----" , "" )
319+ .replaceAll ("-----END .*PRIVATE KEY-----" , "" )
320+ .replaceAll ("\\ s" , "" );
321+
322+ // Decode the key into a nice byte array
323+ byte [] decoded = Base64 .getDecoder ().decode (privateKeyPEM );
324+ PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec (decoded );
325+
326+ try
327+ {
328+ LOG .debug ("Trying private key type EC" );
329+ KeyFactory kf = KeyFactory .getInstance ("EC" );
330+ return kf .generatePrivate (spec );
331+ }
332+ catch (InvalidKeySpecException e1 )
248333 {
249- // No CRL, use regular TrustManagerFqctory
250- LOG .debug ("CRL not enabled" );
251- builder .trustManager (trustManagerFactory );
334+ LOG .debug ("Trying private key type RSA" );
335+ try
336+ {
337+ KeyFactory kf = KeyFactory .getInstance ("RSA" );
338+ return kf .generatePrivate (spec );
339+ }
340+ catch (InvalidKeySpecException e2 )
341+ {
342+ throw new IOException ("Unsupported private key format" , e2 );
343+ }
252344 }
253345 }
254- if ( tlsConfig . getCipherSuites (). isPresent () )
346+ catch ( NoSuchAlgorithmException e )
255347 {
256- builder . ciphers ( Arrays . asList ( tlsConfig . getCipherSuites (). get ()) );
348+ throw new IOException ( "Failed to load the private key file" , e );
257349 }
258- return builder .protocols (tlsConfig .getProtocols ()).build ();
259350 }
260351
261352 protected static KeyManagerFactory getKeyManagerFactory (final CqlTLSConfig tlsConfig ) throws IOException ,
@@ -267,7 +358,7 @@ protected static KeyManagerFactory getKeyManagerFactory(final CqlTLSConfig tlsCo
267358 try (InputStream keystoreFile = new FileInputStream (tlsConfig .getKeyStorePath ()))
268359 {
269360 KeyManagerFactory keyManagerFactory = KeyManagerFactory .getInstance (algorithm );
270- KeyStore keyStore = KeyStore .getInstance (tlsConfig .getStoreType ().orElse ("JKS" ));
361+ KeyStore keyStore = KeyStore .getInstance (tlsConfig .getStoreType ().orElse (DEFAULT_STORE_TYPE_JKS ));
271362 keyStore .load (keystoreFile , keystorePassword );
272363 keyManagerFactory .init (keyStore , keystorePassword );
273364 return keyManagerFactory ;
@@ -283,7 +374,7 @@ protected static TrustManagerFactory getTrustManagerFactory(final CqlTLSConfig t
283374 try (InputStream truststoreFile = new FileInputStream (tlsConfig .getTrustStorePath ()))
284375 {
285376 TrustManagerFactory trustManagerFactory = TrustManagerFactory .getInstance (algorithm );
286- KeyStore keyStore = KeyStore .getInstance (tlsConfig .getStoreType ().orElse ("JKS" ));
377+ KeyStore keyStore = KeyStore .getInstance (tlsConfig .getStoreType ().orElse (DEFAULT_STORE_TYPE_JKS ));
287378 keyStore .load (truststoreFile , truststorePassword );
288379 trustManagerFactory .init (keyStore );
289380 return trustManagerFactory ;
0 commit comments