This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/master by this push:
     new 28a1931  server: Enable revocation checking for uploaded certificates 
(#4065)
28a1931 is described below

commit 28a19311f4bb4eda0ce2ccd0dfcaa2ec23f3a029
Author: Artem Smotrakov <[email protected]>
AuthorDate: Thu Jun 4 04:17:05 2020 +0200

    server: Enable revocation checking for uploaded certificates (#4065)
    
    This update turns on certificate revocation checking for uploaded 
certificates:
    
    - Updated `CertServiceImpl` to be able to enable revocation checking.
    - Introduced a new parameter `ENABLED_REVOCATION_CHECK` for 
`UploadSslCertCmd`.
    - Updated `CertServiceTest`.
    
    Even if no CLRs are specified via `PKIXParameters`, the certificates
    themselves may still provide info for revocation checking:
    
    - The AIA extension may contains a URL to the OCSP responder.
    - The CLRDP extension contains a URL to the CLR.
    
    Those extensions may need to be explicitly enabled by setting the system 
properties `com.sun.security.enableAIAcaIssuers` and 
`com.sun.security.enableCRLDP` to true. See [Java PKI Programmer's 
Guide](https://docs.oracle.com/en/java/javase/11/security/java-pki-programmers-guide.html).
    
    Using a revoked certificate may be dangerous. One of the most common 
reasons why a certificate authority (CA) revokes a certificate is that the 
private key has been compromised. For example, the private key might have been 
stolen by an adversary.
    
    If I understand correctly, the `CertServiceImpl` bean is used for 
operations with certificates on a load balancer. In particular, it validates a 
certificate chain without revocation checking while uploading a certificate. If 
a compromised revoked certificate is then used by the load balancer, then it 
may result to compromising TLS connections. However, the attacker has to be 
able to implement man-in-the-middle attack to compromise the connections. So 
the attacker has to be quite power [...]
    
    This has been discussed on [email protected]
---
 .../org/apache/cloudstack/api/ApiConstants.java    |  1 +
 .../user/loadbalancer/UploadSslCertCmd.java        |  7 +++
 .../cloudstack/network/ssl/CertServiceImpl.java    | 12 ++--
 .../cloudstack/network/ssl/CertServiceTest.java    | 65 ++++++++++++++++++++++
 4 files changed, 79 insertions(+), 6 deletions(-)

diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java 
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 2a201db..932f62d 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -55,6 +55,7 @@ public class ApiConstants {
     public static final String CERTIFICATE_CHAIN = "certchain";
     public static final String CERTIFICATE_FINGERPRINT = "fingerprint";
     public static final String CERTIFICATE_ID = "certid";
+    public static final String ENABLED_REVOCATION_CHECK = 
"enabledrevocationcheck";
     public static final String CONTROLLER = "controller";
     public static final String CONTROLLER_UNIT = "controllerunit";
     public static final String COPY_IMAGE_TAGS = "copyimagetags";
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UploadSslCertCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UploadSslCertCmd.java
index 309e43f..c206a16 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UploadSslCertCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UploadSslCertCmd.java
@@ -76,6 +76,9 @@ public class UploadSslCertCmd extends BaseCmd {
     @Parameter(name = ApiConstants.NAME , type = CommandType.STRING, required 
= true, description = "Name for the uploaded certificate")
     private String name;
 
+    @Parameter(name = ApiConstants.ENABLED_REVOCATION_CHECK, type = 
CommandType.BOOLEAN, description = "Enables revocation checking for 
certificates", since = "4.15")
+    private Boolean enabledRevocationCheck = Boolean.TRUE;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -112,6 +115,10 @@ public class UploadSslCertCmd extends BaseCmd {
         return name;
     }
 
+    public Boolean getEnabledRevocationCheck() {
+        return enabledRevocationCheck;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
diff --git 
a/server/src/main/java/org/apache/cloudstack/network/ssl/CertServiceImpl.java 
b/server/src/main/java/org/apache/cloudstack/network/ssl/CertServiceImpl.java
index c602475..ae34748 100644
--- 
a/server/src/main/java/org/apache/cloudstack/network/ssl/CertServiceImpl.java
+++ 
b/server/src/main/java/org/apache/cloudstack/network/ssl/CertServiceImpl.java
@@ -125,7 +125,7 @@ public class CertServiceImpl implements CertService {
         final String chain = certCmd.getChain();
         final String name = certCmd.getName();
 
-        validate(cert, key, password, chain);
+        validate(cert, key, password, chain, 
certCmd.getEnabledRevocationCheck());
         s_logger.debug("Certificate Validation succeeded");
 
         final String fingerPrint = 
CertificateHelper.generateFingerPrint(parseCertificate(cert));
@@ -278,7 +278,7 @@ public class CertServiceImpl implements CertService {
         return certResponseList;
     }
 
-    private void validate(final String certInput, final String keyInput, final 
String password, final String chainInput) {
+    private void validate(final String certInput, final String keyInput, final 
String password, final String chainInput, boolean revocationEnabled) {
         try {
             List<Certificate> chain = null;
             final Certificate cert = parseCertificate(certInput);
@@ -292,7 +292,7 @@ public class CertServiceImpl implements CertService {
             validateKeys(cert.getPublicKey(), key);
 
             if (chainInput != null) {
-                validateChain(chain, cert);
+                validateChain(chain, cert, revocationEnabled);
             }
         } catch (final IOException | CertificateException e) {
             throw new IllegalStateException("Parsing certificate/key failed: " 
+ e.getMessage(), e);
@@ -388,7 +388,7 @@ public class CertServiceImpl implements CertService {
         }
     }
 
-    private void validateChain(final List<Certificate> chain, final 
Certificate cert) {
+    private void validateChain(final List<Certificate> chain, final 
Certificate cert, boolean revocationEnabled) {
 
         final List<Certificate> certs = new ArrayList<Certificate>();
         final Set<TrustAnchor> anchors = new HashSet<TrustAnchor>();
@@ -410,7 +410,7 @@ public class CertServiceImpl implements CertService {
         PKIXBuilderParameters params = null;
         try {
             params = new PKIXBuilderParameters(anchors, target);
-            params.setRevocationEnabled(false);
+            params.setRevocationEnabled(revocationEnabled);
             params.addCertStore(CertStore.getInstance("Collection", new 
CollectionCertStoreParameters(certs)));
             final CertPathBuilder builder = 
CertPathBuilder.getInstance("PKIX", "BC");
             builder.build(params);
@@ -458,4 +458,4 @@ public class CertServiceImpl implements CertService {
 
         return certificateFactory.generateCertificate(bais);
     }
-}
\ No newline at end of file
+}
diff --git 
a/server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest.java 
b/server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest.java
index a8514f9..7008a81 100644
--- 
a/server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest.java
+++ 
b/server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest.java
@@ -140,9 +140,74 @@ public class CertServiceTest {
         chainField.setAccessible(true);
         chainField.set(uploadCmd, chain);
 
+        final Field enabledRevocationCheckField = 
klazz.getDeclaredField("enabledRevocationCheck");
+        enabledRevocationCheckField.setAccessible(true);
+        enabledRevocationCheckField.set(uploadCmd, Boolean.FALSE);
+
         certService.uploadSslCert(uploadCmd);
     }
 
+    @Test
+    /**
+     * Given a certificate signed by a CA and a valid CA chain, but without 
any info for revocation checking, upload should fail.
+     */
+    public void runUploadSslCertWithNoRevocationInfo() throws Exception {
+        Assume.assumeTrue(isOpenJdk() || isJCEInstalled());
+
+        TransactionLegacy.open("runUploadSslCertWithCAChain");
+
+        final String certFile = 
URLDecoder.decode(getClass().getResource("/certs/rsa_ca_signed.crt").getFile(),Charset.defaultCharset().name());
+        final String keyFile = 
URLDecoder.decode(getClass().getResource("/certs/rsa_ca_signed.key").getFile(),Charset.defaultCharset().name());
+        final String chainFile = 
URLDecoder.decode(getClass().getResource("/certs/root_chain.crt").getFile(),Charset.defaultCharset().name());
+
+        final String cert = readFileToString(new File(certFile));
+        final String key = readFileToString(new File(keyFile));
+        final String chain = readFileToString(new File(chainFile));
+
+        final CertServiceImpl certService = new CertServiceImpl();
+
+        //setting mock objects
+        certService._accountMgr = Mockito.mock(AccountManager.class);
+        final Account account = new AccountVO("testaccount", 1, 
"networkdomain", (short)0, UUID.randomUUID().toString());
+        
when(certService._accountMgr.getAccount(anyLong())).thenReturn(account);
+
+        certService._domainDao = Mockito.mock(DomainDao.class);
+        final DomainVO domain = new DomainVO("networkdomain", 1L, 1L, 
"networkdomain");
+        
when(certService._domainDao.findByIdIncludingRemoved(anyLong())).thenReturn(domain);
+
+        certService._sslCertDao = Mockito.mock(SslCertDao.class);
+        
when(certService._sslCertDao.persist(Matchers.any(SslCertVO.class))).thenReturn(new
 SslCertVO());
+
+        certService._accountDao = Mockito.mock(AccountDao.class);
+        
when(certService._accountDao.findByIdIncludingRemoved(anyLong())).thenReturn((AccountVO)account);
+
+        //creating the command
+        final UploadSslCertCmd uploadCmd = new UploadSslCertCmdExtn();
+        final Class<?> klazz = uploadCmd.getClass().getSuperclass();
+
+        final Field certField = klazz.getDeclaredField("cert");
+        certField.setAccessible(true);
+        certField.set(uploadCmd, cert);
+
+        final Field keyField = klazz.getDeclaredField("key");
+        keyField.setAccessible(true);
+        keyField.set(uploadCmd, key);
+
+        final Field chainField = klazz.getDeclaredField("chain");
+        chainField.setAccessible(true);
+        chainField.set(uploadCmd, chain);
+
+        try {
+            certService.uploadSslCert(uploadCmd);
+        } catch (Exception e) {
+            Assert.assertTrue(e.getMessage().contains("Invalid certificate 
chain"));
+            Throwable cause = e.getCause();
+            Assert.assertTrue(cause.getMessage().contains("Certification path 
could not be validated"));
+            cause = cause.getCause();
+            Assert.assertTrue(cause.getMessage().contains("No CRLs found"));
+        }
+    }
+
     //    @Test
     /**
      * Given a Self-signed Certificate with encrypted key, upload should 
succeed

Reply via email to