diff --git a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManager.java b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManager.java index 5ff036fef12f..1dbcc1c8c9ce 100644 --- a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManager.java +++ b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManager.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.ca.provider; import java.math.BigInteger; +import java.security.GeneralSecurityException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; @@ -91,6 +92,17 @@ public void checkClientTrusted(final X509Certificate[] certificates, final Strin return; } + if (authStrictness) { + if (caCertificate == null) { + throw new CertificateException("Root CA certificate is required to validate client: " + clientAddress); + } + try { + primaryClientCertificate.verify(caCertificate.getPublicKey()); + } catch (final GeneralSecurityException e) { + throw new CertificateException("Client certificate signature verification failed for client: " + clientAddress, e); + } + } + // Revocation check final BigInteger serialNumber = primaryClientCertificate.getSerialNumber(); if (serialNumber == null || crlDao.findBySerial(serialNumber) != null) { diff --git a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java index d4ded3023321..0fa70eb2096f 100644 --- a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java +++ b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java @@ -61,6 +61,70 @@ public void setUp() throws Exception { "CN=cloudstack.apache.org", "SHA256withRSA", 0, Collections.singletonList("cloudstack.apache.org"), Collections.singletonList(clientIp)); } + @Test + public void testAuthStrictWithRootSignedClientCertificate() throws Exception { + final X509Certificate certificate = CertUtils.generateV3Certificate(caCertificate, caKeypair, clientKeypair.getPublic(), + "CN=client", "SHA256withRSA", 1, null, Collections.singletonList(clientIp)); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, false, certMap, caCertificate, crlDao); + + trustManager.checkClientTrusted(new X509Certificate[]{certificate}, "RSA"); + + Assert.assertEquals(certificate, certMap.get(clientIp)); + } + + @Test + public void testAuthStrictWithSelfSignedClientCertificate() throws Exception { + final X509Certificate certificate = CertUtils.generateV3Certificate(null, clientKeypair, clientKeypair.getPublic(), + "CN=client", "SHA256withRSA", 1, null, Collections.singletonList(clientIp)); + + assertCertificateRejected(new X509Certificate[]{certificate}, caCertificate); + } + + @Test + public void testAuthStrictWithDifferentRootUsingSameSubject() throws Exception { + final KeyPair otherKeypair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate otherCa = CertUtils.generateV3Certificate(null, otherKeypair, otherKeypair.getPublic(), + "CN=ca", "SHA256withRSA", 365, null, null); + final X509Certificate certificate = CertUtils.generateV3Certificate(otherCa, otherKeypair, clientKeypair.getPublic(), + "CN=client", "SHA256withRSA", 1, null, Collections.singletonList(clientIp)); + + assertCertificateRejected(new X509Certificate[]{certificate, otherCa}, caCertificate); + } + + @Test + public void testAuthStrictWithUntrustedClientAndTrustedRootInChain() throws Exception { + final X509Certificate certificate = CertUtils.generateV3Certificate(null, clientKeypair, clientKeypair.getPublic(), + "CN=client", "SHA256withRSA", 1, null, Collections.singletonList(clientIp)); + + assertCertificateRejected(new X509Certificate[]{certificate, caCertificate}, caCertificate); + } + + @Test + public void testAuthStrictWithoutRootCertificate() throws Exception { + assertCertificateRejected(new X509Certificate[]{expiredClientCertificate}, null); + } + + private void assertCertificateRejected(final X509Certificate[] certificates, final X509Certificate rootCertificate) throws Exception { + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, rootCertificate, crlDao); + try { + trustManager.checkClientTrusted(certificates, "RSA"); + Assert.fail("Expected client certificate rejection"); + } catch (final CertificateException expected) { + Assert.assertTrue(certMap.isEmpty()); + } + } + + @Test + public void testAuthNotStrictWithSelfSignedClientCertificate() throws Exception { + final X509Certificate certificate = CertUtils.generateV3Certificate(null, clientKeypair, clientKeypair.getPublic(), + "CN=client", "SHA256withRSA", 1, null, Collections.singletonList(clientIp)); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao); + + trustManager.checkClientTrusted(new X509Certificate[]{certificate}, "RSA"); + + Assert.assertEquals(certificate, certMap.get(clientIp)); + } + @Test public void testAuthNotStrictWithInvalidCert() throws Exception { final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao);