From 29f246227f56876872a4a43b79a135aefcc79d7d Mon Sep 17 00:00:00 2001 From: Joao Viegas Date: Tue, 7 Feb 2023 16:03:21 +0100 Subject: [PATCH 1/2] only update the keystore if the private key matches the certificate --- .../FileWatchingX509ExtendedKeyManager.java | 41 +++++++++++++++---- ...ileWatchingX509ExtendedKeyManagerTest.java | 10 ++++- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/cloudfoundry/security/FileWatchingX509ExtendedKeyManager.java b/src/main/java/org/cloudfoundry/security/FileWatchingX509ExtendedKeyManager.java index b931975..de2c141 100644 --- a/src/main/java/org/cloudfoundry/security/FileWatchingX509ExtendedKeyManager.java +++ b/src/main/java/org/cloudfoundry/security/FileWatchingX509ExtendedKeyManager.java @@ -24,16 +24,20 @@ import java.lang.reflect.UndeclaredThrowableException; import java.net.Socket; import java.nio.file.Path; +import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; +import java.security.Signature; +import java.security.SignatureException; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.List; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; @@ -124,24 +128,47 @@ private KeyStore getKeyStore() { PrivateKey privateKey = PrivateKeyFactory.generate(this.privateKey); List certificates = X509CertificateFactory.generate(this.certificates); + // private key should match the first certificate in the chain + if (!validateCertificateKey(privateKey, certificates.get(0))) { + this.logger.fine("Private key doesn't match certificate"); + return null; + } + KeyStoreEntryCollector.accumulate(keyStore, privateKey, new char[0], certificates.toArray(new Certificate[certificates.size()])); return keyStore; - } catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException e) { + } catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { throw new UndeclaredThrowableException(e); } } + public static boolean validateCertificateKey(PrivateKey key, X509Certificate certificate) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + byte[] challenge = new byte[10000]; + ThreadLocalRandom.current().nextBytes(challenge); + + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initSign(key); + sig.update(challenge); + byte[] signature = sig.sign(); + + sig.initVerify(certificate.getPublicKey()); + sig.update(challenge); + + return sig.verify(signature); + } private final class FileWatcherCallback implements Runnable { @Override public void run() { - if (FileWatchingX509ExtendedKeyManager.this.keyManager.getAndSet(getKeyManager(getKeyStore())) == null) { - FileWatchingX509ExtendedKeyManager.this.logger.info(String.format("Initialized KeyManager for %s and %s", FileWatchingX509ExtendedKeyManager.this.privateKey, - FileWatchingX509ExtendedKeyManager.this.certificates)); - } else { - FileWatchingX509ExtendedKeyManager.this.logger.info(String.format("Updated KeyManager for %s and %s", FileWatchingX509ExtendedKeyManager.this.privateKey, - FileWatchingX509ExtendedKeyManager.this.certificates)); + KeyStore keyStore = getKeyStore(); + if (keyStore != null) { + if (FileWatchingX509ExtendedKeyManager.this.keyManager.getAndSet(getKeyManager(keyStore)) == null) { + FileWatchingX509ExtendedKeyManager.this.logger.info(String.format("Initialized KeyManager for %s and %s", FileWatchingX509ExtendedKeyManager.this.privateKey, + FileWatchingX509ExtendedKeyManager.this.certificates)); + } else { + FileWatchingX509ExtendedKeyManager.this.logger.info(String.format("Updated KeyManager for %s and %s", FileWatchingX509ExtendedKeyManager.this.privateKey, + FileWatchingX509ExtendedKeyManager.this.certificates)); + } } } diff --git a/src/test/java/org/cloudfoundry/security/FileWatchingX509ExtendedKeyManagerTest.java b/src/test/java/org/cloudfoundry/security/FileWatchingX509ExtendedKeyManagerTest.java index 44c1e68..253bc71 100644 --- a/src/test/java/org/cloudfoundry/security/FileWatchingX509ExtendedKeyManagerTest.java +++ b/src/test/java/org/cloudfoundry/security/FileWatchingX509ExtendedKeyManagerTest.java @@ -25,6 +25,8 @@ import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.security.NoSuchAlgorithmException; +import java.util.logging.Level; +import java.util.logging.Logger; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -46,6 +48,7 @@ public void initializedWithWatchedFile() throws IOException, NoSuchAlgorithmExce @Test public void watchesWatchedFile() throws IOException, InterruptedException, NoSuchAlgorithmException { + Logger.getGlobal().setLevel(Level.ALL); Path watchedCertificates = getWatchedCertificatesFile(); Files.copy(Paths.get("src/test/resources/client-certificates-1.pem"), watchedCertificates); @@ -58,15 +61,20 @@ public void watchesWatchedFile() throws IOException, InterruptedException, NoSuc Thread.sleep(5_000); Files.copy(Paths.get("src/test/resources/client-certificates-2.pem"), watchedCertificates, StandardCopyOption.REPLACE_EXISTING); + Thread.sleep(5_000); + checkAliasPresence(keyManager,alias,true); Files.copy(Paths.get("src/test/resources/client-private-key-2.pem"), watchedPrivateKey, StandardCopyOption.REPLACE_EXISTING); + checkAliasPresence(keyManager,alias,false); + } + private void checkAliasPresence(FileWatchingX509ExtendedKeyManager keyManager, String alias, boolean present) throws InterruptedException { long timeout = System.currentTimeMillis() + 300_000; for (; ; ) { if (System.currentTimeMillis() > timeout) { fail("Failed to update within timeout"); } - if (!keyManager.getClientAliases("RSA", null)[0].equals(alias)) { + if (keyManager.getClientAliases("RSA", null)[0].equals(alias) == present) { return; } From 1874539f9b0e147153d71678f34733d92a3d2769 Mon Sep 17 00:00:00 2001 From: jcvrabo <122891959+jcvrabo@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:08:46 +0100 Subject: [PATCH 2/2] Update README.md with archive notification --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index dc498c0..cc9980e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +The issue this fork was created for has been solved in the main project (sometimes not properly refreshing the instance client certificate). Prject is archived. + # Java Buildpack Security Provider | Job | Status