cloudstack-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bhais...@apache.org
Subject [cloudstack] branch master updated: CLOUDSTACK-9993: Securing Agents Communications (#2239)
Date Mon, 28 Aug 2017 10:15:15 GMT
This is an automated email from the ASF dual-hosted git repository.

bhaisaab 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 7ce54bf  CLOUDSTACK-9993: Securing Agents Communications (#2239)
7ce54bf is described below

commit 7ce54bf7a85d6df72f84c00fadf9b0fd42ab0d99
Author: Rohit Yadav <bhaisaab@apache.org>
AuthorDate: Mon Aug 28 12:15:11 2017 +0200

    CLOUDSTACK-9993: Securing Agents Communications (#2239)
    
    This introduces a new certificate authority framework that allows
    pluggable CA provider implementations to handle certificate operations
    around issuance, revocation and propagation. The framework injects
    itself to `NioServer` to handle agent connections securely. The
    framework adds assumptions in `NioClient` that a keystore if available
    with known name `cloud.jks` will be used for SSL negotiations and
    handshake.
    
    This includes a default 'root' CA provider plugin which creates its own
    self-signed root certificate authority on first run and uses it for
    issuance and provisioning of certificate to CloudStack agents such as
    the KVM, CPVM and SSVM agents and also for the management server for
    peer clustering.
    
    Additional changes and notes:
    - Comma separate list of management server IPs can be set to the 'host'
      global setting. Newly provisioned agents (KVM/CPVM/SSVM etc) will get
      radomized comma separated list to which they will attempt connection
      or reconnection in provided order. This removes need of a TCP LB on
      port 8250 (default) of the management server(s).
    - All fresh deployment will enforce two-way SSL authentication where
      connecting agents will be required to present certificates issued
      by the 'root' CA plugin.
    - Existing environment on upgrade will continue to use one-way SSL
      authentication and connecting agents will not be required to present
      certificates.
    - A script `keystore-setup` is responsible for initial keystore setup
      and CSR generation on the agent/hosts.
    - A script `keystore-cert-import` is responsible for import provided
      certificate payload to the java keystore file.
    - Agent security (keystore, certificates etc) are setup initially using
      SSH, and later provisioning is handled via an existing agent connection
      using command-answers. The supported clients and agents are limited to
      CPVM, SSVM, and KVM agents, and clustered management server (peering).
    - Certificate revocation does not revoke an existing agent-mgmt server
      connection, however rejects a revoked certificate used during SSL
      handshake.
    - Older `cloudstackmanagement.keystore` is deprecated and will no longer
      be used by mgmt server(s) for SSL negotiations and handshake. New
      keystores will be named `cloud.jks`, any additional SSL certificates
      should not be imported in it for use with tomcat etc. The `cloud.jks`
      keystore is stricly used for agent-server communications.
    - Management server keystore are validated and renewed on start up only,
      the validity of them are same as the CA certificates.
    
    New APIs:
    - listCaProviders: lists all available CA provider plugins
    - listCaCertificate: lists the CA certificate(s)
    - issueCertificate: issues X509 client certificate with/without a CSR
    - provisionCertificate: provisions certificate to a host
    - revokeCertificate: revokes a client certificate using its serial
    
    Global settings for the CA framework:
    - ca.framework.provider.plugin: The configured CA provider plugin
    - ca.framework.cert.keysize: The key size for certificate generation
    - ca.framework.cert.signature.algorithm: The certificate signature algorithm
    - ca.framework.cert.validity.period: Certificate validity in days
    - ca.framework.cert.automatic.renewal: Certificate auto-renewal setting
    - ca.framework.background.task.delay: CA background task delay/interval
    - ca.framework.cert.expiry.alert.period: Days to check and alert expiring certificates
    
    Global settings for the default 'root' CA provider:
    - ca.plugin.root.private.key: (hidden/encrypted) CA private key
    - ca.plugin.root.public.key: (hidden/encrypted) CA public key
    - ca.plugin.root.ca.certificate: (hidden/encrypted) CA certificate
    - ca.plugin.root.issuer.dn: The CA issue distinguished name
    - ca.plugin.root.auth.strictness: Are clients required to present certificates
    - ca.plugin.root.allow.expired.cert: Are clients with expired certificates allowed
    
    UI changes:
    - Button to download/save the CA certificates.
    
    Misc changes:
    - Upgrades bountycastle version and uses newer classes
    - Refactors SAMLUtil to use new CertUtils
    
    Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
---
 .travis.yml                                        |   1 +
 agent/src/com/cloud/agent/Agent.java               | 127 +++++-
 agent/src/com/cloud/agent/AgentShell.java          |  13 +-
 .../cloud/agent/dao/impl/PropertiesStorage.java    |  29 +-
 .../consoleproxy/ConsoleProxyResource.java         |  12 +-
 agent/test/com/cloud/agent/AgentShellTest.java     |  15 +
 api/pom.xml                                        |   5 +
 api/src/com/cloud/event/EventTypes.java            |  22 +-
 .../org/apache/cloudstack/alert/AlertService.java  |   1 +
 .../org/apache/cloudstack/api/ApiConstants.java    |   5 +
 .../api/command/admin/ca/IssueCertificateCmd.java  | 162 +++++++
 .../api/command/admin/ca/ListCAProvidersCmd.java   | 102 +++++
 .../api/command/admin/ca/ListCaCertificateCmd.java |  90 ++++
 .../command/admin/ca/ProvisionCertificateCmd.java  | 125 ++++++
 .../api/command/admin/ca/RevokeCertificateCmd.java | 116 +++++
 .../api/response/CAProviderResponse.java           |  52 +++
 .../api/response/CertificateResponse.java          |  58 +++
 api/src/org/apache/cloudstack/ca/CAManager.java    | 163 ++++++++
 .../apache/cloudstack/poll/BackgroundPollTask.java |   6 +
 client/pom.xml                                     |  11 +
 client/tomcatconf/cloudmanagementserver.keystore   | Bin 1316 -> 0 bytes
 client/tomcatconf/server-ssl.xml.in                |   2 +-
 client/tomcatconf/server7-ssl.xml.in               |   2 +-
 client/tomcatconf/tomcat6-ssl.conf.in              |   2 +-
 .../META-INF/cloudstack/ca/module.properties       |  38 +-
 ...pring-core-lifecycle-ca-context-inheritable.xml |  32 ++
 .../core/spring-core-registry-core-context.xml     |   4 +
 .../agent/api/routing/NetworkElementCommand.java   |  13 +
 .../virtualnetwork/VirtualRoutingResource.java     |  44 ++
 .../cloudstack/ca/SetupCertificateAnswer.java      |  12 +-
 .../cloudstack/ca/SetupCertificateCommand.java     |  99 +++++
 .../apache/cloudstack/ca/SetupKeyStoreCommand.java |  75 ++++
 .../apache/cloudstack/ca/SetupKeystoreAnswer.java  |  20 +-
 debian/cloudstack-management.postinst              |   3 -
 developer/developer-prefill.sql                    |   5 +
 .../service/NetworkOrchestrationService.java       |   2 +
 .../com/cloud/agent/manager/AgentManagerImpl.java  |   6 +-
 .../agent/manager/ClusteredAgentManagerImpl.java   |  21 +-
 .../com/cloud/vm/VirtualMachineManagerImpl.java    |  27 +-
 .../engine/orchestration/NetworkOrchestrator.java  |  35 ++
 .../spring-engine-schema-core-daos-context.xml     |   1 +
 engine/schema/src/com/cloud/certificate/CrlVO.java |  85 ++++
 .../src/com/cloud/certificate/dao/CrlDao.java      |  13 +-
 .../src/com/cloud/certificate/dao/CrlDaoImpl.java  |  57 +++
 engine/schema/src/com/cloud/host/dao/HostDao.java  |   2 +
 .../schema/src/com/cloud/host/dao/HostDaoImpl.java |  13 +
 framework/{ => ca}/pom.xml                         |  38 +-
 .../apache/cloudstack/framework/ca/CAProvider.java |  93 +++++
 .../apache/cloudstack/framework/ca/CAService.java  |  19 +-
 .../cloudstack/framework/ca/Certificate.java       |  38 +-
 framework/pom.xml                                  |   1 +
 packaging/centos63/cloud.spec                      |   6 -
 packaging/fedora20/cloud.spec                      |   6 -
 packaging/fedora21/cloud.spec                      |   6 -
 packaging/systemd/cloudstack-management.default    |   4 +-
 .../systemd/cloudstack-management.default.ubuntu   |   4 +-
 {api => plugins/ca/root-ca}/pom.xml                |  36 +-
 .../META-INF/cloudstack/root-ca/module.properties  |  37 +-
 .../cloudstack/root-ca/spring-root-ca-context.xml  |  29 ++
 .../ca/provider/RootCACustomTrustManager.java      | 146 +++++++
 .../cloudstack/ca/provider/RootCAProvider.java     | 465 +++++++++++++++++++++
 .../ca/provider/RootCACustomTrustManagerTest.java  | 111 +++++
 .../cloudstack/ca/provider/RootCAProviderTest.java | 155 +++++++
 .../com/cloud/agent/manager/MockAgentManager.java  |   7 +
 .../cloud/agent/manager/MockAgentManagerImpl.java  |  24 +-
 .../cloud/agent/manager/SimulatorManagerImpl.java  |   6 +
 plugins/pom.xml                                    |   1 +
 .../cloudstack/saml/SAML2AuthManagerImpl.java      |  99 +++--
 .../src/org/apache/cloudstack/saml/SAMLUtils.java  | 158 ++-----
 .../GetServiceProviderMetaDataCmdTest.java         |  30 +-
 .../test/org/apache/cloudstack/SAMLUtilsTest.java  |  20 +-
 .../command/SAML2LoginAPIAuthenticatorCmdTest.java |  35 +-
 .../SAML2LogoutAPIAuthenticatorCmdTest.java        |  19 +-
 pom.xml                                            |   7 +-
 scripts/common/keys/ssl-keys.py                    |  58 ---
 scripts/installer/windows/acs.wxs                  |   3 -
 scripts/installer/windows/client.wxs               |   3 -
 scripts/network/domr/router_proxy.sh               |  10 +-
 scripts/util/keystore-cert-import                  | 100 +++++
 scripts/util/keystore-setup                        |  51 +++
 server/pom.xml                                     |   5 +
 .../core/spring-server-core-managers-context.xml   |   8 +
 server/src/com/cloud/alert/AlertManagerImpl.java   |   3 +-
 .../consoleproxy/ConsoleProxyManagerImpl.java      |   3 +-
 .../kvm/discoverer/LibvirtServerDiscoverer.java    | 116 ++++-
 .../com/cloud/resource/ResourceManagerImpl.java    |   3 +-
 .../com/cloud/server/ConfigurationServerImpl.java  | 126 +-----
 .../org/apache/cloudstack/ca/CAManagerImpl.java    | 428 +++++++++++++++++++
 .../OutOfBandManagementServiceImpl.java            |   6 +
 .../cloudstack/poll/BackgroundPollManagerImpl.java |   6 +-
 .../cloud/server/ConfigurationServerImplTest.java  |  61 +--
 .../test/com/cloud/vpc/MockNetworkManagerImpl.java |   5 +
 .../apache/cloudstack/ca/CABackgroundTaskTest.java | 152 +++++++
 .../apache/cloudstack/ca/CAManagerImplTest.java    | 119 ++++++
 .../poll/BackgroundPollManagerImplTest.java        |   6 +
 .../SecondaryStorageManagerImpl.java               |   3 +-
 .../resource/NfsSecondaryStorageResource.java      | 136 +++---
 setup/db/db/schema-41000to41100.sql                |  15 +
 setup/db/server-setup.sql                          |   3 +
 setup/db/server-setup.xml                          |   7 +
 .../debian/config/opt/cloud/bin/patchsystemvm.sh   |   4 -
 systemvm/pom.xml                                   |   5 +
 systemvm/systemvm-descriptor.xml                   |   9 +
 test/integration/smoke/test_certauthority_root.py  | 229 ++++++++++
 tools/apidoc/gen_toc.py                            |   3 +-
 tools/travis/before_install.sh                     |   7 +-
 ui/css/cloudstack3.css                             |  27 ++
 ui/images/sprites.png                              | Bin 198421 -> 207062 bytes
 ui/index.html                                      |   1 +
 ui/scripts/ui-custom/ca.js                         |  53 +++
 utils/pom.xml                                      |   9 +
 .../src/main/java/com/cloud/utils/StringUtils.java |   8 +
 .../utils/exception/TaskExecutionException.java    |   2 +-
 utils/src/main/java/com/cloud/utils/nio/Link.java  | 175 ++++----
 .../main/java/com/cloud/utils/nio/NioClient.java   |   2 +-
 .../java/com/cloud/utils/nio/NioConnection.java    |  27 +-
 .../main/java/com/cloud/utils/nio/NioServer.java   |   4 +-
 .../main/java/com/cloud/utils/script/Script.java   |   3 +-
 .../java/com/cloud/utils/ssh/SSHCmdHelper.java     |  93 +++--
 .../cloudstack/utils/security/CertUtils.java       | 240 +++++++++++
 .../cloudstack/utils/security/KeyStoreUtils.java   |  70 ++++
 .../test/java/com/cloud/utils/StringUtilsTest.java |  13 +
 .../java/com/cloud/utils/testcase/NioTest.java     |   2 +-
 .../cloudstack/utils/security/CertUtilsTest.java   | 118 ++++++
 124 files changed, 4954 insertions(+), 914 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 4301d75..b25becb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -34,6 +34,7 @@ env:
   matrix:
     - TESTS="smoke/test_affinity_groups
              smoke/test_affinity_groups_projects
+             smoke/test_certauthority_root
              smoke/test_deploy_vgpu_enabled_vm
              smoke/test_deploy_vm_iso
              smoke/test_deploy_vm_root_resize
diff --git a/agent/src/com/cloud/agent/Agent.java b/agent/src/com/cloud/agent/Agent.java
index 7fab5f4..7e80220 100644
--- a/agent/src/com/cloud/agent/Agent.java
+++ b/agent/src/com/cloud/agent/Agent.java
@@ -16,12 +16,14 @@
 // under the License.
 package com.cloud.agent;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.nio.channels.ClosedChannelException;
+import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -35,7 +37,13 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.naming.ConfigurationException;
 
+import org.apache.cloudstack.ca.SetupCertificateAnswer;
+import org.apache.cloudstack.ca.SetupCertificateCommand;
+import org.apache.cloudstack.ca.SetupKeyStoreCommand;
+import org.apache.cloudstack.ca.SetupKeystoreAnswer;
 import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.commons.io.FileUtils;
 import org.apache.log4j.Logger;
 import org.slf4j.MDC;
 
@@ -68,6 +76,7 @@ import com.cloud.utils.nio.NioConnection;
 import com.cloud.utils.nio.Task;
 import com.cloud.utils.script.OutputInterpreter;
 import com.cloud.utils.script.Script;
+import com.google.common.base.Strings;
 
 /**
  * @config
@@ -126,6 +135,9 @@ public class Agent implements HandlerFactory, IAgentControl {
     private final ThreadPoolExecutor _ugentTaskPool;
     ExecutorService _executor;
 
+    private String _keystoreSetupPath;
+    private String _keystoreCertImportPath;
+
     // for simulator use only
     public Agent(final IAgentShell shell) {
         _shell = shell;
@@ -166,7 +178,8 @@ public class Agent implements HandlerFactory, IAgentControl {
             throw new ConfigurationException("Unable to configure " + _resource.getName());
         }
 
-        _connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this);
+        final String host = _shell.getHost();
+        _connection = new NioClient("Agent", host, _shell.getPort(), _shell.getWorkers(), this);
 
         // ((NioClient)_connection).setBindAddress(_shell.getPrivateIp());
 
@@ -182,7 +195,7 @@ public class Agent implements HandlerFactory, IAgentControl {
                         "agentRequest-Handler"));
 
         s_logger.info("Agent [id = " + (_id != null ? _id : "new") + " : type = " + getResourceName() + " : zone = " + _shell.getZone() + " : pod = " + _shell.getPod() +
-                " : workers = " + _shell.getWorkers() + " : host = " + _shell.getHost() + " : port = " + _shell.getPort());
+                " : workers = " + _shell.getWorkers() + " : host = " + host + " : port = " + _shell.getPort());
     }
 
     public String getVersion() {
@@ -224,6 +237,16 @@ public class Agent implements HandlerFactory, IAgentControl {
             throw new CloudRuntimeException("Unable to start the resource: " + _resource.getName());
         }
 
+        _keystoreSetupPath = Script.findScript("scripts/util/", KeyStoreUtils.keyStoreSetupScript);
+        if (_keystoreSetupPath == null) {
+            throw new CloudRuntimeException(String.format("Unable to find the '%s' script", KeyStoreUtils.keyStoreSetupScript));
+        }
+
+        _keystoreCertImportPath = Script.findScript("scripts/util/", KeyStoreUtils.keyStoreImportScript);
+        if (_keystoreCertImportPath == null) {
+            throw new CloudRuntimeException(String.format("Unable to find the '%s' script", KeyStoreUtils.keyStoreImportScript));
+        }
+
         try {
             _connection.start();
         } catch (final NioConnectionException e) {
@@ -231,8 +254,10 @@ public class Agent implements HandlerFactory, IAgentControl {
             s_logger.info("Attempted to connect to the server, but received an unexpected exception, trying again...");
         }
         while (!_connection.isStartup()) {
+            final String host = _shell.getHost();
             _shell.getBackoffAlgorithm().waitBeforeRetry();
-            _connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this);
+            _connection = new NioClient("Agent", host, _shell.getPort(), _shell.getWorkers(), this);
+            s_logger.info("Connecting to host:" + host);
             try {
                 _connection.start();
             } catch (final NioConnectionException e) {
@@ -408,14 +433,21 @@ public class Agent implements HandlerFactory, IAgentControl {
             _shell.getBackoffAlgorithm().waitBeforeRetry();
         }
 
-        _connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this);
+        final String host = _shell.getHost();
         do {
-            s_logger.info("Reconnecting...");
+            _connection = new NioClient("Agent", host, _shell.getPort(), _shell.getWorkers(), this);
+            s_logger.info("Reconnecting to host:" + host);
             try {
                 _connection.start();
             } catch (final NioConnectionException e) {
                 s_logger.warn("NIO Connection Exception  " + e);
                 s_logger.info("Attempted to connect to the server, but received an unexpected exception, trying again...");
+                _connection.stop();
+                try {
+                    _connection.cleanUp();
+                } catch (final IOException ex) {
+                    s_logger.warn("Fail to clean up old connection. " + ex);
+                }
             }
             _shell.getBackoffAlgorithm().waitBeforeRetry();
         } while (!_connection.isStartup());
@@ -515,7 +547,10 @@ public class Agent implements HandlerFactory, IAgentControl {
                             s_logger.warn("No handler found to process cmd: " + cmd.toString());
                             answer = new AgentControlAnswer(cmd);
                         }
-
+                    } else if (cmd instanceof SetupKeyStoreCommand && ((SetupKeyStoreCommand) cmd).isHandleByAgent()) {
+                        answer = setupAgentKeystore((SetupKeyStoreCommand) cmd);
+                    } else if (cmd instanceof SetupCertificateCommand && ((SetupCertificateCommand) cmd).isHandleByAgent()) {
+                        answer = setupAgentCertificate((SetupCertificateCommand) cmd);
                     } else {
                         if (cmd instanceof ReadyCommand) {
                             processReadyCommand(cmd);
@@ -565,6 +600,86 @@ public class Agent implements HandlerFactory, IAgentControl {
         }
     }
 
+    public Answer setupAgentKeystore(final SetupKeyStoreCommand cmd) {
+        final String keyStorePassword = cmd.getKeystorePassword();
+        final long validityDays = cmd.getValidityDays();
+
+        s_logger.debug("Setting up agent keystore file and generating CSR");
+
+        final File agentFile = PropertiesUtil.findConfigFile("agent.properties");
+        if (agentFile == null) {
+            return new Answer(cmd, false, "Failed to find agent.properties file");
+        }
+        final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultKeystoreFile;
+        final String csrFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultCsrFile;
+
+        String storedPassword = _shell.getPersistentProperty(null, KeyStoreUtils.passphrasePropertyName);
+        if (Strings.isNullOrEmpty(storedPassword)) {
+            storedPassword = keyStorePassword;
+            _shell.setPersistentProperty(null, KeyStoreUtils.passphrasePropertyName, storedPassword);
+        }
+
+        Script script = new Script(_keystoreSetupPath, 60000, s_logger);
+        script.add(agentFile.getAbsolutePath());
+        script.add(keyStoreFile);
+        script.add(storedPassword);
+        script.add(String.valueOf(validityDays));
+        script.add(csrFile);
+        String result = script.execute();
+        if (result != null) {
+            throw new CloudRuntimeException("Unable to setup keystore file");
+        }
+
+        final String csrString;
+        try {
+            csrString = FileUtils.readFileToString(new File(csrFile), Charset.defaultCharset());
+        } catch (IOException e) {
+            throw new CloudRuntimeException("Unable to read generated CSR file", e);
+        }
+        return new SetupKeystoreAnswer(csrString);
+    }
+
+    private Answer setupAgentCertificate(final SetupCertificateCommand cmd) {
+        final String certificate = cmd.getCertificate();
+        final String privateKey = cmd.getPrivateKey();
+        final String caCertificates = cmd.getCaCertificates();
+
+        s_logger.debug("Importing received certificate to agent's keystore");
+
+        final File agentFile = PropertiesUtil.findConfigFile("agent.properties");
+        if (agentFile == null) {
+            return new Answer(cmd, false, "Failed to find agent.properties file");
+        }
+        final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultKeystoreFile;
+        final String certFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultCertFile;
+        final String privateKeyFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultPrivateKeyFile;
+        final String caCertFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultCaCertFile;
+
+        try {
+            FileUtils.writeStringToFile(new File(certFile), certificate, Charset.defaultCharset());
+            FileUtils.writeStringToFile(new File(caCertFile), caCertificates, Charset.defaultCharset());
+            s_logger.debug("Saved received client certificate to: " + certFile);
+        } catch (IOException e) {
+            throw new CloudRuntimeException("Unable to save received agent client and ca certificates", e);
+        }
+
+        Script script = new Script(_keystoreCertImportPath, 60000, s_logger);
+        script.add(agentFile.getAbsolutePath());
+        script.add(keyStoreFile);
+        script.add(KeyStoreUtils.agentMode);
+        script.add(certFile);
+        script.add("");
+        script.add(caCertFile);
+        script.add("");
+        script.add(privateKeyFile);
+        script.add(privateKey);
+        String result = script.execute();
+        if (result != null) {
+            throw new CloudRuntimeException("Unable to import certificate into keystore file");
+        }
+        return new SetupCertificateAnswer(true);
+    }
+
     public void processResponse(final Response response, final Link link) {
         final Answer answer = response.getAnswer();
         if (s_logger.isDebugEnabled()) {
diff --git a/agent/src/com/cloud/agent/AgentShell.java b/agent/src/com/cloud/agent/AgentShell.java
index 5e0da68..5950bc7 100644
--- a/agent/src/com/cloud/agent/AgentShell.java
+++ b/agent/src/com/cloud/agent/AgentShell.java
@@ -67,6 +67,7 @@ public class AgentShell implements IAgentShell, Daemon {
     private int _proxyPort;
     private int _workers;
     private String _guid;
+    private int _hostCounter = 0;
     private int _nextAgentId = 1;
     private volatile boolean _exit = false;
     private int _pingRetries;
@@ -107,7 +108,17 @@ public class AgentShell implements IAgentShell, Daemon {
 
     @Override
     public String getHost() {
-        return _host;
+        final String[] hosts = _host.split(",");
+        if (_hostCounter >= hosts.length) {
+            _hostCounter = 0;
+        }
+        final String host = hosts[_hostCounter % hosts.length];
+        _hostCounter++;
+        return host;
+    }
+
+    public void setHost(final String host) {
+        _host = host;
     }
 
     @Override
diff --git a/agent/src/com/cloud/agent/dao/impl/PropertiesStorage.java b/agent/src/com/cloud/agent/dao/impl/PropertiesStorage.java
index df1b1ea..e9eac64 100644
--- a/agent/src/com/cloud/agent/dao/impl/PropertiesStorage.java
+++ b/agent/src/com/cloud/agent/dao/impl/PropertiesStorage.java
@@ -51,6 +51,9 @@ public class PropertiesStorage implements StorageComponent {
 
     @Override
     public synchronized void persist(String key, String value) {
+        if (!loadFromFile(_file)) {
+            s_logger.error("Failed to load changes and then write to them");
+        }
         _properties.setProperty(key, value);
         FileOutputStream output = null;
         try {
@@ -65,6 +68,20 @@ public class PropertiesStorage implements StorageComponent {
         }
     }
 
+    private synchronized boolean loadFromFile(final File file) {
+        try {
+            PropertiesUtil.loadFromFile(_properties, file);
+            _file = file;
+        } catch (FileNotFoundException e) {
+            s_logger.error("How did we get here? ", e);
+            return false;
+        } catch (IOException e) {
+            s_logger.error("IOException: ", e);
+            return false;
+        }
+        return true;
+    }
+
     @Override
     public synchronized boolean configure(String name, Map<String, Object> params) {
         _name = name;
@@ -86,17 +103,7 @@ public class PropertiesStorage implements StorageComponent {
                 return false;
             }
         }
-        try {
-            PropertiesUtil.loadFromFile(_properties, file);
-            _file = file;
-        } catch (FileNotFoundException e) {
-            s_logger.error("How did we get here? ", e);
-            return false;
-        } catch (IOException e) {
-            s_logger.error("IOException: ", e);
-            return false;
-        }
-        return true;
+        return loadFromFile(file);
     }
 
     @Override
diff --git a/agent/src/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java b/agent/src/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java
index 08f0982..1fed3be 100644
--- a/agent/src/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java
+++ b/agent/src/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java
@@ -32,11 +32,8 @@ import java.util.Properties;
 
 import javax.naming.ConfigurationException;
 
-import org.apache.log4j.Logger;
-
-import com.google.gson.Gson;
-
 import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.log4j.Logger;
 
 import com.cloud.agent.Agent.ExitStatus;
 import com.cloud.agent.api.AgentControlAnswer;
@@ -64,6 +61,7 @@ import com.cloud.resource.ServerResourceBase;
 import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.net.NetUtils;
 import com.cloud.utils.script.Script;
+import com.google.gson.Gson;
 
 /**
  *
@@ -240,9 +238,11 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe
         _proxyVmId = NumbersUtil.parseLong(value, 0);
 
         if (_localgw != null) {
-            String mgmtHost = (String)params.get("host");
+            String mgmtHosts = (String)params.get("host");
             if (_eth1ip != null) {
-                addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost);
+                for (final String mgmtHost : mgmtHosts.split(",")) {
+                    addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost);
+                }
                 String internalDns1 = (String) params.get("internaldns1");
                 if (internalDns1 == null) {
                     s_logger.warn("No DNS entry found during configuration of NfsSecondaryStorage");
diff --git a/agent/test/com/cloud/agent/AgentShellTest.java b/agent/test/com/cloud/agent/AgentShellTest.java
index 5baa7bf..8ceba45 100644
--- a/agent/test/com/cloud/agent/AgentShellTest.java
+++ b/agent/test/com/cloud/agent/AgentShellTest.java
@@ -16,6 +16,8 @@
 // under the License.
 package com.cloud.agent;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.UUID;
 
 import javax.naming.ConfigurationException;
@@ -23,6 +25,8 @@ import javax.naming.ConfigurationException;
 import org.junit.Assert;
 import org.junit.Test;
 
+import com.cloud.utils.StringUtils;
+
 public class AgentShellTest {
     @Test
     public void parseCommand() throws ConfigurationException {
@@ -44,4 +48,15 @@ public class AgentShellTest {
         Assert.assertNotNull(shell.getProperties());
         Assert.assertFalse(shell.getProperties().entrySet().isEmpty());
     }
+
+    @Test
+    public void testGetHost() {
+        AgentShell shell = new AgentShell();
+        List<String> hosts = Arrays.asList("10.1.1.1", "20.2.2.2", "30.3.3.3", "2001:db8::1");
+        shell.setHost(StringUtils.listToCsvTags(hosts));
+        for (String host : hosts) {
+            Assert.assertEquals(host, shell.getHost());
+        }
+        Assert.assertEquals(shell.getHost(), hosts.get(0));
+    }
 }
diff --git a/api/pom.xml b/api/pom.xml
index 93859f1..6352e11 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -51,6 +51,11 @@
       <artifactId>cloud-framework-config</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-framework-ca</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java
index 32a3138..66bfbfe 100644
--- a/api/src/com/cloud/event/EventTypes.java
+++ b/api/src/com/cloud/event/EventTypes.java
@@ -16,6 +16,14 @@
 // under the License.
 package com.cloud.event;
 
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cloudstack.acl.Role;
+import org.apache.cloudstack.acl.RolePermission;
+import org.apache.cloudstack.config.Configuration;
+import org.apache.cloudstack.usage.Usage;
+
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.Pod;
 import com.cloud.dc.StorageNetworkIpRange;
@@ -54,10 +62,10 @@ import com.cloud.offering.NetworkOffering;
 import com.cloud.offering.ServiceOffering;
 import com.cloud.projects.Project;
 import com.cloud.server.ResourceTag;
-import com.cloud.storage.StoragePool;
 import com.cloud.storage.GuestOS;
 import com.cloud.storage.GuestOSHypervisor;
 import com.cloud.storage.Snapshot;
+import com.cloud.storage.StoragePool;
 import com.cloud.storage.Volume;
 import com.cloud.storage.snapshot.SnapshotPolicy;
 import com.cloud.template.VirtualMachineTemplate;
@@ -66,13 +74,6 @@ import com.cloud.user.User;
 import com.cloud.vm.Nic;
 import com.cloud.vm.NicSecondaryIp;
 import com.cloud.vm.VirtualMachine;
-import org.apache.cloudstack.acl.Role;
-import org.apache.cloudstack.acl.RolePermission;
-import org.apache.cloudstack.config.Configuration;
-import org.apache.cloudstack.usage.Usage;
-
-import java.util.HashMap;
-import java.util.Map;
 
 public class EventTypes {
 
@@ -176,6 +177,11 @@ public class EventTypes {
     public static final String EVENT_ROLE_PERMISSION_UPDATE = "ROLE.PERMISSION.UPDATE";
     public static final String EVENT_ROLE_PERMISSION_DELETE = "ROLE.PERMISSION.DELETE";
 
+    // CA events
+    public static final String EVENT_CA_CERTIFICATE_ISSUE = "CA.CERTIFICATE.ISSUE";
+    public static final String EVENT_CA_CERTIFICATE_REVOKE = "CA.CERTIFICATE.REVOKE";
+    public static final String EVENT_CA_CERTIFICATE_PROVISION = "CA.CERTIFICATE.PROVISION";
+
     // Account events
     public static final String EVENT_ACCOUNT_ENABLE = "ACCOUNT.ENABLE";
     public static final String EVENT_ACCOUNT_DISABLE = "ACCOUNT.DISABLE";
diff --git a/api/src/org/apache/cloudstack/alert/AlertService.java b/api/src/org/apache/cloudstack/alert/AlertService.java
index ad711ec..8412969 100644
--- a/api/src/org/apache/cloudstack/alert/AlertService.java
+++ b/api/src/org/apache/cloudstack/alert/AlertService.java
@@ -67,6 +67,7 @@ public interface AlertService {
         public static final AlertType ALERT_TYPE_SYNC = new AlertType((short)27, "ALERT.TYPE.SYNC", true);
         public static final AlertType ALERT_TYPE_UPLOAD_FAILED = new AlertType((short)28, "ALERT.UPLOAD.FAILED", true);
         public static final AlertType ALERT_TYPE_OOBM_AUTH_ERROR = new AlertType((short)29, "ALERT.OOBM.AUTHERROR", true);
+        public static final AlertType ALERT_TYPE_CA_CERT = new AlertType((short)31, "ALERT.CA.CERT", true);
 
         public short getType() {
             return type;
diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java
index b77b83a..da39ff8 100644
--- a/api/src/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/org/apache/cloudstack/api/ApiConstants.java
@@ -37,10 +37,12 @@ public class ApiConstants {
     public static final String BYTES_WRITE_RATE = "byteswriterate";
     public static final String CATEGORY = "category";
     public static final String CAN_REVERT = "canrevert";
+    public static final String CA_CERTIFICATES = "cacertificates";
     public static final String CERTIFICATE = "certificate";
     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 CSR = "csr";
     public static final String PRIVATE_KEY = "privatekey";
     public static final String DOMAIN_SUFFIX = "domainsuffix";
     public static final String DNS_SEARCH_ORDER = "dnssearchorder";
@@ -54,6 +56,7 @@ public class ApiConstants {
     public static final String CLUSTER_ID = "clusterid";
     public static final String CLUSTER_NAME = "clustername";
     public static final String CLUSTER_TYPE = "clustertype";
+    public static final String CN = "cn";
     public static final String COMMAND = "command";
     public static final String CMD_EVENT_TYPE = "cmdeventtype";
     public static final String COMPONENT = "component";
@@ -216,6 +219,7 @@ public class ApiConstants {
     public static final String PUBLIC_END_PORT = "publicendport";
     public static final String PUBLIC_ZONE = "publiczone";
     public static final String RECEIVED_BYTES = "receivedbytes";
+    public static final String RECONNECT = "reconnect";
     public static final String REQUIRES_HVM = "requireshvm";
     public static final String RESOURCE_TYPE = "resourcetype";
     public static final String RESPONSE = "response";
@@ -234,6 +238,7 @@ public class ApiConstants {
     public static final String SECURITY_GROUP_ID = "securitygroupid";
     public static final String SENT = "sent";
     public static final String SENT_BYTES = "sentbytes";
+    public static final String SERIAL = "serial";
     public static final String SERVICE_OFFERING_ID = "serviceofferingid";
     public static final String SESSIONKEY = "sessionkey";
     public static final String SHOW_CAPACITIES = "showcapacities";
diff --git a/api/src/org/apache/cloudstack/api/command/admin/ca/IssueCertificateCmd.java b/api/src/org/apache/cloudstack/api/command/admin/ca/IssueCertificateCmd.java
new file mode 100644
index 0000000..8926829
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/command/admin/ca/IssueCertificateCmd.java
@@ -0,0 +1,162 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.command.admin.ca;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.CertificateResponse;
+import org.apache.cloudstack.ca.CAManager;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.ca.Certificate;
+import org.apache.cloudstack.utils.security.CertUtils;
+import org.apache.log4j.Logger;
+
+import com.cloud.event.EventTypes;
+import com.google.common.base.Strings;
+
+@APICommand(name = IssueCertificateCmd.APINAME,
+        description = "Issues a client certificate using configured or provided CA plugin",
+        responseObject = CertificateResponse.class,
+        requestHasSensitiveInfo = true,
+        responseHasSensitiveInfo = true,
+        since = "4.11.0",
+        authorized = {RoleType.Admin})
+public class IssueCertificateCmd extends BaseAsyncCmd {
+    private static final Logger LOG = Logger.getLogger(IssueCertificateCmd.class);
+
+    public static final String APINAME = "issueCertificate";
+
+    @Inject
+    private CAManager caManager;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.CSR, type = BaseCmd.CommandType.STRING, description = "The certificate signing request (in pem format), if CSR is not provided then configured/provided options are considered", length = 65535)
+    private String csr;
+
+    @Parameter(name = ApiConstants.DOMAIN, type = BaseCmd.CommandType.STRING, description = "Comma separated list of domains, the certificate should be issued for. When csr is not provided, the first domain is used as a subject/CN")
+    private String domains;
+
+    @Parameter(name = ApiConstants.IP_ADDRESS, type = BaseCmd.CommandType.STRING, description = "Comma separated list of IP addresses, the certificate should be issued for")
+    private String addresses;
+
+    @Parameter(name = ApiConstants.DURATION, type = CommandType.INTEGER, description = "Certificate validity duration in number of days, when not provided the default configured value will be used")
+    private Integer validityDuration;
+
+    @Parameter(name = ApiConstants.PROVIDER, type = BaseCmd.CommandType.STRING, description = "Name of the CA service provider, otherwise the default configured provider plugin will be used")
+    private String provider;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public String getCsr() {
+        return csr;
+    }
+
+    private List<String> processList(final String string) {
+        final List<String> list = new ArrayList<>();
+        if (!Strings.isNullOrEmpty(string)) {
+            for (final String address: string.split(",")) {
+                list.add(address.trim());
+            }
+        }
+        return list;
+    }
+
+    public List<String> getAddresses() {
+        return processList(addresses);
+    }
+
+    public List<String> getDomains() {
+        return processList(domains);
+    }
+
+    public Integer getValidityDuration() {
+        return validityDuration;
+    }
+
+    public String getProvider() {
+        return provider;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        if (Strings.isNullOrEmpty(getCsr()) && getDomains().isEmpty()) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide the domains or the CSR, none of them are provided");
+        }
+        final Certificate certificate = caManager.issueCertificate(getCsr(), getDomains(), getAddresses(), getValidityDuration(), getProvider());
+        if (certificate == null) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to issue client certificate with given provider");
+        }
+
+        final CertificateResponse certificateResponse = new CertificateResponse();
+        try {
+            certificateResponse.setCertificate(CertUtils.x509CertificateToPem(certificate.getClientCertificate()));
+            if (certificate.getPrivateKey() != null) {
+                certificateResponse.setPrivateKey(CertUtils.privateKeyToPem(certificate.getPrivateKey()));
+            }
+            if (certificate.getCaCertificates() != null) {
+                certificateResponse.setCaCertificate(CertUtils.x509CertificatesToPem(certificate.getCaCertificates()));
+            }
+        } catch (final IOException e) {
+            LOG.error("Failed to generate and convert client certificate(s) to PEM due to error: ", e);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to process and return client certificate");
+        }
+        certificateResponse.setResponseName(getCommandName());
+        setResponseObject(certificateResponse);
+    }
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccount().getId();
+    }
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_CA_CERTIFICATE_ISSUE;
+    }
+
+    @Override
+    public String getEventDescription() {
+        return "issuing certificate for domain(s)=" + domains + ", ip(s)=" + addresses;
+    }
+}
diff --git a/api/src/org/apache/cloudstack/api/command/admin/ca/ListCAProvidersCmd.java b/api/src/org/apache/cloudstack/api/command/admin/ca/ListCAProvidersCmd.java
new file mode 100644
index 0000000..e1e8e37
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/command/admin/ca/ListCAProvidersCmd.java
@@ -0,0 +1,102 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.command.admin.ca;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.CAProviderResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.ca.CAManager;
+import org.apache.cloudstack.framework.ca.CAProvider;
+
+import com.cloud.user.Account;
+
+@APICommand(name = ListCAProvidersCmd.APINAME,
+        description = "Lists available certificate authority providers in CloudStack",
+        responseObject = CAProviderResponse.class,
+        requestHasSensitiveInfo = false,
+        responseHasSensitiveInfo = false,
+        since = "4.11.0",
+        authorized = {RoleType.Admin})
+public class ListCAProvidersCmd extends BaseCmd {
+    public static final String APINAME = "listCAProviders";
+
+    @Inject
+    private CAManager caManager;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List CA service provider by name")
+    private String name;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public String getName() {
+        return name;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    private void setupResponse(final List<CAProvider> providers) {
+        final ListResponse<CAProviderResponse> response = new ListResponse<>();
+        final List<CAProviderResponse> responses = new ArrayList<>();
+        for (final CAProvider provider : providers) {
+            if (provider == null || (getName() != null && !provider.getProviderName().equals(getName()))) {
+                continue;
+            }
+            final CAProviderResponse caProviderResponse = new CAProviderResponse();
+            caProviderResponse.setName(provider.getProviderName());
+            caProviderResponse.setDescription(provider.getDescription());
+            caProviderResponse.setObjectName("caprovider");
+            responses.add(caProviderResponse);
+        }
+        response.setResponses(responses);
+        response.setResponseName(getCommandName());
+        setResponseObject(response);
+    }
+
+    @Override
+    public void execute() {
+        final List<CAProvider> caProviders = caManager.getCaProviders();
+        setupResponse(caProviders);
+    }
+}
diff --git a/api/src/org/apache/cloudstack/api/command/admin/ca/ListCaCertificateCmd.java b/api/src/org/apache/cloudstack/api/command/admin/ca/ListCaCertificateCmd.java
new file mode 100644
index 0000000..1baa841
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/command/admin/ca/ListCaCertificateCmd.java
@@ -0,0 +1,90 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.command.admin.ca;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.CertificateResponse;
+import org.apache.cloudstack.ca.CAManager;
+
+import com.cloud.user.Account;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+@APICommand(name = ListCaCertificateCmd.APINAME,
+        description = "Lists the CA public certificate(s) as support by the configured/provided CA plugin",
+        responseObject = CertificateResponse.class,
+        requestHasSensitiveInfo = false,
+        responseHasSensitiveInfo = false,
+        since = "4.11.0",
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class ListCaCertificateCmd extends BaseCmd {
+    public static final String APINAME = "listCaCertificate";
+
+    @Inject
+    private CAManager caManager;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the CA service provider, otherwise the default configured provider plugin will be used")
+    private String provider;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public String getProvider() {
+        return provider;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        final String caCertificates;
+        try {
+            caCertificates = caManager.getCaCertificate(getProvider());
+        } catch (final IOException e) {
+            throw new CloudRuntimeException("Failed to get CA certificates for given CA provider");
+        }
+        final CertificateResponse certificateResponse = new CertificateResponse("cacertificates");
+        certificateResponse.setCertificate(caCertificates);
+        certificateResponse.setResponseName(getCommandName());
+        setResponseObject(certificateResponse);
+    }
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_TYPE_NORMAL;
+    }
+}
diff --git a/api/src/org/apache/cloudstack/api/command/admin/ca/ProvisionCertificateCmd.java b/api/src/org/apache/cloudstack/api/command/admin/ca/ProvisionCertificateCmd.java
new file mode 100644
index 0000000..2745f07
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/command/admin/ca/ProvisionCertificateCmd.java
@@ -0,0 +1,125 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.command.admin.ca;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandJobType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.HostResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.ca.CAManager;
+import org.apache.cloudstack.context.CallContext;
+
+import com.cloud.event.EventTypes;
+import com.cloud.host.Host;
+
+@APICommand(name = ProvisionCertificateCmd.APINAME,
+        description = "Issues and propagates client certificate on a connected host/agent using configured CA plugin",
+        responseObject = SuccessResponse.class,
+        requestHasSensitiveInfo = false,
+        responseHasSensitiveInfo = false,
+        since = "4.11.0",
+        authorized = {RoleType.Admin})
+public class ProvisionCertificateCmd extends BaseAsyncCmd {
+    public static final String APINAME = "provisionCertificate";
+
+    @Inject
+    private CAManager caManager;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, required = true, entityType = HostResponse.class,
+            description = "The host/agent uuid to which the certificate has to be provisioned (issued and propagated)")
+    private Long hostId;
+
+    @Parameter(name = ApiConstants.RECONNECT, type = CommandType.BOOLEAN,
+            description = "Whether to attempt reconnection with host/agent after successful deployment of certificate. When option is not provided, configured global setting is used")
+    private Boolean reconnect;
+
+    @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING,
+            description = "Name of the CA service provider, otherwise the default configured provider plugin will be used")
+    private String provider;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getHostId() {
+        return hostId;
+    }
+
+    public Boolean getReconnect() {
+        return reconnect;
+    }
+
+    public String getProvider() {
+        return provider;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        final Host host = _resourceService.getHost(getHostId());
+        if (host == null) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find host by ID: " + getHostId());
+        }
+
+        boolean result = caManager.provisionCertificate(host, getReconnect(), getProvider());
+        SuccessResponse response = new SuccessResponse(getCommandName());
+        response.setSuccess(result);
+        setResponseObject(response);
+    }
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccount().getId();
+    }
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_CA_CERTIFICATE_PROVISION;
+    }
+
+    @Override
+    public String getEventDescription() {
+        return "provisioning certificate for host id=" + hostId + " using provider=" + provider;
+    }
+
+    @Override
+    public ApiCommandJobType getInstanceType() {
+        return ApiCommandJobType.Host;
+    }
+}
diff --git a/api/src/org/apache/cloudstack/api/command/admin/ca/RevokeCertificateCmd.java b/api/src/org/apache/cloudstack/api/command/admin/ca/RevokeCertificateCmd.java
new file mode 100644
index 0000000..0f154f0
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/command/admin/ca/RevokeCertificateCmd.java
@@ -0,0 +1,116 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.command.admin.ca;
+
+import java.math.BigInteger;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.ca.CAManager;
+import org.apache.cloudstack.context.CallContext;
+
+import com.cloud.event.EventTypes;
+import com.google.common.base.Strings;
+
+@APICommand(name = RevokeCertificateCmd.APINAME,
+        description = "Revokes certificate using configured CA plugin",
+        responseObject = SuccessResponse.class,
+        requestHasSensitiveInfo = true,
+        responseHasSensitiveInfo = false,
+        since = "4.11.0",
+        authorized = {RoleType.Admin})
+public class RevokeCertificateCmd extends BaseAsyncCmd {
+
+    public static final String APINAME = "revokeCertificate";
+
+    @Inject
+    private CAManager caManager;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.SERIAL, type = BaseCmd.CommandType.STRING, required = true, description = "The certificate serial number, as a hex value")
+    private String serial;
+
+    @Parameter(name = ApiConstants.CN, type = BaseCmd.CommandType.STRING, description = "The certificate CN")
+    private String cn;
+
+    @Parameter(name = ApiConstants.PROVIDER, type = BaseCmd.CommandType.STRING, description = "Name of the CA service provider, otherwise the default configured provider plugin will be used")
+    private String provider;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public BigInteger getSerialBigInteger() {
+        if (Strings.isNullOrEmpty(serial)) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Certificate serial cannot be empty");
+        }
+        return new BigInteger(serial, 16);
+    }
+
+    public String getCn() {
+        return cn;
+    }
+
+    public String getProvider() {
+        return provider;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        boolean result = caManager.revokeCertificate(getSerialBigInteger(), getCn(), getProvider());
+        SuccessResponse response = new SuccessResponse(getCommandName());
+        response.setSuccess(result);
+        setResponseObject(response);
+    }
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccount().getId();
+    }
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_CA_CERTIFICATE_REVOKE;
+    }
+
+    @Override
+    public String getEventDescription() {
+        return "revoking certificate with serial id=" + serial + ", cn=" + cn;
+    }
+}
diff --git a/api/src/org/apache/cloudstack/api/response/CAProviderResponse.java b/api/src/org/apache/cloudstack/api/response/CAProviderResponse.java
new file mode 100644
index 0000000..94d5882
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/response/CAProviderResponse.java
@@ -0,0 +1,52 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.response;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.EntityReference;
+import org.apache.cloudstack.framework.ca.CAProvider;
+
+@EntityReference(value = CAProvider.class)
+public class CAProviderResponse extends BaseResponse {
+    @SerializedName(ApiConstants.NAME)
+    @Param(description = "the CA service provider name")
+    private String name;
+
+    @SerializedName(ApiConstants.DESCRIPTION)
+    @Param(description = "the description of the CA service provider")
+    private String description;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+}
diff --git a/api/src/org/apache/cloudstack/api/response/CertificateResponse.java b/api/src/org/apache/cloudstack/api/response/CertificateResponse.java
new file mode 100644
index 0000000..f8c3ecc
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/response/CertificateResponse.java
@@ -0,0 +1,58 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.response;
+
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+
+public class CertificateResponse extends BaseResponse {
+    @SerializedName(ApiConstants.CERTIFICATE)
+    @Param(description = "The client certificate")
+    private String certificate = "";
+
+    @SerializedName(ApiConstants.PRIVATE_KEY)
+    @Param(description = "Private key for the certificate")
+    private String privateKey;
+
+    @SerializedName(ApiConstants.CA_CERTIFICATES)
+    @Param(description = "The CA certificate(s)")
+    private String caCertificate;
+
+    public CertificateResponse() {
+        setObjectName("certificates");
+    }
+
+    public CertificateResponse(final String objectName) {
+        setObjectName(objectName);
+    }
+
+    public void setCertificate(final String certificate) {
+        this.certificate = certificate;
+    }
+
+    public void setPrivateKey(final String privateKey) {
+        this.privateKey = privateKey;
+    }
+
+    public void setCaCertificate(final String caCertificate) {
+        this.caCertificate = caCertificate;
+    }
+}
diff --git a/api/src/org/apache/cloudstack/ca/CAManager.java b/api/src/org/apache/cloudstack/ca/CAManager.java
new file mode 100644
index 0000000..c32cfbf
--- /dev/null
+++ b/api/src/org/apache/cloudstack/ca/CAManager.java
@@ -0,0 +1,163 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.ca;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cloudstack.framework.ca.CAProvider;
+import org.apache.cloudstack.framework.ca.CAService;
+import org.apache.cloudstack.framework.ca.Certificate;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+
+import com.cloud.exception.AgentUnavailableException;
+import com.cloud.exception.OperationTimedoutException;
+import com.cloud.host.Host;
+import com.cloud.utils.component.PluggableService;
+
+public interface CAManager extends CAService, Configurable, PluggableService {
+
+    ConfigKey<String> CAProviderPlugin = new ConfigKey<>("Advanced", String.class,
+            "ca.framework.provider.plugin",
+            "root",
+            "The CA provider plugin that is used for secure CloudStack management server-agent communication for encryption and authentication. Restart management server(s) when changed.", true);
+
+    ConfigKey<Integer> CertKeySize = new ConfigKey<>("Advanced", Integer.class,
+                                    "ca.framework.cert.keysize",
+                                    "2048",
+                                    "The key size to be used for random certificate keypair generation.", true);
+
+    ConfigKey<String> CertSignatureAlgorithm = new ConfigKey<>("Advanced", String.class,
+            "ca.framework.cert.signature.algorithm",
+            "SHA256withRSA",
+            "The default signature algorithm to use for certificate generation.", true);
+
+
+    ConfigKey<Integer> CertValidityPeriod = new ConfigKey<>("Advanced", Integer.class,
+                                            "ca.framework.cert.validity.period",
+                                            "365",
+                                            "The validity period of a client certificate in number of days. Set the value to be more than the expiry alert period.", true);
+
+    ConfigKey<Boolean> AutomaticCertRenewal = new ConfigKey<>("Advanced", Boolean.class,
+            "ca.framework.cert.automatic.renewal",
+            "true",
+            "Enable automatic renewal and provisioning of certificate to agents as supported by the configured CA plugin.", true, ConfigKey.Scope.Cluster);
+
+    ConfigKey<Long> CABackgroundJobDelay = new ConfigKey<>("Advanced", Long.class,
+            "ca.framework.background.task.delay",
+            "3600",
+            "The CA framework background task delay in seconds. Background task runs expiry checks and renews certificate if auto-renewal is enabled.", true);
+
+    ConfigKey<Integer> CertExpiryAlertPeriod = new ConfigKey<>("Advanced", Integer.class,
+                                                    "ca.framework.cert.expiry.alert.period",
+                                                    "15",
+                                                    "The number of days before expiry of a client certificate, the validations are checked. Admins are alerted when auto-renewal is not allowed, otherwise auto-renewal is attempted.", true, ConfigKey.Scope.Cluster);
+
+    /**
+     * Returns a list of available CA provider plugins
+     * @return returns list of CAProvider
+     */
+    List<CAProvider> getCaProviders();
+
+    /**
+     * Returns a map of active agents/hosts certificates
+     * @return returns a non-null map
+     */
+    Map<String, X509Certificate> getActiveCertificatesMap();
+
+    /**
+     * Checks whether the configured CA plugin can provision/create certificates
+     * @return returns certificate creation capability
+     */
+    boolean canProvisionCertificates();
+
+    /**
+     * Returns PEM-encoded chained CA certificate
+     * @param caProvider
+     * @return returns CA certificate chain string
+     */
+    String getCaCertificate(final String caProvider) throws IOException;
+
+    /**
+     * Issues client Certificate
+     * @param csr
+     * @param ipAddresses
+     * @param domainNames
+     * @param validityDays
+     * @param provider
+     * @return returns Certificate
+     */
+    Certificate issueCertificate(final String csr, final List<String> domainNames, final List<String> ipAddresses, final Integer validityDays, final String provider);
+
+    /**
+     * Revokes certificate from provided serial and CN
+     * @param certSerial
+     * @param certCn
+     * @return returns success/failure as boolean
+     */
+    boolean revokeCertificate(final BigInteger certSerial, final String certCn, final String provider);
+
+    /**
+     * Provisions certificate for given active and connected agent host
+     * @param host
+     * @param provider
+     * @return returns success/failure as boolean
+     */
+    boolean provisionCertificate(final Host host, final Boolean reconnect, final String provider);
+
+    /**
+     * Setups up a new keystore and generates CSR for a host
+     * @param host
+     * @param sshAccessDetails when provided, VirtualRoutingResource uses router proxy to execute commands via SSH in systemvms
+     * @return
+     * @throws AgentUnavailableException
+     * @throws OperationTimedoutException
+     */
+    String generateKeyStoreAndCsr(final Host host, final Map<String, String> sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException;
+
+    /**
+     * Deploys a Certificate payload to a provided host
+     * @param host
+     * @param certificate
+     * @param reconnect when true the host/agent is reconnected on successful deployment of the certificate
+     * @param sshAccessDetails when provided, VirtualRoutingResource uses router proxy to execute commands via SSH in systemvms
+     * @return
+     * @throws AgentUnavailableException
+     * @throws OperationTimedoutException
+     */
+    boolean deployCertificate(final Host host, final Certificate certificate, final Boolean reconnect, final Map<String, String> sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException;
+
+    /**
+     * Removes the host from an internal active client/certificate map
+     * @param host
+     */
+    void purgeHostCertificate(final Host host);
+
+    /**
+     * Sends a CA cert event alert to admins with a subject and a message
+     * @param host
+     * @param subject
+     * @param message
+     */
+    void sendAlert(final Host host, final String subject, final String message);
+
+}
diff --git a/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java b/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java
index 8eea147..5f1b330 100644
--- a/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java
+++ b/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java
@@ -18,4 +18,10 @@
 package org.apache.cloudstack.poll;
 
 public interface BackgroundPollTask extends Runnable {
+    /**
+     * Returns delay in milliseconds between two rounds
+     * When it returns null a default value is used
+     * @return
+     */
+    Long getDelay();
 }
diff --git a/client/pom.xml b/client/pom.xml
index 9b8fa38..7af4324 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -65,6 +65,11 @@
     </dependency>
     <dependency>
       <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-plugin-ca-rootca</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
       <artifactId>cloud-plugin-dedicated-resources</artifactId>
       <version>${project.version}</version>
     </dependency>
@@ -276,6 +281,11 @@
     </dependency>
     <dependency>
       <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-framework-ca</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
       <artifactId>cloud-framework-ipc</artifactId>
       <version>${project.version}</version>
     </dependency>
@@ -478,6 +488,7 @@
           <webAppConfig>
             <contextPath>/client</contextPath>
             <extraClasspath>${project.build.directory}/utilities/scripts/db/;${project.build.directory}/utilities/scripts/db/db/</extraClasspath>
+            <webInfIncludeJarPattern>.*/cloud.*jar$|.*/classes/.*</webInfIncludeJarPattern>
           </webAppConfig>
           
           <systemProperties>
diff --git a/client/tomcatconf/cloudmanagementserver.keystore b/client/tomcatconf/cloudmanagementserver.keystore
deleted file mode 100644
index 3ee4d13..0000000
Binary files a/client/tomcatconf/cloudmanagementserver.keystore and /dev/null differ
diff --git a/client/tomcatconf/server-ssl.xml.in b/client/tomcatconf/server-ssl.xml.in
index 595879f..8368a65 100755
--- a/client/tomcatconf/server-ssl.xml.in
+++ b/client/tomcatconf/server-ssl.xml.in
@@ -94,7 +94,7 @@
                maxThreads="150" scheme="https" secure="true" URIEncoding="UTF-8"
                clientAuth="false" sslProtocol="TLS" 
                keystoreType="JKS"
-               keystoreFile="/etc/cloudstack/management/cloudmanagementserver.keystore"
+               keystoreFile="/etc/cloudstack/management/cloud.jks"
                keystorePass="vmops.com"/>
 			   
     <!-- Define an AJP 1.3 Connector on port 20400 -->
diff --git a/client/tomcatconf/server7-ssl.xml.in b/client/tomcatconf/server7-ssl.xml.in
index 2633bca..d4fe899 100755
--- a/client/tomcatconf/server7-ssl.xml.in
+++ b/client/tomcatconf/server7-ssl.xml.in
@@ -94,7 +94,7 @@
                maxThreads="150" scheme="https" secure="true" URIEncoding="UTF-8"
                clientAuth="false" sslProtocol="TLS"
                keystoreType="JKS"
-               keystoreFile="/etc/cloudstack/management/cloudmanagementserver.keystore"
+               keystoreFile="/etc/cloudstack/management/cloud.jks"
                keystorePass="vmops.com"/>
 
     <!-- Define an AJP 1.3 Connector on port 20400 -->
diff --git a/client/tomcatconf/tomcat6-ssl.conf.in b/client/tomcatconf/tomcat6-ssl.conf.in
index e7c53ac..1d6f59b 100644
--- a/client/tomcatconf/tomcat6-ssl.conf.in
+++ b/client/tomcatconf/tomcat6-ssl.conf.in
@@ -40,7 +40,7 @@ CATALINA_TMPDIR="@MSENVIRON@/temp"
 
 # Use JAVA_OPTS to set java.library.path for libtcnative.so
 #JAVA_OPTS="-Djava.library.path=/usr/lib64"
-JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloudmanagementserver.keystore -Djavax.net.ssl.trustStorePassword=vmops.com -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=@MSLOGDIR@ -XX:MaxPermSize=800m -XX:PermSize=512M -Djava.security.properties=/etc/cloudstack/management/java.security.ciphers"
+JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloud.jks -Djavax.net.ssl.trustStorePassword=vmops.com -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=@MSLOGDIR@ -XX:MaxPermSize=800m -XX:PermSize=512M -Djava.security.properties=/etc/cloudstack/management/java.security.ciphers"
 
 # What user should run tomcat
 TOMCAT_USER="@MSUSER@"
diff --git a/scripts/network/domr/router_proxy.sh b/core/resources/META-INF/cloudstack/ca/module.properties
old mode 100755
new mode 100644
similarity index 64%
copy from scripts/network/domr/router_proxy.sh
copy to core/resources/META-INF/cloudstack/ca/module.properties
index f9cb7ca..1a6915a
--- a/scripts/network/domr/router_proxy.sh
+++ b/core/resources/META-INF/cloudstack/ca/module.properties
@@ -1,4 +1,4 @@
-#!/bin/bash
+#
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information
@@ -6,42 +6,16 @@
 # to you under the Apache License, Version 2.0 (the
 # "License"); you may not use this file except in compliance
 # with the License.  You may obtain a copy of the License at
-# 
+#
 #   http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 # Unless required by applicable law or agreed to in writing,
 # software distributed under the License is distributed on an
 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+#
 
-# used as a proxy to call script inside virtual router 
-
-#set -x
-
-check_gw() {
-  ping -c 1 -n -q $1 > /dev/null
-  if [ $? -gt 0 ]
-  then
-    sleep 1
-    ping -c 1 -n -q $1 > /dev/null
-  fi
-  if [ $? -gt 0 ]
-  then
-    exit 1
-  fi
-}
-
-cert="/root/.ssh/id_rsa.cloud"
-
-script=$1
-shift
-
-domRIp=$1
-shift
-
-check_gw "$domRIp"
-
-ssh -p 3922 -q -o StrictHostKeyChecking=no -i $cert root@$domRIp "/opt/cloud/bin/$script $*"
-exit $?
+name=ca
+parent=backend
diff --git a/core/resources/META-INF/cloudstack/ca/spring-core-lifecycle-ca-context-inheritable.xml b/core/resources/META-INF/cloudstack/ca/spring-core-lifecycle-ca-context-inheritable.xml
new file mode 100644
index 0000000..1566a4b
--- /dev/null
+++ b/core/resources/META-INF/cloudstack/ca/spring-core-lifecycle-ca-context-inheritable.xml
@@ -0,0 +1,32 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
+>
+
+    <bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
+        <property name="registry" ref="caProvidersRegistry" />
+        <property name="typeClass" value="org.apache.cloudstack.framework.ca.CAProvider" />
+    </bean>
+
+</beans>
diff --git a/core/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/core/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml
index a9124d3..d5b912a 100644
--- a/core/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml
+++ b/core/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml
@@ -312,4 +312,8 @@
         <property name="orderConfigDefault" value="IPMITOOL" />
     </bean>
 
+    <bean id="caProvidersRegistry"
+          class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
+    </bean>
+
 </beans>
diff --git a/core/src/com/cloud/agent/api/routing/NetworkElementCommand.java b/core/src/com/cloud/agent/api/routing/NetworkElementCommand.java
index a7b5b5e..ae482ac 100644
--- a/core/src/com/cloud/agent/api/routing/NetworkElementCommand.java
+++ b/core/src/com/cloud/agent/api/routing/NetworkElementCommand.java
@@ -20,6 +20,7 @@
 package com.cloud.agent.api.routing;
 
 import java.util.HashMap;
+import java.util.Map;
 
 import com.cloud.agent.api.Command;
 
@@ -46,6 +47,18 @@ public abstract class NetworkElementCommand extends Command {
         super();
     }
 
+    public void setAccessDetail(final Map<String, String> details) {
+        if (details == null) {
+            return;
+        }
+        for (final Map.Entry<String, String> detail : details.entrySet()) {
+            if (detail == null) {
+                continue;
+            }
+            setAccessDetail(detail.getKey(), detail.getValue());
+        }
+    }
+
     public void setAccessDetail(final String name, final String value) {
         accessDetails.put(name, value);
     }
diff --git a/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java b/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java
index 75f55f9..d7c3d56 100644
--- a/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java
+++ b/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java
@@ -35,6 +35,11 @@ import java.util.concurrent.locks.ReentrantLock;
 
 import javax.naming.ConfigurationException;
 
+import org.apache.cloudstack.ca.SetupCertificateAnswer;
+import org.apache.cloudstack.ca.SetupCertificateCommand;
+import org.apache.cloudstack.ca.SetupKeyStoreCommand;
+import org.apache.cloudstack.ca.SetupKeystoreAnswer;
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.agent.api.Answer;
@@ -108,6 +113,14 @@ public class VirtualRoutingResource {
                 return executeQueryCommand(cmd);
             }
 
+            if (cmd instanceof SetupKeyStoreCommand) {
+                return execute((SetupKeyStoreCommand) cmd);
+            }
+
+            if (cmd instanceof SetupCertificateCommand) {
+                return execute((SetupCertificateCommand) cmd);
+            }
+
             if (cmd instanceof AggregationControlCommand) {
                 return execute((AggregationControlCommand)cmd);
             }
@@ -139,6 +152,37 @@ public class VirtualRoutingResource {
         }
     }
 
+    private Answer execute(final SetupKeyStoreCommand cmd) {
+        final String args = String.format("/usr/local/cloud/systemvm/conf/agent.properties " +
+                        "/usr/local/cloud/systemvm/conf/%s " +
+                        "%s %d " +
+                        "/usr/local/cloud/systemvm/conf/%s",
+                KeyStoreUtils.defaultKeystoreFile,
+                cmd.getKeystorePassword(),
+                cmd.getValidityDays(),
+                KeyStoreUtils.defaultCsrFile);
+        ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), KeyStoreUtils.keyStoreSetupScript, args);
+        return new SetupKeystoreAnswer(result.getDetails());
+    }
+
+    private Answer execute(final SetupCertificateCommand cmd) {
+        final String args = String.format("/usr/local/cloud/systemvm/conf/agent.properties " +
+                        "/usr/local/cloud/systemvm/conf/%s %s " +
+                        "/usr/local/cloud/systemvm/conf/%s \"%s\" " +
+                        "/usr/local/cloud/systemvm/conf/%s \"%s\" " +
+                        "/usr/local/cloud/systemvm/conf/%s \"%s\"",
+                KeyStoreUtils.defaultKeystoreFile,
+                KeyStoreUtils.sshMode,
+                KeyStoreUtils.defaultCertFile,
+                cmd.getEncodedCertificate(),
+                KeyStoreUtils.defaultCaCertFile,
+                cmd.getEncodedCaCertificates(),
+                KeyStoreUtils.defaultPrivateKeyFile,
+                cmd.getEncodedPrivateKey());
+        ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), KeyStoreUtils.keyStoreImportScript, args);
+        return new SetupCertificateAnswer(result.isSuccess());
+    }
+
     private Answer executeQueryCommand(NetworkElementCommand cmd) {
         if (cmd instanceof CheckRouterCommand) {
             return execute((CheckRouterCommand)cmd);
diff --git a/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java b/core/src/org/apache/cloudstack/ca/SetupCertificateAnswer.java
similarity index 76%
copy from api/src/org/apache/cloudstack/poll/BackgroundPollTask.java
copy to core/src/org/apache/cloudstack/ca/SetupCertificateAnswer.java
index 8eea147..4df5d15 100644
--- a/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java
+++ b/core/src/org/apache/cloudstack/ca/SetupCertificateAnswer.java
@@ -1,3 +1,4 @@
+//
 // Licensed to the Apache Software Foundation (ASF) under one
 // or more contributor license agreements.  See the NOTICE file
 // distributed with this work for additional information
@@ -14,8 +15,15 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+//
+
+package org.apache.cloudstack.ca;
 
-package org.apache.cloudstack.poll;
+import com.cloud.agent.api.Answer;
 
-public interface BackgroundPollTask extends Runnable {
+public class SetupCertificateAnswer extends Answer {
+    public SetupCertificateAnswer(final boolean result) {
+        super(null);
+        this.result = result;
+    }
 }
diff --git a/core/src/org/apache/cloudstack/ca/SetupCertificateCommand.java b/core/src/org/apache/cloudstack/ca/SetupCertificateCommand.java
new file mode 100644
index 0000000..1cd3150
--- /dev/null
+++ b/core/src/org/apache/cloudstack/ca/SetupCertificateCommand.java
@@ -0,0 +1,99 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.ca;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.cloudstack.framework.ca.Certificate;
+import org.apache.cloudstack.utils.security.CertUtils;
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
+
+import com.cloud.agent.api.LogLevel;
+import com.cloud.agent.api.routing.NetworkElementCommand;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+public class SetupCertificateCommand extends NetworkElementCommand {
+    @LogLevel(LogLevel.Log4jLevel.Off)
+    private String certificate;
+    @LogLevel(LogLevel.Log4jLevel.Off)
+    private String privateKey = "";
+    @LogLevel(LogLevel.Log4jLevel.Off)
+    private String caCertificates;
+
+    private boolean handleByAgent = true;
+
+    public SetupCertificateCommand(final Certificate certificate) {
+        super();
+        if (certificate == null) {
+            throw new CloudRuntimeException("A null certificate was provided to setup");
+        }
+        setWait(60);
+        try {
+            this.certificate = CertUtils.x509CertificateToPem(certificate.getClientCertificate());
+            this.caCertificates = CertUtils.x509CertificatesToPem(certificate.getCaCertificates());
+            if (certificate.getPrivateKey() != null) {
+                this.privateKey = CertUtils.privateKeyToPem(certificate.getPrivateKey());
+            }
+        } catch (final IOException e) {
+            throw new CloudRuntimeException("Failed to transform X509 cert to PEM format", e);
+        }
+    }
+
+    @Override
+    public void setAccessDetail(final Map<String, String> accessDetails) {
+        handleByAgent = false;
+        super.setAccessDetail(accessDetails);
+    }
+
+    @Override
+    public void setAccessDetail(String name, String value) {
+        handleByAgent = false;
+        super.setAccessDetail(name, value);
+    }
+
+    public String getPrivateKey() {
+        return privateKey;
+    }
+
+    public String getCertificate() {
+        return certificate;
+    }
+
+    public String getCaCertificates() {
+        return caCertificates;
+    }
+
+    public String getEncodedPrivateKey() {
+        return privateKey.replace("\n", KeyStoreUtils.certNewlineEncoder).replace(" ", KeyStoreUtils.certSpaceEncoder);
+    }
+
+    public String getEncodedCertificate() {
+        return certificate.replace("\n", KeyStoreUtils.certNewlineEncoder).replace(" ", KeyStoreUtils.certSpaceEncoder);
+    }
+
+    public String getEncodedCaCertificates() {
+        return caCertificates.replace("\n", KeyStoreUtils.certNewlineEncoder).replace(" ", KeyStoreUtils.certSpaceEncoder);
+    }
+
+    public boolean isHandleByAgent() {
+        return handleByAgent;
+    }
+}
diff --git a/core/src/org/apache/cloudstack/ca/SetupKeyStoreCommand.java b/core/src/org/apache/cloudstack/ca/SetupKeyStoreCommand.java
new file mode 100644
index 0000000..7cd5cbe
--- /dev/null
+++ b/core/src/org/apache/cloudstack/ca/SetupKeyStoreCommand.java
@@ -0,0 +1,75 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.ca;
+
+import java.util.Map;
+
+import com.cloud.agent.api.LogLevel;
+import com.cloud.agent.api.routing.NetworkElementCommand;
+import com.cloud.utils.PasswordGenerator;
+
+public class SetupKeyStoreCommand extends NetworkElementCommand {
+    @LogLevel(LogLevel.Log4jLevel.Off)
+    private int validityDays;
+    @LogLevel(LogLevel.Log4jLevel.Off)
+    private String keystorePassword;
+
+    private boolean handleByAgent = true;
+
+    public SetupKeyStoreCommand(final int validityDays) {
+        super();
+        setWait(60);
+        this.validityDays = validityDays;
+        if (this.validityDays < 1) {
+            this.validityDays = 1;
+        }
+        this.keystorePassword = PasswordGenerator.generateRandomPassword(16);
+    }
+
+    @Override
+    public void setAccessDetail(final Map<String, String> accessDetails) {
+        handleByAgent = false;
+        super.setAccessDetail(accessDetails);
+    }
+
+
+    @Override
+    public void setAccessDetail(String name, String value) {
+        handleByAgent = false;
+        super.setAccessDetail(name, value);
+    }
+
+    public int getValidityDays() {
+        return validityDays;
+    }
+
+    public String getKeystorePassword() {
+        return keystorePassword;
+    }
+
+    public boolean isHandleByAgent() {
+        return handleByAgent;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+}
diff --git a/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java b/core/src/org/apache/cloudstack/ca/SetupKeystoreAnswer.java
similarity index 64%
copy from api/src/org/apache/cloudstack/poll/BackgroundPollTask.java
copy to core/src/org/apache/cloudstack/ca/SetupKeystoreAnswer.java
index 8eea147..16ddc96 100644
--- a/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java
+++ b/core/src/org/apache/cloudstack/ca/SetupKeystoreAnswer.java
@@ -1,3 +1,4 @@
+//
 // Licensed to the Apache Software Foundation (ASF) under one
 // or more contributor license agreements.  See the NOTICE file
 // distributed with this work for additional information
@@ -14,8 +15,23 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+//
+
+package org.apache.cloudstack.ca;
+
+import com.cloud.agent.api.LogLevel;
+import com.google.common.base.Strings;
+
+public class SetupKeystoreAnswer extends SetupCertificateAnswer {
+    @LogLevel(LogLevel.Log4jLevel.Off)
+    private final String csr;
 
-package org.apache.cloudstack.poll;
+    public SetupKeystoreAnswer(final String csr) {
+        super(!Strings.isNullOrEmpty(csr));
+        this.csr = csr;
+    }
 
-public interface BackgroundPollTask extends Runnable {
+    public String getCsr() {
+        return csr;
+    }
 }
diff --git a/debian/cloudstack-management.postinst b/debian/cloudstack-management.postinst
index 240224d..5c9a7fa 100644
--- a/debian/cloudstack-management.postinst
+++ b/debian/cloudstack-management.postinst
@@ -50,9 +50,6 @@ if [ "$1" = configure ]; then
                 cp -a $OLDCONFDIR/$FILE $NEWCONFDIR/$FILE
             fi
         done
-        if [ -f "$OLDCONFDIR/cloud.keystore" ]; then
-            cp -a $OLDCONFDIR/cloud.keystore $NEWCONFDIR/cloudmanagementserver.keystore
-        fi
     fi
 
     CONFDIR="/etc/cloudstack/management"
diff --git a/developer/developer-prefill.sql b/developer/developer-prefill.sql
index 7fd85c6..cc67748 100644
--- a/developer/developer-prefill.sql
+++ b/developer/developer-prefill.sql
@@ -119,6 +119,11 @@ INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
             VALUES ('Advanced', 'DEFAULT', 'RoleService',
             'dynamic.apichecker.enabled', 'true');
 
+-- Enable RootCA auth strictness for fresh deployments
+INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
+            VALUES ('Advanced', 'DEFAULT', 'RootCAProvider',
+            'ca.plugin.root.auth.strictness', 'true');
+
 -- Add developer configuration entry; allows management server to be run as a user other than "cloud"
 INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
             VALUES ('Advanced', 'DEFAULT', 'management-server',
diff --git a/engine/api/src/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
index 13bfee6..68531e3 100644
--- a/engine/api/src/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
+++ b/engine/api/src/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
@@ -98,6 +98,8 @@ public interface NetworkOrchestrationService {
 
     List<NicProfile> getNicProfiles(VirtualMachine vm);
 
+    Map<String, String> getSystemVMAccessDetails(VirtualMachine vm);
+
     Pair<? extends NetworkGuru, ? extends Network> implementNetwork(long networkId, DeployDestination dest, ReservationContext context)
         throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;
 
diff --git a/engine/orchestration/src/com/cloud/agent/manager/AgentManagerImpl.java b/engine/orchestration/src/com/cloud/agent/manager/AgentManagerImpl.java
index 597ea67..325f3ec 100644
--- a/engine/orchestration/src/com/cloud/agent/manager/AgentManagerImpl.java
+++ b/engine/orchestration/src/com/cloud/agent/manager/AgentManagerImpl.java
@@ -37,6 +37,7 @@ import java.util.concurrent.locks.ReentrantLock;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import org.apache.cloudstack.ca.CAManager;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@@ -135,6 +136,8 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
     private final Lock _agentStatusLock = new ReentrantLock();
 
     @Inject
+    protected CAManager caService;
+    @Inject
     protected EntityManager _entityMgr;
 
     protected NioServer _connection;
@@ -223,7 +226,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
         // allow core threads to time out even when there are no items in the queue
         _connectExecutor.allowCoreThreadTimeOut(true);
 
-        _connection = new NioServer("AgentManager", Port.value(), Workers.value() + 10, this);
+        _connection = new NioServer("AgentManager", Port.value(), Workers.value() + 10, this, caService);
         s_logger.info("Listening on " + Port.value() + " with " + Workers.value() + " workers");
 
         // executes all agent commands other than cron and ping
@@ -813,6 +816,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
                     s_logger.debug("The next status of agent " + hostId + "is " + nextStatus + ", current status is " + currentStatus);
                 }
             }
+            caService.purgeHostCertificate(host);
         }
 
         if (s_logger.isDebugEnabled()) {
diff --git a/engine/orchestration/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java b/engine/orchestration/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java
index b69ca5b..faf3a3b 100644
--- a/engine/orchestration/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java
+++ b/engine/orchestration/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java
@@ -495,6 +495,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
                 }
                 final String ip = ms.getServiceIP();
                 InetAddress addr;
+                int port = Port.value();
                 try {
                     addr = InetAddress.getByName(ip);
                 } catch (final UnknownHostException e) {
@@ -502,21 +503,21 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
                 }
                 SocketChannel ch1 = null;
                 try {
-                    ch1 = SocketChannel.open(new InetSocketAddress(addr, Port.value()));
+                    ch1 = SocketChannel.open(new InetSocketAddress(addr, port));
                     ch1.configureBlocking(false);
                     ch1.socket().setKeepAlive(true);
                     ch1.socket().setSoTimeout(60 * 1000);
                     try {
-                        final SSLContext sslContext = Link.initSSLContext(true);
-                        sslEngine = sslContext.createSSLEngine(ip, Port.value());
+                        SSLContext sslContext = Link.initClientSSLContext();
+                        sslEngine = sslContext.createSSLEngine(ip, port);
                         sslEngine.setUseClientMode(true);
                         sslEngine.setEnabledProtocols(SSLUtils.getSupportedProtocols(sslEngine.getEnabledProtocols()));
                         sslEngine.beginHandshake();
                         if (!Link.doHandshake(ch1, sslEngine, true)) {
                             ch1.close();
-                            throw new IOException("SSL handshake failed!");
+                            throw new IOException(String.format("SSL: Handshake failed with peer management server '%s' on %s:%d ", peerName, ip, port));
                         }
-                        s_logger.info("SSL: Handshake done");
+                        s_logger.info(String.format("SSL: Handshake done with peer management server '%s' on %s:%d ", peerName, ip, port));
                     } catch (final Exception e) {
                         ch1.close();
                         throw new IOException("SSL: Fail to init SSL! " + e);
@@ -528,10 +529,12 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
                     _sslEngines.put(peerName, sslEngine);
                     return ch1;
                 } catch (final IOException e) {
-                    try {
-                        ch1.close();
-                    } catch (final IOException ex) {
-                        s_logger.error("failed to close failed peer socket: " + ex);
+                    if (ch1 != null) {
+                        try {
+                            ch1.close();
+                        } catch (final IOException ex) {
+                            s_logger.error("failed to close failed peer socket: " + ex);
+                        }
                     }
                     s_logger.warn("Unable to connect to peer management server: " + peerName + ", ip: " + ip + " due to " + e.getMessage(), e);
                     return null;
diff --git a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
index babfbdb..638a000 100755
--- a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
+++ b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -22,6 +22,7 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -39,12 +40,14 @@ import javax.naming.ConfigurationException;
 
 import com.cloud.agent.api.AttachOrDettachConfigDriveCommand;
 import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
+import org.apache.cloudstack.ca.CAManager;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
 import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
+import org.apache.cloudstack.framework.ca.Certificate;
 import org.apache.cloudstack.framework.config.ConfigDepot;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
@@ -97,6 +100,7 @@ import com.cloud.agent.api.StopCommand;
 import com.cloud.agent.api.UnPlugNicAnswer;
 import com.cloud.agent.api.UnPlugNicCommand;
 import com.cloud.agent.api.UnregisterVMCommand;
+import com.cloud.agent.api.routing.NetworkElementCommand;
 import com.cloud.agent.api.to.DiskTO;
 import com.cloud.agent.api.to.GPUDeviceTO;
 import com.cloud.agent.api.to.NicTO;
@@ -205,6 +209,7 @@ import com.cloud.vm.dao.VMInstanceDao;
 import com.cloud.vm.snapshot.VMSnapshotManager;
 import com.cloud.vm.snapshot.VMSnapshotVO;
 import com.cloud.vm.snapshot.dao.VMSnapshotDao;
+import com.google.common.base.Strings;
 
 public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable {
     private static final Logger s_logger = Logger.getLogger(VirtualMachineManagerImpl.class);
@@ -284,7 +289,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
     @Inject
     protected UserVmDetailsDao _vmDetailsDao;
     @Inject
-    ServiceOfferingDao _serviceOfferingDao = null;
+    protected ServiceOfferingDao _serviceOfferingDao = null;
+    @Inject
+    protected CAManager caManager;
 
     @Inject
     ConfigDepot _configDepot;
@@ -1023,7 +1030,6 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
 
                     cmds.addCommand(new StartCommand(vmTO, dest.getHost(), getExecuteInSequence(vm.getHypervisorType())));
 
-
                     vmGuru.finalizeDeployment(cmds, vmProfile, dest, ctx);
 
                     work = _workDao.findById(work.getId());
@@ -1073,6 +1079,23 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
                             if (s_logger.isDebugEnabled()) {
                                 s_logger.debug("Start completed for VM " + vm);
                             }
+                            final Host vmHost = _hostDao.findById(destHostId);
+                            if (vmHost != null && (VirtualMachine.Type.ConsoleProxy.equals(vm.getType()) ||
+                                    VirtualMachine.Type.SecondaryStorageVm.equals(vm.getType())) && caManager.canProvisionCertificates()) {
+                                final Map<String, String> sshAccessDetails = _networkMgr.getSystemVMAccessDetails(vm);
+                                final String csr = caManager.generateKeyStoreAndCsr(vmHost, sshAccessDetails);
+                                if (!Strings.isNullOrEmpty(csr)) {
+                                    final Map<String, String> ipAddressDetails = new HashMap<>(sshAccessDetails);
+                                    ipAddressDetails.remove(NetworkElementCommand.ROUTER_NAME);
+                                    final Certificate certificate = caManager.issueCertificate(csr, Arrays.asList(vm.getHostName(), vm.getInstanceName()), new ArrayList<>(ipAddressDetails.values()), CAManager.CertValidityPeriod.value(), null);
+                                    final boolean result = caManager.deployCertificate(vmHost, certificate, false, sshAccessDetails);
+                                    if (!result) {
+                                        s_logger.error("Failed to setup certificate for system vm: " + vm.getInstanceName());
+                                    }
+                                } else {
+                                    s_logger.error("Failed to setup keystore and generate CSR for system vm: " + vm.getInstanceName());
+                                }
+                            }
                             return;
                         } else {
                             if (s_logger.isDebugEnabled()) {
diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
index 37f5330..3956617 100644
--- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
+++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
@@ -68,6 +68,7 @@ import com.cloud.agent.api.CheckNetworkCommand;
 import com.cloud.agent.api.Command;
 import com.cloud.agent.api.StartupCommand;
 import com.cloud.agent.api.StartupRoutingCommand;
+import com.cloud.agent.api.routing.NetworkElementCommand;
 import com.cloud.agent.api.to.NicTO;
 import com.cloud.alert.AlertManager;
 import com.cloud.configuration.ConfigurationManager;
@@ -215,6 +216,7 @@ import com.cloud.vm.dao.NicSecondaryIpDao;
 import com.cloud.vm.dao.NicSecondaryIpVO;
 import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.VMInstanceDao;
+import com.google.common.base.Strings;
 
 /**
  * NetworkManagerImpl implements NetworkManager.
@@ -3488,6 +3490,39 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
         return profiles;
     }
 
+    @Override
+    public Map<String, String> getSystemVMAccessDetails(final VirtualMachine vm) {
+        final Map<String, String> accessDetails = new HashMap<>();
+        accessDetails.put(NetworkElementCommand.ROUTER_NAME, vm.getInstanceName());
+        String privateIpAddress = null;
+        for (final NicProfile profile : getNicProfiles(vm)) {
+            if (profile == null) {
+                continue;
+            }
+            final Network network = _networksDao.findById(profile.getNetworkId());
+            if (network == null) {
+                continue;
+            }
+            final String address = profile.getIPv4Address();
+            if (network.getTrafficType() == Networks.TrafficType.Control) {
+                accessDetails.put(NetworkElementCommand.ROUTER_IP, address);
+            }
+            if (network.getTrafficType() == Networks.TrafficType.Guest) {
+                accessDetails.put(NetworkElementCommand.ROUTER_GUEST_IP, address);
+            }
+            if (network.getTrafficType() == Networks.TrafficType.Management) {
+                privateIpAddress = address;
+            }
+            if (network.getTrafficType() != null && !Strings.isNullOrEmpty(address)) {
+                accessDetails.put(network.getTrafficType().name(), address);
+            }
+        }
+        if (privateIpAddress != null && Strings.isNullOrEmpty(accessDetails.get(NetworkElementCommand.ROUTER_IP))) {
+            accessDetails.put(NetworkElementCommand.ROUTER_IP,  privateIpAddress);
+        }
+        return accessDetails;
+    }
+
     protected boolean stateTransitTo(final NetworkVO network, final Network.Event e) throws NoTransitionException {
         return _stateMachine.transitTo(network, e, null, _networksDao);
     }
diff --git a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
index 8b7a604..e8d6633 100644
--- a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
+++ b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
@@ -130,6 +130,7 @@
   <bean id="autoScaleVmProfileDaoImpl" class="com.cloud.network.as.dao.AutoScaleVmProfileDaoImpl" />
   <bean id="capacityDaoImpl" class="com.cloud.capacity.dao.CapacityDaoImpl" />
   <bean id="certificateDaoImpl" class="com.cloud.certificate.dao.CertificateDaoImpl" />
+  <bean id="crlDaoImpl" class="com.cloud.certificate.dao.CrlDaoImpl" />
   <bean id="clusterDaoImpl" class="com.cloud.dc.dao.ClusterDaoImpl" />
   <bean id="clusterDetailsDaoImpl" class="com.cloud.dc.ClusterDetailsDaoImpl" />
   <bean id="clusterVSMMapDaoImpl" class="com.cloud.dc.dao.ClusterVSMMapDaoImpl" />
diff --git a/engine/schema/src/com/cloud/certificate/CrlVO.java b/engine/schema/src/com/cloud/certificate/CrlVO.java
new file mode 100644
index 0000000..6df7530
--- /dev/null
+++ b/engine/schema/src/com/cloud/certificate/CrlVO.java
@@ -0,0 +1,85 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package com.cloud.certificate;
+
+import java.math.BigInteger;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.cloudstack.api.InternalIdentity;
+
+@Entity
+@Table(name = "crl")
+public class CrlVO implements InternalIdentity {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Long id = null;
+
+    @Column(name = "serial")
+    private String certSerial;
+
+    @Column(name = "cn")
+    private String certCn;
+
+    @Column(name = "revoker_uuid")
+    private String revokerUuid;
+
+    @Temporal(value = TemporalType.TIMESTAMP)
+    @Column(name = "revoked", updatable = true)
+    private Date revoked;
+
+    public CrlVO() {
+    }
+
+    public CrlVO(final BigInteger certSerial, final String certCn, final String revokerUuid) {
+        this.certSerial = certSerial.toString(16);
+        this.certCn = certCn;
+        this.revokerUuid = revokerUuid;
+        this.revoked = new Date();
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    public BigInteger getCertSerial() {
+        return new BigInteger(certSerial, 16);
+    }
+
+    public String getCertCn() {
+        return certCn;
+    }
+
+    public String getRevokerUuid() {
+        return revokerUuid;
+    }
+
+    public Date getRevoked() {
+        return revoked;
+    }
+}
diff --git a/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java b/engine/schema/src/com/cloud/certificate/dao/CrlDao.java
similarity index 70%
copy from api/src/org/apache/cloudstack/poll/BackgroundPollTask.java
copy to engine/schema/src/com/cloud/certificate/dao/CrlDao.java
index 8eea147..613d9ca 100644
--- a/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java
+++ b/engine/schema/src/com/cloud/certificate/dao/CrlDao.java
@@ -15,7 +15,14 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package org.apache.cloudstack.poll;
+package com.cloud.certificate.dao;
 
-public interface BackgroundPollTask extends Runnable {
-}
+import java.math.BigInteger;
+
+import com.cloud.certificate.CrlVO;
+import com.cloud.utils.db.GenericDao;
+
+public interface CrlDao extends GenericDao<CrlVO, Long> {
+    CrlVO findBySerial(final BigInteger certSerial);
+    CrlVO revokeCertificate(final BigInteger certSerial, final String certCn);
+}
\ No newline at end of file
diff --git a/engine/schema/src/com/cloud/certificate/dao/CrlDaoImpl.java b/engine/schema/src/com/cloud/certificate/dao/CrlDaoImpl.java
new file mode 100644
index 0000000..2eee308
--- /dev/null
+++ b/engine/schema/src/com/cloud/certificate/dao/CrlDaoImpl.java
@@ -0,0 +1,57 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package com.cloud.certificate.dao;
+
+import java.math.BigInteger;
+
+import org.apache.cloudstack.context.CallContext;
+
+import com.cloud.certificate.CrlVO;
+import com.cloud.utils.db.DB;
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+
+@DB
+public class CrlDaoImpl extends GenericDaoBase<CrlVO, Long> implements CrlDao {
+
+    private final SearchBuilder<CrlVO> CrlBySerialSearch;
+
+    public CrlDaoImpl() {
+        super();
+
+        CrlBySerialSearch = createSearchBuilder();
+        CrlBySerialSearch.and("certSerial", CrlBySerialSearch.entity().getCertSerial(), SearchCriteria.Op.EQ);
+        CrlBySerialSearch.done();
+    }
+
+    @Override
+    public CrlVO findBySerial(final BigInteger certSerial) {
+        if (certSerial == null) {
+            return null;
+        }
+        final SearchCriteria<CrlVO> sc = CrlBySerialSearch.create("certSerial", certSerial.toString(16));
+        return findOneBy(sc);
+    }
+
+    @Override
+    public CrlVO revokeCertificate(final BigInteger certSerial, final String certCn) {
+        final CrlVO revokedCertificate = new CrlVO(certSerial, certCn == null ? "" : certCn, CallContext.current().getCallingUserUuid());
+        return persist(revokedCertificate);
+    }
+}
diff --git a/engine/schema/src/com/cloud/host/dao/HostDao.java b/engine/schema/src/com/cloud/host/dao/HostDao.java
index 7ffe1ed..f98e8c1 100644
--- a/engine/schema/src/com/cloud/host/dao/HostDao.java
+++ b/engine/schema/src/com/cloud/host/dao/HostDao.java
@@ -101,4 +101,6 @@ public interface HostDao extends GenericDao<HostVO, Long>, StateDao<Status, Stat
     List<Long> listClustersByHostTag(String hostTagOnOffering);
 
     List<HostVO> listByType(Type type);
+
+    HostVO findByIp(String ip);
 }
diff --git a/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java b/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java
index 309d17e..0ac4f43 100644
--- a/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java
+++ b/engine/schema/src/com/cloud/host/dao/HostDaoImpl.java
@@ -89,6 +89,7 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
     protected SearchBuilder<HostVO> DcPrivateIpAddressSearch;
     protected SearchBuilder<HostVO> DcStorageIpAddressSearch;
     protected SearchBuilder<HostVO> PublicIpAddressSearch;
+    protected SearchBuilder<HostVO> AnyIpAddressSearch;
 
     protected SearchBuilder<HostVO> GuidSearch;
     protected SearchBuilder<HostVO> DcSearch;
@@ -216,6 +217,11 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
         PublicIpAddressSearch.and("publicIpAddress", PublicIpAddressSearch.entity().getPublicIpAddress(), SearchCriteria.Op.EQ);
         PublicIpAddressSearch.done();
 
+        AnyIpAddressSearch = createSearchBuilder();
+        AnyIpAddressSearch.or("publicIpAddress", AnyIpAddressSearch.entity().getPublicIpAddress(), SearchCriteria.Op.EQ);
+        AnyIpAddressSearch.or("privateIpAddress", AnyIpAddressSearch.entity().getPrivateIpAddress(), SearchCriteria.Op.EQ);
+        AnyIpAddressSearch.done();
+
         GuidSearch = createSearchBuilder();
         GuidSearch.and("guid", GuidSearch.entity().getGuid(), SearchCriteria.Op.EQ);
         GuidSearch.done();
@@ -1118,6 +1124,13 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
         return findOneBy(sc);
     }
 
+    @Override
+    public HostVO findByIp(final String ipAddress) {
+        SearchCriteria<HostVO> sc = AnyIpAddressSearch.create();
+        sc.setParameters("publicIpAddress", ipAddress);
+        sc.setParameters("privateIpAddress", ipAddress);
+        return findOneBy(sc);
+    }
 
     @Override
     public List<HostVO> findHypervisorHostInCluster(long clusterId) {
diff --git a/framework/pom.xml b/framework/ca/pom.xml
similarity index 53%
copy from framework/pom.xml
copy to framework/ca/pom.xml
index 3fccc4c..9412649 100644
--- a/framework/pom.xml
+++ b/framework/ca/pom.xml
@@ -18,42 +18,12 @@
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
-  <artifactId>cloudstack-framework</artifactId>
-  <name>Apache CloudStack Framework</name>
-  <packaging>pom</packaging>
+  <artifactId>cloud-framework-ca</artifactId>
+  <name>Apache CloudStack Framework - Certificate Authority</name>
   <parent>
     <groupId>org.apache.cloudstack</groupId>
-    <artifactId>cloudstack</artifactId>
+    <artifactId>cloudstack-framework</artifactId>
     <version>4.11.0.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
   </parent>
-  <build>
-    <defaultGoal>install</defaultGoal>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-checkstyle-plugin</artifactId>
-          <executions>
-            <execution>
-              <id>cloudstack-checkstyle</id>
-              <phase>none</phase>
-              <inherited>false</inherited>
-            </execution>
-          </executions>
-      </plugin>
-    </plugins>
-  </build>
-  <modules>
-    <module>ipc</module>
-    <module>rest</module>
-    <module>events</module>
-    <module>jobs</module>
-    <module>quota</module>
-    <module>cluster</module>
-    <module>db</module>
-    <module>config</module>
-    <module>managed-context</module>
-    <module>spring/lifecycle</module>
-    <module>spring/module</module>
-	<module>security</module>
-  </modules>
 </project>
diff --git a/framework/ca/src/org/apache/cloudstack/framework/ca/CAProvider.java b/framework/ca/src/org/apache/cloudstack/framework/ca/CAProvider.java
new file mode 100644
index 0000000..8dc343b
--- /dev/null
+++ b/framework/ca/src/org/apache/cloudstack/framework/ca/CAProvider.java
@@ -0,0 +1,93 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.framework.ca;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+
+public interface CAProvider {
+
+    /**
+     * Method returns capability of the plugin to participate in certificate issuance, revocation and provisioning
+     * @return returns true when CA provider can do certificate lifecycle tasks
+     */
+    boolean canProvisionCertificates();
+
+    /**
+     * Returns root CA certificate
+     * @return returns concatenated root CA certificate string
+     */
+    List<X509Certificate> getCaCertificate();
+
+    /**
+     * Issues certificate with provided options
+     * @param domainNames
+     * @param ipAddresses
+     * @param validityDays
+     * @return returns issued certificate
+     */
+    Certificate issueCertificate(final List<String> domainNames, final List<String> ipAddresses, final int validityDays);
+
+    /**
+     * Issues certificate using given CSR and other options
+     * @param csr
+     * @param domainNames
+     * @param ipAddresses
+     * @param validityDays
+     * @return returns issued certificate using provided CSR and other options
+     */
+    Certificate issueCertificate(final String csr, final List<String> domainNames, final List<String> ipAddresses, final int validityDays);
+
+    /**
+     * Revokes certificate using certificate serial and CN
+     * @param certSerial
+     * @param certCn
+     * @return returns true on success
+     */
+    boolean revokeCertificate(final BigInteger certSerial, final String certCn);
+
+    /**
+     * This method can add/inject custom TrustManagers for client connection validations.
+     * @param sslContext The SSL context used while accepting a client connection
+     * @param remoteAddress
+     * @param certMap
+     * @return returns created SSL engine instance
+     * @throws GeneralSecurityException
+     * @throws IOException
+     */
+    SSLEngine createSSLEngine(final SSLContext sslContext, final String remoteAddress, final Map<String, X509Certificate> certMap) throws GeneralSecurityException, IOException;
+
+    /**
+     * Returns the unique name of the provider
+     * @return returns provider name
+     */
+    String getProviderName();
+
+    /**
+     * Returns description about the CA provider plugin
+     * @return returns description
+     */
+    String getDescription();
+}
diff --git a/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java b/framework/ca/src/org/apache/cloudstack/framework/ca/CAService.java
similarity index 58%
copy from api/src/org/apache/cloudstack/poll/BackgroundPollTask.java
copy to framework/ca/src/org/apache/cloudstack/framework/ca/CAService.java
index 8eea147..3aacb3b 100644
--- a/api/src/org/apache/cloudstack/poll/BackgroundPollTask.java
+++ b/framework/ca/src/org/apache/cloudstack/framework/ca/CAService.java
@@ -15,7 +15,22 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package org.apache.cloudstack.poll;
+package org.apache.cloudstack.framework.ca;
 
-public interface BackgroundPollTask extends Runnable {
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+
+public interface CAService {
+    /**
+     * Returns a SSLEngine to be used for handling client connections
+     * @param context
+     * @param remoteAddress
+     * @return
+     * @throws GeneralSecurityException
+     * @throws IOException
+     */
+    SSLEngine createSSLEngine(final SSLContext context, final String remoteAddress) throws GeneralSecurityException, IOException;
 }
diff --git a/utils/src/main/java/com/cloud/utils/exception/TaskExecutionException.java b/framework/ca/src/org/apache/cloudstack/framework/ca/Certificate.java
similarity index 50%
copy from utils/src/main/java/com/cloud/utils/exception/TaskExecutionException.java
copy to framework/ca/src/org/apache/cloudstack/framework/ca/Certificate.java
index be639ba..b3a230d 100644
--- a/utils/src/main/java/com/cloud/utils/exception/TaskExecutionException.java
+++ b/framework/ca/src/org/apache/cloudstack/framework/ca/Certificate.java
@@ -1,4 +1,3 @@
-//
 // Licensed to the Apache Software Foundation (ASF) under one
 // or more contributor license agreements.  See the NOTICE file
 // distributed with this work for additional information
@@ -15,34 +14,33 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-//
-
-package com.cloud.utils.exception;
 
-import com.cloud.utils.SerialVersionUID;
+package org.apache.cloudstack.framework.ca;
 
-/**
- * Used by the Task class to wrap-up its exceptions.
- */
-public class TaskExecutionException extends Exception {
-    private static final long serialVersionUID = SerialVersionUID.NioConnectionException;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.List;
 
-    protected int csErrorCode;
+public class Certificate {
+    private X509Certificate clientCertificate;
+    private PrivateKey privateKey;
+    private List<X509Certificate> caCertificates;
 
-    public TaskExecutionException(final String msg, final Throwable cause) {
-        super(msg, cause);
-        setCSErrorCode(CSExceptionErrorCode.getCSErrCode(this.getClass().getName()));
+    public Certificate(final X509Certificate clientCertificate, final PrivateKey privateKey, final List<X509Certificate> caCertificates) {
+        this.clientCertificate = clientCertificate;
+        this.privateKey = privateKey;
+        this.caCertificates = caCertificates;
     }
 
-    public TaskExecutionException(final String msg) {
-        super(msg);
+    public X509Certificate getClientCertificate() {
+        return clientCertificate;
     }
 
-    public void setCSErrorCode(final int cserrcode) {
-        csErrorCode = cserrcode;
+    public PrivateKey getPrivateKey() {
+        return privateKey;
     }
 
-    public int getCSErrorCode() {
-        return csErrorCode;
+    public List<X509Certificate> getCaCertificates() {
+        return caCertificates;
     }
 }
diff --git a/framework/pom.xml b/framework/pom.xml
index 3fccc4c..5bfb1d0 100644
--- a/framework/pom.xml
+++ b/framework/pom.xml
@@ -44,6 +44,7 @@
   </build>
   <modules>
     <module>ipc</module>
+    <module>ca</module>
     <module>rest</module>
     <module>events</module>
     <module>jobs</module>
diff --git a/packaging/centos63/cloud.spec b/packaging/centos63/cloud.spec
index abe50bc..dbf55bb 100644
--- a/packaging/centos63/cloud.spec
+++ b/packaging/centos63/cloud.spec
@@ -499,12 +499,6 @@ else
     echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually"
 fi
 
-if [ -f "%{_sysconfdir}/cloud.rpmsave/management/cloud.keystore" ]; then
-    cp -p %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/%{name}/management/cloudmanagementserver.keystore
-    # make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall
-    mv %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore.rpmsave
-fi
-
 %preun agent
 /sbin/service cloudstack-agent stop || true
 if [ "$1" == "0" ] ; then
diff --git a/packaging/fedora20/cloud.spec b/packaging/fedora20/cloud.spec
index 546e439..3f960cf 100644
--- a/packaging/fedora20/cloud.spec
+++ b/packaging/fedora20/cloud.spec
@@ -464,12 +464,6 @@ else
     echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually"
 fi
 
-if [ -f "%{_sysconfdir}/cloud.rpmsave/management/cloud.keystore" ]; then
-    cp -p %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/%{name}/management/cloudmanagementserver.keystore
-    # make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall
-    mv %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore.rpmsave
-fi
-
 %preun agent
 /sbin/service cloudstack-agent stop || true
 if [ "$1" == "0" ] ; then
diff --git a/packaging/fedora21/cloud.spec b/packaging/fedora21/cloud.spec
index a79d172..de05370 100644
--- a/packaging/fedora21/cloud.spec
+++ b/packaging/fedora21/cloud.spec
@@ -464,12 +464,6 @@ else
     echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually"
 fi
 
-if [ -f "%{_sysconfdir}/cloud.rpmsave/management/cloud.keystore" ]; then
-    cp -p %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/%{name}/management/cloudmanagementserver.keystore
-    # make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall
-    mv %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore.rpmsave
-fi
-
 %preun agent
 /sbin/service cloudstack-agent stop || true
 if [ "$1" == "0" ] ; then
diff --git a/packaging/systemd/cloudstack-management.default b/packaging/systemd/cloudstack-management.default
index 6e5fcf9..eb5b654 100644
--- a/packaging/systemd/cloudstack-management.default
+++ b/packaging/systemd/cloudstack-management.default
@@ -28,8 +28,8 @@ JASPER_HOME="/usr/share/cloudstack-management"
 CATALINA_TMPDIR="/usr/share/cloudstack-management/temp"
 
 
-if [ -r "/etc/cloudstack/management/cloudmanagementserver.keystore" ] ; then
-  JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloudmanagementserver.keystore -Djavax.net.ssl.trustStorePassword=vmops.com "
+if [ -r "/etc/cloudstack/management/cloud.jks" ] ; then
+  JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloud.jks -Djavax.net.ssl.trustStorePassword=vmops.com "
 else
   JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m"
 fi
diff --git a/packaging/systemd/cloudstack-management.default.ubuntu b/packaging/systemd/cloudstack-management.default.ubuntu
index 0087495..9b3d0c4 100644
--- a/packaging/systemd/cloudstack-management.default.ubuntu
+++ b/packaging/systemd/cloudstack-management.default.ubuntu
@@ -28,8 +28,8 @@ JASPER_HOME="/usr/share/cloudstack-management"
 CATALINA_TMPDIR="/usr/share/cloudstack-management/temp"
 
 
-if [ -r "/etc/cloudstack/management/cloudmanagementserver.keystore" ] ; then
-  JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloudmanagementserver.keystore -Djavax.net.ssl.trustStorePassword=vmops.com "
+if [ -r "/etc/cloudstack/management/cloud.jks" ] ; then
+  JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloud.jks -Djavax.net.ssl.trustStorePassword=vmops.com "
 else
   JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m"
 fi
diff --git a/api/pom.xml b/plugins/ca/root-ca/pom.xml
similarity index 64%
copy from api/pom.xml
copy to plugins/ca/root-ca/pom.xml
index 93859f1..e27f491 100644
--- a/api/pom.xml
+++ b/plugins/ca/root-ca/pom.xml
@@ -18,12 +18,13 @@
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
-  <artifactId>cloud-api</artifactId>
-  <name>Apache CloudStack API</name>
+  <artifactId>cloud-plugin-ca-rootca</artifactId>
+  <name>Apache CloudStack Plugin - Inbuilt Root Certificate Authority</name>
   <parent>
     <groupId>org.apache.cloudstack</groupId>
-    <artifactId>cloudstack</artifactId>
+    <artifactId>cloudstack-plugins</artifactId>
     <version>4.11.0.0-SNAPSHOT</version>
+    <relativePath>../../pom.xml</relativePath>
   </parent>
   <dependencies>
     <dependency>
@@ -32,39 +33,14 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>com.google.code.gson</groupId>
-      <artifactId>gson</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>javax.servlet</groupId>
-      <artifactId>javax.servlet-api</artifactId>
-      <version>${cs.servlet.version}</version>
-    </dependency>
-    <dependency>
       <groupId>org.apache.cloudstack</groupId>
-      <artifactId>cloud-framework-db</artifactId>
+      <artifactId>cloud-api</artifactId>
       <version>${project.version}</version>
-      <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.cloudstack</groupId>
-      <artifactId>cloud-framework-config</artifactId>
+      <artifactId>cloud-framework-ca</artifactId>
       <version>${project.version}</version>
     </dependency>
   </dependencies>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>test-jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
 </project>
diff --git a/scripts/network/domr/router_proxy.sh b/plugins/ca/root-ca/resources/META-INF/cloudstack/root-ca/module.properties
old mode 100755
new mode 100644
similarity index 64%
copy from scripts/network/domr/router_proxy.sh
copy to plugins/ca/root-ca/resources/META-INF/cloudstack/root-ca/module.properties
index f9cb7ca..e086bc0
--- a/scripts/network/domr/router_proxy.sh
+++ b/plugins/ca/root-ca/resources/META-INF/cloudstack/root-ca/module.properties
@@ -1,4 +1,3 @@
-#!/bin/bash
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information
@@ -6,42 +5,14 @@
 # to you under the Apache License, Version 2.0 (the
 # "License"); you may not use this file except in compliance
 # with the License.  You may obtain a copy of the License at
-# 
+#
 #   http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 # Unless required by applicable law or agreed to in writing,
 # software distributed under the License is distributed on an
 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-# used as a proxy to call script inside virtual router 
-
-#set -x
-
-check_gw() {
-  ping -c 1 -n -q $1 > /dev/null
-  if [ $? -gt 0 ]
-  then
-    sleep 1
-    ping -c 1 -n -q $1 > /dev/null
-  fi
-  if [ $? -gt 0 ]
-  then
-    exit 1
-  fi
-}
-
-cert="/root/.ssh/id_rsa.cloud"
-
-script=$1
-shift
-
-domRIp=$1
-shift
-
-check_gw "$domRIp"
-
-ssh -p 3922 -q -o StrictHostKeyChecking=no -i $cert root@$domRIp "/opt/cloud/bin/$script $*"
-exit $?
+name=root-ca
+parent=ca
diff --git a/plugins/ca/root-ca/resources/META-INF/cloudstack/root-ca/spring-root-ca-context.xml b/plugins/ca/root-ca/resources/META-INF/cloudstack/root-ca/spring-root-ca-context.xml
new file mode 100644
index 0000000..46503fe
--- /dev/null
+++ b/plugins/ca/root-ca/resources/META-INF/cloudstack/root-ca/spring-root-ca-context.xml
@@ -0,0 +1,29 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements. See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership. The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License. You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied. See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
+>
+
+    <bean id="rootCAProvider" class="org.apache.cloudstack.ca.provider.RootCAProvider">
+        <property name="name" value="RootCAProvider" />
+    </bean>
+
+</beans>
diff --git a/plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCACustomTrustManager.java b/plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCACustomTrustManager.java
new file mode 100644
index 0000000..90f6203
--- /dev/null
+++ b/plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCACustomTrustManager.java
@@ -0,0 +1,146 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.ca.provider;
+
+import java.math.BigInteger;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.log4j.Logger;
+
+import com.cloud.certificate.dao.CrlDao;
+import com.google.common.base.Strings;
+
+public final class RootCACustomTrustManager implements X509TrustManager {
+    private static final Logger LOG = Logger.getLogger(RootCACustomTrustManager.class);
+
+    private String clientAddress = "Unknown";
+    private boolean authStrictness = true;
+    private boolean allowExpiredCertificate = true;
+    private CrlDao crlDao;
+    private X509Certificate caCertificate;
+    private Map<String, X509Certificate> activeCertMap;
+
+    public RootCACustomTrustManager(final String clientAddress, final boolean authStrictness, final boolean allowExpiredCertificate, final Map<String, X509Certificate> activeCertMap, final X509Certificate caCertificate, final CrlDao crlDao) {
+        if (!Strings.isNullOrEmpty(clientAddress)) {
+            this.clientAddress = clientAddress.replace("/", "").split(":")[0];
+        }
+        this.authStrictness = authStrictness;
+        this.allowExpiredCertificate = allowExpiredCertificate;
+        this.activeCertMap = activeCertMap;
+        this.caCertificate = caCertificate;
+        this.crlDao = crlDao;
+    }
+
+    private void printCertificateChain(final X509Certificate[] certificates, final String s) throws CertificateException {
+        if (certificates == null) {
+            return;
+        }
+        final StringBuilder builder = new StringBuilder();
+        builder.append("A client/agent attempting connection from address=").append(clientAddress).append(" has presented these certificate(s):");
+        int counter = 1;
+        for (final X509Certificate certificate: certificates) {
+            builder.append("\nCertificate [").append(counter++).append("] :");
+            builder.append(String.format("\n Serial: %x", certificate.getSerialNumber()));
+            builder.append("\n  Not Before:" + certificate.getNotBefore());
+            builder.append("\n  Not After:" + certificate.getNotAfter());
+            builder.append("\n  Signature Algorithm:" + certificate.getSigAlgName());
+            builder.append("\n  Version:" + certificate.getVersion());
+            builder.append("\n  Subject DN:" + certificate.getSubjectDN());
+            builder.append("\n  Issuer DN:" + certificate.getIssuerDN());
+            builder.append("\n  Alternative Names:" + certificate.getSubjectAlternativeNames());
+        }
+        LOG.debug(builder.toString());
+    }
+
+    @Override
+    public void checkClientTrusted(final X509Certificate[] certificates, final String s) throws CertificateException {
+        if (LOG.isDebugEnabled()) {
+            printCertificateChain(certificates, s);
+        }
+        if (!authStrictness) {
+            return;
+        }
+        if (certificates == null || certificates.length < 1 || certificates[0] == null) {
+            throw new CertificateException("In strict auth mode, certificate(s) are expected from client:" + clientAddress);
+        }
+        final X509Certificate primaryClientCertificate = certificates[0];
+
+        // Revocation check
+        final BigInteger serialNumber = primaryClientCertificate.getSerialNumber();
+        if (serialNumber == null || crlDao.findBySerial(serialNumber) != null) {
+            final String errorMsg = String.format("Client is using revoked certificate of serial=%x, subject=%s from address=%s",
+                    primaryClientCertificate.getSerialNumber(), primaryClientCertificate.getSubjectDN(), clientAddress);
+            LOG.error(errorMsg);
+            throw new CertificateException(errorMsg);
+        }
+
+        // Validity check
+        if (!allowExpiredCertificate) {
+            try {
+                primaryClientCertificate.checkValidity();
+            } catch (final CertificateExpiredException | CertificateNotYetValidException e) {
+                final String errorMsg = String.format("Client certificate has expired with serial=%x, subject=%s from address=%s",
+                        primaryClientCertificate.getSerialNumber(), primaryClientCertificate.getSubjectDN(), clientAddress);
+                LOG.error(errorMsg);
+                throw new CertificateException(errorMsg);                }
+        }
+
+        // Ownership check
+        boolean certMatchesOwnership = false;
+        if (primaryClientCertificate.getSubjectAlternativeNames() != null) {
+            for (final List<?> list : primaryClientCertificate.getSubjectAlternativeNames()) {
+                if (list != null && list.size() == 2 && list.get(1) instanceof String) {
+                    final String alternativeName = (String) list.get(1);
+                    if (clientAddress.equals(alternativeName)) {
+                        certMatchesOwnership = true;
+                    }
+                }
+            }
+        }
+        if (!certMatchesOwnership) {
+            final String errorMsg = "Certificate ownership verification failed for client: " + clientAddress;
+            LOG.error(errorMsg);
+            throw new CertificateException(errorMsg);
+        }
+        if (activeCertMap != null && !Strings.isNullOrEmpty(clientAddress)) {
+            activeCertMap.put(clientAddress, primaryClientCertificate);
+        }
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Client/agent connection from ip=" + clientAddress + " has been validated and trusted.");
+        }
+    }
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
+    }
+
+    @Override
+    public X509Certificate[] getAcceptedIssuers() {
+        if (!authStrictness) {
+            return null;
+        }
+        return new X509Certificate[]{caCertificate};
+    }
+}
diff --git a/plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCAProvider.java b/plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCAProvider.java
new file mode 100644
index 0000000..1f39853
--- /dev/null
+++ b/plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCAProvider.java
@@ -0,0 +1,465 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.ca.provider;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyManagementException;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.SignatureException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.cloudstack.ca.CAManager;
+import org.apache.cloudstack.framework.ca.CAProvider;
+import org.apache.cloudstack.framework.ca.Certificate;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.utils.security.CertUtils;
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.log4j.Logger;
+import org.bouncycastle.jce.PKCS10CertificationRequest;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemReader;
+
+import com.cloud.certificate.dao.CrlDao;
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.component.AdapterBase;
+import com.cloud.utils.db.DbProperties;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.net.NetUtils;
+import com.cloud.utils.nio.Link;
+import com.google.common.base.Strings;
+
+public final class RootCAProvider extends AdapterBase implements CAProvider, Configurable {
+    private static final Logger LOG = Logger.getLogger(RootCAProvider.class);
+
+    public static final Integer caValidityYears = 30;
+    public static final String caAlias = "root";
+    public static final String managementAlias = "management";
+
+    private static KeyPair caKeyPair = null;
+    private static X509Certificate caCertificate = null;
+
+    @Inject
+    private ConfigurationDao configDao;
+    @Inject
+    private CrlDao crlDao;
+
+    ////////////////////////////////////////////////////
+    /////////////// Root CA Settings ///////////////////
+    ////////////////////////////////////////////////////
+
+    private static ConfigKey<String> rootCAPrivateKey = new ConfigKey<>("Hidden", String.class,
+            "ca.plugin.root.private.key",
+            null,
+            "The ROOT CA private key.", true);
+
+    private static ConfigKey<String> rootCAPublicKey = new ConfigKey<>("Hidden", String.class,
+            "ca.plugin.root.public.key",
+            null,
+            "The ROOT CA public key.", true);
+
+    private static ConfigKey<String> rootCACertificate = new ConfigKey<>("Hidden", String.class,
+            "ca.plugin.root.ca.certificate",
+            null,
+            "The ROOT CA certificate.", true);
+
+    private static ConfigKey<String> rootCAIssuerDN = new ConfigKey<>("Advanced", String.class,
+            "ca.plugin.root.issuer.dn",
+            "CN=ca.cloudstack.apache.org",
+            "The ROOT CA issuer distinguished name.", true);
+
+    protected static ConfigKey<Boolean> rootCAAuthStrictness = new ConfigKey<>("Advanced", Boolean.class,
+            "ca.plugin.root.auth.strictness",
+            "false",
+            "Set client authentication strictness, setting to true will enforce and require client certificate for authentication in applicable CA providers.", true);
+
+    private static ConfigKey<Boolean> rootCAAllowExpiredCert = new ConfigKey<>("Advanced", Boolean.class,
+            "ca.plugin.root.allow.expired.cert",
+            "true",
+            "When set to true, it will allow expired client certificate during SSL handshake.", true);
+
+
+    ///////////////////////////////////////////////////////////
+    /////////////// Root CA Private Methods ///////////////////
+    ///////////////////////////////////////////////////////////
+
+    private Certificate generateCertificate(final List<String> domainNames, final List<String> ipAddresses, final int validityDays) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, CertificateException, SignatureException, IOException, OperatorCreationException {
+        if (domainNames == null || domainNames.size() < 1 || Strings.isNullOrEmpty(domainNames.get(0))) {
+            throw new CloudRuntimeException("No domain name is specified, cannot generate certificate");
+        }
+        final String subject = "CN=" + domainNames.get(0);
+
+        final KeyPair keyPair = CertUtils.generateRandomKeyPair(CAManager.CertKeySize.value());
+        final X509Certificate clientCertificate = CertUtils.generateV3Certificate(
+                caCertificate,
+                caKeyPair.getPrivate(),
+                keyPair.getPublic(),
+                subject,
+                CAManager.CertSignatureAlgorithm.value(),
+                validityDays,
+                domainNames,
+                ipAddresses);
+        return new Certificate(clientCertificate, keyPair.getPrivate(), Collections.singletonList(caCertificate));
+    }
+
+    private Certificate generateCertificateUsingCsr(final String csr, final List<String> domainNames, final List<String> ipAddresses, final int validityDays) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, CertificateException, SignatureException, IOException, OperatorCreationException {
+        PemObject pemObject = null;
+
+        try {
+            final PemReader pemReader = new PemReader(new StringReader(csr));
+            pemObject = pemReader.readPemObject();
+        } catch (IOException e) {
+            LOG.error("Failed to read provided CSR string as a PEM object", e);
+        }
+
+        if (pemObject == null) {
+            throw new CloudRuntimeException("Unable to read/process CSR: " + csr);
+        }
+
+        final PKCS10CertificationRequest request = new PKCS10CertificationRequest(pemObject.getContent());
+
+        final X509Certificate clientCertificate = CertUtils.generateV3Certificate(
+                caCertificate, caKeyPair.getPrivate(),
+                request.getPublicKey(),
+                request.getCertificationRequestInfo().getSubject().toString(),
+                CAManager.CertSignatureAlgorithm.value(),
+                validityDays,
+                domainNames,
+                ipAddresses);
+        return new Certificate(clientCertificate, null, Collections.singletonList(caCertificate));
+
+    }
+
+    ////////////////////////////////////////////////////////
+    /////////////// Root CA API Handlers ///////////////////
+    ////////////////////////////////////////////////////////
+
+    @Override
+    public boolean canProvisionCertificates() {
+        return true;
+    }
+
+    @Override
+    public List<X509Certificate> getCaCertificate() {
+        return Collections.singletonList(caCertificate);
+    }
+
+    @Override
+    public Certificate issueCertificate(final List<String> domainNames, final List<String> ipAddresses, final int validityDays) {
+        try {
+            return generateCertificate(domainNames, ipAddresses, validityDays);
+        } catch (final CertificateException | IOException | SignatureException | NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | OperatorCreationException e) {
+            LOG.error("Failed to create client certificate, due to: ", e);
+            throw new CloudRuntimeException("Failed to generate certificate due to:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public Certificate issueCertificate(final String csr, final List<String> domainNames, final List<String> ipAddresses, final int validityDays) {
+        try {
+            return generateCertificateUsingCsr(csr, domainNames, ipAddresses, validityDays);
+        } catch (final CertificateException | IOException | SignatureException | NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | OperatorCreationException e) {
+            LOG.error("Failed to generate certificate from CSR: ", e);
+            throw new CloudRuntimeException("Failed to generate certificate using CSR due to:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public boolean revokeCertificate(final BigInteger certSerial, final String certCn) {
+        return true;
+    }
+
+    ////////////////////////////////////////////////////////////
+    /////////////// Root CA Trust Management ///////////////////
+    ////////////////////////////////////////////////////////////
+
+    private char[] getCaKeyStorePassphrase() {
+        return KeyStoreUtils.defaultKeystorePassphrase;
+    }
+
+    private KeyStore getCaKeyStore() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException {
+        final KeyStore ks = KeyStore.getInstance("JKS");
+        ks.load(null, null);
+        if (caKeyPair != null && caCertificate != null) {
+            ks.setKeyEntry(caAlias, caKeyPair.getPrivate(), getCaKeyStorePassphrase(), new X509Certificate[]{caCertificate});
+        } else {
+            return null;
+        }
+        return ks;
+    }
+
+    @Override
+    public SSLEngine createSSLEngine(final SSLContext sslContext, final String remoteAddress, final Map<String, X509Certificate> certMap) throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
+        final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+        final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+
+        final KeyStore ks = getCaKeyStore();
+        kmf.init(ks, getCaKeyStorePassphrase());
+        tmf.init(ks);
+
+        final boolean authStrictness = rootCAAuthStrictness.value();
+        final boolean allowExpiredCertificate = rootCAAllowExpiredCert.value();
+
+        TrustManager[] tms = new TrustManager[]{new RootCACustomTrustManager(remoteAddress, authStrictness, allowExpiredCertificate, certMap, caCertificate, crlDao)};
+        sslContext.init(kmf.getKeyManagers(), tms, new SecureRandom());
+        final SSLEngine sslEngine = sslContext.createSSLEngine();
+        sslEngine.setWantClientAuth(authStrictness);
+        return sslEngine;
+    }
+
+    //////////////////////////////////////////////////
+    /////////////// Root CA Config ///////////////////
+    //////////////////////////////////////////////////
+
+    private char[] findKeyStorePassphrase() {
+        char[] passphrase = KeyStoreUtils.defaultKeystorePassphrase;
+        final String configuredPassphrase = DbProperties.getDbProperties().getProperty("db.cloud.keyStorePassphrase");
+        if (configuredPassphrase != null) {
+            passphrase = configuredPassphrase.toCharArray();
+        }
+        return passphrase;
+    }
+
+    private boolean createManagementServerKeystore(final String keyStoreFilePath, final char[] passphrase) {
+        final Certificate managementServerCertificate = issueCertificate(Collections.singletonList(NetUtils.getHostName()),
+                Collections.singletonList(NetUtils.getDefaultHostIp()), caValidityYears * 365);
+        if (managementServerCertificate == null || managementServerCertificate.getPrivateKey() == null) {
+            throw new CloudRuntimeException("Failed to generate certificate and setup management server keystore");
+        }
+        LOG.info("Creating new management server certificate and keystore");
+        try {
+            final KeyStore keyStore = KeyStore.getInstance("JKS");
+            keyStore.load(null, null);
+            keyStore.setCertificateEntry(caAlias, caCertificate);
+            keyStore.setKeyEntry(managementAlias, managementServerCertificate.getPrivateKey(), passphrase,
+                    new X509Certificate[]{managementServerCertificate.getClientCertificate(), caCertificate});
+            final String tmpFile = KeyStoreUtils.defaultTmpKeyStoreFile;
+            final FileOutputStream stream = new FileOutputStream(tmpFile);
+            keyStore.store(stream, passphrase);
+            stream.close();
+            KeyStoreUtils.copyKeystore(keyStoreFilePath, tmpFile);
+            LOG.debug("Saved default root CA (server) keystore file at:" + keyStoreFilePath);
+        } catch (final CertificateException | NoSuchAlgorithmException | KeyStoreException | IOException  e) {
+            LOG.error("Failed to save root CA (server) keystore due to exception: ", e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean checkManagementServerKeystore() {
+        final File confFile = PropertiesUtil.findConfigFile("db.properties");
+        if (confFile == null) {
+            return false;
+        }
+        final char[] passphrase = findKeyStorePassphrase();
+        final String keystorePath = confFile.getParent() + "/" + KeyStoreUtils.defaultKeystoreFile;
+        final File keystoreFile = new File(keystorePath);
+        if (keystoreFile.exists()) {
+            try {
+                final KeyStore msKeystore = Link.loadKeyStore(new FileInputStream(keystorePath), passphrase);
+                try {
+                    final java.security.cert.Certificate[] msCertificates = msKeystore.getCertificateChain(managementAlias);
+                    if (msCertificates != null && msCertificates.length > 1) {
+                        msCertificates[0].verify(caKeyPair.getPublic());
+                        ((X509Certificate)msCertificates[0]).checkValidity();
+                        return true;
+                    }
+                } catch (final CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | SignatureException e) {
+                    LOG.info("Renewing management server keystore, current certificate has expired");
+                    return createManagementServerKeystore(keystoreFile.getAbsolutePath(), passphrase);
+                }
+            } catch (final GeneralSecurityException | IOException e) {
+                LOG.error("Failed to read current management server keystore, renewing keystore!");
+            }
+        }
+        return createManagementServerKeystore(keystoreFile.getAbsolutePath(), passphrase);
+    }
+
+    /////////////////////////////////////////////////
+    /////////////// Root CA Setup ///////////////////
+    /////////////////////////////////////////////////
+
+    private boolean saveNewRootCAKeypair() {
+        try {
+            LOG.debug("Generating root CA public/private keys");
+            final KeyPair keyPair = CertUtils.generateRandomKeyPair(2 * CAManager.CertKeySize.value());
+            if (!configDao.update(rootCAPublicKey.key(), rootCAPublicKey.category(), CertUtils.publicKeyToPem(keyPair.getPublic()))) {
+                LOG.error("Failed to save RootCA public key");
+            }
+            if (!configDao.update(rootCAPrivateKey.key(), rootCAPrivateKey.category(), CertUtils.privateKeyToPem(keyPair.getPrivate()))) {
+                LOG.error("Failed to save RootCA private key");
+            }
+        } catch (final NoSuchProviderException | NoSuchAlgorithmException | IOException e) {
+            LOG.error("Failed to generate/save RootCA private/public keys due to exception:", e);
+        }
+        return loadRootCAKeyPair();
+    }
+
+    private boolean saveNewRootCACertificate() {
+        if (caKeyPair == null) {
+            throw new CloudRuntimeException("Cannot issue self-signed root CA certificate as CA keypair is not initialized");
+        }
+        try {
+            LOG.debug("Generating root CA certificate");
+            final X509Certificate rootCaCertificate = CertUtils.generateV1Certificate(
+                    caKeyPair,
+                    rootCAIssuerDN.value(),
+                    rootCAIssuerDN.value(),
+                    caValidityYears,
+                    CAManager.CertSignatureAlgorithm.value());
+            if (!configDao.update(rootCACertificate.key(), rootCACertificate.category(), CertUtils.x509CertificateToPem(rootCaCertificate))) {
+                LOG.error("Failed to update RootCA public/x509 certificate");
+            }
+        } catch (final CertificateException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException | InvalidKeyException | OperatorCreationException | IOException e) {
+            LOG.error("Failed to generate RootCA certificate from private/public keys due to exception:", e);
+            return false;
+        }
+        return loadRootCACertificate();
+    }
+
+    private boolean loadRootCAKeyPair() {
+        if (Strings.isNullOrEmpty(rootCAPublicKey.value()) || Strings.isNullOrEmpty(rootCAPrivateKey.value())) {
+            return false;
+        }
+        try {
+            caKeyPair = new KeyPair(CertUtils.pemToPublicKey(rootCAPublicKey.value()), CertUtils.pemToPrivateKey(rootCAPrivateKey.value()));
+        } catch (InvalidKeySpecException | IOException e) {
+            LOG.error("Failed to load saved RootCA private/public keys due to exception:", e);
+            return false;
+        }
+        return caKeyPair.getPrivate() != null && caKeyPair.getPublic() != null;
+    }
+
+    private boolean loadRootCACertificate() {
+        if (Strings.isNullOrEmpty(rootCACertificate.value())) {
+            return false;
+        }
+        try {
+            caCertificate = CertUtils.pemToX509Certificate(rootCACertificate.value());
+            caCertificate.verify(caKeyPair.getPublic());
+        } catch (final IOException | CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
+            LOG.error("Failed to load saved RootCA certificate due to exception:", e);
+            return false;
+        }
+        return caCertificate != null;
+    }
+
+    private boolean setupCA() {
+        if (!loadRootCAKeyPair() && !saveNewRootCAKeypair()) {
+            LOG.error("Failed to save and load root CA keypair");
+            return false;
+        }
+        if (!loadRootCACertificate() && !saveNewRootCACertificate()) {
+            LOG.error("Failed to save and load root CA certificate");
+            return false;
+        }
+        if (!checkManagementServerKeystore()) {
+            LOG.error("Failed to check and configure management server keystore");
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean start() {
+        return loadRootCAKeyPair() && loadRootCAKeyPair() && checkManagementServerKeystore();
+    }
+
+    @Override
+    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
+        super.configure(name, params);
+        Security.addProvider(new BouncyCastleProvider());
+        final GlobalLock caLock = GlobalLock.getInternLock("RootCAProviderSetup");
+        try {
+            if (caLock.lock(5 * 60)) {
+                try {
+                    return setupCA();
+                } finally {
+                    caLock.unlock();
+                }
+            } else {
+                LOG.error("Failed to grab lock and setup CA, startup method will try to load the CA certificate and keypair.");
+            }
+        } finally {
+            caLock.releaseRef();
+        }
+        return true;
+    }
+
+    ///////////////////////////////////////////////////////
+    /////////////// Root CA Descriptors ///////////////////
+    ///////////////////////////////////////////////////////
+
+    @Override
+    public String getConfigComponentName() {
+        return RootCAProvider.class.getSimpleName();
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[]{
+                rootCAPrivateKey,
+                rootCAPublicKey,
+                rootCACertificate,
+                rootCAIssuerDN,
+                rootCAAuthStrictness,
+                rootCAAllowExpiredCert
+        };
+    }
+
+    @Override
+    public String getProviderName() {
+        return "root";
+    }
+
+    @Override
+    public String getDescription() {
+        return "CloudStack's Root CA provider plugin";
+    }
+}
diff --git a/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java b/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java
new file mode 100644
index 0000000..cab1941
--- /dev/null
+++ b/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java
@@ -0,0 +1,111 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.ca.provider;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cloudstack.utils.security.CertUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import com.cloud.certificate.CrlVO;
+import com.cloud.certificate.dao.CrlDao;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RootCACustomTrustManagerTest {
+
+    @Mock
+    private CrlDao crlDao;
+    private KeyPair caKeypair;
+    private KeyPair clientKeypair;
+    private X509Certificate caCertificate;
+    private X509Certificate expiredClientCertificate;
+    private String clientIp = "1.2.3.4";
+    private Map<String, X509Certificate> certMap = new HashMap<>();
+
+    @Before
+    public void setUp() throws Exception {
+        certMap.clear();
+        caKeypair = CertUtils.generateRandomKeyPair(1024);
+        clientKeypair = CertUtils.generateRandomKeyPair(1024);
+        caCertificate = CertUtils.generateV1Certificate(caKeypair, "CN=ca", "CN=ca", 1,
+                "SHA256withRSA");
+        expiredClientCertificate = CertUtils.generateV3Certificate(caCertificate, caKeypair.getPrivate(), clientKeypair.getPublic(),
+                "CN=cloudstack.apache.org", "SHA256withRSA", 0, Collections.singletonList("cloudstack.apache.org"), Collections.singletonList(clientIp));
+    }
+
+    @Test
+    public void testAuthNotStrict() throws Exception {
+        final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao);
+        trustManager.checkClientTrusted(null, null);
+        Assert.assertNull(trustManager.getAcceptedIssuers());
+    }
+
+    @Test(expected = CertificateException.class)
+    public void testAuthStrictWithInvalidCert() throws Exception {
+        final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
+        trustManager.checkClientTrusted(null, null);
+    }
+
+    @Test(expected = CertificateException.class)
+    public void testAuthStrictWithRevokedCert() throws Exception {
+        Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(new CrlVO());
+        final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
+        trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA");
+    }
+
+    @Test(expected = CertificateException.class)
+    public void testAuthStrictWithInvalidCertOwnership() throws Exception {
+        Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
+        final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
+        trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA");
+    }
+
+    @Test(expected = CertificateException.class)
+    public void testAuthStrictWithDenyExpiredCertAndOwnership() throws Exception {
+        Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
+        final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, false, certMap, caCertificate, crlDao);
+        trustManager.checkClientTrusted(new X509Certificate[]{expiredClientCertificate}, "RSA");
+    }
+
+    @Test
+    public void testAuthStrictWithAllowExpiredCertAndOwnership() throws Exception {
+        Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
+        final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
+        Assert.assertTrue(trustManager.getAcceptedIssuers() != null);
+        Assert.assertTrue(trustManager.getAcceptedIssuers().length == 1);
+        Assert.assertEquals(trustManager.getAcceptedIssuers()[0], caCertificate);
+        trustManager.checkClientTrusted(new X509Certificate[]{expiredClientCertificate}, "RSA");
+        Assert.assertTrue(certMap.containsKey(clientIp));
+        Assert.assertEquals(certMap.get(clientIp), expiredClientCertificate);
+    }
+
+}
\ No newline at end of file
diff --git a/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCAProviderTest.java b/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCAProviderTest.java
new file mode 100644
index 0000000..bdd3f08
--- /dev/null
+++ b/plugins/ca/root-ca/test/org/apache/cloudstack/ca/provider/RootCAProviderTest.java
@@ -0,0 +1,155 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.ca.provider;
+
+import java.lang.reflect.Field;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+import javax.net.ssl.SSLEngine;
+
+import org.apache.cloudstack.framework.ca.Certificate;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.utils.security.CertUtils;
+import org.apache.cloudstack.utils.security.SSLUtils;
+import org.joda.time.DateTime;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RootCAProviderTest {
+
+    private KeyPair caKeyPair;
+    private X509Certificate caCertificate;
+
+    private RootCAProvider provider;
+
+    private void addField(final RootCAProvider provider, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
+        Field f = RootCAProvider.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(provider, o);
+    }
+
+    private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
+        Field f = ConfigKey.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(configKey, o);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        caKeyPair = CertUtils.generateRandomKeyPair(1024);
+        caCertificate = CertUtils.generateV1Certificate(caKeyPair, "CN=ca", "CN=ca", 1, "SHA256withRSA");
+
+        provider = new RootCAProvider();
+
+        addField(provider, "caKeyPair", caKeyPair);
+        addField(provider, "caCertificate", caCertificate);
+        addField(provider, "caKeyPair", caKeyPair);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    @Test
+    public void testCanProvisionCertificates() {
+        Assert.assertTrue(provider.canProvisionCertificates());
+    }
+
+    @Test
+    public void testGetCaCertificate() {
+        Assert.assertTrue(provider.getCaCertificate().size() == 1);
+        Assert.assertEquals(provider.getCaCertificate().get(0), caCertificate);
+    }
+
+    @Test
+    public void testIssueCertificateWithoutCsr() throws NoSuchProviderException, CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
+        final Certificate certificate = provider.issueCertificate(Arrays.asList("domain1.com", "domain2.com"), null, 1);
+        Assert.assertTrue(certificate != null);
+        Assert.assertTrue(certificate.getPrivateKey() != null);
+        Assert.assertEquals(certificate.getCaCertificates().get(0), caCertificate);
+        Assert.assertEquals(certificate.getClientCertificate().getIssuerDN(), caCertificate.getIssuerDN());
+        Assert.assertTrue(certificate.getClientCertificate().getNotAfter().before(new DateTime().plusDays(1).toDate()));
+        certificate.getClientCertificate().verify(caCertificate.getPublicKey());
+    }
+
+    @Test
+    public void testIssueCertificateWithCsr() throws NoSuchProviderException, CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
+        final String csr = "-----BEGIN NEW CERTIFICATE REQUEST-----\n" +
+                "MIICxTCCAa0CAQAwUDETMBEGA1UEBhMKY2xvdWRzdGFjazETMBEGA1UEChMKY2xvdWRzdGFjazET\n" +
+                "MBEGA1UECxMKY2xvdWRzdGFjazEPMA0GA1UEAxMGdi0xLVZNMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" +
+                "AQ8AMIIBCgKCAQEAhi3hOrt/p0hUmoW2A+2gFAMxSINItRrHfQ6VUnHhYKZGcTN9honVFuu30tz7\n" +
+                "oSLUUx1laWEWLlIozpUcPSjOuPa5a0JS8kjplMd8DLfLNeQ6gcuEWznMRJqCaKM72qn/FAK3r11l\n" +
+                "2NofEfWbHU5QVQ5CsYF0JndspLcnmf0tnmreAzz6vlSEPQd4g2hTSsPb72eAqYd0eJnl2oXe7cF3\n" +
+                "iemg6/lWoxlh8njVFDKJ5ibNQA/RSc5syzzaQ8fn/AkZlChR5pml47elfC3GuqetfZPAEP4rebXV\n" +
+                "zEw+UVbMo5bWx4AYm1S2HxhmsWC/1J5oxluZDtC6tjMqnkKQze8HbQIDAQABoDAwLgYJKoZIhvcN\n" +
+                "AQkOMSEwHzAdBgNVHQ4EFgQUdgA1C/7vW3lUcb/dnolGjZB55/AwDQYJKoZIhvcNAQELBQADggEB\n" +
+                "AH6ynWbyW5o4h2yEvmcr+upmu/LZYkpfwIWIo+dfrHX9OHu0rhHDIgMgqEStWzrOfhAkcEocQo21\n" +
+                "E4Q39nECO+cgTCQ1nfH5BVqaMEg++n6tqXBwLmAQJkftEmB+YUPFB9OGn5TQY9Pcnof95Y8xnvtR\n" +
+                "0DvVQa9RM9IsqxgvU4wQCcaNHuEC46Wzo7lyYJ6p//GLw8UQnHxsWktt8U+vyaqXjOvz0+nJobUz\n" +
+                "Jv7r7DFkOwgS6ObBczaZsv1yx2YklcKfbsI7xVsvZAXFey2RsvSJi1QPEJC5XbwDenWnCSrPfjJg\n" +
+                "SLJ0p9tV70D6v07r1OOmBtvU5AH4N+vioAZA0BE=\n" +
+                "-----END NEW CERTIFICATE REQUEST-----\n";
+        final Certificate certificate = provider.issueCertificate(csr, Arrays.asList("v-1-VM", "domain1.com", "domain2.com"), null, 1);
+        Assert.assertTrue(certificate != null);
+        Assert.assertTrue(certificate.getPrivateKey() == null);
+        Assert.assertEquals(certificate.getCaCertificates().get(0), caCertificate);
+        Assert.assertTrue(certificate.getClientCertificate().getSubjectDN().toString().startsWith("CN=v-1-VM,"));
+        certificate.getClientCertificate().verify(caCertificate.getPublicKey());
+    }
+
+    @Test
+    public void testRevokeCertificate() throws Exception {
+        Assert.assertTrue(provider.revokeCertificate(CertUtils.generateRandomBigInt(), "anyString"));
+    }
+
+    @Test
+    public void testCreateSSLEngineWithoutAuthStrictness() throws Exception {
+        overrideDefaultConfigValue(RootCAProvider.rootCAAuthStrictness, "_defaultValue", "false");
+        final SSLEngine e = provider.createSSLEngine(SSLUtils.getSSLContext(), "/1.2.3.4:5678", null);
+        Assert.assertFalse(e.getUseClientMode());
+        Assert.assertFalse(e.getWantClientAuth());
+    }
+
+    @Test
+    public void testCreateSSLEngineWithAuthStrictness() throws Exception {
+        overrideDefaultConfigValue(RootCAProvider.rootCAAuthStrictness, "_defaultValue", "true");
+        final SSLEngine e = provider.createSSLEngine(SSLUtils.getSSLContext(), "/1.2.3.4:5678", null);
+        Assert.assertFalse(e.getUseClientMode());
+        Assert.assertTrue(e.getWantClientAuth());
+    }
+
+    @Test
+    public void testGetProviderName() throws Exception {
+        Assert.assertEquals(provider.getProviderName(), "root");
+    }
+
+}
\ No newline at end of file
diff --git a/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockAgentManager.java b/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockAgentManager.java
index 0851023..3a31550 100644
--- a/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockAgentManager.java
+++ b/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockAgentManager.java
@@ -20,6 +20,9 @@ import java.util.Map;
 
 import javax.naming.ConfigurationException;
 
+import org.apache.cloudstack.ca.SetupCertificateCommand;
+import org.apache.cloudstack.ca.SetupKeyStoreCommand;
+
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.CheckHealthCommand;
 import com.cloud.agent.api.CheckNetworkCommand;
@@ -52,6 +55,10 @@ public interface MockAgentManager extends Manager {
 
     Answer pingTest(PingTestCommand cmd);
 
+    Answer setupKeyStore(SetupKeyStoreCommand cmd);
+
+    Answer setupCertificate(SetupCertificateCommand cmd);
+
     MockHost getHost(String guid);
 
     Answer maintain(MaintainCommand cmd);
diff --git a/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockAgentManagerImpl.java b/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockAgentManagerImpl.java
index 9211214..9d1e407 100644
--- a/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockAgentManagerImpl.java
+++ b/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockAgentManagerImpl.java
@@ -31,7 +31,10 @@ import java.util.regex.PatternSyntaxException;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
-import com.cloud.user.AccountManager;
+import org.apache.cloudstack.ca.SetupCertificateAnswer;
+import org.apache.cloudstack.ca.SetupCertificateCommand;
+import org.apache.cloudstack.ca.SetupKeyStoreCommand;
+import org.apache.cloudstack.ca.SetupKeystoreAnswer;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
@@ -62,6 +65,7 @@ import com.cloud.simulator.MockHostVO;
 import com.cloud.simulator.MockVMVO;
 import com.cloud.simulator.dao.MockHostDao;
 import com.cloud.simulator.dao.MockVMDao;
+import com.cloud.user.AccountManager;
 import com.cloud.utils.Pair;
 import com.cloud.utils.component.ManagerBase;
 import com.cloud.utils.concurrency.NamedThreadFactory;
@@ -460,6 +464,24 @@ public class MockAgentManagerImpl extends ManagerBase implements MockAgentManage
     }
 
     @Override
+    public Answer setupKeyStore(SetupKeyStoreCommand cmd) {
+        return new SetupKeystoreAnswer(
+                "-----BEGIN CERTIFICATE REQUEST-----\n" +
+                "MIIBHjCByQIBADBkMQswCQYDVQQGEwJJTjELMAkGA1UECAwCSFIxETAPBgNVBAcM\n" +
+                "CEd1cnVncmFtMQ8wDQYDVQQKDAZBcGFjaGUxEzARBgNVBAsMCkNsb3VkU3RhY2sx\n" +
+                "DzANBgNVBAMMBnYtMS1WTTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQD46KFWKYrJ\n" +
+                "F43Y1oqWUfrl4mj4Qm05Bgsi6nuigZv7ufiAKK0nO4iJKdRa2hFMUvBi2/bU3IyY\n" +
+                "Nvg7cdJsn4K9AgMBAAGgADANBgkqhkiG9w0BAQUFAANBAIta9glu/ZSjA/ncyXix\n" +
+                "yDOyAKmXXxsRIsdrEuIzakUuJS7C8IG0FjUbDyIaiwWQa5x+Lt4oMqCmpNqRzaGP\n" +
+                "fOo=\n" + "-----END CERTIFICATE REQUEST-----");
+    }
+
+    @Override
+    public Answer setupCertificate(SetupCertificateCommand cmd) {
+        return new SetupCertificateAnswer(true);
+    }
+
+    @Override
     public boolean start() {
         for (Discoverer discoverer : discoverers) {
             if (discoverer instanceof SimulatorSecondaryDiscoverer) {
diff --git a/plugins/hypervisors/simulator/src/com/cloud/agent/manager/SimulatorManagerImpl.java b/plugins/hypervisors/simulator/src/com/cloud/agent/manager/SimulatorManagerImpl.java
index 0359339..b20bd3d 100644
--- a/plugins/hypervisors/simulator/src/com/cloud/agent/manager/SimulatorManagerImpl.java
+++ b/plugins/hypervisors/simulator/src/com/cloud/agent/manager/SimulatorManagerImpl.java
@@ -26,6 +26,8 @@ import java.util.Map;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import org.apache.cloudstack.ca.SetupCertificateCommand;
+import org.apache.cloudstack.ca.SetupKeyStoreCommand;
 import org.apache.cloudstack.storage.command.DeleteCommand;
 import org.apache.cloudstack.storage.command.DownloadCommand;
 import org.apache.cloudstack.storage.command.DownloadProgressCommand;
@@ -280,6 +282,10 @@ public class SimulatorManagerImpl extends ManagerBase implements SimulatorManage
                     answer = _mockAgentMgr.checkHealth((CheckHealthCommand)cmd);
                 } else if (cmd instanceof PingTestCommand) {
                     answer = _mockAgentMgr.pingTest((PingTestCommand)cmd);
+                } else if (cmd instanceof SetupKeyStoreCommand) {
+                    answer = _mockAgentMgr.setupKeyStore((SetupKeyStoreCommand)cmd);
+                } else if (cmd instanceof SetupCertificateCommand) {
+                    answer = _mockAgentMgr.setupCertificate((SetupCertificateCommand)cmd);
                 } else if (cmd instanceof PrepareForMigrationCommand) {
                     answer = _mockVmMgr.prepareForMigrate((PrepareForMigrationCommand)cmd);
                 } else if (cmd instanceof MigrateCommand) {
diff --git a/plugins/pom.xml b/plugins/pom.xml
index 4045349..bcc7240 100755
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -53,6 +53,7 @@
     <module>acl/dynamic-role-based</module>
     <module>affinity-group-processors/host-anti-affinity</module>
     <module>affinity-group-processors/explicit-dedication</module>
+    <module>ca/root-ca</module>
     <module>deployment-planners/user-concentrated-pod</module>
     <module>deployment-planners/user-dispersing</module>
     <module>deployment-planners/implicit-dedication</module>
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
index e2d1b88..d280ed5 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
@@ -16,13 +16,34 @@
 // under the License.
 package org.apache.cloudstack.saml;
 
-import com.cloud.domain.Domain;
-import com.cloud.user.DomainManager;
-import com.cloud.user.User;
-import com.cloud.user.UserVO;
-import com.cloud.user.dao.UserDao;
-import com.cloud.utils.PropertiesUtil;
-import com.cloud.utils.component.AdapterBase;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.inject.Inject;
+import javax.xml.stream.FactoryConfigurationError;
+
 import org.apache.cloudstack.api.command.AuthorizeSAMLSSOCmd;
 import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd;
 import org.apache.cloudstack.api.command.ListAndSwitchSAMLAccountCmd;
@@ -34,9 +55,11 @@ import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
 import org.apache.cloudstack.framework.security.keystore.KeystoreDao;
 import org.apache.cloudstack.framework.security.keystore.KeystoreVO;
+import org.apache.cloudstack.utils.security.CertUtils;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.log4j.Logger;
+import org.bouncycastle.operator.OperatorCreationException;
 import org.opensaml.DefaultBootstrap;
 import org.opensaml.common.xml.SAMLConstants;
 import org.opensaml.saml2.metadata.ContactPerson;
@@ -61,32 +84,13 @@ import org.opensaml.xml.security.credential.UsageType;
 import org.opensaml.xml.security.keyinfo.KeyInfoHelper;
 import org.springframework.stereotype.Component;
 
-import javax.inject.Inject;
-import javax.xml.stream.FactoryConfigurationError;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutput;
-import java.io.ObjectOutputStream;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
+import com.cloud.domain.Domain;
+import com.cloud.user.DomainManager;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
+import com.cloud.user.dao.UserDao;
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.component.AdapterBase;
 
 @Component
 public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManager, Configurable {
@@ -141,12 +145,14 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
         KeystoreVO keyStoreVO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_KEYPAIR);
         if (keyStoreVO == null) {
             try {
-                KeyPair keyPair = SAMLUtils.generateRandomKeyPair();
-                _ksDao.save(SAMLPluginConstants.SAMLSP_KEYPAIR, SAMLUtils.savePrivateKey(keyPair.getPrivate()), SAMLUtils.savePublicKey(keyPair.getPublic()), "samlsp-keypair");
+                KeyPair keyPair = CertUtils.generateRandomKeyPair(4096);
+                _ksDao.save(SAMLPluginConstants.SAMLSP_KEYPAIR,
+                        CertUtils.privateKeyToPem(keyPair.getPrivate()),
+                        CertUtils.publicKeyToPem(keyPair.getPublic()), "samlsp-keypair");
                 keyStoreVO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_KEYPAIR);
                 s_logger.info("No SAML keystore found, created and saved a new Service Provider keypair");
-            } catch (NoSuchProviderException | NoSuchAlgorithmException e) {
-                s_logger.error("Unable to create and save SAML keypair: " + e.toString());
+            } catch (final NoSuchProviderException | NoSuchAlgorithmException | IOException e) {
+                s_logger.error("Unable to create and save SAML keypair, due to: ", e);
             }
         }
 
@@ -160,8 +166,19 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
         KeyPair spKeyPair = null;
         X509Certificate spX509Key = null;
         if (keyStoreVO != null) {
-            PrivateKey privateKey = SAMLUtils.loadPrivateKey(keyStoreVO.getCertificate());
-            PublicKey publicKey = SAMLUtils.loadPublicKey(keyStoreVO.getKey());
+
+            PrivateKey privateKey = null;
+            try {
+                privateKey = CertUtils.pemToPrivateKey(keyStoreVO.getCertificate());
+            } catch (final InvalidKeySpecException | IOException e) {
+                s_logger.error("Failed to read private key, due to error: ", e);
+            }
+            PublicKey publicKey = null;
+            try {
+                publicKey = CertUtils.pemToPublicKey(keyStoreVO.getKey());
+            } catch (final InvalidKeySpecException | IOException e) {
+                s_logger.error("Failed to read public key, due to error: ", e);
+            }
             if (privateKey != null && publicKey != null) {
                 spKeyPair = new KeyPair(publicKey, privateKey);
                 KeystoreVO x509VO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_X509CERT);
@@ -174,8 +191,8 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
                         out.flush();
                         _ksDao.save(SAMLPluginConstants.SAMLSP_X509CERT, Base64.encodeBase64String(bos.toByteArray()), "", "samlsp-x509cert");
                         bos.close();
-                    } catch (NoSuchAlgorithmException | NoSuchProviderException | CertificateEncodingException | SignatureException | InvalidKeyException | IOException e) {
-                        s_logger.error("SAML Plugin won't be able to use X509 signed authentication");
+                    } catch (final NoSuchAlgorithmException | NoSuchProviderException | CertificateException | SignatureException | InvalidKeyException | IOException | OperatorCreationException e) {
+                        s_logger.error("SAML plugin won't be able to use X509 signed authentication", e);
                     }
                 } else {
                     try {
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
index ec6b2c1..364ef86 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLUtils.java
@@ -19,14 +19,41 @@
 
 package org.apache.cloudstack.saml;
 
-import com.cloud.utils.HttpUtils;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.FactoryConfigurationError;
+
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.response.LoginCmdResponse;
+import org.apache.cloudstack.utils.security.CertUtils;
 import org.apache.log4j.Logger;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.x509.X509V1CertificateGenerator;
+import org.bouncycastle.operator.OperatorCreationException;
 import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.opensaml.Configuration;
 import org.opensaml.DefaultBootstrap;
 import org.opensaml.common.SAMLVersion;
@@ -63,41 +90,7 @@ import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.xml.sax.SAXException;
 
-import javax.security.auth.x500.X500Principal;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletResponse;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.stream.FactoryConfigurationError;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.math.BigInteger;
-import java.net.URLEncoder;
-import java.nio.charset.Charset;
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.List;
-import java.util.zip.Deflater;
-import java.util.zip.DeflaterOutputStream;
+import com.cloud.utils.HttpUtils;
 
 public class SAMLUtils {
     public static final Logger s_logger = Logger.getLogger(SAMLUtils.class);
@@ -271,89 +264,10 @@ public class SAMLUtils {
         return url;
     }
 
-    public static KeyFactory getKeyFactory() {
-        KeyFactory keyFactory = null;
-        try {
-            Security.addProvider(new BouncyCastleProvider());
-            keyFactory = KeyFactory.getInstance("RSA", "BC");
-        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
-            s_logger.error("Unable to create KeyFactory:" + e.getMessage());
-        }
-        return keyFactory;
-    }
-
-    public static String savePublicKey(PublicKey key) {
-        try {
-            KeyFactory keyFactory = SAMLUtils.getKeyFactory();
-            if (keyFactory == null) return null;
-            X509EncodedKeySpec spec = keyFactory.getKeySpec(key, X509EncodedKeySpec.class);
-            return new String(org.bouncycastle.util.encoders.Base64.encode(spec.getEncoded()), Charset.forName("UTF-8"));
-        } catch (InvalidKeySpecException e) {
-            s_logger.error("Unable to create KeyFactory:" + e.getMessage());
-        }
-        return null;
-    }
-
-    public static String savePrivateKey(PrivateKey key) {
-        try {
-            KeyFactory keyFactory = SAMLUtils.getKeyFactory();
-            if (keyFactory == null) return null;
-            PKCS8EncodedKeySpec spec = keyFactory.getKeySpec(key,
-                    PKCS8EncodedKeySpec.class);
-            return new String(org.bouncycastle.util.encoders.Base64.encode(spec.getEncoded()), Charset.forName("UTF-8"));
-        } catch (InvalidKeySpecException e) {
-            s_logger.error("Unable to create KeyFactory:" + e.getMessage());
-        }
-        return null;
-    }
-
-    public static PublicKey loadPublicKey(String publicKey) {
-        byte[] sigBytes = org.bouncycastle.util.encoders.Base64.decode(publicKey);
-        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(sigBytes);
-        KeyFactory keyFact = SAMLUtils.getKeyFactory();
-        if (keyFact == null)
-            return null;
-        try {
-            return keyFact.generatePublic(x509KeySpec);
-        } catch (InvalidKeySpecException e) {
-            s_logger.error("Unable to create PrivateKey from privateKey string:" + e.getMessage());
-        }
-        return null;
-    }
-
-    public static PrivateKey loadPrivateKey(String privateKey) {
-        byte[] sigBytes = org.bouncycastle.util.encoders.Base64.decode(privateKey);
-        PKCS8EncodedKeySpec pkscs8KeySpec = new PKCS8EncodedKeySpec(sigBytes);
-        KeyFactory keyFact = SAMLUtils.getKeyFactory();
-        if (keyFact == null)
-            return null;
-        try {
-            return keyFact.generatePrivate(pkscs8KeySpec);
-        } catch (InvalidKeySpecException e) {
-            s_logger.error("Unable to create PrivateKey from privateKey string:" + e.getMessage());
-        }
-        return null;
-    }
-
-    public static KeyPair generateRandomKeyPair() throws NoSuchProviderException, NoSuchAlgorithmException {
-        Security.addProvider(new BouncyCastleProvider());
-        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
-        keyPairGenerator.initialize(4096, new SecureRandom());
-        return keyPairGenerator.generateKeyPair();
-    }
-
-    public static X509Certificate generateRandomX509Certificate(KeyPair keyPair) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateEncodingException, SignatureException, InvalidKeyException {
-        DateTime now = DateTime.now(DateTimeZone.UTC);
-        X500Principal dnName = new X500Principal("CN=ApacheCloudStack");
-        X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
-        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
-        certGen.setSubjectDN(dnName);
-        certGen.setIssuerDN(dnName);
-        certGen.setNotBefore(now.minusDays(1).toDate());
-        certGen.setNotAfter(now.plusYears(3).toDate());
-        certGen.setPublicKey(keyPair.getPublic());
-        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
-        return certGen.generate(keyPair.getPrivate(), "BC");
+    public static X509Certificate generateRandomX509Certificate(KeyPair keyPair) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateException, SignatureException, InvalidKeyException, OperatorCreationException {
+        return CertUtils.generateV1Certificate(keyPair,
+                "CN=ApacheCloudStack", "CN=ApacheCloudStack",
+                3, "SHA256WithRSA");
     }
 
     public static void setupSamlUserCookies(final LoginCmdResponse loginResponse, final HttpServletResponse resp) throws IOException {
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
index 86009ac..3df0fcc 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/GetServiceProviderMetaDataCmdTest.java
@@ -19,13 +19,22 @@
 
 package org.apache.cloudstack;
 
-import com.cloud.utils.HttpUtils;
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
 import org.apache.cloudstack.api.ApiServerService;
 import org.apache.cloudstack.api.auth.APIAuthenticationType;
 import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd;
 import org.apache.cloudstack.saml.SAML2AuthManager;
 import org.apache.cloudstack.saml.SAMLProviderMetadata;
 import org.apache.cloudstack.saml.SAMLUtils;
+import org.apache.cloudstack.utils.security.CertUtils;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -33,20 +42,7 @@ import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import java.lang.reflect.Field;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
+import com.cloud.utils.HttpUtils;
 
 @RunWith(MockitoJUnitRunner.class)
 public class GetServiceProviderMetaDataCmdTest {
@@ -67,7 +63,7 @@ public class GetServiceProviderMetaDataCmdTest {
     HttpServletRequest req;
 
     @Test
-    public void testAuthenticate() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, CertificateParsingException, CertificateEncodingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, UnknownHostException {
+    public void testAuthenticate() throws Exception {
         GetServiceProviderMetaDataCmd cmd = new GetServiceProviderMetaDataCmd();
 
         Field apiServerField = GetServiceProviderMetaDataCmd.class.getDeclaredField("_apiServer");
@@ -80,7 +76,7 @@ public class GetServiceProviderMetaDataCmdTest {
 
         String spId = "someSPID";
         String url = "someUrl";
-        KeyPair kp = SAMLUtils.generateRandomKeyPair();
+        KeyPair kp = CertUtils.generateRandomKeyPair(4096);
         X509Certificate cert = SAMLUtils.generateRandomX509Certificate(kp);
 
         SAMLProviderMetadata providerMetadata = new SAMLProviderMetadata();
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
index bd87831..4986d7a 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/SAMLUtilsTest.java
@@ -19,15 +19,17 @@
 
 package org.apache.cloudstack;
 
-import junit.framework.TestCase;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
 import org.apache.cloudstack.saml.SAMLUtils;
+import org.apache.cloudstack.utils.security.CertUtils;
 import org.junit.Test;
 import org.opensaml.saml2.core.AuthnRequest;
 import org.opensaml.saml2.core.LogoutRequest;
 
-import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
+import junit.framework.TestCase;
 
 public class SAMLUtilsTest extends TestCase {
 
@@ -60,13 +62,13 @@ public class SAMLUtilsTest extends TestCase {
 
     @Test
     public void testX509Helpers() throws Exception {
-        KeyPair keyPair = SAMLUtils.generateRandomKeyPair();
+        KeyPair keyPair = CertUtils.generateRandomKeyPair(4096);
 
-        String privateKeyString = SAMLUtils.savePrivateKey(keyPair.getPrivate());
-        String publicKeyString = SAMLUtils.savePublicKey(keyPair.getPublic());
+        String privateKeyString = CertUtils.privateKeyToPem(keyPair.getPrivate());
+        String publicKeyString = CertUtils.publicKeyToPem(keyPair.getPublic());
 
-        PrivateKey privateKey = SAMLUtils.loadPrivateKey(privateKeyString);
-        PublicKey publicKey = SAMLUtils.loadPublicKey(publicKeyString);
+        PrivateKey privateKey = CertUtils.pemToPrivateKey(privateKeyString);
+        PublicKey publicKey = CertUtils.pemToPublicKey(publicKeyString);
 
         assertTrue(privateKey.equals(keyPair.getPrivate()));
         assertTrue(publicKey.equals(keyPair.getPublic()));
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
index 36140f2..2ce8841 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java
@@ -21,12 +21,16 @@ package org.apache.cloudstack.api.command;
 
 import static org.junit.Assert.assertFalse;
 
-import com.cloud.domain.Domain;
-import com.cloud.user.AccountService;
-import com.cloud.user.DomainManager;
-import com.cloud.user.UserAccountVO;
-import com.cloud.user.dao.UserAccountDao;
-import com.cloud.utils.HttpUtils;
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
 
 import org.apache.cloudstack.api.ApiServerService;
 import org.apache.cloudstack.api.BaseCmd;
@@ -36,6 +40,7 @@ import org.apache.cloudstack.saml.SAML2AuthManager;
 import org.apache.cloudstack.saml.SAMLPluginConstants;
 import org.apache.cloudstack.saml.SAMLProviderMetadata;
 import org.apache.cloudstack.saml.SAMLUtils;
+import org.apache.cloudstack.utils.security.CertUtils;
 import org.joda.time.DateTime;
 import org.junit.Assert;
 import org.junit.Test;
@@ -64,16 +69,12 @@ import org.opensaml.saml2.core.impl.StatusBuilder;
 import org.opensaml.saml2.core.impl.StatusCodeBuilder;
 import org.opensaml.saml2.core.impl.SubjectBuilder;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import java.lang.reflect.Field;
-import java.security.KeyPair;
-import java.security.cert.X509Certificate;
-import java.util.HashMap;
-import java.util.Map;
-import java.net.InetAddress;
+import com.cloud.domain.Domain;
+import com.cloud.user.AccountService;
+import com.cloud.user.DomainManager;
+import com.cloud.user.UserAccountVO;
+import com.cloud.user.dao.UserAccountDao;
+import com.cloud.utils.HttpUtils;
 
 @RunWith(MockitoJUnitRunner.class)
 public class SAML2LoginAPIAuthenticatorCmdTest {
@@ -158,7 +159,7 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
         userAccountDaoField.setAccessible(true);
         userAccountDaoField.set(cmd, userAccountDao);
 
-        KeyPair kp = SAMLUtils.generateRandomKeyPair();
+        KeyPair kp = CertUtils.generateRandomKeyPair(4096);
         X509Certificate cert = SAMLUtils.generateRandomX509Certificate(kp);
 
         SAMLProviderMetadata providerMetadata = new SAMLProviderMetadata();
diff --git a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
index cbfcc55..09391c5 100644
--- a/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
+++ b/plugins/user-authenticators/saml2/test/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmdTest.java
@@ -19,11 +19,19 @@
 
 package org.apache.cloudstack.api.command;
 
-import com.cloud.utils.HttpUtils;
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.security.cert.X509Certificate;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
 import org.apache.cloudstack.api.ApiServerService;
 import org.apache.cloudstack.api.auth.APIAuthenticationType;
 import org.apache.cloudstack.saml.SAML2AuthManager;
 import org.apache.cloudstack.saml.SAMLUtils;
+import org.apache.cloudstack.utils.security.CertUtils;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,12 +39,7 @@ import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import java.lang.reflect.Field;
-import java.security.cert.X509Certificate;
-import java.net.InetAddress;
+import com.cloud.utils.HttpUtils;
 
 @RunWith(MockitoJUnitRunner.class)
 public class SAML2LogoutAPIAuthenticatorCmdTest {
@@ -70,7 +73,7 @@ public class SAML2LogoutAPIAuthenticatorCmdTest {
 
         String spId = "someSPID";
         String url = "someUrl";
-        X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
+        X509Certificate cert = SAMLUtils.generateRandomX509Certificate(CertUtils.generateRandomKeyPair(4096));
         Mockito.when(session.getAttribute(Mockito.anyString())).thenReturn(null);
 
         cmd.authenticate("command", null, session, InetAddress.getByName("127.0.0.1"), HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
diff --git a/pom.xml b/pom.xml
index edfd649..2ff63e6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,7 +64,7 @@
     <cs.junit.version>4.12</cs.junit.version>
     <cs.hamcrest.version>1.3</cs.hamcrest.version>
     <cs.junit.dataprovider.version>1.12.0</cs.junit.dataprovider.version>
-    <cs.bcprov.version>1.55</cs.bcprov.version>
+    <cs.bcprov.version>1.57</cs.bcprov.version>
     <cs.jsch.version>0.1.54</cs.jsch.version>
     <cs.jpa.version>2.1.1</cs.jpa.version>
     <cs.jasypt.version>1.9.2</cs.jasypt.version>
@@ -226,6 +226,11 @@
         <version>${cs.bcprov.version}</version>
       </dependency>
       <dependency>
+        <groupId>org.bouncycastle</groupId>
+        <artifactId>bcpkix-jdk15on</artifactId>
+        <version>${cs.bcprov.version}</version>
+      </dependency>
+      <dependency>
         <groupId>org.apache.xmlgraphics</groupId>
         <artifactId>batik-css</artifactId>
         <version>${cs.batik.version}</version>
diff --git a/scripts/common/keys/ssl-keys.py b/scripts/common/keys/ssl-keys.py
deleted file mode 100644
index d6804cc..0000000
--- a/scripts/common/keys/ssl-keys.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/bin/bash
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-
-# Copies keys that enable SSH communication with system vms
-# $1 = new public key
-# $2 = new private key
-'''
-All imports go here...
-'''
-from subprocess import call
-import socket
-import sys
-import os
-import subprocess
-import traceback
-
-def generateSSLKey(outputPath):
-  logf = open("ssl-keys.log", "w")
-  hostName = socket.gethostbyname(socket.gethostname())
-  keyFile = outputPath + os.sep + "cloudmanagementserver.keystore"
-  logf.write("HostName = %s\n" % hostName)
-  logf.write("OutputPath = %s\n" % keyFile)
-  dname='cn="Cloudstack User",ou="' + hostName + '",o="' + hostName + '",c="Unknown"';
-  logf.write("dname = %s\n" % dname)
-  logf.flush()
-  try :
-    return_code = subprocess.Popen(["keytool", "-genkey", "-keystore", keyFile, "-storepass", "vmops.com", "-keypass", "vmops.com", "-keyalg", "RSA", "-validity", "3650", "-dname", dname],shell=True,stdout=logf, stderr=logf)
-    return_code.wait()
-  except OSError as e:
-    logf.flush()
-    traceback.print_exc(file=logf)
-  logf.flush()
-  logf.write("SSL key generated is : %s" % return_code)
-  logf.flush()
-
-argsSize=len(sys.argv)
-if argsSize != 2:
-	print("Usage: ssl-keys.py <SSL File Key Path>")
-	sys.exit(None)
-sslKeyPath=sys.argv[1]
-
-generateSSLKey(sslKeyPath)
\ No newline at end of file
diff --git a/scripts/installer/windows/acs.wxs b/scripts/installer/windows/acs.wxs
index fa8ff41..6f2aec0 100644
--- a/scripts/installer/windows/acs.wxs
+++ b/scripts/installer/windows/acs.wxs
@@ -255,9 +255,6 @@
     <CustomAction Id="SetuptoolsInstallation" Directory='INSTALLDIR'
       ExeCommand='[PYTHON_HOME]\python "[INSTALLDIR]\ez_setup.py"'
       Execute="deferred" Return="check" />
-    <CustomAction Id="GenerateSSLKey" Directory='CSMANAGEMENT'
-      ExeCommand='[PYTHON_HOME]\python "[CSMANAGEMENT]\webapps\client\WEB-INF\classes\scripts\common\keys\ssl-keys.py" "[CSMANAGEMENT]\lib"'
-      Execute="deferred" Return="check" />
     <CustomAction Id="DbHostWithPort" Execute="immediate" Property="DB_HOSTNAME" Value="[DB_HOSTNAME]:[DB_PORT]" />
     <CustomAction Id="DeployDB" Directory='CSMANAGEMENT'
       ExeCommand='[PYTHON_HOME]\python "[INSTALLDIR]\scripts\cloud-setup-databases" [DB_USERNAME]:[DB_PASSWORD]@[DB_HOSTNAME] --deploy-as=root:[DB_ROOT_PASSWORD] -c "[CSMANAGEMENT]\lib" -f "[CSMANAGEMENT]\setup" -j "[CSMANAGEMENT]\webapps\client\WEB-INF\lib\jasypt-1.9.2.jar" -n "[CSMANAGEMENT]\lib\key" -b "[MYSQL]\bin"'
diff --git a/scripts/installer/windows/client.wxs b/scripts/installer/windows/client.wxs
index 91b15fb..4468ae8 100644
--- a/scripts/installer/windows/client.wxs
+++ b/scripts/installer/windows/client.wxs
@@ -586,9 +586,6 @@
                         <Component Id="cmp6001B626D853C4E4FAB8511246B8D2B4" Guid="{DB90C42E-A2DC-4742-8BC8-49AF7556E7D1}">
                             <File Id="fil5A0CF0340938B7ACC3B69C1353BDC625" KeyPath="yes" Source="!(wix.SourceClient)\WEB-INF\classes\classpath.conf" />
                         </Component>
-                        <Component Id="cmp71D36BFB6B214FAAD323C31A1CE3BC19" Guid="{EF4C61E1-F77E-4E4D-80EA-131511E0804E}">
-                            <File Id="fil44B623C422B90349A635A113C5F4D417" KeyPath="yes" Source="!(wix.SourceClient)\WEB-INF\classes\cloudmanagementserver.keystore" />
-                        </Component>
                         <Component Id="cmp68E096BB729948107692341D8202CC5A" Guid="{0D5D3AF3-0BC0-48EE-ABA3-AF07535169BF}">
                             <File Id="fil3E9BCB1A8CB8F8415FE3E71B65A94878" KeyPath="yes" Source="!(wix.SourceClient)\WEB-INF\classes\context.xml" />
                         </Component>
diff --git a/scripts/network/domr/router_proxy.sh b/scripts/network/domr/router_proxy.sh
index f9cb7ca..945ca96 100755
--- a/scripts/network/domr/router_proxy.sh
+++ b/scripts/network/domr/router_proxy.sh
@@ -35,13 +35,11 @@ check_gw() {
 
 cert="/root/.ssh/id_rsa.cloud"
 
-script=$1
-shift
-
-domRIp=$1
-shift
+script="$1"
+domRIp="$2"
 
 check_gw "$domRIp"
 
-ssh -p 3922 -q -o StrictHostKeyChecking=no -i $cert root@$domRIp "/opt/cloud/bin/$script $*"
+ssh -p 3922 -q -o StrictHostKeyChecking=no -i $cert root@$domRIp "/opt/cloud/bin/$script ${@:3}"
+
 exit $?
diff --git a/scripts/util/keystore-cert-import b/scripts/util/keystore-cert-import
new file mode 100755
index 0000000..bb03b6f
--- /dev/null
+++ b/scripts/util/keystore-cert-import
@@ -0,0 +1,100 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+PROPS_FILE="$1"
+KS_FILE="$2"
+MODE="$3"
+CERT_FILE="$4"
+CERT=$(echo "$5" | tr '^' '\n' | tr '~' ' ')
+CACERT_FILE="$6"
+CACERT=$(echo "$7" | tr '^' '\n' | tr '~' ' ')
+PRIVKEY_FILE="$8"
+PRIVKEY=$(echo "$9" | tr '^' '\n' | tr '~' ' ')
+
+ALIAS="cloud"
+SYSTEM_FILE="/var/cache/cloud/cmdline"
+
+# Find keystore password
+KS_PASS=$(sed -n '/keystore.passphrase/p' "$PROPS_FILE" 2>/dev/null  | sed 's/keystore.passphrase=//g' 2>/dev/null)
+
+if [ -z "${KS_PASS// }" ]; then
+    echo "Failed to find keystore passphrase from file: $PROPS_FILE, quiting!"
+    exit 1
+fi
+
+# Use a new keystore file
+NEW_KS_FILE="$KS_FILE.new"
+
+# Import certificate
+if [ ! -z "${CERT// }" ]; then
+    echo "$CERT" > "$CERT_FILE"
+fi
+
+# Import ca certs
+if [ ! -z "${CACERT// }" ]; then
+    echo "$CACERT" > "$CACERT_FILE"
+fi
+
+# Import cacerts into the keystore
+awk '/-----BEGIN CERTIFICATE-----?/{n++}{print > "cloudca." n }' "$CACERT_FILE"
+for caChain in $(ls cloudca.*); do
+    keytool -delete -noprompt -alias "$caChain" -keystore "$NEW_KS_FILE" -storepass "$KS_PASS" > /dev/null 2>&1 || true
+    keytool -import -noprompt -storepass "$KS_PASS" -trustcacerts -alias "$caChain" -file "$caChain" -keystore "$NEW_KS_FILE" > /dev/null 2>&1
+done
+rm -f cloudca.*
+
+# Import private key if available
+if [ ! -z "${PRIVKEY// }" ]; then
+    echo "$PRIVKEY" > "$PRIVKEY_FILE"
+    # Re-initialize keystore when private key is provided
+    keytool -delete -noprompt -alias "$ALIAS" -keystore "$NEW_KS_FILE" -storepass "$KS_PASS" 2>/dev/null || true
+    openssl pkcs12 -export -name "$ALIAS" -in "$CERT_FILE" -inkey "$PRIVKEY_FILE" -out "$NEW_KS_FILE.p12" -password pass:"$KS_PASS" > /dev/null 2>&1
+    keytool -importkeystore -srckeystore "$NEW_KS_FILE.p12" -destkeystore "$NEW_KS_FILE" -srcstoretype PKCS12 -alias "$ALIAS" -deststorepass "$KS_PASS" -destkeypass "$KS_PASS" -srcstorepass "$KS_PASS" -srckeypass "$KS_PASS" > /dev/null 2>&1
+else
+    # Import certificate into the keystore
+    keytool -import -storepass "$KS_PASS" -alias "$ALIAS" -file "$CERT_FILE" -keystore "$NEW_KS_FILE" > /dev/null 2>&1 || true
+    # Export private key from keystore
+    rm -f "$PRIVKEY_FILE"
+    keytool -importkeystore -srckeystore "$NEW_KS_FILE" -destkeystore "$NEW_KS_FILE.p12" -deststoretype PKCS12 -srcalias "$ALIAS" -deststorepass "$KS_PASS" -destkeypass "$KS_PASS" -srcstorepass "$KS_PASS" -srckeypass "$KS_PASS" > /dev/null 2>&1
+    openssl pkcs12 -in "$NEW_KS_FILE.p12" -nodes -nocerts -nomac -password pass:"$KS_PASS" 2>/dev/null | openssl rsa -out "$PRIVKEY_FILE" > /dev/null 2>&1
+fi
+
+# Commit the new keystore
+rm -f "$NEW_KS_FILE.p12"
+mv -f "$NEW_KS_FILE" "$KS_FILE"
+
+# Update ca-certs if we're in systemvm
+if [ -f "$SYSTEM_FILE" ]; then
+    mkdir -p /usr/local/share/ca-certificates/cloudstack
+    cp "$CACERT_FILE" /usr/local/share/ca-certificates/cloudstack/ca.crt
+    chmod 755 /usr/local/share/ca-certificates/cloudstack
+    chmod 644 /usr/local/share/ca-certificates/cloudstack/ca.crt
+    update-ca-certificates > /dev/null 2>&1 || true
+fi
+
+# Restart cloud service if we're in systemvm
+if [ "$MODE" == "ssh" ] && [ -f $SYSTEM_FILE ]; then
+    /etc/init.d/cloud stop > /dev/null 2>&1
+    sleep 2
+    /etc/init.d/cloud start > /dev/null 2>&1
+fi
+
+# Fix file permission
+chmod 600 $CACERT_FILE
+chmod 600 $CERT_FILE
+chmod 600 $PRIVKEY_FILE
diff --git a/scripts/util/keystore-setup b/scripts/util/keystore-setup
new file mode 100755
index 0000000..28ce61c
--- /dev/null
+++ b/scripts/util/keystore-setup
@@ -0,0 +1,51 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+PROPS_FILE="$1"
+KS_FILE="$2.new"
+KS_PASS="$3"
+KS_VALIDITY="$4"
+CSR_FILE="$5"
+
+ALIAS="cloud"
+
+# Re-use existing password or use the one provided
+if [ -f "$PROPS_FILE" ]; then
+    OLD_PASS=$(sed -n '/keystore.passphrase/p' "$PROPS_FILE" 2>/dev/null  | sed 's/keystore.passphrase=//g' 2>/dev/null)
+    if [ ! -z "${OLD_PASS// }" ]; then
+        KS_PASS="$OLD_PASS"
+    else
+        sed -i "/keystore.passphrase.*/d" $PROPS_FILE 2> /dev/null || true
+        echo "keystore.passphrase=$KS_PASS" >> $PROPS_FILE
+    fi
+fi
+
+# Generate keystore
+rm -f "$KS_FILE"
+CN=$(hostname --fqdn)
+keytool -genkey -storepass "$KS_PASS" -keypass "$KS_PASS" -alias "$ALIAS" -keyalg RSA -validity "$KS_VALIDITY" -dname cn="$CN",ou="cloudstack",o="cloudstack",c="cloudstack" -keystore "$KS_FILE"
+
+# Generate CSR
+rm -f "$CSR_FILE"
+keytool -certreq -storepass "$KS_PASS" -alias "$ALIAS" -file $CSR_FILE -keystore "$KS_FILE"
+cat "$CSR_FILE"
+
+# Fix file permissions
+chmod 600 $KS_FILE
+chmod 600 $PROPS_FILE
+chmod 600 $CSR_FILE
diff --git a/server/pom.xml b/server/pom.xml
index c49b614..4067b85 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -54,6 +54,11 @@
     </dependency>
     <dependency>
       <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-framework-ca</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
       <artifactId>cloud-framework-jobs</artifactId>
       <version>${project.version}</version>
     </dependency>
diff --git a/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
index 56da591..8a8d645 100644
--- a/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
+++ b/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
@@ -87,8 +87,10 @@
         <property name="networkElements" value="#{networkElementsRegistry.registered}" />
     </bean>
 
+
     <bean id="configurationServerImpl" class="com.cloud.server.ConfigurationServerImpl" />
 
+
     <bean id="userVmManagerImpl" class="com.cloud.vm.UserVmManagerImpl" />
 
     <bean id="consoleProxyManagerImpl" class="com.cloud.consoleproxy.ConsoleProxyManagerImpl">
@@ -277,4 +279,10 @@
     </bean>
     <bean id="certServiceImpl" class="org.apache.cloudstack.network.ssl.CertServiceImpl" />
     <bean id="imageStoreUploadMonitorImpl" class="com.cloud.storage.ImageStoreUploadMonitorImpl" />
+
+    <!-- the new CA manager -->
+    <bean id="caManager" class="org.apache.cloudstack.ca.CAManagerImpl">
+        <property name="caProviders" value="#{caProvidersRegistry.registered}" />
+    </bean>
+
 </beans>
diff --git a/server/src/com/cloud/alert/AlertManagerImpl.java b/server/src/com/cloud/alert/AlertManagerImpl.java
index c751c6a..0232843 100644
--- a/server/src/com/cloud/alert/AlertManagerImpl.java
+++ b/server/src/com/cloud/alert/AlertManagerImpl.java
@@ -759,7 +759,8 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi
                 (alertType != AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE) &&
                 (alertType != AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED) &&
                 (alertType != AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED) &&
-                (alertType != AlertManager.AlertType.ALERT_TYPE_OOBM_AUTH_ERROR)) {
+                (alertType != AlertManager.AlertType.ALERT_TYPE_OOBM_AUTH_ERROR) &&
+                (alertType != AlertManager.AlertType.ALERT_TYPE_CA_CERT)) {
                 alert = _alertDao.getLastAlert(alertType.getType(), dataCenterId, podId, clusterId);
             }
 
diff --git a/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java b/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java
index 1817ade..cee0745 100644
--- a/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java
+++ b/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java
@@ -110,6 +110,7 @@ import com.cloud.user.AccountManager;
 import com.cloud.utils.DateUtil;
 import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.Pair;
+import com.cloud.utils.StringUtils;
 import com.cloud.utils.component.ManagerBase;
 import com.cloud.utils.db.DB;
 import com.cloud.utils.db.GlobalLock;
@@ -1354,7 +1355,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
 
         StringBuilder buf = profile.getBootArgsBuilder();
         buf.append(" template=domP type=consoleproxy");
-        buf.append(" host=").append(ApiServiceConfiguration.ManagementHostIPAdr.value());
+        buf.append(" host=").append(StringUtils.shuffleCSVList(ApiServiceConfiguration.ManagementHostIPAdr.value()));
         buf.append(" port=").append(_mgmtPort);
         buf.append(" name=").append(profile.getVirtualMachine().getHostName());
         if (_sslEnabled) {
diff --git a/server/src/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java b/server/src/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java
index 13a1a64..ac5b48a 100644
--- a/server/src/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java
+++ b/server/src/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java
@@ -16,6 +16,23 @@
 // under the License.
 package com.cloud.hypervisor.kvm.discoverer;
 
+import java.net.InetAddress;
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+
+import org.apache.cloudstack.ca.CAManager;
+import org.apache.cloudstack.ca.SetupCertificateCommand;
+import org.apache.cloudstack.framework.ca.Certificate;
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.log4j.Logger;
+
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.Listener;
 import com.cloud.agent.api.AgentControlAnswer;
@@ -42,17 +59,10 @@ import com.cloud.resource.DiscovererBase;
 import com.cloud.resource.ResourceStateAdapter;
 import com.cloud.resource.ServerResource;
 import com.cloud.resource.UnableDeleteHostException;
+import com.cloud.utils.PasswordGenerator;
+import com.cloud.utils.StringUtils;
 import com.cloud.utils.ssh.SSHCmdHelper;
-import org.apache.log4j.Logger;
-
-import javax.inject.Inject;
-import javax.naming.ConfigurationException;
-import java.net.InetAddress;
-import java.net.URI;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import com.trilead.ssh2.Connection;
 
 public abstract class LibvirtServerDiscoverer extends DiscovererBase implements Discoverer, Listener, ResourceStateAdapter {
     private static final Logger s_logger = Logger.getLogger(LibvirtServerDiscoverer.class);
@@ -62,7 +72,9 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
     private String _kvmPublicNic;
     private String _kvmGuestNic;
     @Inject
-    AgentManager _agentMgr;
+    private AgentManager agentMgr;
+    @Inject
+    private CAManager caManager;
 
     @Override
     public abstract Hypervisor.HypervisorType getHypervisorType();
@@ -125,6 +137,73 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
         return false;
     }
 
+    private void setupAgentSecurity(final Connection sshConnection, final String agentIp, final String agentHostname) {
+        if (!caManager.canProvisionCertificates()) {
+            s_logger.warn("Cannot secure agent communication because configure CA plugin cannot provision client certificate");
+            return;
+        }
+
+        if (sshConnection == null) {
+            s_logger.warn("Cannot secure agent communication because ssh connection is invalid for host ip=" + agentIp);
+            return;
+        }
+
+        Integer validityPeriod = CAManager.CertValidityPeriod.value();
+        if (validityPeriod < 1) {
+            validityPeriod = 1;
+        }
+
+        final SSHCmdHelper.SSHCmdResult keystoreSetupResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection,
+                String.format("/usr/share/cloudstack-common/scripts/util/%s " +
+                                "/etc/cloudstack/agent/agent.properties " +
+                                "/etc/cloudstack/agent/%s " +
+                                "%s %d " +
+                                "/etc/cloudstack/agent/%s",
+                        KeyStoreUtils.keyStoreSetupScript,
+                        KeyStoreUtils.defaultKeystoreFile,
+                        PasswordGenerator.generateRandomPassword(16),
+                        validityPeriod,
+                        KeyStoreUtils.defaultCsrFile));
+
+        if (!keystoreSetupResult.isSuccess()) {
+            s_logger.error("Failing, the keystore setup script failed execution on the KVM host: " + agentIp);
+            return;
+        }
+
+        final Certificate certificate = caManager.issueCertificate(keystoreSetupResult.getStdOut(), Collections.singletonList(agentHostname), Collections.singletonList(agentIp), null, null);
+        if (certificate == null || certificate.getClientCertificate() == null) {
+            s_logger.error("Failing, the configured CA plugin failed to issue certificates for KVM host agent: " + agentIp);
+            return;
+        }
+
+        final SetupCertificateCommand certificateCommand = new SetupCertificateCommand(certificate);
+        final SSHCmdHelper.SSHCmdResult setupCertResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection,
+                    String.format("/usr/share/cloudstack-common/scripts/util/%s " +
+                                    "/etc/cloudstack/agent/agent.properties " +
+                                    "/etc/cloudstack/agent/%s %s " +
+                                    "/etc/cloudstack/agent/%s \"%s\" " +
+                                    "/etc/cloudstack/agent/%s \"%s\" " +
+                                    "/etc/cloudstack/agent/%s \"%s\"",
+                            KeyStoreUtils.keyStoreImportScript,
+                            KeyStoreUtils.defaultKeystoreFile,
+                            KeyStoreUtils.sshMode,
+                            KeyStoreUtils.defaultCertFile,
+                            certificateCommand.getEncodedCertificate(),
+                            KeyStoreUtils.defaultCaCertFile,
+                            certificateCommand.getEncodedCaCertificates(),
+                            KeyStoreUtils.defaultPrivateKeyFile,
+                            certificateCommand.getEncodedPrivateKey()));
+
+        if (setupCertResult != null && !setupCertResult.isSuccess()) {
+            s_logger.error("Failed to setup certificate in the KVM agent's keystore file, please configure manually!");
+            return;
+        }
+
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("Succeeded to import certificate in the keystore for agent on the KVM host: " + agentIp + ". Agent secured and trusted.");
+        }
+    }
+
     @Override
     public Map<? extends ServerResource, Map<String, String>>
         find(long dcId, Long podId, Long clusterId, URI uri, String username, String password, List<String> hostTags) throws DiscoveryException {
@@ -143,7 +222,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
             s_logger.debug(msg);
             return null;
         }
-        com.trilead.ssh2.Connection sshConnection = null;
+        Connection sshConnection = null;
         String agentIp = null;
         try {
 
@@ -162,7 +241,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
                 }
             }
 
-            sshConnection = new com.trilead.ssh2.Connection(agentIp, 22);
+            sshConnection = new Connection(agentIp, 22);
 
             sshConnection.connect(null, 60000, 60000);
             if (!sshConnection.authenticateWithPassword(username, password)) {
@@ -170,7 +249,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
                 throw new DiscoveredWithErrorException("Authentication error");
             }
 
-            if (!SSHCmdHelper.sshExecuteCmd(sshConnection, "lsmod|grep kvm", 3)) {
+            if (!SSHCmdHelper.sshExecuteCmd(sshConnection, "lsmod|grep kvm")) {
                 s_logger.debug("It's not a KVM enabled machine");
                 return null;
             }
@@ -210,7 +289,9 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
                 kvmGuestNic = (kvmPublicNic != null) ? kvmPublicNic : kvmPrivateNic;
             }
 
-            String parameters = " -m " + _hostIp + " -z " + dcId + " -p " + podId + " -c " + clusterId + " -g " + guid + " -a";
+            setupAgentSecurity(sshConnection, agentIp, hostname);
+
+            String parameters = " -m " + StringUtils.shuffleCSVList(_hostIp) + " -z " + dcId + " -p " + podId + " -c " + clusterId + " -g " + guid + " -a";
 
             parameters += " --pubNic=" + kvmPublicNic;
             parameters += " --prvNic=" + kvmPrivateNic;
@@ -221,8 +302,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
             if (!username.equals("root")) {
                 setupAgentCommand = "sudo cloudstack-setup-agent ";
             }
-            if (!SSHCmdHelper.sshExecuteCmd(sshConnection,
-                    setupAgentCommand + parameters, 3)) {
+            if (!SSHCmdHelper.sshExecuteCmd(sshConnection, setupAgentCommand + parameters)) {
                 s_logger.info("cloudstack agent setup command failed: "
                         + setupAgentCommand + parameters);
                 return null;
@@ -392,7 +472,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
         _resourceMgr.deleteRoutingHost(host, isForced, isForceDeleteStorage);
         try {
             ShutdownCommand cmd = new ShutdownCommand(ShutdownCommand.DeleteHost, null);
-            _agentMgr.send(host.getId(), cmd);
+            agentMgr.send(host.getId(), cmd);
         } catch (AgentUnavailableException e) {
             s_logger.warn("Sending ShutdownCommand failed: ", e);
         } catch (OperationTimedoutException e) {
diff --git a/server/src/com/cloud/resource/ResourceManagerImpl.java b/server/src/com/cloud/resource/ResourceManagerImpl.java
index af5344c..5eee246 100755
--- a/server/src/com/cloud/resource/ResourceManagerImpl.java
+++ b/server/src/com/cloud/resource/ResourceManagerImpl.java
@@ -2272,7 +2272,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
                 }
 
                 try {
-                    SSHCmdHelper.sshExecuteCmdOneShot(connection, "service cloudstack-agent restart");
+                    SSHCmdHelper.SSHCmdResult result = SSHCmdHelper.sshExecuteCmdOneShot(connection, "service cloudstack-agent restart");
+                    s_logger.debug("cloudstack-agent restart result: " + result.toString());
                 } catch (final SshException e) {
                     return false;
                 }
diff --git a/server/src/com/cloud/server/ConfigurationServerImpl.java b/server/src/com/cloud/server/ConfigurationServerImpl.java
index c94d92c..27172b4 100644
--- a/server/src/com/cloud/server/ConfigurationServerImpl.java
+++ b/server/src/com/cloud/server/ConfigurationServerImpl.java
@@ -23,8 +23,6 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
 import java.security.NoSuchAlgorithmException;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -36,24 +34,21 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.UUID;
-import java.util.regex.Pattern;
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang.StringUtils;
-import org.apache.log4j.Logger;
-
 import org.apache.cloudstack.config.ApiServiceConfiguration;
 import org.apache.cloudstack.framework.config.ConfigDepot;
 import org.apache.cloudstack.framework.config.ConfigDepotAdmin;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
 
 import com.cloud.configuration.Config;
 import com.cloud.configuration.ConfigurationManager;
@@ -117,7 +112,6 @@ import com.cloud.utils.db.TransactionLegacy;
 import com.cloud.utils.db.TransactionStatus;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.net.NetUtils;
-import com.cloud.utils.nio.Link;
 import com.cloud.utils.script.Script;
 
 public class ConfigurationServerImpl extends ManagerBase implements ConfigurationServer {
@@ -306,9 +300,6 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio
         // Update resource count if needed
         updateResourceCount();
 
-        // keystore for SSL/TLS connection
-        updateSSLKeystore();
-
         // store the public and private keys in the database
         updateKeyPairs();
 
@@ -544,117 +535,6 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio
         }
     }
 
-    static String getBase64Keystore(String keystorePath) throws IOException {
-        byte[] storeBytes = FileUtils.readFileToByteArray(new File(keystorePath));
-        if (storeBytes.length > 3000) { // Base64 codec would enlarge data by 1/3, and we have 4094 bytes in database entry at most
-            throw new IOException("KeyStore is too big for database! Length " + storeBytes.length);
-        }
-
-        return new String(Base64.encodeBase64(storeBytes));
-    }
-
-    private void generateDefaultKeystore(String keystorePath) throws IOException {
-        String cn = "Cloudstack User";
-        String ou;
-
-        try {
-            ou = InetAddress.getLocalHost().getCanonicalHostName();
-            String[] group = ou.split("\\.");
-
-            // Simple check to see if we got IP Address...
-            boolean isIPAddress = Pattern.matches("[0-9]$", group[group.length - 1]);
-            if (isIPAddress) {
-                ou = "cloud.com"; // leaving this example reference to cloud.com as it has no real world relevance
-            } else {
-                ou = group[group.length - 1];
-                for (int i = group.length - 2; i >= 0 && i >= group.length - 3; i--)
-                    ou = group[i] + "." + ou;
-            }
-        } catch (UnknownHostException ex) {
-            s_logger.info("Fail to get user's domain name. Would use cloud.com. ", ex);
-            ou = "cloud.com"; // leaving this example reference to cloud.com as it has no real world relevance
-        }
-
-        String o = ou;
-        String c = "Unknown";
-        String dname = "cn=\"" + cn + "\",ou=\"" + ou + "\",o=\"" + o + "\",c=\"" + c + "\"";
-        Script script = new Script(true, "keytool", 5000, null);
-        script.add("-genkey");
-        script.add("-keystore", keystorePath);
-        script.add("-storepass", "vmops.com");
-        script.add("-keypass", "vmops.com");
-        script.add("-keyalg", "RSA");
-        script.add("-validity", "3650");
-        script.add("-dname", dname);
-        String result = script.execute();
-        if (result != null) {
-            throw new IOException("Fail to generate certificate!: " + result);
-        }
-    }
-
-    protected void updateSSLKeystore() {
-        if (s_logger.isInfoEnabled()) {
-            s_logger.info("Processing updateSSLKeyStore");
-        }
-
-        String dbString = _configDao.getValue("ssl.keystore");
-
-        File confFile = PropertiesUtil.findConfigFile("db.properties");
-        String confPath = null;
-        String keystorePath = null;
-        File keystoreFile = null;
-
-        if (null != confFile) {
-            confPath = confFile.getParent();
-            keystorePath = confPath + Link.keystoreFile;
-            keystoreFile = new File(keystorePath);
-        }
-
-        boolean dbExisted = (dbString != null && !dbString.isEmpty());
-
-        s_logger.info("SSL keystore located at " + keystorePath);
-        try {
-            if (!dbExisted && null != confFile) {
-                if (!keystoreFile.exists()) {
-                    generateDefaultKeystore(keystorePath);
-                    s_logger.info("Generated SSL keystore.");
-                }
-                String base64Keystore = getBase64Keystore(keystorePath);
-                ConfigurationVO configVO =
-                        new ConfigurationVO("Hidden", "DEFAULT", "management-server", "ssl.keystore", base64Keystore,
-                                "SSL Keystore for the management servers");
-                _configDao.persist(configVO);
-                s_logger.info("Stored SSL keystore to database.");
-            } else { // !keystoreFile.exists() and dbExisted
-                // Export keystore to local file
-                byte[] storeBytes = Base64.decodeBase64(dbString);
-                String tmpKeystorePath = "/tmp/tmpkey";
-                try (
-                        FileOutputStream fo = new FileOutputStream(tmpKeystorePath);
-                    ) {
-                    fo.write(storeBytes);
-                    Script script = new Script(true, "cp", 5000, null);
-                    script.add("-f");
-                    script.add(tmpKeystorePath);
-
-                    //There is a chance, although small, that the keystorePath is null. In that case, do not add it to the script.
-                    if (null != keystorePath) {
-                        script.add(keystorePath);
-                    }
-                    String result = script.execute();
-                    if (result != null) {
-                        throw new IOException();
-                    }
-                } catch (Exception e) {
-                    throw new IOException("Fail to create keystore file!", e);
-                }
-                s_logger.info("Stored database keystore to local.");
-            }
-        } catch (Exception ex) {
-            s_logger.warn("Would use fail-safe keystore to continue.", ex);
-        }
-    }
-
     @DB
     protected void updateSystemvmPassword() {
         String userid = System.getProperty("user.name");
diff --git a/server/src/org/apache/cloudstack/ca/CAManagerImpl.java b/server/src/org/apache/cloudstack/ca/CAManagerImpl.java
new file mode 100644
index 0000000..b8b752d
--- /dev/null
+++ b/server/src/org/apache/cloudstack/ca/CAManagerImpl.java
@@ -0,0 +1,428 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.ca;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.command.admin.ca.IssueCertificateCmd;
+import org.apache.cloudstack.api.command.admin.ca.ListCAProvidersCmd;
+import org.apache.cloudstack.api.command.admin.ca.ListCaCertificateCmd;
+import org.apache.cloudstack.api.command.admin.ca.ProvisionCertificateCmd;
+import org.apache.cloudstack.api.command.admin.ca.RevokeCertificateCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.ca.CAProvider;
+import org.apache.cloudstack.framework.ca.Certificate;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.poll.BackgroundPollManager;
+import org.apache.cloudstack.poll.BackgroundPollTask;
+import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.apache.cloudstack.utils.security.CertUtils;
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.cloud.agent.AgentManager;
+import com.cloud.alert.AlertManager;
+import com.cloud.certificate.CrlVO;
+import com.cloud.certificate.dao.CrlDao;
+import com.cloud.event.ActionEvent;
+import com.cloud.event.EventTypes;
+import com.cloud.exception.AgentUnavailableException;
+import com.cloud.exception.OperationTimedoutException;
+import com.cloud.host.Host;
+import com.cloud.host.Status;
+import com.cloud.host.dao.HostDao;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.google.common.base.Strings;
+
+public class CAManagerImpl extends ManagerBase implements CAManager {
+    public static final Logger LOG = Logger.getLogger(CAManagerImpl.class);
+
+    @Inject
+    private CrlDao crlDao;
+    @Inject
+    private HostDao hostDao;
+    @Inject
+    private AgentManager agentManager;
+    @Inject
+    private BackgroundPollManager backgroundPollManager;
+    @Inject
+    private AlertManager alertManager;
+
+    private static CAProvider configuredCaProvider;
+    private static Map<String, CAProvider> caProviderMap = new HashMap<>();
+    private static Map<String, Date> alertMap = new ConcurrentHashMap<>();
+    private static Map<String, X509Certificate> activeCertMap = new ConcurrentHashMap<>();
+
+    private List<CAProvider> caProviders;
+
+    private CAProvider getConfiguredCaProvider() {
+        if (configuredCaProvider != null) {
+            return configuredCaProvider;
+        }
+        if (caProviderMap.containsKey(CAProviderPlugin.value()) && caProviderMap.get(CAProviderPlugin.value()) != null) {
+            configuredCaProvider = caProviderMap.get(CAProviderPlugin.value());
+            return configuredCaProvider;
+        }
+        throw new CloudRuntimeException("Failed to find default configured CA provider plugin");
+    }
+
+    private CAProvider getCAProvider(final String provider) {
+        if (Strings.isNullOrEmpty(provider)) {
+            return getConfiguredCaProvider();
+        }
+        final String caProviderName = provider.toLowerCase();
+        if (!caProviderMap.containsKey(caProviderName)) {
+            throw new CloudRuntimeException(String.format("CA provider plugin '%s' not found", caProviderName));
+        }
+        final CAProvider caProvider = caProviderMap.get(caProviderName);
+        if (caProvider == null) {
+            throw new CloudRuntimeException(String.format("CA provider plugin '%s' returned is null", caProviderName));
+        }
+        return caProvider;
+    }
+
+    ///////////////////////////////////////////////////////////
+    /////////////// CA Manager API Handlers ///////////////////
+    ///////////////////////////////////////////////////////////
+
+    @Override
+    public List<CAProvider> getCaProviders() {
+        return caProviders;
+    }
+
+    @Override
+    public Map<String, X509Certificate> getActiveCertificatesMap() {
+        return activeCertMap;
+    }
+
+    @Override
+    public boolean canProvisionCertificates() {
+        return getConfiguredCaProvider().canProvisionCertificates();
+    }
+
+    @Override
+    public String getCaCertificate(final String caProvider) throws IOException {
+        final CAProvider provider = getCAProvider(caProvider);
+        return CertUtils.x509CertificatesToPem(provider.getCaCertificate());
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_CA_CERTIFICATE_ISSUE, eventDescription = "issuing certificate", async = true)
+    public Certificate issueCertificate(final String csr, final List<String> domainNames, final List<String> ipAddresses, final Integer validityDuration, final String caProvider) {
+        CallContext.current().setEventDetails("domain(s): " + domainNames + " addresses: " + ipAddresses);
+        final CAProvider provider = getCAProvider(caProvider);
+        Integer validity = CAManager.CertValidityPeriod.value();
+        if (validityDuration != null) {
+            validity = validityDuration;
+        }
+        if (Strings.isNullOrEmpty(csr)) {
+            if (domainNames == null || domainNames.isEmpty()) {
+                throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "No domains or CSR provided");
+            }
+            return provider.issueCertificate(domainNames, ipAddresses, validity);
+        }
+        return provider.issueCertificate(csr, domainNames, ipAddresses, validity);
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_CA_CERTIFICATE_REVOKE, eventDescription = "revoking certificate", async = true)
+    public boolean revokeCertificate(final BigInteger certSerial, final String certCn, final String caProvider) {
+        CallContext.current().setEventDetails("cert serial: " + certSerial);
+        final CrlVO crl = crlDao.revokeCertificate(certSerial, certCn);
+        if (crl != null && crl.getCertSerial().equals(certSerial)) {
+            final CAProvider provider = getCAProvider(caProvider);
+            return provider.revokeCertificate(certSerial, certCn);
+        }
+        return false;
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_CA_CERTIFICATE_PROVISION, eventDescription = "provisioning certificate for host", async = true)
+    public boolean provisionCertificate(final Host host, final Boolean reconnect, final String caProvider) {
+        if (host == null) {
+            throw new CloudRuntimeException("Unable to find valid host to renew certificate for");
+        }
+        CallContext.current().setEventDetails("host id: " + host.getId());
+        CallContext.current().putContextParameter(Host.class, host.getUuid());
+        final String csr;
+        try {
+            csr = generateKeyStoreAndCsr(host, null);
+            if (Strings.isNullOrEmpty(csr)) {
+                return false;
+            }
+            final Certificate certificate = issueCertificate(csr, Collections.singletonList(host.getName()), Arrays.asList(host.getPrivateIpAddress(), host.getPublicIpAddress(), host.getStorageIpAddress()), CAManager.CertValidityPeriod.value(), caProvider);
+            return deployCertificate(host, certificate, reconnect, null);
+        } catch (final AgentUnavailableException | OperationTimedoutException e) {
+            LOG.error("Host/agent is not available or operation timed out, failed to setup keystore and generate CSR for host/agent id=" + host.getId() + ", due to: ", e);
+            throw new CloudRuntimeException("Failed to generate keystore and get CSR from the host/agent id=" + host.getId());
+        }
+    }
+
+    @Override
+    public String generateKeyStoreAndCsr(final Host host, final Map<String, String> sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException {
+        final SetupKeyStoreCommand cmd = new SetupKeyStoreCommand(CertValidityPeriod.value());
+        if (sshAccessDetails != null && !sshAccessDetails.isEmpty()) {
+            cmd.setAccessDetail(sshAccessDetails);
+        }
+        CallContext.current().setEventDetails("generating keystore and CSR for host id: " + host.getId());
+        final SetupKeystoreAnswer answer = (SetupKeystoreAnswer) agentManager.send(host.getId(), cmd);
+        return answer.getCsr();
+    }
+
+    @Override
+    public boolean deployCertificate(final Host host, final Certificate certificate, final Boolean reconnect, final Map<String, String> sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException {
+        final SetupCertificateCommand cmd = new SetupCertificateCommand(certificate);
+        if (sshAccessDetails != null && !sshAccessDetails.isEmpty()) {
+            cmd.setAccessDetail(sshAccessDetails);
+        }
+        CallContext.current().setEventDetails("deploying certificate for host id: " + host.getId());
+        final SetupCertificateAnswer answer = (SetupCertificateAnswer) agentManager.send(host.getId(), cmd);
+        if (answer.getResult()) {
+            CallContext.current().setEventDetails("successfully deployed certificate for host id: " + host.getId());
+        } else {
+            CallContext.current().setEventDetails("failed to deploy certificate for host id: " + host.getId());
+        }
+
+        if (answer.getResult()) {
+            getActiveCertificatesMap().put(host.getPrivateIpAddress(), certificate.getClientCertificate());
+            if (sshAccessDetails == null && reconnect != null && reconnect) {
+                LOG.info(String.format("Successfully setup certificate on host, reconnecting with agent with id=%d, name=%s, address=%s",
+                        host.getId(), host.getName(), host.getPublicIpAddress()));
+                return agentManager.reconnect(host.getId());
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void purgeHostCertificate(final Host host) {
+        if (host == null) {
+            return;
+        }
+        final String privateAddress = host.getPrivateIpAddress();
+        final String publicAddress = host.getPublicIpAddress();
+        final Map<String, X509Certificate> activeCertsMap = getActiveCertificatesMap();
+        if (!Strings.isNullOrEmpty(privateAddress) && activeCertsMap.containsKey(privateAddress)) {
+            activeCertsMap.remove(privateAddress);
+        }
+        if (!Strings.isNullOrEmpty(publicAddress) && activeCertsMap.containsKey(publicAddress)) {
+            activeCertsMap.remove(publicAddress);
+        }
+    }
+
+    @Override
+    public void sendAlert(final Host host, final String subject, final String message) {
+        if (host == null) {
+            return;
+        }
+        alertManager.sendAlert(AlertManager.AlertType.ALERT_TYPE_CA_CERT,
+                host.getDataCenterId(), host.getPodId(), subject, message);
+    }
+
+    @Override
+    public SSLEngine createSSLEngine(final SSLContext sslContext, final String remoteAddress) throws GeneralSecurityException, IOException {
+        if (sslContext == null) {
+            throw new CloudRuntimeException("SSLContext provided to create SSLEngine is null, aborting");
+        }
+        if (Strings.isNullOrEmpty(remoteAddress)) {
+            throw new CloudRuntimeException("Remote client address connecting to mgmt server cannot be empty/null");
+        }
+        return getConfiguredCaProvider().createSSLEngine(sslContext, remoteAddress, getActiveCertificatesMap());
+    }
+
+    ////////////////////////////////////////////////////
+    /////////////// CA Manager Setup ///////////////////
+    ////////////////////////////////////////////////////
+
+    public static final class CABackgroundTask extends ManagedContextRunnable implements BackgroundPollTask {
+        private CAManager caManager;
+        private HostDao hostDao;
+
+        public CABackgroundTask(final CAManager caManager, final HostDao hostDao) {
+            this.caManager = caManager;
+            this.hostDao = hostDao;
+        }
+
+        @Override
+        protected void runInContext() {
+            try {
+                if (LOG.isTraceEnabled()) {
+                    LOG.trace("CA background task is running...");
+                }
+                final DateTime now = DateTime.now(DateTimeZone.UTC);
+                final Map<String, X509Certificate> certsMap = caManager.getActiveCertificatesMap();
+                for (final Iterator<Map.Entry<String, X509Certificate>> it = certsMap.entrySet().iterator(); it.hasNext(); ) {
+                    final Map.Entry<String, X509Certificate> entry = it.next();
+                    if (entry == null) {
+                        continue;
+                    }
+                    final String hostIp = entry.getKey();
+                    final X509Certificate certificate = entry.getValue();
+                    if (certificate == null) {
+                        it.remove();
+                        continue;
+                    }
+                    final Host host = hostDao.findByIp(hostIp);
+                    if (host == null || host.getManagementServerId() == null ||
+                            host.getManagementServerId() != ManagementServerNode.getManagementServerId() ||
+                            host.getStatus() != Status.Up) {
+                        if (host == null ||
+                                (host.getManagementServerId() != null &&
+                                        host.getManagementServerId() != ManagementServerNode.getManagementServerId())) {
+                            it.remove();
+                        }
+                        continue;
+                    }
+
+                    final String hostDescription = String.format("host id=%d, uuid=%s, name=%s, ip=%s, zone id=%d",
+                            host.getId(), host.getUuid(), host.getName(), hostIp, host.getDataCenterId());
+
+                    try {
+                        certificate.checkValidity(now.plusDays(CertExpiryAlertPeriod.valueIn(host.getClusterId())).toDate());
+                    } catch (final CertificateExpiredException | CertificateNotYetValidException e) {
+                        LOG.warn("Certificate is going to expire for " + hostDescription);
+                        if (AutomaticCertRenewal.valueIn(host.getClusterId())) {
+                            try {
+                                LOG.debug("Attempting certificate auto-renewal for " + hostDescription);
+                                boolean result = caManager.provisionCertificate(host, false, null);
+                                if (result) {
+                                    LOG.debug("Succeeded in auto-renewing certificate for " + hostDescription);
+                                } else {
+                                    LOG.debug("Failed in auto-renewing certificate for " + hostDescription);
+                                }
+                            } catch (final Throwable ex) {
+                                LOG.warn("Failed to auto-renew certificate for " + hostDescription + ", with error=", ex);
+                                caManager.sendAlert(host, "Certificate auto-renewal failed for " + hostDescription,
+                                        String.format("Certificate is going to expire for %s. Auto-renewal failed to renew the certificate, please renew it manually. It is not valid after %s.", hostDescription, certificate.getNotAfter()));
+                            }
+                        } else {
+                            if (alertMap.containsKey(hostIp)) {
+                                final Date lastSentDate = alertMap.get(hostIp);
+                                if (now.minusDays(1).toDate().before(lastSentDate)) {
+                                    continue;
+                                }
+                            }
+                            caManager.sendAlert(host, "Certificate expiring soon for " + hostDescription,
+                                    String.format("Certificate is going to expire for %s. Please renew it, it is not valid after %s.",
+                                            hostDescription, certificate.getNotAfter()));
+                            alertMap.put(hostIp, new Date());
+                        }
+                    }
+                }
+            } catch (final Throwable t) {
+                LOG.error("Error trying to run CA background task", t);
+            }
+        }
+
+        @Override
+        public Long getDelay() {
+            return CABackgroundJobDelay.value() * 1000L;
+        }
+    }
+
+    public void setCaProviders(final List<CAProvider> caProviders) {
+        this.caProviders = caProviders;
+        initializeCaProviderMap();
+    }
+
+    private void initializeCaProviderMap() {
+        if (caProviderMap != null && caProviderMap.size() != caProviders.size()) {
+            for (final CAProvider caProvider : caProviders) {
+                caProviderMap.put(caProvider.getProviderName().toLowerCase(), caProvider);
+            }
+        }
+    }
+
+    @Override
+    public boolean start() {
+        super.start();
+        initializeCaProviderMap();
+        if (caProviderMap.containsKey(CAProviderPlugin.value())) {
+            configuredCaProvider = caProviderMap.get(CAProviderPlugin.value());
+        }
+        if (configuredCaProvider == null) {
+            LOG.error("Failed to find valid configured CA provider, please check!");
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
+        backgroundPollManager.submitTask(new CABackgroundTask(this, hostDao));
+        return true;
+    }
+
+    //////////////////////////////////////////////////////////
+    /////////////// CA Manager Descriptors ///////////////////
+    //////////////////////////////////////////////////////////
+
+    @Override
+    public List<Class<?>> getCommands() {
+        final List<Class<?>> cmdList = new ArrayList<Class<?>>();
+        cmdList.add(ListCAProvidersCmd.class);
+        cmdList.add(ListCaCertificateCmd.class);
+        cmdList.add(IssueCertificateCmd.class);
+        cmdList.add(ProvisionCertificateCmd.class);
+        cmdList.add(RevokeCertificateCmd.class);
+        return cmdList;
+    }
+
+    @Override
+    public String getConfigComponentName() {
+        return CAManager.class.getSimpleName();
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[]{
+                CAProviderPlugin,
+                CertKeySize,
+                CertSignatureAlgorithm,
+                CertValidityPeriod,
+                AutomaticCertRenewal,
+                CABackgroundJobDelay,
+                CertExpiryAlertPeriod
+        };
+    }
+}
diff --git a/server/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceImpl.java b/server/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceImpl.java
index 4f0ac74..cb6ac10 100644
--- a/server/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceImpl.java
+++ b/server/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceImpl.java
@@ -577,5 +577,11 @@ public class OutOfBandManagementServiceImpl extends ManagerBase implements OutOf
                 LOG.error("Error trying to retrieve host out-of-band management stats", t);
             }
         }
+
+        @Override
+        public Long getDelay() {
+            return null;
+        }
+
     }
 }
diff --git a/server/src/org/apache/cloudstack/poll/BackgroundPollManagerImpl.java b/server/src/org/apache/cloudstack/poll/BackgroundPollManagerImpl.java
index c0a7f1c..f4a6340 100644
--- a/server/src/org/apache/cloudstack/poll/BackgroundPollManagerImpl.java
+++ b/server/src/org/apache/cloudstack/poll/BackgroundPollManagerImpl.java
@@ -52,7 +52,11 @@ public final class BackgroundPollManagerImpl extends ManagerBase implements Back
         }
         backgroundPollTaskScheduler = Executors.newScheduledThreadPool(submittedTasks.size() + 1, new NamedThreadFactory("BackgroundTaskPollManager"));
         for (final BackgroundPollTask task : submittedTasks) {
-            backgroundPollTaskScheduler.scheduleWithFixedDelay(task, getInitialDelay(), getRoundDelay(), TimeUnit.MILLISECONDS);
+            Long delay = task.getDelay();
+            if (delay == null) {
+                delay = getRoundDelay();
+            }
+            backgroundPollTaskScheduler.scheduleWithFixedDelay(task, getInitialDelay(), delay, TimeUnit.MILLISECONDS);
             LOG.debug("Scheduled background poll task: " + task.getClass().getName());
         }
         isConfiguredAndStarted = true;
diff --git a/server/test/com/cloud/server/ConfigurationServerImplTest.java b/server/test/com/cloud/server/ConfigurationServerImplTest.java
index b64f3f7..8b0af99 100644
--- a/server/test/com/cloud/server/ConfigurationServerImplTest.java
+++ b/server/test/com/cloud/server/ConfigurationServerImplTest.java
@@ -16,8 +16,17 @@
 // under the License.
 package com.cloud.server;
 
-import java.io.File;
-import java.io.IOException;
+import org.apache.cloudstack.framework.config.ConfigDepot;
+import org.apache.cloudstack.framework.config.ConfigDepotAdmin;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.runners.MockitoJUnitRunner;
 
 import com.cloud.configuration.ConfigurationManager;
 import com.cloud.configuration.dao.ResourceCountDao;
@@ -32,19 +41,6 @@ import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.storage.dao.DiskOfferingDao;
 import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.db.TransactionLegacy;
-import org.apache.cloudstack.framework.config.ConfigDepot;
-import org.apache.cloudstack.framework.config.ConfigDepotAdmin;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.io.FileUtils;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.Spy;
-import org.mockito.runners.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ConfigurationServerImplTest {
@@ -101,41 +97,6 @@ public class ConfigurationServerImplTest {
       }
     };
 
-    final static String TEST = "the quick brown fox jumped over the lazy dog";
-
-    @Test(expected = IOException.class)
-    public void testGetBase64KeystoreNoSuchFile() throws IOException {
-        ConfigurationServerImpl.getBase64Keystore("notexisting" + System.currentTimeMillis());
-    }
-
-    @Test(expected = IOException.class)
-    public void testGetBase64KeystoreTooBigFile() throws IOException {
-        File temp = File.createTempFile("keystore", "");
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0; i < 1000; i++) {
-            builder.append("way too long...\n");
-        }
-        FileUtils.writeStringToFile(temp, builder.toString());
-        try {
-            ConfigurationServerImpl.getBase64Keystore(temp.getPath());
-        } finally {
-            temp.delete();
-        }
-    }
-
-    @Test
-    public void testGetBase64Keystore() throws IOException {
-        File temp = File.createTempFile("keystore", "");
-        try {
-            FileUtils.writeStringToFile(temp, Base64.encodeBase64String(TEST.getBytes()));
-            final String keystore = ConfigurationServerImpl.getBase64Keystore(temp.getPath());
-            // let's decode it to make sure it makes sense
-            Base64.decodeBase64(keystore);
-        } finally {
-            temp.delete();
-        }
-    }
-
     @Test
     public void testWindowsScript() {
       Assert.assertTrue(windowsImpl.isOnWindows());
diff --git a/server/test/com/cloud/vpc/MockNetworkManagerImpl.java b/server/test/com/cloud/vpc/MockNetworkManagerImpl.java
index 495989d..f6f818d 100644
--- a/server/test/com/cloud/vpc/MockNetworkManagerImpl.java
+++ b/server/test/com/cloud/vpc/MockNetworkManagerImpl.java
@@ -574,6 +574,11 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches
         return null;
     }
 
+    @Override
+    public Map<String, String> getSystemVMAccessDetails(VirtualMachine vm) {
+        return null;
+    }
+
     /* (non-Javadoc)
      * @see com.cloud.network.NetworkManager#implementNetwork(long, com.cloud.deploy.DeployDestination, com.cloud.vm.ReservationContext)
      */
diff --git a/server/test/org/apache/cloudstack/ca/CABackgroundTaskTest.java b/server/test/org/apache/cloudstack/ca/CABackgroundTaskTest.java
new file mode 100644
index 0000000..d2c800d
--- /dev/null
+++ b/server/test/org/apache/cloudstack/ca/CABackgroundTaskTest.java
@@ -0,0 +1,152 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.ca;
+
+import static org.apache.cloudstack.ca.CAManager.AutomaticCertRenewal;
+
+import java.lang.reflect.Field;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.apache.cloudstack.utils.security.CertUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.host.Status;
+import com.cloud.host.dao.HostDao;
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CABackgroundTaskTest {
+
+    @Mock
+    private CAManager caManager;
+    @Mock
+    private HostDao hostDao;
+
+    private String hostIp = "1.2.3.4";
+    private HostVO host = new HostVO(1L, "some.host",Host.Type.Routing, hostIp, "255.255.255.0", null, null, null, null, null, null, null, null, null, null,
+                       UUID.randomUUID().toString(), Status.Up, "1.0", null, null, 1L, null, 0, 0, "aa", 0, Storage.StoragePoolType.NetworkFilesystem);
+
+    private X509Certificate expiredCertificate;
+    private Map<String, X509Certificate> certMap = new HashMap<>();
+    private CAManagerImpl.CABackgroundTask task;
+
+    @Before
+    public void setUp() throws Exception {
+        host.setManagementServerId(ManagementServerNode.getManagementServerId());
+        task = new CAManagerImpl.CABackgroundTask(caManager, hostDao);
+        final KeyPair keypair = CertUtils.generateRandomKeyPair(1024);
+        expiredCertificate = CertUtils.generateV1Certificate(keypair, "CN=ca", "CN=ca", 0,
+                "SHA256withRSA");
+
+        Mockito.when(hostDao.findByIp(Mockito.anyString())).thenReturn(host);
+        Mockito.when(caManager.getActiveCertificatesMap()).thenReturn(certMap);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        certMap.clear();
+        Mockito.reset(caManager);
+        Mockito.reset(hostDao);
+    }
+
+    private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
+        Field f = ConfigKey.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(configKey, o);
+    }
+
+    @Test
+    public void testNullCert() throws Exception {
+        certMap.put(hostIp, null);
+        Assert.assertTrue(certMap.size() == 1);
+        task.runInContext();
+        Assert.assertTrue(certMap.size() == 0);
+    }
+
+    @Test
+    public void testNullHost() throws Exception {
+        Mockito.when(hostDao.findByIp(Mockito.anyString())).thenReturn(null);
+        certMap.put(hostIp, expiredCertificate);
+        Assert.assertTrue(certMap.size() == 1);
+        task.runInContext();
+        Assert.assertTrue(certMap.size() == 0);
+    }
+
+    @Test
+    public void testAutoRenewalEnabledWithNoExceptionsOnProvisioning() throws Exception {
+        overrideDefaultConfigValue(AutomaticCertRenewal, "_defaultValue", "true");
+        host.setManagementServerId(ManagementServerNode.getManagementServerId());
+        certMap.put(hostIp, expiredCertificate);
+        Assert.assertTrue(certMap.size() == 1);
+        task.runInContext();
+        Mockito.verify(caManager, Mockito.times(1)).provisionCertificate(host, false, null);
+        Mockito.verify(caManager, Mockito.times(0)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString());
+    }
+
+    @Test
+    public void testAutoRenewalEnabledWithExceptionsOnProvisioning() throws Exception {
+        overrideDefaultConfigValue(AutomaticCertRenewal, "_defaultValue", "true");
+        Mockito.when(caManager.provisionCertificate(Mockito.any(Host.class), Mockito.anyBoolean(), Mockito.anyString())).thenThrow(new CloudRuntimeException("some error"));
+        host.setManagementServerId(ManagementServerNode.getManagementServerId());
+        certMap.put(hostIp, expiredCertificate);
+        Assert.assertTrue(certMap.size() == 1);
+        task.runInContext();
+        Mockito.verify(caManager, Mockito.times(1)).provisionCertificate(host, false, null);
+        Mockito.verify(caManager, Mockito.times(1)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString());
+    }
+
+    @Test
+    public void testAutoRenewalDisabled() throws Exception {
+        overrideDefaultConfigValue(AutomaticCertRenewal, "_defaultValue", "false");
+        certMap.put(hostIp, expiredCertificate);
+        Assert.assertTrue(certMap.size() == 1);
+        // First round
+        task.runInContext();
+        Mockito.verify(caManager, Mockito.times(0)).provisionCertificate(Mockito.any(Host.class), Mockito.anyBoolean(), Mockito.anyString());
+        Mockito.verify(caManager, Mockito.times(1)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString());
+        Mockito.reset(caManager);
+        // Second round
+        task.runInContext();
+        Mockito.verify(caManager, Mockito.times(0)).provisionCertificate(Mockito.any(Host.class), Mockito.anyBoolean(), Mockito.anyString());
+        Mockito.verify(caManager, Mockito.times(0)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString());
+    }
+
+    @Test
+    public void testGetDelay() throws Exception {
+        Assert.assertTrue(task.getDelay() == CAManager.CABackgroundJobDelay.value() * 1000L);
+    }
+
+}
\ No newline at end of file
diff --git a/server/test/org/apache/cloudstack/ca/CAManagerImplTest.java b/server/test/org/apache/cloudstack/ca/CAManagerImplTest.java
new file mode 100644
index 0000000..87e128c
--- /dev/null
+++ b/server/test/org/apache/cloudstack/ca/CAManagerImplTest.java
@@ -0,0 +1,119 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.ca;
+
+import java.lang.reflect.Field;
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.framework.ca.CAProvider;
+import org.apache.cloudstack.framework.ca.Certificate;
+import org.apache.cloudstack.utils.security.CertUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.Answer;
+import com.cloud.certificate.CrlVO;
+import com.cloud.certificate.dao.CrlDao;
+import com.cloud.host.Host;
+import com.cloud.host.dao.HostDao;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CAManagerImplTest {
+
+    @Mock
+    private HostDao hostDao;
+    @Mock
+    private CrlDao crlDao;
+    @Mock
+    private AgentManager agentManager;
+    @Mock
+    private CAProvider caProvider;
+
+    private CAManagerImpl caManager;
+
+    private void addField(final CAManagerImpl provider, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
+        Field f = CAManagerImpl.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(provider, o);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        caManager = new CAManagerImpl();
+        addField(caManager, "crlDao", crlDao);
+        addField(caManager, "hostDao", hostDao);
+        addField(caManager, "agentManager", agentManager);
+        addField(caManager, "configuredCaProvider", caProvider);
+
+        Mockito.when(caProvider.getProviderName()).thenReturn("root");
+        caManager.setCaProviders(Collections.singletonList(caProvider));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Mockito.reset(crlDao);
+        Mockito.reset(agentManager);
+        Mockito.reset(caProvider);
+    }
+
+    @Test(expected = ServerApiException.class)
+    public void testIssueCertificateThrowsException() throws Exception {
+        caManager.issueCertificate(null, null, null, 1, null);
+    }
+
+    @Test
+    public void testIssueCertificate() throws Exception {
+        caManager.issueCertificate(null, Collections.singletonList("domain.example"), null, 1, null);
+        Mockito.verify(caProvider, Mockito.times(1)).issueCertificate(Mockito.anyList(), Mockito.anyList(), Mockito.anyInt());
+        Mockito.verify(caProvider, Mockito.times(0)).issueCertificate(Mockito.anyString(), Mockito.anyList(), Mockito.anyList(), Mockito.anyInt());
+    }
+
+    @Test
+    public void testRevokeCertificate() throws Exception {
+        final CrlVO crl = new CrlVO(CertUtils.generateRandomBigInt(), "some.domain", "some-uuid");
+        Mockito.when(crlDao.revokeCertificate(Mockito.any(BigInteger.class), Mockito.anyString())).thenReturn(crl);
+        Mockito.when(caProvider.revokeCertificate(Mockito.any(BigInteger.class), Mockito.anyString())).thenReturn(true);
+        Assert.assertTrue(caManager.revokeCertificate(crl.getCertSerial(), crl.getCertCn(), null));
+        Mockito.verify(caProvider, Mockito.times(1)).revokeCertificate(Mockito.any(BigInteger.class), Mockito.anyString());
+    }
+
+    @Test
+    public void testProvisionCertificate() throws Exception {
+        final Host host = Mockito.mock(Host.class);
+        Mockito.when(host.getPrivateIpAddress()).thenReturn("1.2.3.4");
+        final X509Certificate certificate = CertUtils.generateV1Certificate(CertUtils.generateRandomKeyPair(1024), "CN=ca", "CN=ca", 1, "SHA256withRSA");
+        Mockito.when(caProvider.issueCertificate(Mockito.anyString(), Mockito.anyList(), Mockito.anyList(), Mockito.anyInt())).thenReturn(new Certificate(certificate, null, Collections.singletonList(certificate)));
+        Mockito.when(agentManager.send(Mockito.anyLong(), Mockito.any(SetupKeyStoreCommand.class))).thenReturn(new SetupKeystoreAnswer("someCsr"));
+        Mockito.when(agentManager.reconnect(Mockito.anyLong())).thenReturn(true);
+        Assert.assertTrue(caManager.provisionCertificate(host, true, null));
+        Mockito.verify(agentManager, Mockito.times(2)).send(Mockito.anyLong(), Mockito.any(Answer.class));
+        Mockito.verify(agentManager, Mockito.times(1)).reconnect(Mockito.anyLong());
+    }
+}
\ No newline at end of file
diff --git a/server/test/org/apache/cloudstack/poll/BackgroundPollManagerImplTest.java b/server/test/org/apache/cloudstack/poll/BackgroundPollManagerImplTest.java
index 3304aba..f35d49c 100644
--- a/server/test/org/apache/cloudstack/poll/BackgroundPollManagerImplTest.java
+++ b/server/test/org/apache/cloudstack/poll/BackgroundPollManagerImplTest.java
@@ -45,6 +45,12 @@ public class BackgroundPollManagerImplTest {
             didIRun = true;
             counter++;
         }
+
+        @Override
+        public Long getDelay() {
+            return null;
+        }
+
     }
 
     @Before
diff --git a/services/secondary-storage/controller/src/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java b/services/secondary-storage/controller/src/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java
index 79c6bab..273fdd0 100644
--- a/services/secondary-storage/controller/src/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java
+++ b/services/secondary-storage/controller/src/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java
@@ -123,6 +123,7 @@ import com.cloud.user.AccountService;
 import com.cloud.utils.DateUtil;
 import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.Pair;
+import com.cloud.utils.StringUtils;
 import com.cloud.utils.component.ManagerBase;
 import com.cloud.utils.db.GlobalLock;
 import com.cloud.utils.db.QueryBuilder;
@@ -1118,7 +1119,7 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar
 
         StringBuilder buf = profile.getBootArgsBuilder();
         buf.append(" template=domP type=secstorage");
-        buf.append(" host=").append(ApiServiceConfiguration.ManagementHostIPAdr.value());
+        buf.append(" host=").append(StringUtils.shuffleCSVList(ApiServiceConfiguration.ManagementHostIPAdr.value()));
         buf.append(" port=").append(_mgmtPort);
         buf.append(" name=").append(profile.getVirtualMachine().getHostName());
 
diff --git a/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java b/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java
index 60424c1..4f3ad07 100644
--- a/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java
+++ b/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java
@@ -16,6 +16,71 @@
 // under the License.
 package org.apache.cloudstack.storage.resource;
 
+import static com.cloud.utils.StringUtils.join;
+import static com.cloud.utils.storage.S3.S3Utils.putFile;
+import static java.lang.String.format;
+import static java.util.Arrays.asList;
+import static org.apache.commons.lang.StringUtils.substringAfterLast;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.naming.ConfigurationException;
+
+import org.apache.cloudstack.framework.security.keystore.KeystoreManager;
+import org.apache.cloudstack.storage.command.CopyCmdAnswer;
+import org.apache.cloudstack.storage.command.CopyCommand;
+import org.apache.cloudstack.storage.command.DeleteCommand;
+import org.apache.cloudstack.storage.command.DownloadCommand;
+import org.apache.cloudstack.storage.command.DownloadProgressCommand;
+import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
+import org.apache.cloudstack.storage.command.UploadStatusAnswer;
+import org.apache.cloudstack.storage.command.UploadStatusAnswer.UploadStatus;
+import org.apache.cloudstack.storage.command.UploadStatusCommand;
+import org.apache.cloudstack.storage.template.DownloadManager;
+import org.apache.cloudstack.storage.template.DownloadManagerImpl;
+import org.apache.cloudstack.storage.template.DownloadManagerImpl.ZfsPathParser;
+import org.apache.cloudstack.storage.template.UploadEntity;
+import org.apache.cloudstack.storage.template.UploadManager;
+import org.apache.cloudstack.storage.template.UploadManagerImpl;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.to.TemplateObjectTO;
+import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+import org.joda.time.format.ISODateTimeFormat;
+
 import com.amazonaws.services.s3.model.S3ObjectSummary;
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.CheckHealthAnswer;
@@ -83,6 +148,7 @@ import com.cloud.utils.storage.S3.S3Utils;
 import com.cloud.vm.SecondaryStorageVm;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
+
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelInitializer;
@@ -96,69 +162,6 @@ import io.netty.handler.codec.http.HttpRequestDecoder;
 import io.netty.handler.codec.http.HttpResponseEncoder;
 import io.netty.handler.logging.LogLevel;
 import io.netty.handler.logging.LoggingHandler;
-import org.apache.cloudstack.framework.security.keystore.KeystoreManager;
-import org.apache.cloudstack.storage.command.CopyCmdAnswer;
-import org.apache.cloudstack.storage.command.CopyCommand;
-import org.apache.cloudstack.storage.command.DeleteCommand;
-import org.apache.cloudstack.storage.command.DownloadCommand;
-import org.apache.cloudstack.storage.command.DownloadProgressCommand;
-import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
-import org.apache.cloudstack.storage.command.UploadStatusAnswer;
-import org.apache.cloudstack.storage.command.UploadStatusAnswer.UploadStatus;
-import org.apache.cloudstack.storage.command.UploadStatusCommand;
-import org.apache.cloudstack.storage.template.DownloadManager;
-import org.apache.cloudstack.storage.template.DownloadManagerImpl;
-import org.apache.cloudstack.storage.template.DownloadManagerImpl.ZfsPathParser;
-import org.apache.cloudstack.storage.template.UploadEntity;
-import org.apache.cloudstack.storage.template.UploadManager;
-import org.apache.cloudstack.storage.template.UploadManagerImpl;
-import org.apache.cloudstack.storage.to.SnapshotObjectTO;
-import org.apache.cloudstack.storage.to.TemplateObjectTO;
-import org.apache.cloudstack.storage.to.VolumeObjectTO;
-import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang.StringUtils;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.utils.URLEncodedUtils;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.log4j.Logger;
-import org.joda.time.DateTime;
-import org.joda.time.format.ISODateTimeFormat;
-
-import javax.naming.ConfigurationException;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.math.BigInteger;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.UnknownHostException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import static com.cloud.utils.StringUtils.join;
-import static com.cloud.utils.storage.S3.S3Utils.putFile;
-import static java.lang.String.format;
-import static java.util.Arrays.asList;
-import static org.apache.commons.lang.StringUtils.substringAfterLast;
 
 public class NfsSecondaryStorageResource extends ServerResourceBase implements SecondaryStorageResource {
 
@@ -2231,9 +2234,10 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
         if (_inSystemVM) {
             _localgw = (String)params.get("localgw");
             if (_localgw != null) { // can only happen inside service vm
-                String mgmtHost = (String)params.get("host");
-                addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost);
-
+                String mgmtHosts = (String)params.get("host");
+                for (final String mgmtHost : mgmtHosts.split(",")) {
+                    addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost);
+                }
                 String internalDns1 = (String)params.get("internaldns1");
                 if (internalDns1 == null) {
                     s_logger.warn("No DNS entry found during configuration of NfsSecondaryStorage");
diff --git a/setup/db/db/schema-41000to41100.sql b/setup/db/db/schema-41000to41100.sql
index 911def8..eacddc1 100644
--- a/setup/db/db/schema-41000to41100.sql
+++ b/setup/db/db/schema-41000to41100.sql
@@ -123,3 +123,18 @@ CREATE VIEW `template_view` AS
              OR (`resource_tags`.`resource_type` = 'ISO')))));
 
 UPDATE `cloud`.`configuration` SET value = '600', default_value = '600' WHERE category = 'Advanced' AND name = 'router.aggregation.command.each.timeout';
+
+-- CA framework changes
+DELETE from `cloud`.`configuration` where name='ssl.keystore';
+
+-- Certificate Revocation List
+CREATE TABLE IF NOT EXISTS `cloud`.`crl` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `serial` varchar(255) UNIQUE NOT NULL COMMENT 'certificate\'s serial number as hex string',
+  `cn` varchar(255) COMMENT 'certificate\'s common name',
+  `revoker_uuid` varchar(40) COMMENT 'revoker user account uuid',
+  `revoked` datetime COMMENT 'date of revocation',
+  PRIMARY KEY (`id`),
+  KEY (`serial`),
+  UNIQUE KEY (`serial`, `cn`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/setup/db/server-setup.sql b/setup/db/server-setup.sql
index df2c924..1c4635c 100644
--- a/setup/db/server-setup.sql
+++ b/setup/db/server-setup.sql
@@ -27,3 +27,6 @@ INSERT INTO `cloud`.`configuration` (category, instance, component, name, value,
 
 -- Enable dynamic RBAC by default for fresh deployments
 INSERT INTO `cloud`.`configuration` (category, instance, component, name, value) VALUES ('Advanced', 'DEFAULT', 'RoleService', 'dynamic.apichecker.enabled', 'true');
+
+-- Enable RootCA auth strictness for fresh deployments
+INSERT INTO `cloud`.`configuration` (category, instance, component, name, value) VALUES ('Advanced', 'DEFAULT', 'RootCAProvider', 'ca.plugin.root.auth.strictness', 'true');
diff --git a/setup/db/server-setup.xml b/setup/db/server-setup.xml
index 178f29a..955a3a5 100755
--- a/setup/db/server-setup.xml
+++ b/setup/db/server-setup.xml
@@ -246,6 +246,13 @@ under the License.
       <value>true</value>
     </configuration>
     <!--
+    Enable RootCA auth strictness for fresh installations
+    -->
+    <configuration>
+      <name>ca.plugin.root.auth.strictness</name>
+      <value>true</value>
+    </configuration>
+    <!--
     The instance.name parameter is tacked to the end of the names of the VMs you create.
     So, for example, with the TEST value as it ships by default, your VMs would be named:
     i-X-Y-TEST, where X is the account ID and Y is the serially incrementing VM ID.
diff --git a/systemvm/patches/debian/config/opt/cloud/bin/patchsystemvm.sh b/systemvm/patches/debian/config/opt/cloud/bin/patchsystemvm.sh
index 9a47f8e..9222a8c 100755
--- a/systemvm/patches/debian/config/opt/cloud/bin/patchsystemvm.sh
+++ b/systemvm/patches/debian/config/opt/cloud/bin/patchsystemvm.sh
@@ -16,10 +16,6 @@
 # specific language governing permissions and limitations
 # under the License.
 
- 
-
-# $Id: patchsystemvm.sh 10800 2010-07-16 13:48:39Z edison $ $HeadURL: svn://svn.lab.vmops.com/repos/branches/2.1.x/java/scripts/vm/hypervisor/xenserver/prepsystemvm.sh $
-
 #set -x
 logfile="/var/log/patchsystemvm.log"
 # To use existing console proxy .zip-based package file
diff --git a/systemvm/pom.xml b/systemvm/pom.xml
index 1639305..4d657b7 100644
--- a/systemvm/pom.xml
+++ b/systemvm/pom.xml
@@ -125,6 +125,11 @@
                     <exclude name="**/xen-*" />
                   </fileset>
                 </copy>
+                <copy overwrite="true" todir="${basedir}/target/build-patch/opt/cloud/bin/">
+                  <fileset dir="${basedir}/../scripts/util/">
+                    <include name="**/keystore-*" />
+                  </fileset>
+                </copy>
                 <copy overwrite="true" todir="${basedir}/target/build-patch/usr/sbin/">
                   <fileset dir="${basedir}/patches/debian/xe/">
                     <include name="**/xe-*" />
diff --git a/systemvm/systemvm-descriptor.xml b/systemvm/systemvm-descriptor.xml
index 1a943b0..eec916e 100644
--- a/systemvm/systemvm-descriptor.xml
+++ b/systemvm/systemvm-descriptor.xml
@@ -31,6 +31,15 @@
   </dependencySets>
   <fileSets>
     <fileSet>
+      <directory>../scripts/util/</directory>
+      <outputDirectory>scripts/util</outputDirectory>
+      <directoryMode>555</directoryMode>
+      <fileMode>555</fileMode>
+      <includes>
+        <include>keystore*</include>
+      </includes>
+    </fileSet>
+    <fileSet>
       <directory>../scripts/storage/secondary/</directory>
       <outputDirectory>scripts/storage/secondary</outputDirectory>
       <directoryMode>555</directoryMode>
diff --git a/test/integration/smoke/test_certauthority_root.py b/test/integration/smoke/test_certauthority_root.py
new file mode 100644
index 0000000..45ffd52
--- /dev/null
+++ b/test/integration/smoke/test_certauthority_root.py
@@ -0,0 +1,229 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from nose.plugins.attrib import attr
+from marvin.cloudstackTestCase import *
+from marvin.cloudstackAPI import *
+from marvin.lib.utils import *
+from marvin.lib.base import *
+from marvin.lib.common import *
+
+from cryptography import x509
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import serialization
+from OpenSSL.crypto import FILETYPE_PEM, verify, X509
+
+PUBKEY_VERIFY=True
+try:
+    from OpenSSL.crypto import load_publickey
+except ImportError:
+    PUBKEY_VERIFY=False
+
+
+class TestCARootProvider(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestCARootProvider, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls.services = testClient.getParsedTestDataConfig()
+        cls.hypervisor = cls.testClient.getHypervisorInfo()
+        cls.cleanup = []
+
+
+    @classmethod
+    def tearDownClass(cls):
+        try:
+            cleanup_resources(cls.apiclient, cls.cleanup)
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.cleanup = []
+
+
+    def tearDown(self):
+        try:
+            cleanup_resources(self.apiclient, self.cleanup)
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+
+
+    def getUpSystemVMHosts(self, hostId=None):
+        hosts = list_hosts(
+            self.apiclient,
+            type='SecondaryStorageVM',
+            state='Up',
+            resourcestate='Enabled',
+            id=hostId
+        )
+        return hosts
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_list_ca_providers(self):
+        """
+            Tests default ca providers list
+        """
+        cmd = listCAProviders.listCAProvidersCmd()
+        response = self.apiclient.listCAProviders(cmd)
+        self.assertEqual(len(response), 1)
+        self.assertEqual(response[0].name, 'root')
+
+
+    def getCaCertificate(self):
+        cmd = listCaCertificate.listCaCertificateCmd()
+        cmd.provider = 'root'
+        response = self.apiclient.listCaCertificate(cmd)
+        return response.cacertificates.certificate
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_list_ca_certificate(self):
+        """
+            Tests the ca certificate
+        """
+        certificate = self.getCaCertificate()
+        self.assertTrue(len(certificate) > 0)
+
+        cert =  x509.load_pem_x509_certificate(str(certificate), default_backend())
+        self.assertEqual(cert.signature_hash_algorithm.name, 'sha256')
+        self.assertEqual(cert.issuer.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)[0].value, 'ca.cloudstack.apache.org')
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_issue_certificate_without_csr(self):
+        """
+            Tests issuance of a certificate
+        """
+        cmd = issueCertificate.issueCertificateCmd()
+        cmd.domain = 'apache.org,cloudstack.apache.org'
+        cmd.ipaddress = '10.1.1.1,10.2.2.2'
+        cmd.provider = 'root'
+
+        response = self.apiclient.issueCertificate(cmd)
+        self.assertTrue(len(response.privatekey) > 0)
+        self.assertTrue(len(response.cacertificates) > 0)
+        self.assertTrue(len(response.certificate) > 0)
+
+        cert =  x509.load_pem_x509_certificate(str(response.certificate), default_backend())
+
+        # Validate basic certificate attributes
+        self.assertEqual(cert.signature_hash_algorithm.name, 'sha256')
+        self.assertEqual(cert.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)[0].value, 'apache.org')
+
+        # Validate alternative names
+        altNames = cert.extensions.get_extension_for_oid(x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
+        for domain in cmd.domain.split(','):
+            self.assertTrue(domain in altNames.value.get_values_for_type(x509.DNSName))
+        for address in cmd.ipaddress.split(','):
+            self.assertTrue(address in map(lambda x: str(x), altNames.value.get_values_for_type(x509.IPAddress)))
+
+        # Validate certificate against CA public key
+        global PUBKEY_VERIFY
+        if not PUBKEY_VERIFY:
+            return
+        caCert =  x509.load_pem_x509_certificate(str(self.getCaCertificate()), default_backend())
+        x = X509()
+        x.set_pubkey(load_publickey(FILETYPE_PEM, str(caCert.public_key().public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo))))
+        verify(x, cert.signature, cert.tbs_certificate_bytes, cert.signature_hash_algorithm.name)
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_issue_certificate_with_csr(self):
+        """
+            Tests issuance of a certificate
+        """
+        cmd = issueCertificate.issueCertificateCmd()
+        cmd.csr = "-----BEGIN CERTIFICATE REQUEST-----\nMIIBHjCByQIBADBkMQswCQYDVQQGEwJJTjELMAkGA1UECAwCSFIxETAPBgNVBAcM\nCEd1cnVncmFtMQ8wDQYDVQQKDAZBcGFjaGUxEzARBgNVBAsMCkNsb3VkU3RhY2sx\nDzANBgNVBAMMBnYtMS1WTTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQD46KFWKYrJ\nF43Y1oqWUfrl4mj4Qm05Bgsi6nuigZv7ufiAKK0nO4iJKdRa2hFMUvBi2/bU3IyY\nNvg7cdJsn4K9AgMBAAGgADANBgkqhkiG9w0BAQUFAANBAIta9glu/ZSjA/ncyXix\nyDOyAKmXXxsRIsdrEuIzakUuJS7C8IG0FjUbDyIaiwWQa5x+Lt4oMqCmpNqRzaGP\nfOo=\n-----END CERTIFICATE REQUEST-----"
+        cmd.provider = 'root'
+
+        response = self.apiclient.issueCertificate(cmd)
+        self.assertTrue(response.privatekey is None)
+        self.assertTrue(len(response.cacertificates) > 0)
+        self.assertTrue(len(response.certificate) > 0)
+
+        cert =  x509.load_pem_x509_certificate(str(response.certificate), default_backend())
+
+        # Validate basic certificate attributes
+        self.assertEqual(cert.signature_hash_algorithm.name, 'sha256')
+        self.assertEqual(cert.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)[0].value, 'v-1-VM')
+
+        # Validate certificate against CA public key
+        global PUBKEY_VERIFY
+        if not PUBKEY_VERIFY:
+            return
+        caCert =  x509.load_pem_x509_certificate(str(self.getCaCertificate()), default_backend())
+        x = X509()
+        x.set_pubkey(load_publickey(FILETYPE_PEM, str(caCert.public_key().public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo))))
+        verify(x, cert.signature, cert.tbs_certificate_bytes, cert.signature_hash_algorithm.name)
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_revoke_certificate(self):
+        """
+            Tests certificate revocation
+        """
+        cmd = revokeCertificate.revokeCertificateCmd()
+        cmd.serial = 'abc123' # hex value
+        cmd.cn = 'example.com'
+        cmd.provider = 'root'
+
+        self.dbclient.execute("delete from crl where serial='%s'" % cmd.serial)
+
+        response = self.apiclient.revokeCertificate(cmd)
+        self.assertTrue(response.success)
+
+        crl = self.dbclient.execute("select serial, cn from crl where serial='%s'" % cmd.serial)[0]
+        self.assertEqual(crl[0], cmd.serial)
+        self.assertEqual(crl[1], cmd.cn)
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_provision_certificate(self):
+        """
+            Tests certificate provisioning
+        """
+        hosts = self.getUpSystemVMHosts()
+        if not hosts or len(hosts) < 1:
+            raise self.skipTest("No Up systemvm hosts found, skipping test")
+
+        host = hosts[0]
+
+        cmd = provisionCertificate.provisionCertificateCmd()
+        cmd.hostid = host.id
+        cmd.reconnect = True
+        cmd.provider = 'root'
+
+        response = self.apiclient.provisionCertificate(cmd)
+        self.assertTrue(response.success)
+
+        if self.hypervisor.lower() == 'simulator':
+            hosts = self.getUpSystemVMHosts(host.id)
+            self.assertTrue(hosts is None or len(hosts) == 0)
+        else:
+            def checkHostIsUp(hostId):
+                hosts = self.getUpSystemVMHosts(host.id)
+                return (hosts is not None), hosts
+            result, hosts = wait_until(1, 30, checkHostIsUp, host.id)
+            if result:
+                self.assertTrue(len(hosts) == 1)
+            else:
+                self.fail("Failed to have systemvm host in Up state after cert provisioning")
diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py
index a261037..762a0b0 100644
--- a/tools/apidoc/gen_toc.py
+++ b/tools/apidoc/gen_toc.py
@@ -180,7 +180,8 @@ known_categories = {
     'stopNetScalerVpx' : 'Load Balancer',
     'deleteServicePackageOffering' : 'Load Balancer',
     'destroyNsVpx' : 'Load Balancer',
-    'startNsVpx' : 'Load Balancer'
+    'startNsVpx' : 'Load Balancer',
+    'CA': 'Certificate'
     }
 
 
diff --git a/tools/travis/before_install.sh b/tools/travis/before_install.sh
index 7907a9c..409bf8a 100755
--- a/tools/travis/before_install.sh
+++ b/tools/travis/before_install.sh
@@ -23,6 +23,9 @@
 
 echo -e "#### System Information ####"
 
+echo -e "\nWho am I:"
+whoami
+
 echo -e "\nJava Version: "
 javac -version
 
@@ -76,7 +79,7 @@ fi
 sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1397BC53640DB551
 sudo sh -c 'echo "deb http://archive.ubuntu.com/ubuntu xenial main universe" >> /etc/apt/sources.list'
 sudo apt-get update -q -y > /dev/null
-sudo apt-get -q -y -V install freeipmi-common libfreeipmi16 libgcrypt20 libgpg-error-dev libgpg-error0 libopenipmi0 ipmitool --no-install-recommends > /dev/null
+sudo apt-get -q -y -V install freeipmi-common libfreeipmi16 libgcrypt20 libgpg-error-dev libgpg-error0 libopenipmi0 ipmitool libpython-dev libssl-dev libffi-dev python-openssl build-essential --no-install-recommends > /dev/null
 
 ipmitool -V
 
@@ -97,7 +100,7 @@ pip install --user --upgrade pip
 
 for ((i=0;i<$RETRY_COUNT;i++))
 do
-  pip install --user --upgrade lxml paramiko nose texttable ipmisim > /tmp/piplog
+  pip install --user --upgrade lxml paramiko nose texttable ipmisim pyopenssl > /tmp/piplog
   if [[ $? -eq 0 ]]; then
     echo -e "\npython packages installed successfully"
     break;
diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css
index 0477c4e..d2087b5 100644
--- a/ui/css/cloudstack3.css
+++ b/ui/css/cloudstack3.css
@@ -12464,6 +12464,33 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it
   background-position: -70px -1311px;
 }
 
+/*CA Cert*/
+.cacert-download {
+  display: inline-block;
+  position: relative;
+  height: 30px;
+  float: right;
+  cursor: pointer;
+  position: absolute;
+  left: 1175px;
+  top: 20px;
+}
+
+.cacert-download .icon {
+  display: inline-block;
+  float: left;
+  display: block;
+  width: 32px;
+  height: 30px;
+  background: url(../images/sprites.png) -142px -348px;
+  position: absolute;
+}
+
+.cacert-download:hover .icon,
+.cacert-download.active .icon {
+  background-position: -368px -348px;
+}
+
 /*Action icons*/
 .action.edit .icon {
   background-position: 1px -1px;
diff --git a/ui/images/sprites.png b/ui/images/sprites.png
index 0ddafaf..745c55c 100755
Binary files a/ui/images/sprites.png and b/ui/images/sprites.png differ
diff --git a/ui/index.html b/ui/index.html
index 6cc7db9..b94e8e5 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -1884,6 +1884,7 @@
         <script type="text/javascript" src="scripts/vm_snapshots.js"></script>
         <script type="text/javascript" src="scripts/ui-custom/projectSelect.js"></script>
         <script type="text/javascript" src="scripts/ui-custom/saml.js"></script>
+        <script type="text/javascript" src="scripts/ui-custom/ca.js"></script>
         <script type="text/javascript" src="scripts/metrics.js"></script>
 
         <!-- Plugin/module API -->
diff --git a/ui/scripts/ui-custom/ca.js b/ui/scripts/ui-custom/ca.js
new file mode 100644
index 0000000..c529829
--- /dev/null
+++ b/ui/scripts/ui-custom/ca.js
@@ -0,0 +1,53 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+(function($, cloudStack) {
+    $(window).bind('cloudStack.ready', function() {
+        var caCert = "";
+        var downloadCaCert = function() {
+            var blob = new Blob([caCert], {type: 'application/x-x509-ca-cert'});
+            var filename = "cloud-ca.pem";
+            if(window.navigator.msSaveOrOpenBlob) {
+                window.navigator.msSaveBlob(blob, filename);
+            } else{
+                var elem = window.document.createElement('a');
+                elem.href = window.URL.createObjectURL(blob);
+                elem.download = filename;
+                document.body.appendChild(elem)
+                elem.click();
+                document.body.removeChild(elem);
+            }
+        };
+
+        $.ajax({
+            url: createURL('listCaCertificate'),
+            success: function(json) {
+                caCert = json.listcacertificateresponse.cacertificates.certificate;
+                if (caCert) {
+                    var $caCertDownloadButton = $('<div>').addClass('cacert-download');
+                    $caCertDownloadButton.append($('<span>').addClass('icon').html('&nbsp;').attr('title', 'Download CA Certificate'));
+                    $caCertDownloadButton.click(function() {
+                        downloadCaCert();
+                    });
+                    $('#header .controls .view-switcher:last').after($caCertDownloadButton);
+                }
+            },
+            error: function(data) {
+            }
+        });
+    });
+}(jQuery, cloudStack));
diff --git a/utils/pom.xml b/utils/pom.xml
index 013d683..52e5dd5 100755
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -36,6 +36,11 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-framework-ca</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context</artifactId>
     </dependency>
@@ -72,6 +77,10 @@
       <artifactId>bcprov-jdk15on</artifactId>
     </dependency>
     <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcpkix-jdk15on</artifactId>
+    </dependency>
+    <dependency>
       <groupId>com.jcraft</groupId>
       <artifactId>jsch</artifactId>
     </dependency>
diff --git a/utils/src/main/java/com/cloud/utils/StringUtils.java b/utils/src/main/java/com/cloud/utils/StringUtils.java
index 9554e87..6ada2ad 100644
--- a/utils/src/main/java/com/cloud/utils/StringUtils.java
+++ b/utils/src/main/java/com/cloud/utils/StringUtils.java
@@ -21,10 +21,12 @@ package com.cloud.utils;
 
 import java.nio.charset.Charset;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -320,4 +322,10 @@ public class StringUtils {
         }
         return listOfChunks;
     }
+
+    public static String shuffleCSVList(final String csvList) {
+        List<String> list = csvTagsToList(csvList);
+        Collections.shuffle(list, new Random(System.nanoTime()));
+        return join(list, ",");
+    }
 }
diff --git a/utils/src/main/java/com/cloud/utils/exception/TaskExecutionException.java b/utils/src/main/java/com/cloud/utils/exception/TaskExecutionException.java
index be639ba..635874e 100644
--- a/utils/src/main/java/com/cloud/utils/exception/TaskExecutionException.java
+++ b/utils/src/main/java/com/cloud/utils/exception/TaskExecutionException.java
@@ -25,7 +25,7 @@ import com.cloud.utils.SerialVersionUID;
  * Used by the Task class to wrap-up its exceptions.
  */
 public class TaskExecutionException extends Exception {
-    private static final long serialVersionUID = SerialVersionUID.NioConnectionException;
+    private static final long serialVersionUID = SerialVersionUID.TaskExecutionException;
 
     protected int csErrorCode;
 
diff --git a/utils/src/main/java/com/cloud/utils/nio/Link.java b/utils/src/main/java/com/cloud/utils/nio/Link.java
index 02ffaab..e8f36c6 100644
--- a/utils/src/main/java/com/cloud/utils/nio/Link.java
+++ b/utils/src/main/java/com/cloud/utils/nio/Link.java
@@ -19,20 +19,6 @@
 
 package com.cloud.utils.nio;
 
-import com.cloud.utils.PropertiesUtil;
-import com.cloud.utils.db.DbProperties;
-import org.apache.cloudstack.utils.security.SSLUtils;
-import org.apache.log4j.Logger;
-
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -44,8 +30,27 @@ import java.nio.channels.SelectionKey;
 import java.nio.channels.SocketChannel;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
+import java.security.SecureRandom;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.cloudstack.framework.ca.CAService;
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.cloudstack.utils.security.SSLUtils;
+import org.apache.log4j.Logger;
+
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.db.DbProperties;
+
 /**
  */
 public class Link {
@@ -62,7 +67,6 @@ public class Link {
     private boolean _gotFollowingPacket;
 
     private SSLEngine _sslEngine;
-    public static final String keystoreFile = "/cloudmanagementserver.keystore";
 
     public Link(InetSocketAddress addr, NioConnection connection) {
         _addr = addr;
@@ -97,49 +101,6 @@ public class Link {
         _sslEngine = sslEngine;
     }
 
-    /**
-     * No user, so comment it out.
-     *
-     * Static methods for reading from a channel in case
-     * you need to add a client that doesn't require nio.
-     * @param ch channel to read from.
-     * @param bytebuffer to use.
-     * @return bytes read
-     * @throws IOException if not read to completion.
-    public static byte[] read(SocketChannel ch, ByteBuffer buff) throws IOException {
-        synchronized(buff) {
-            buff.clear();
-            buff.limit(4);
-
-            while (buff.hasRemaining()) {
-                if (ch.read(buff) == -1) {
-                    throw new IOException("Connection closed with -1 on reading size.");
-                }
-            }
-
-            buff.flip();
-
-            int length = buff.getInt();
-            ByteArrayOutputStream output = new ByteArrayOutputStream(length);
-            WritableByteChannel outCh = Channels.newChannel(output);
-
-            int count = 0;
-            while (count < length) {
-                buff.clear();
-                int read = ch.read(buff);
-                if (read < 0) {
-                    throw new IOException("Connection closed with -1 on reading data.");
-                }
-                count += read;
-                buff.flip();
-                outCh.write(buff);
-            }
-
-            return output.toByteArray();
-        }
-    }
-     */
-
     private static void doWrite(SocketChannel ch, ByteBuffer[] buffers, SSLEngine sslEngine) throws IOException {
         SSLSession sslSession = sslEngine.getSession();
         ByteBuffer pkgBuf = ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40);
@@ -404,44 +365,78 @@ public class Link {
         _connection.scheduleTask(task);
     }
 
-    public static SSLContext initSSLContext(boolean isClient) throws GeneralSecurityException, IOException {
-        InputStream stream;
-        SSLContext sslContext = null;
-        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
-        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
-        KeyStore ks = KeyStore.getInstance("JKS");
-        TrustManager[] tms;
+    public static SSLEngine initServerSSLEngine(final CAService caService, final String clientAddress) throws GeneralSecurityException, IOException {
+        final SSLContext sslContext = SSLUtils.getSSLContext();
+        if (caService != null) {
+            return caService.createSSLEngine(sslContext, clientAddress);
+        }
+        s_logger.error("CA service is not configured, by-passing CA manager to create SSL engine");
+        char[] passphrase = KeyStoreUtils.defaultKeystorePassphrase;
+        final KeyStore ks = loadKeyStore(NioConnection.class.getResourceAsStream("/cloud.keystore"), passphrase);
+        final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+        final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+        kmf.init(ks, passphrase);
+        tmf.init(ks);
+        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
+        return sslContext.createSSLEngine();
+    }
+
+    public static KeyStore loadKeyStore(final InputStream stream, final char[] passphrase) throws GeneralSecurityException, IOException {
+        final KeyStore ks = KeyStore.getInstance("JKS");
+        ks.load(stream, passphrase);
+        return ks;
+    }
 
-        File confFile = PropertiesUtil.findConfigFile("db.properties");
-        if (null != confFile && !isClient) {
-            final String pass = DbProperties.getDbProperties().getProperty("db.cloud.keyStorePassphrase");
-            char[] passphrase = "vmops.com".toCharArray();
+    public static SSLContext initClientSSLContext() throws GeneralSecurityException, IOException {
+        final SSLContext sslContext = SSLUtils.getSSLContext();
+
+        char[] passphrase = KeyStoreUtils.defaultKeystorePassphrase;
+        File confFile = PropertiesUtil.findConfigFile("agent.properties");
+        if (confFile != null) {
+            s_logger.info("Conf file found: " + confFile.getAbsolutePath());
+            final String pass = PropertiesUtil.loadFromFile(confFile).getProperty(KeyStoreUtils.passphrasePropertyName);
             if (pass != null) {
                 passphrase = pass.toCharArray();
             }
-            String confPath = confFile.getParent();
-            String keystorePath = confPath + keystoreFile;
+        } else {
+            confFile = PropertiesUtil.findConfigFile("db.properties");
+            if (confFile != null) {
+                final String pass = DbProperties.getDbProperties().getProperty("db.cloud.keyStorePassphrase");
+                if (pass != null) {
+                    passphrase = pass.toCharArray();
+                }
+            }
+        }
+
+        InputStream stream = null;
+        if (confFile != null) {
+            final String keystorePath = confFile.getParent() + "/" + KeyStoreUtils.defaultKeystoreFile;
             if (new File(keystorePath).exists()) {
                 stream = new FileInputStream(keystorePath);
-            } else {
-                s_logger.warn("SSL: Fail to find the generated keystore. Loading fail-safe one to continue.");
-                stream = NioConnection.class.getResourceAsStream("/cloud.keystore");
-                passphrase = "vmops.com".toCharArray();
             }
-            ks.load(stream, passphrase);
-            stream.close();
-            kmf.init(ks, passphrase);
-            tmf.init(ks);
+        }
+
+        final KeyStore ks = loadKeyStore(stream, passphrase);
+        final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+        tmf.init(ks);
+        TrustManager[] tms;
+        if (stream != null) {
+            // This enforces a two-way SSL authentication
             tms = tmf.getTrustManagers();
         } else {
-            ks.load(null, null);
-            kmf.init(ks, null);
-            tms = new TrustManager[1];
-            tms[0] = new TrustAllManager();
+            // This enforces a one-way SSL authentication
+            tms = new TrustManager[]{new TrustAllManager()};
+            s_logger.warn("Failed to load keystore, using trust all manager");
         }
 
-        sslContext = SSLUtils.getSSLContext();
-        sslContext.init(kmf.getKeyManagers(), tms, null);
+        if (stream != null) {
+            stream.close();
+        }
+
+        final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+        kmf.init(ks, passphrase);
+        sslContext.init(kmf.getKeyManagers(), tms, new SecureRandom());
+
         if (s_logger.isTraceEnabled()) {
             s_logger.trace("SSL: SSLcontext has been initialized");
         }
@@ -498,8 +493,9 @@ public class Link {
         try {
             result = sslEngine.unwrap(peerNetData, peerAppData);
             peerNetData.compact();
-        } catch (SSLException sslException) {
-            s_logger.error("SSL error occurred while processing unwrap data: " + sslException.getMessage());
+        } catch (final SSLException sslException) {
+            s_logger.error(String.format("SSL error caught during unwrap data: %s, for local address=%s, remote address=%s. The client may have invalid ca-certificates.",
+                    sslException.getMessage(), socketChannel.getLocalAddress(), socketChannel.getRemoteAddress()));
             sslEngine.closeOutbound();
             return true;
         }
@@ -539,8 +535,9 @@ public class Link {
         SSLEngineResult result = null;
         try {
             result = sslEngine.wrap(myAppData, myNetData);
-        } catch (SSLException sslException) {
-            s_logger.error("SSL error occurred while processing wrap data: " + sslException.getMessage());
+        } catch (final SSLException sslException) {
+            s_logger.error(String.format("SSL error caught during wrap data: %s, for local address=%s, remote address=%s.",
+                    sslException.getMessage(), socketChannel.getLocalAddress(), socketChannel.getRemoteAddress()));
             sslEngine.closeOutbound();
             return true;
         }
diff --git a/utils/src/main/java/com/cloud/utils/nio/NioClient.java b/utils/src/main/java/com/cloud/utils/nio/NioClient.java
index dc4f670..1c29b0c 100644
--- a/utils/src/main/java/com/cloud/utils/nio/NioClient.java
+++ b/utils/src/main/java/com/cloud/utils/nio/NioClient.java
@@ -56,7 +56,7 @@ public class NioClient extends NioConnection {
             _clientConnection.connect(peerAddr);
             _clientConnection.configureBlocking(false);
 
-            final SSLContext sslContext = Link.initSSLContext(true);
+            final SSLContext sslContext = Link.initClientSSLContext();
             SSLEngine sslEngine = sslContext.createSSLEngine(_host, _port);
             sslEngine.setUseClientMode(true);
             sslEngine.setEnabledProtocols(SSLUtils.getSupportedProtocols(sslEngine.getEnabledProtocols()));
diff --git a/utils/src/main/java/com/cloud/utils/nio/NioConnection.java b/utils/src/main/java/com/cloud/utils/nio/NioConnection.java
index ce03246..30000cf 100644
--- a/utils/src/main/java/com/cloud/utils/nio/NioConnection.java
+++ b/utils/src/main/java/com/cloud/utils/nio/NioConnection.java
@@ -19,13 +19,8 @@
 
 package com.cloud.utils.nio;
 
-import com.cloud.utils.concurrency.NamedThreadFactory;
-import com.cloud.utils.exception.NioConnectionException;
-import org.apache.cloudstack.utils.security.SSLUtils;
-import org.apache.log4j.Logger;
+import static com.cloud.utils.AutoCloseableUtil.closeAutoCloseable;
 
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
 import java.io.IOException;
 import java.net.ConnectException;
 import java.net.InetSocketAddress;
@@ -49,7 +44,14 @@ import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
-import static com.cloud.utils.AutoCloseableUtil.closeAutoCloseable;
+import javax.net.ssl.SSLEngine;
+
+import org.apache.cloudstack.framework.ca.CAService;
+import org.apache.cloudstack.utils.security.SSLUtils;
+import org.apache.log4j.Logger;
+
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.exception.NioConnectionException;
 
 /**
  * NioConnection abstracts the NIO socket operations.  The Java implementation
@@ -70,6 +72,7 @@ public abstract class NioConnection implements Callable<Boolean> {
     protected String _name;
     protected ExecutorService _executor;
     protected ExecutorService _sslHandshakeExecutor;
+    protected CAService caService;
 
     public NioConnection(final String name, final int port, final int workers, final HandlerFactory factory) {
         _name = name;
@@ -81,6 +84,10 @@ public abstract class NioConnection implements Callable<Boolean> {
         _sslHandshakeExecutor = Executors.newCachedThreadPool(new NamedThreadFactory(name + "-SSLHandshakeHandler"));
     }
 
+    public void setCAService(final CAService caService) {
+        this.caService = caService;
+    }
+
     public void start() throws NioConnectionException {
         _todos = new ArrayList<ChangeRequest>();
 
@@ -124,7 +131,7 @@ public abstract class NioConnection implements Callable<Boolean> {
     public Boolean call() throws NioConnectionException {
         while (_isRunning) {
             try {
-                _selector.select(100);
+                _selector.select(50);
 
                 // Someone is ready for I/O, get the ready keys
                 final Set<SelectionKey> readyKeys = _selector.selectedKeys();
@@ -196,10 +203,8 @@ public abstract class NioConnection implements Callable<Boolean> {
 
         final SSLEngine sslEngine;
         try {
-            final SSLContext sslContext = Link.initSSLContext(false);
-            sslEngine = sslContext.createSSLEngine();
+            sslEngine = Link.initServerSSLEngine(caService, socketChannel.getRemoteAddress().toString());
             sslEngine.setUseClientMode(false);
-            sslEngine.setNeedClientAuth(false);
             sslEngine.setEnabledProtocols(SSLUtils.getSupportedProtocols(sslEngine.getEnabledProtocols()));
             final NioConnection nioConnection = this;
             _sslHandshakeExecutor.submit(new Runnable() {
diff --git a/utils/src/main/java/com/cloud/utils/nio/NioServer.java b/utils/src/main/java/com/cloud/utils/nio/NioServer.java
index b655f18..ff54165 100644
--- a/utils/src/main/java/com/cloud/utils/nio/NioServer.java
+++ b/utils/src/main/java/com/cloud/utils/nio/NioServer.java
@@ -27,6 +27,7 @@ import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.spi.SelectorProvider;
 import java.util.WeakHashMap;
 
+import org.apache.cloudstack.framework.ca.CAService;
 import org.apache.log4j.Logger;
 
 public class NioServer extends NioConnection {
@@ -37,8 +38,9 @@ public class NioServer extends NioConnection {
 
     protected WeakHashMap<InetSocketAddress, Link> _links;
 
-    public NioServer(final String name, final int port, final int workers, final HandlerFactory factory) {
+    public NioServer(final String name, final int port, final int workers, final HandlerFactory factory, final CAService caService) {
         super(name, port, workers, factory);
+        setCAService(caService);
         _localAddr = null;
         _links = new WeakHashMap<InetSocketAddress, Link>(1024);
     }
diff --git a/utils/src/main/java/com/cloud/utils/script/Script.java b/utils/src/main/java/com/cloud/utils/script/Script.java
index 7608475..01f18bd 100644
--- a/utils/src/main/java/com/cloud/utils/script/Script.java
+++ b/utils/src/main/java/com/cloud/utils/script/Script.java
@@ -37,6 +37,7 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.log4j.Logger;
 import org.joda.time.Duration;
@@ -202,7 +203,7 @@ public class Script implements Callable<String> {
         String[] command = _command.toArray(new String[_command.size()]);
 
         if (_logger.isDebugEnabled()) {
-            _logger.debug("Executing: " + buildCommandLine(command));
+            _logger.debug("Executing: " + buildCommandLine(command).split(KeyStoreUtils.defaultKeystoreFile)[0]);
         }
 
         try {
diff --git a/utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java b/utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java
index 61d01c4..10407b6 100644
--- a/utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java
+++ b/utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java
@@ -22,8 +22,10 @@ package com.cloud.utils.ssh;
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
 import org.apache.log4j.Logger;
 
+import com.google.common.base.Strings;
 import com.trilead.ssh2.ChannelCondition;
 import com.trilead.ssh2.Session;
 
@@ -32,6 +34,44 @@ public class SSHCmdHelper {
     private static final int DEFAULT_CONNECT_TIMEOUT = 180000;
     private static final int DEFAULT_KEX_TIMEOUT = 60000;
 
+    public static class SSHCmdResult {
+        private int returnCode = -1;
+        private String stdOut;
+        private String stdErr;
+
+        public SSHCmdResult(final int returnCode, final String stdOut, final String stdErr) {
+            this.returnCode = returnCode;
+            this.stdOut = stdOut;
+            this.stdErr = stdErr;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("SSH cmd result: return code=%d, stdout=%s, stderr=%s",
+                    getReturnCode(), getStdOut().split("-----BEGIN")[0], getStdErr());
+        }
+
+        public boolean isSuccess() {
+            return returnCode == 0;
+        }
+
+        public int getReturnCode() {
+            return returnCode;
+        }
+
+        public void setReturnCode(int returnCode) {
+            this.returnCode = returnCode;
+        }
+
+        public String getStdOut() {
+            return stdOut;
+        }
+
+        public String getStdErr() {
+            return stdErr;
+        }
+    }
+
     public static com.trilead.ssh2.Connection acquireAuthorizedConnection(String ip, String username, String password) {
         return acquireAuthorizedConnection(ip, 22, username, password);
     }
@@ -65,36 +105,41 @@ public class SSHCmdHelper {
     public static boolean sshExecuteCmd(com.trilead.ssh2.Connection sshConnection, String cmd, int nTimes) {
         for (int i = 0; i < nTimes; i++) {
             try {
-                if (sshExecuteCmdOneShot(sshConnection, cmd))
+                final SSHCmdResult result = sshExecuteCmdOneShot(sshConnection, cmd);
+                if (result.isSuccess()) {
                     return true;
-            } catch (SshException e) {
+                }
+            } catch (SshException ignored) {
                 continue;
             }
         }
         return false;
     }
 
-    public static int sshExecuteCmdWithExitCode(com.trilead.ssh2.Connection sshConnection, String cmd) {
-        return sshExecuteCmdWithExitCode(sshConnection, cmd, 3);
-    }
-
-    public static int sshExecuteCmdWithExitCode(com.trilead.ssh2.Connection sshConnection, String cmd, int nTimes) {
+    public static SSHCmdResult sshExecuteCmdWithResult(com.trilead.ssh2.Connection sshConnection, String cmd, int nTimes) {
         for (int i = 0; i < nTimes; i++) {
             try {
-                return sshExecuteCmdOneShotWithExitCode(sshConnection, cmd);
-            } catch (SshException e) {
+                final SSHCmdResult result = sshExecuteCmdOneShot(sshConnection, cmd);
+                if (result.isSuccess()) {
+                    return result;
+                }
+            } catch (SshException ignored) {
                 continue;
             }
         }
-        return -1;
+        return new SSHCmdResult(-1, null, null);
     }
 
     public static boolean sshExecuteCmd(com.trilead.ssh2.Connection sshConnection, String cmd) {
         return sshExecuteCmd(sshConnection, cmd, 3);
     }
 
-    public static int sshExecuteCmdOneShotWithExitCode(com.trilead.ssh2.Connection sshConnection, String cmd) throws SshException {
-        s_logger.debug("Executing cmd: " + cmd);
+    public static SSHCmdResult sshExecuteCmdWithResult(com.trilead.ssh2.Connection sshConnection, String cmd) {
+        return sshExecuteCmdWithResult(sshConnection, cmd, 3);
+    }
+
+    public static SSHCmdResult sshExecuteCmdOneShot(com.trilead.ssh2.Connection sshConnection, String cmd) throws SshException {
+        s_logger.debug("Executing cmd: " + cmd.split(KeyStoreUtils.defaultKeystoreFile)[0]);
         Session sshSession = null;
         try {
             sshSession = sshConnection.openSession();
@@ -112,7 +157,8 @@ public class SSHCmdHelper {
             InputStream stderr = sshSession.getStderr();
 
             byte[] buffer = new byte[8192];
-            StringBuffer sbResult = new StringBuffer();
+            StringBuffer sbStdoutResult = new StringBuffer();
+            StringBuffer sbStdErrResult = new StringBuffer();
 
             int currentReadBytes = 0;
             while (true) {
@@ -145,27 +191,30 @@ public class SSHCmdHelper {
 
                 while (stdout.available() > 0) {
                     currentReadBytes = stdout.read(buffer);
-                    sbResult.append(new String(buffer, 0, currentReadBytes));
+                    sbStdoutResult.append(new String(buffer, 0, currentReadBytes));
                 }
 
                 while (stderr.available() > 0) {
                     currentReadBytes = stderr.read(buffer);
-                    sbResult.append(new String(buffer, 0, currentReadBytes));
+                    sbStdErrResult.append(new String(buffer, 0, currentReadBytes));
                 }
             }
 
-            String result = sbResult.toString();
-            if (result != null && !result.isEmpty())
-                s_logger.debug(cmd + " output:" + result);
+            final SSHCmdResult result = new SSHCmdResult(-1, sbStdoutResult.toString(), sbStdErrResult.toString());
+            if (!Strings.isNullOrEmpty(result.getStdOut()) || !Strings.isNullOrEmpty(result.getStdErr())) {
+                s_logger.debug("SSH command: " + cmd.split(KeyStoreUtils.defaultKeystoreFile)[0] + "\nSSH command output:" + result.getStdOut().split("-----BEGIN")[0] + "\n" + result.getStdErr());
+            }
+
             // exit status delivery might get delayed
             for(int i = 0 ; i<10 ; i++ ) {
                 Integer status = sshSession.getExitStatus();
                 if( status != null ) {
-                    return status;
+                    result.setReturnCode(status);
+                    return result;
                 }
                 Thread.sleep(100);
             }
-            return -1;
+            return result;
         } catch (Exception e) {
             s_logger.debug("Ssh executed failed", e);
             throw new SshException("Ssh executed failed " + e.getMessage());
@@ -174,8 +223,4 @@ public class SSHCmdHelper {
                 sshSession.close();
         }
     }
-
-    public static boolean sshExecuteCmdOneShot(com.trilead.ssh2.Connection sshConnection, String cmd) throws SshException {
-        return sshExecuteCmdOneShotWithExitCode(sshConnection, cmd) == 0;
-    }
 }
diff --git a/utils/src/main/java/org/apache/cloudstack/utils/security/CertUtils.java b/utils/src/main/java/org/apache/cloudstack/utils/security/CertUtils.java
new file mode 100644
index 0000000..c2ef9ed
--- /dev/null
+++ b/utils/src/main/java/org/apache/cloudstack/utils/security/CertUtils.java
@@ -0,0 +1,240 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.utils.security;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.log4j.Logger;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemReader;
+import org.bouncycastle.util.io.pem.PemWriter;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.common.base.Strings;
+
+//import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
+
+public class CertUtils {
+
+    private static final Logger LOG = Logger.getLogger(CertUtils.class);
+
+    public static KeyPair generateRandomKeyPair(final int keySize) throws NoSuchProviderException, NoSuchAlgorithmException {
+        Security.addProvider(new BouncyCastleProvider());
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
+        keyPairGenerator.initialize(keySize, new SecureRandom());
+        return keyPairGenerator.generateKeyPair();
+    }
+
+    public static KeyFactory getKeyFactory() {
+        KeyFactory keyFactory = null;
+        try {
+            Security.addProvider(new BouncyCastleProvider());
+            keyFactory = KeyFactory.getInstance("RSA", "BC");
+        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
+            LOG.error("Unable to create KeyFactory:" + e.getMessage());
+        }
+        return keyFactory;
+    }
+
+    public static X509Certificate pemToX509Certificate(final String pem) throws CertificateException, IOException {
+        final PEMParser pemParser = new PEMParser(new StringReader(pem));
+        return new JcaX509CertificateConverter().setProvider("BC").getCertificate((X509CertificateHolder) pemParser.readObject());
+    }
+
+    public static String x509CertificateToPem(final X509Certificate cert) throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final JcaPEMWriter pw = new JcaPEMWriter(sw)) {
+            pw.writeObject(cert);
+            pw.flush();
+        }
+        return sw.toString();
+    }
+
+    public static String x509CertificatesToPem(final List<X509Certificate> certificates) throws IOException {
+        if (certificates == null) {
+            return "";
+        }
+        final StringBuilder buffer = new StringBuilder();
+        for (final X509Certificate certificate: certificates) {
+            buffer.append(CertUtils.x509CertificateToPem(certificate));
+        }
+        return buffer.toString();
+    }
+
+    public static PrivateKey pemToPrivateKey(final String pem) throws InvalidKeySpecException, IOException {
+        final PemReader pr = new PemReader(new StringReader(pem));
+        final PemObject pemObject = pr.readPemObject();
+        final KeyFactory keyFactory = getKeyFactory();
+        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pemObject.getContent()));
+    }
+
+    public static String privateKeyToPem(final PrivateKey key) throws IOException {
+        final PemObject pemObject = new PemObject("RSA PRIVATE KEY", key.getEncoded());
+        final StringWriter sw = new StringWriter();
+        try (final PemWriter pw = new PemWriter(sw)) {
+            pw.writeObject(pemObject);
+        }
+        return sw.toString();
+    }
+
+    public static PublicKey pemToPublicKey(final String pem) throws InvalidKeySpecException, IOException {
+        final PemReader pr = new PemReader(new StringReader(pem));
+        final PemObject pemObject = pr.readPemObject();
+        final KeyFactory keyFactory = getKeyFactory();
+        return keyFactory.generatePublic(new X509EncodedKeySpec(pemObject.getContent()));
+    }
+
+    public static String publicKeyToPem(final PublicKey key) throws IOException {
+        final PemObject pemObject = new PemObject("PUBLIC KEY", key.getEncoded());
+        final StringWriter sw = new StringWriter();
+        try (final PemWriter pw = new PemWriter(sw)) {
+            pw.writeObject(pemObject);
+        }
+        return sw.toString();
+    }
+
+    public static BigInteger generateRandomBigInt() {
+        return new BigInteger(64, new SecureRandom());
+    }
+
+    public static X509Certificate generateV1Certificate(final KeyPair keyPair,
+                                                        final String subject,
+                                                        final String issuer,
+                                                        final int validityYears,
+                                                        final String signatureAlgorithm) throws CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
+        final DateTime now = DateTime.now(DateTimeZone.UTC);
+        final X509v1CertificateBuilder certBuilder = new JcaX509v1CertificateBuilder(
+                new X500Name(issuer),
+                generateRandomBigInt(),
+                now.minusDays(1).toDate(),
+                now.plusYears(validityYears).toDate(),
+                new X500Name(subject),
+                keyPair.getPublic());
+        final ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).setProvider("BC").build(keyPair.getPrivate());
+        final X509CertificateHolder certHolder = certBuilder.build(signer);
+        return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
+    }
+
+    public static X509Certificate generateV3Certificate(final X509Certificate caCert,
+                                                        final PrivateKey caPrivateKey,
+                                                        final PublicKey clientPublicKey,
+                                                        final String subject,
+                                                        final String signatureAlgorithm,
+                                                        final int validityDays,
+                                                        final List<String> dnsNames,
+                                                        final List<String> publicIPAddresses) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, InvalidKeyException, SignatureException, OperatorCreationException {
+
+        final DateTime now = DateTime.now(DateTimeZone.UTC);
+        final BigInteger serial = generateRandomBigInt();
+        final X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
+                caCert,
+                serial,
+                now.minusHours(12).toDate(),
+                now.plusDays(validityDays).toDate(),
+                new X500Principal(subject),
+                clientPublicKey);
+
+        final JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+
+        certBuilder.addExtension(
+                Extension.subjectKeyIdentifier,
+                false,
+                extUtils.createSubjectKeyIdentifier(clientPublicKey));
+
+        certBuilder.addExtension(
+                Extension.authorityKeyIdentifier,
+                false,
+                extUtils.createAuthorityKeyIdentifier(caCert));
+
+        final List<ASN1Encodable> subjectAlternativeNames = new ArrayList<ASN1Encodable>();
+        if (publicIPAddresses != null) {
+            for (final String publicIPAddress: publicIPAddresses) {
+                if (Strings.isNullOrEmpty(publicIPAddress)) {
+                    continue;
+                }
+                subjectAlternativeNames.add(new GeneralName(GeneralName.iPAddress, publicIPAddress));
+            }
+        }
+        if (dnsNames != null) {
+            for (final String dnsName : dnsNames) {
+                if (Strings.isNullOrEmpty(dnsName)) {
+                    continue;
+                }
+                subjectAlternativeNames.add(new GeneralName(GeneralName.dNSName, dnsName));
+            }
+        }
+        if (subjectAlternativeNames.size() > 0) {
+            final GeneralNames subjectAltNames = GeneralNames.getInstance(new DERSequence(subjectAlternativeNames.toArray(new ASN1Encodable[] {})));
+            certBuilder.addExtension(
+                    Extension.subjectAlternativeName,
+                    false,
+                    subjectAltNames);
+        }
+
+        final ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).setProvider("BC").build(caPrivateKey);
+        final X509CertificateHolder certHolder = certBuilder.build(signer);
+        final X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
+        cert.verify(caCert.getPublicKey());
+        return cert;
+    }
+}
diff --git a/utils/src/main/java/org/apache/cloudstack/utils/security/KeyStoreUtils.java b/utils/src/main/java/org/apache/cloudstack/utils/security/KeyStoreUtils.java
new file mode 100644
index 0000000..e02d3b0
--- /dev/null
+++ b/utils/src/main/java/org/apache/cloudstack/utils/security/KeyStoreUtils.java
@@ -0,0 +1,70 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.utils.security;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.cloud.utils.script.Script;
+import com.google.common.base.Strings;
+
+public class KeyStoreUtils {
+
+    public static String defaultTmpKeyStoreFile = "/tmp/tmp.jks";
+    public static String defaultKeystoreFile = "cloud.jks";
+    public static String defaultPrivateKeyFile = "cloud.key";
+    public static String defaultCsrFile = "cloud.csr";
+    public static String defaultCertFile = "cloud.crt";
+    public static String defaultCaCertFile = "cloud.ca.crt";
+    public static char[] defaultKeystorePassphrase = "vmops.com".toCharArray();
+
+    public static String certNewlineEncoder = "^";
+    public static String certSpaceEncoder = "~";
+
+    public static String keyStoreSetupScript = "keystore-setup";
+    public static String keyStoreImportScript = "keystore-cert-import";
+    public static String passphrasePropertyName = "keystore.passphrase";
+
+    public static String sshMode = "ssh";
+    public static String agentMode = "agent";
+
+    public static void copyKeystore(final String keystorePath, final String tmpKeystorePath) throws IOException {
+        if (Strings.isNullOrEmpty(keystorePath) || Strings.isNullOrEmpty(tmpKeystorePath)) {
+            throw new IOException("Invalid keystore path provided");
+        }
+        try {
+            final Script script = new Script(true, "cp", 5000, null);
+            script.add("-f");
+            script.add(tmpKeystorePath);
+            script.add(keystorePath);
+            final String result = script.execute();
+            if (result != null) {
+                throw new IOException("Failed to execute cp to copy keystore file to mgmt server conf location");
+            }
+        } catch (final Exception e) {
+            throw new IOException("Failed to create keystore file: " + keystorePath, e);
+        }
+        try {
+            new File(tmpKeystorePath).delete();
+        } catch (Exception ignored) {
+        }
+    }
+
+}
diff --git a/utils/src/test/java/com/cloud/utils/StringUtilsTest.java b/utils/src/test/java/com/cloud/utils/StringUtilsTest.java
index 3619ede..e8e62b0 100644
--- a/utils/src/test/java/com/cloud/utils/StringUtilsTest.java
+++ b/utils/src/test/java/com/cloud/utils/StringUtilsTest.java
@@ -20,6 +20,8 @@
 package com.cloud.utils;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertNotEquals;
 
 import java.nio.charset.Charset;
@@ -250,4 +252,15 @@ public class StringUtilsTest {
         assertEquals("a,b,c", StringUtils.listToCsvTags(Arrays.asList("a","b", "c")));
         assertEquals("", StringUtils.listToCsvTags(new ArrayList<String>()));
     }
+
+    @Test
+    public void testShuffleCSVList() {
+        String input = "one,two,three,four,five,six,seven,eight,nine,ten";
+        String output = StringUtils.shuffleCSVList(input);
+        assertFalse(input.equals(output));
+
+        input = "only-one";
+        output = StringUtils.shuffleCSVList("only-one");
+        assertTrue(input.equals(output));
+    }
 }
diff --git a/utils/src/test/java/com/cloud/utils/testcase/NioTest.java b/utils/src/test/java/com/cloud/utils/testcase/NioTest.java
index 894aa1a..0a9deea 100644
--- a/utils/src/test/java/com/cloud/utils/testcase/NioTest.java
+++ b/utils/src/test/java/com/cloud/utils/testcase/NioTest.java
@@ -98,7 +98,7 @@ public class NioTest {
         testBytes = new byte[1000000];
         randomGenerator.nextBytes(testBytes);
 
-        server = new NioServer("NioTestServer", 0, 1, new NioTestServer());
+        server = new NioServer("NioTestServer", 0, 1, new NioTestServer(), null);
         try {
             server.start();
         } catch (final NioConnectionException e) {
diff --git a/utils/src/test/java/org/apache/cloudstack/utils/security/CertUtilsTest.java b/utils/src/test/java/org/apache/cloudstack/utils/security/CertUtilsTest.java
new file mode 100644
index 0000000..406f604
--- /dev/null
+++ b/utils/src/test/java/org/apache/cloudstack/utils/security/CertUtilsTest.java
@@ -0,0 +1,118 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.utils.security;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Arrays;
+import java.util.List;
+
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CertUtilsTest {
+    KeyPair caKeyPair;
+    X509Certificate caCertificate;
+
+    @Before
+    public void setUp() throws Exception {
+        caKeyPair = CertUtils.generateRandomKeyPair(1024);
+        caCertificate = CertUtils.generateV1Certificate(caKeyPair, "CN=test", "CN=test", 1, "SHA256WithRSAEncryption");
+    }
+
+    @Test
+    public void testGenerateRandomKeyPair() throws Exception {
+        final int size = 2048;
+        final KeyPair kp = CertUtils.generateRandomKeyPair(size);
+        Assert.assertEquals(((RSAPublicKey)kp.getPublic()).getModulus().bitLength(), size);
+    }
+
+    @Test
+    public void testCertificateConversionMethods() throws Exception {
+        final X509Certificate in = caCertificate;
+        final String pem = CertUtils.x509CertificateToPem(in);
+        final X509Certificate out = CertUtils.pemToX509Certificate(pem);
+        Assert.assertTrue(pem.startsWith("-----BEGIN CERTIFICATE-----\n"));
+        Assert.assertTrue(pem.endsWith("-----END CERTIFICATE-----\n"));
+        Assert.assertEquals(in.getSerialNumber(), out.getSerialNumber());
+        Assert.assertArrayEquals(in.getSignature(), out.getSignature());
+        Assert.assertEquals(in.getSigAlgName(), out.getSigAlgName());
+        Assert.assertEquals(in.getPublicKey(), out.getPublicKey());
+        Assert.assertEquals(in.getNotBefore(), out.getNotBefore());
+        Assert.assertEquals(in.getNotAfter(), out.getNotAfter());
+        Assert.assertEquals(in.getIssuerDN().toString(), out.getIssuerDN().toString());
+    }
+
+    @Test
+    public void testKeysConversionMethods() throws Exception {
+        final KeyPair kp = CertUtils.generateRandomKeyPair(2048);
+
+        final PrivateKey inPrivateKey = kp.getPrivate();
+        final PrivateKey outPrivateKey = CertUtils.pemToPrivateKey(CertUtils.privateKeyToPem(inPrivateKey));
+        Assert.assertEquals(inPrivateKey.getAlgorithm(), outPrivateKey.getAlgorithm());
+        Assert.assertEquals(inPrivateKey.getFormat(), outPrivateKey.getFormat());
+        Assert.assertArrayEquals(inPrivateKey.getEncoded(), outPrivateKey.getEncoded());
+
+        final PublicKey inPublicKey = kp.getPublic();
+        final PublicKey outPublicKey = CertUtils.pemToPublicKey(CertUtils.publicKeyToPem(inPublicKey));
+        Assert.assertEquals(inPublicKey.getAlgorithm(), outPublicKey.getAlgorithm());
+        Assert.assertEquals(inPublicKey.getFormat(), inPublicKey.getFormat());
+        Assert.assertArrayEquals(inPublicKey.getEncoded(), outPublicKey.getEncoded());
+    }
+
+    @Test
+    public void testGenerateRandomBigInt() throws Exception {
+        Assert.assertNotEquals(CertUtils.generateRandomBigInt(), CertUtils.generateRandomBigInt());
+    }
+
+    @Test
+    public void testGenerateCertificate() throws Exception {
+        final KeyPair clientKeyPair = CertUtils.generateRandomKeyPair(1024);
+        final List<String> domainNames = Arrays.asList("domain1.com", "www.2.domain2.com", "3.domain3.com");
+        final List<String> addressList = Arrays.asList("1.2.3.4", "192.168.1.1", "2a02:120b:2c16:f6d0:d9df:8ebc:e44a:f181");
+
+        final X509Certificate clientCert = CertUtils.generateV3Certificate(caCertificate, caKeyPair.getPrivate(), clientKeyPair.getPublic(),
+                "CN=domain.example", "SHA256WithRSAEncryption", 10, domainNames, addressList);
+
+        clientCert.verify(caKeyPair.getPublic());
+        Assert.assertEquals(clientCert.getIssuerDN(), caCertificate.getIssuerDN());
+        Assert.assertEquals(clientCert.getSigAlgName(), "SHA256WITHRSA");
+        Assert.assertArrayEquals(clientCert.getPublicKey().getEncoded(), clientKeyPair.getPublic().getEncoded());
+        Assert.assertNotNull(clientCert.getSubjectAlternativeNames());
+
+        for (final List<?> altNames : clientCert.getSubjectAlternativeNames()) {
+            Assert.assertTrue(altNames.size() == 2);
+            final Object first = altNames.get(0);
+            final Object second = altNames.get(1);
+            if (first instanceof Integer && ((Integer) first) == GeneralName.iPAddress) {
+                Assert.assertTrue(addressList.contains((String) second));
+            }
+            if (first instanceof Integer && ((Integer) first) == GeneralName.dNSName) {
+                Assert.assertTrue(domainNames.contains((String) second));
+            }
+        }
+    }
+
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
['"commits@cloudstack.apache.org" <commits@cloudstack.apache.org>'].

Mime
View raw message