Skip to content

Commit 43cfa67

Browse files
authored
Reworked the enabling of the CRL function
Closes #932
1 parent 61acfde commit 43cfa67

File tree

6 files changed

+129
-34
lines changed

6 files changed

+129
-34
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Version 6.0.4
44

5+
* CRL not properly applied when using certificate files - Issue #932
56
* Return default values if schema changes in Cassandra have not been completed - Issue #922
67

78
## Version 6.0.3

application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/CRLFileManager.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ private void refreshCRLs()
164164
if (!fileModified() && myAttempt == 1)
165165
{
166166
// No file changes and no reattempts needed, so no refresh is necessary at this time
167+
LOG.debug("No CRL file changes detected; skipping refresh");
167168
return;
168169
}
169170

@@ -180,16 +181,17 @@ private void refreshCRLs()
180181
long endTime = System.nanoTime();
181182
long duration = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
182183
// CRL file was refreshed; notify and log the details
183-
updateFileMetadata();
184-
notifyRefresh();
185184
LOG.info("CRL file refreshed in {} ms; read {} record(s), discarded {} duplicate(s)",
186185
duration, this.myCRLs.size(), dupes);
187186
}
188187
catch (IOException | CRLException e)
189188
{
190189
myCRLs.clear();
191-
LOG.error("Failed to read CRL file; any cached CRLs have been purged", e);
190+
LOG.error("Failed to read CRL file; any previously cached CRLs have been purged");
192191
}
192+
// Make sure listeners are aware of the changes
193+
updateFileMetadata();
194+
notifyRefresh();
193195
}
194196

195197
}

application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/CustomCRLValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public void validateCertificate(final X509Certificate cert, final X509Certificat
120120
+ cert.getIssuerX500Principal().getName());
121121
}
122122

123-
// If this point is reached, the certificate is valid and not revoked
123+
// If this point is reached, the certificate is not revoked
124124
}
125125

126126
public void addRefreshListener(final Runnable listener)

application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/CustomX509TrustManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,14 @@ public void onRefresh()
156156
revalidateServerTrust();
157157
revalidateClientTrust();
158158
myCRLValidator.resetAttempts();
159+
LOG.info("Certificates are not revoked by current CRL (any previous failed attempts was reset)");
159160
}
160161
catch (CertificateException e)
161162
{
162163
if (myCRLValidator.inStrictMode())
163164
{
164165
// Strict mode: Log it, check for attempts made and eventually shut down if all attempts are consumed
165-
LOG.warn("Provided certificate is rejected by CRL (strict mode, attempt {} of {} made)",
166+
LOG.warn("Certificates are revoked by current CRL (strict mode, attempt {} of {} made)",
166167
myCRLValidator.increaseAttempts(),
167168
myCRLValidator.maxAttempts());
168169
// If the last attempt was made, do a graceful shutdown

application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ReloadingCertificateHandler.java

Lines changed: 119 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,22 @@
3838
import java.nio.file.Files;
3939
import java.nio.file.Paths;
4040
import java.security.InvalidAlgorithmParameterException;
41+
import java.security.KeyFactory;
4142
import java.security.KeyManagementException;
4243
import java.security.KeyStore;
4344
import java.security.KeyStoreException;
4445
import java.security.MessageDigest;
4546
import java.security.NoSuchAlgorithmException;
47+
import java.security.PrivateKey;
4648
import java.security.UnrecoverableKeyException;
4749
import java.security.cert.CRLException;
50+
import java.security.cert.Certificate;
4851
import java.security.cert.CertificateException;
52+
import java.security.cert.CertificateFactory;
53+
import java.security.spec.InvalidKeySpecException;
54+
import java.security.spec.PKCS8EncodedKeySpec;
4955
import java.util.Arrays;
56+
import java.util.Base64;
5057
import java.util.HashMap;
5158
import java.util.Map;
5259
import 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;

docs/SETUP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ jmx:
153153
cipher_suites:
154154
```
155155

156-
CQL also supports certificates in PEM format.
156+
CQL also supports certificates in PEM format (EC and RSA algorithms only).
157157

158158
```yaml
159159
cql:

0 commit comments

Comments
 (0)