Return-Path: X-Original-To: apmail-cloudstack-commits-archive@www.apache.org Delivered-To: apmail-cloudstack-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 8F541F7BD for ; Tue, 16 Apr 2013 18:34:19 +0000 (UTC) Received: (qmail 4839 invoked by uid 500); 16 Apr 2013 18:34:09 -0000 Delivered-To: apmail-cloudstack-commits-archive@cloudstack.apache.org Received: (qmail 4578 invoked by uid 500); 16 Apr 2013 18:34:09 -0000 Mailing-List: contact commits-help@cloudstack.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cloudstack.apache.org Delivered-To: mailing list commits@cloudstack.apache.org Received: (qmail 3131 invoked by uid 99); 16 Apr 2013 18:34:07 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 16 Apr 2013 18:34:07 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 3CB9F1FAC6; Tue, 16 Apr 2013 18:34:07 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: alena1108@apache.org To: commits@cloudstack.apache.org Date: Tue, 16 Apr 2013 18:34:38 -0000 Message-Id: <2831e11a8fe04ef5b7ceb597069318eb@git.apache.org> In-Reply-To: <34ec31dfbac6410bb190d7586f5a30a1@git.apache.org> References: <34ec31dfbac6410bb190d7586f5a30a1@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [33/46] git commit: updated refs/heads/internallb to b7709b8 CLOUDSTACK-2039: Improve console access security with 128-bit AES encryption and securely-randomized key generation Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/34f8f795 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/34f8f795 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/34f8f795 Branch: refs/heads/internallb Commit: 34f8f795e15b89477f72aa6f1e69f7797c930db4 Parents: 8d33353 Author: Kelven Yang Authored: Mon Apr 15 16:51:50 2013 -0700 Committer: Kelven Yang Committed: Mon Apr 15 16:52:22 2013 -0700 ---------------------------------------------------------------------- server/src/com/cloud/configuration/Config.java | 2 + .../AgentBasedConsoleProxyManager.java | 8 +- .../src/com/cloud/consoleproxy/AgentHookBase.java | 50 ++- .../consoleproxy/ConsoleProxyManagerImpl.java | 21 +- server/src/com/cloud/server/ManagementServer.java | 3 + .../src/com/cloud/server/ManagementServerImpl.java | 63 ++++- .../ConsoleProxyPasswordBasedEncryptor.java | 99 +++++-- .../src/com/cloud/servlet/ConsoleProxyServlet.java | 16 +- .../ConsoleProxyPasswordBasedEncryptor.java | 253 +++++++++------ 9 files changed, 358 insertions(+), 157 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/34f8f795/server/src/com/cloud/configuration/Config.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index e3e3053..e69ea19 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -305,6 +305,8 @@ public enum Config { SSOAuthTolerance("Advanced", ManagementServer.class, Long.class, "security.singlesignon.tolerance.millis", "300000", "The allowable clock difference in milliseconds between when an SSO login request is made and when it is received.", null), //NetworkType("Hidden", ManagementServer.class, String.class, "network.type", "vlan", "The type of network that this deployment will use.", "vlan,direct"), HashKey("Hidden", ManagementServer.class, String.class, "security.hash.key", null, "for generic key-ed hash", null), + EncryptionKey("Hidden", ManagementServer.class, String.class, "security.encryption.key", null, "base64 encoded key data", null), + EncryptionIV("Hidden", ManagementServer.class, String.class, "security.encryption.iv", null, "base64 encoded IV data", null), RouterRamSize("Hidden", NetworkManager.class, Integer.class, "router.ram.size", "128", "Default RAM for router VM (in MB).", null), VmOpWaitInterval("Advanced", ManagementServer.class, Integer.class, "vm.op.wait.interval", "120", "Time (in seconds) to wait before checking if a previous operation has succeeded", null), http://git-wip-us.apache.org/repos/asf/cloudstack/blob/34f8f795/server/src/com/cloud/consoleproxy/AgentBasedConsoleProxyManager.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/consoleproxy/AgentBasedConsoleProxyManager.java b/server/src/com/cloud/consoleproxy/AgentBasedConsoleProxyManager.java index ff6e64e..df53e0d 100755 --- a/server/src/com/cloud/consoleproxy/AgentBasedConsoleProxyManager.java +++ b/server/src/com/cloud/consoleproxy/AgentBasedConsoleProxyManager.java @@ -33,6 +33,7 @@ import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.info.ConsoleProxyInfo; import com.cloud.keystore.KeystoreManager; +import com.cloud.server.ManagementServer; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ManagerBase; import com.cloud.vm.ConsoleProxyVO; @@ -69,12 +70,13 @@ public class AgentBasedConsoleProxyManager extends ManagerBase implements Consol protected KeystoreManager _ksMgr; @Inject ConfigurationDao _configDao; + @Inject ManagementServer _ms; public class AgentBasedAgentHook extends AgentHookBase { public AgentBasedAgentHook(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, - KeystoreManager ksMgr, AgentManager agentMgr) { - super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr); + KeystoreManager ksMgr, AgentManager agentMgr, ManagementServer ms) { + super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr, ms); } @Override @@ -120,7 +122,7 @@ public class AgentBasedConsoleProxyManager extends ManagerBase implements Consol _consoleProxyUrlDomain = configs.get("consoleproxy.url.domain"); _listener = - new ConsoleProxyListener(new AgentBasedAgentHook(_instanceDao, _hostDao, _configDao, _ksMgr, _agentMgr)); + new ConsoleProxyListener(new AgentBasedAgentHook(_instanceDao, _hostDao, _configDao, _ksMgr, _agentMgr, _ms)); _agentMgr.registerForHostEvents(_listener, true, true, false); if (s_logger.isInfoEnabled()) { http://git-wip-us.apache.org/repos/asf/cloudstack/blob/34f8f795/server/src/com/cloud/consoleproxy/AgentHookBase.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/consoleproxy/AgentHookBase.java b/server/src/com/cloud/consoleproxy/AgentHookBase.java index b969f6d..2748a8c 100644 --- a/server/src/com/cloud/consoleproxy/AgentHookBase.java +++ b/server/src/com/cloud/consoleproxy/AgentHookBase.java @@ -42,10 +42,14 @@ import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; import com.cloud.keystore.KeystoreManager; +import com.cloud.server.ManagementServer; +import com.cloud.servlet.ConsoleProxyPasswordBasedEncryptor; import com.cloud.servlet.ConsoleProxyServlet; import com.cloud.utils.Ternary; import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.VMInstanceDao; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; /** * Utility class to manage interactions with agent-based console access @@ -60,28 +64,19 @@ public abstract class AgentHookBase implements AgentHook { ConfigurationDao _configDao; AgentManager _agentMgr; KeystoreManager _ksMgr; + ManagementServer _ms; final Random _random = new Random(System.currentTimeMillis()); private String _hashKey; public AgentHookBase(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr, - AgentManager agentMgr) { + AgentManager agentMgr, ManagementServer ms) { this._instanceDao = instanceDao; this._hostDao = hostDao; this._agentMgr = agentMgr; this._configDao = cfgDao; this._ksMgr = ksMgr; - } - - public String getHashKey() { - // although we may have race condition here, database transaction - // serialization should give us the same key - if (_hashKey == null) { - _hashKey = - _configDao.getValueAndInitIfNotExist(Config.HashKey.key(), Config.HashKey.getCategory(), UUID - .randomUUID().toString()); - } - return _hashKey; + this._ms = ms; } public AgentControlAnswer onConsoleAccessAuthentication(ConsoleAccessAuthenticationCommand cmd) { @@ -212,10 +207,10 @@ public abstract class AgentHookBase implements AgentHook { s_logger.error("Could not find and construct a valid SSL certificate"); } cmd = new StartConsoleProxyAgentHttpHandlerCommand(ksBits, storePassword); - cmd.setEncryptorPassword(getHashKey()); + cmd.setEncryptorPassword(getEncryptorPassword()); } else { cmd = new StartConsoleProxyAgentHttpHandlerCommand(); - cmd.setEncryptorPassword(getHashKey()); + cmd.setEncryptorPassword(getEncryptorPassword()); } try { @@ -246,6 +241,33 @@ public abstract class AgentHookBase implements AgentHook { + startupCmd.getProxyVmId(), e); } } + + private String getEncryptorPassword() { + String key; + String iv; + ConsoleProxyPasswordBasedEncryptor.KeyIVPair keyIvPair = null; + + // if we failed after reset, something is definitely wrong + for(int i = 0; i < 2; i++) { + key = _ms.getEncryptionKey(); + iv = _ms.getEncryptionIV(); + + keyIvPair = new ConsoleProxyPasswordBasedEncryptor.KeyIVPair(key, iv); + + if(keyIvPair.getIvBytes() == null || keyIvPair.getIvBytes().length != 16 || + keyIvPair.getKeyBytes() == null || keyIvPair.getKeyBytes().length != 16) { + + s_logger.warn("Console access AES KeyIV sanity check failed, reset and regenerate"); + _ms.resetEncryptionKeyIV(); + } else { + break; + } + } + + Gson gson = new GsonBuilder().create(); + return gson.toJson(keyIvPair); + } + protected abstract HostVO findConsoleProxyHost(StartupProxyCommand cmd); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/34f8f795/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java b/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java index 9740d28..c4ad817 100755 --- a/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java +++ b/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java @@ -95,8 +95,10 @@ import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceStateAdapter; import com.cloud.resource.ServerResource; import com.cloud.resource.UnableDeleteHostException; +import com.cloud.server.ManagementServer; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.servlet.ConsoleProxyPasswordBasedEncryptor; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePoolStatus; import com.cloud.storage.VMTemplateHostVO; @@ -220,6 +222,8 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy TemplateManager templateMgr; @Inject IPAddressDao _ipAddressDao; + @Inject + ManagementServer _ms; private ConsoleProxyListener _listener; @@ -254,7 +258,6 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy private Map _zoneProxyCountMap; // map private Map _zoneVmCountMap; // map - private String _hashKey; private String _staticPublicIp; private int _staticPort; @@ -448,8 +451,8 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy public class VmBasedAgentHook extends AgentHookBase { public VmBasedAgentHook(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, - KeystoreManager ksMgr, AgentManager agentMgr) { - super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr); + KeystoreManager ksMgr, AgentManager agentMgr, ManagementServer ms) { + super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr, ms); } @Override @@ -1439,7 +1442,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy _listener = new ConsoleProxyListener(new VmBasedAgentHook(_instanceDao, _hostDao, _configDao, _ksMgr, - _agentMgr)); + _agentMgr, _ms)); _agentMgr.registerForHostEvents(_listener, true, true, false); _itMgr.registerGuru(VirtualMachine.Type.ConsoleProxy, this); @@ -1884,15 +1887,6 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy return sc.find(); } - public String getHashKey() { - // although we may have race conditioning here, database transaction serialization should - // give us the same key - if (_hashKey == null) { - _hashKey = _configDao.getValueAndInitIfNotExist(Config.HashKey.key(), Config.HashKey.getCategory(), UUID.randomUUID().toString()); - } - return _hashKey; - } - @Override public boolean plugNic(Network network, NicTO nic, VirtualMachineTO vm, ReservationContext context, DeployDestination dest) throws ConcurrentOperationException, ResourceUnavailableException, @@ -1912,4 +1906,5 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy @Override public void prepareStop(VirtualMachineProfile profile) { } + } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/34f8f795/server/src/com/cloud/server/ManagementServer.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/server/ManagementServer.java b/server/src/com/cloud/server/ManagementServer.java index 6773725..240464e 100755 --- a/server/src/com/cloud/server/ManagementServer.java +++ b/server/src/com/cloud/server/ManagementServer.java @@ -95,6 +95,9 @@ public interface ManagementServer extends ManagementService, PluggableService { Pair, Integer> searchForStoragePools(Criteria c); String getHashKey(); + String getEncryptionKey(); + String getEncryptionIV(); + void resetEncryptionKeyIV(); public void enableAdminUser(String password); } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/34f8f795/server/src/com/cloud/server/ManagementServerImpl.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 50b21ab..5ed0c92 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -22,6 +22,8 @@ import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.Calendar; import java.util.Comparator; @@ -400,7 +402,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe @Inject ClusterManager _clusterMgr; private String _hashKey = null; - + private String _encryptionKey = null; + private String _encryptionIV = null; + @Inject protected AffinityGroupVMMapDao _affinityGroupVMMapDao; @@ -3000,16 +3004,67 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe @Override public String getHashKey() { - // although we may have race conditioning here, database transaction - // serialization should + // although we may have race conditioning here, database transaction serialization should // give us the same key if (_hashKey == null) { - _hashKey = _configDao.getValueAndInitIfNotExist(Config.HashKey.key(), Config.HashKey.getCategory(), UUID.randomUUID().toString()); + _hashKey = _configDao.getValueAndInitIfNotExist(Config.HashKey.key(), Config.HashKey.getCategory(), + getBase64EncodedRandomKey(128)); } return _hashKey; } @Override + public String getEncryptionKey() { + if (_encryptionKey == null) { + _encryptionKey = _configDao.getValueAndInitIfNotExist(Config.EncryptionKey.key(), + Config.EncryptionKey.getCategory(), + getBase64EncodedRandomKey(128)); + } + return _encryptionKey; + } + + @Override + public String getEncryptionIV() { + if (_encryptionIV == null) { + _encryptionIV = _configDao.getValueAndInitIfNotExist(Config.EncryptionIV.key(), + Config.EncryptionIV.getCategory(), + getBase64EncodedRandomKey(128)); + } + return _encryptionIV; + } + + @Override + @DB + public void resetEncryptionKeyIV() { + + SearchBuilder sb = _configDao.createSearchBuilder(); + sb.and("name1", sb.entity().getName(), SearchCriteria.Op.EQ); + sb.or("name2", sb.entity().getName(), SearchCriteria.Op.EQ); + sb.done(); + + SearchCriteria sc = sb.create(); + sc.setParameters("name1", Config.EncryptionKey.key()); + sc.setParameters("name2", Config.EncryptionIV.key()); + + _configDao.expunge(sc); + _encryptionKey = null; + _encryptionIV = null; + } + + private static String getBase64EncodedRandomKey(int nBits) { + SecureRandom random; + try { + random = SecureRandom.getInstance("SHA1PRNG"); + byte[] keyBytes = new byte[nBits/8]; + random.nextBytes(keyBytes); + return Base64.encodeBase64URLSafeString(keyBytes); + } catch (NoSuchAlgorithmException e) { + s_logger.error("Unhandled exception: ", e); + } + return null; + } + + @Override public SSHKeyPair createSSHKeyPair(CreateSSHKeyPairCmd cmd) { Account caller = UserContext.current().getCaller(); String accountName = cmd.getAccountName(); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/34f8f795/server/src/com/cloud/servlet/ConsoleProxyPasswordBasedEncryptor.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/servlet/ConsoleProxyPasswordBasedEncryptor.java b/server/src/com/cloud/servlet/ConsoleProxyPasswordBasedEncryptor.java index 2638c8b..7463ec0 100644 --- a/server/src/com/cloud/servlet/ConsoleProxyPasswordBasedEncryptor.java +++ b/server/src/com/cloud/servlet/ConsoleProxyPasswordBasedEncryptor.java @@ -16,13 +16,16 @@ // under the License. package com.cloud.servlet; +import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; @@ -35,26 +38,26 @@ import com.google.gson.GsonBuilder; public class ConsoleProxyPasswordBasedEncryptor { private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class); - private String password; private Gson gson; + // key/IV will be set in 128 bit strength + private KeyIVPair keyIvPair; + public ConsoleProxyPasswordBasedEncryptor(String password) { - this.password = password; gson = new GsonBuilder().create(); + keyIvPair = gson.fromJson(password, KeyIVPair.class); } public String encryptText(String text) { if(text == null || text.isEmpty()) return text; - assert(password != null); - assert(!password.isEmpty()); - try { - Cipher cipher = Cipher.getInstance("DES"); - int maxKeySize = 8; - SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES"); - cipher.init(Cipher.ENCRYPT_MODE, keySpec); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKeySpec keySpec = new SecretKeySpec(keyIvPair.getKeyBytes(), "AES"); + + cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(keyIvPair.getIvBytes())); + byte[] encryptedBytes = cipher.doFinal(text.getBytes()); return Base64.encodeBase64URLSafeString(encryptedBytes); } catch (NoSuchAlgorithmException e) { @@ -72,6 +75,9 @@ public class ConsoleProxyPasswordBasedEncryptor { } catch (InvalidKeyException e) { s_logger.error("Unexpected exception ", e); return null; + } catch (InvalidAlgorithmParameterException e) { + s_logger.error("Unexpected exception ", e); + return null; } } @@ -79,14 +85,10 @@ public class ConsoleProxyPasswordBasedEncryptor { if(encryptedText == null || encryptedText.isEmpty()) return encryptedText; - assert(password != null); - assert(!password.isEmpty()); - try { - Cipher cipher = Cipher.getInstance("DES"); - int maxKeySize = 8; - SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES"); - cipher.init(Cipher.DECRYPT_MODE, keySpec); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKeySpec keySpec = new SecretKeySpec(keyIvPair.getKeyBytes(), "AES"); + cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(keyIvPair.getIvBytes())); byte[] encryptedBytes = Base64.decodeBase64(encryptedText); return new String(cipher.doFinal(encryptedBytes)); @@ -105,6 +107,9 @@ public class ConsoleProxyPasswordBasedEncryptor { } catch (InvalidKeyException e) { s_logger.error("Unexpected exception ", e); return null; + } catch (InvalidAlgorithmParameterException e) { + s_logger.error("Unexpected exception ", e); + return null; } } @@ -125,13 +130,63 @@ public class ConsoleProxyPasswordBasedEncryptor { return (T)gson.fromJson(json, clz); } - private static byte[] normalizeKey(byte[] keyBytes, int keySize) { - assert(keySize > 0); - byte[] key = new byte[keySize]; + public static class KeyIVPair { + String base64EncodedKeyBytes; + String base64EncodedIvBytes; + + public KeyIVPair() { + } - for(int i = 0; i < keyBytes.length; i++) - key[i%keySize] ^= keyBytes[i]; + public KeyIVPair(String base64EncodedKeyBytes, String base64EncodedIvBytes) { + this.base64EncodedKeyBytes = base64EncodedKeyBytes; + this.base64EncodedIvBytes = base64EncodedIvBytes; + } + + public byte[] getKeyBytes() { + return Base64.decodeBase64(base64EncodedKeyBytes); + } - return key; + public void setKeyBytes(byte[] keyBytes) { + base64EncodedKeyBytes = Base64.encodeBase64URLSafeString(keyBytes); + } + + public byte[] getIvBytes() { + return Base64.decodeBase64(base64EncodedIvBytes); + } + + public void setIvBytes(byte[] ivBytes) { + base64EncodedIvBytes = Base64.encodeBase64URLSafeString(ivBytes); + } } + + public static void main(String[] args) { + SecureRandom random; + try { + random = SecureRandom.getInstance("SHA1PRNG"); + byte[] keyBytes = new byte[16]; + random.nextBytes(keyBytes); + + byte[] ivBytes = new byte[16]; + random.nextBytes(ivBytes); + + KeyIVPair keyIvPair = new KeyIVPair("8x/xUBgX0Up+3UEo39dSeG277JhVj31+ElHkN5+EC0Q=", "Y2SUiIN6JXTdKNK/ZMDyVtLB7gAM9MCCiyrP1xd3bSQ="); + //keyIvPair.setKeyBytes(keyBytes); + //keyIvPair.setIvBytes(ivBytes); + + Gson gson = new GsonBuilder().create(); + ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(gson.toJson(keyIvPair)); + + String encrypted = encryptor.encryptText("Hello, world"); + + System.out.println("Encrypted result: " + encrypted); + + String decrypted = encryptor.decryptText(encrypted); + + System.out.println("Decrypted result: " + decrypted); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } } + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/34f8f795/server/src/com/cloud/servlet/ConsoleProxyServlet.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/servlet/ConsoleProxyServlet.java b/server/src/com/cloud/servlet/ConsoleProxyServlet.java index c4b9334..ebb9174 100644 --- a/server/src/com/cloud/servlet/ConsoleProxyServlet.java +++ b/server/src/com/cloud/servlet/ConsoleProxyServlet.java @@ -55,6 +55,8 @@ import com.cloud.utils.db.Transaction; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineManager; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; /** * Thumbnail access : /console?cmd=thumbnail&vm=xxx&w=xxx&h=xxx @@ -75,6 +77,8 @@ public class ConsoleProxyServlet extends HttpServlet { static ManagementServer s_ms; + private Gson _gson = new GsonBuilder().create(); + public ConsoleProxyServlet() { } @@ -327,6 +331,14 @@ public class ConsoleProxyServlet extends HttpServlet { return new Ternary(host, tunnelUrl, tunnelSession); } + private String getEncryptorPassword() { + String key = _ms.getEncryptionKey(); + String iv = _ms.getEncryptionIV(); + + ConsoleProxyPasswordBasedEncryptor.KeyIVPair keyIvPair = new ConsoleProxyPasswordBasedEncryptor.KeyIVPair(key, iv); + return _gson.toJson(keyIvPair); + } + private String composeThumbnailUrl(String rootUrl, VMInstanceVO vm, HostVO hostVo, int w, int h) { StringBuffer sb = new StringBuffer(rootUrl); @@ -340,7 +352,7 @@ public class ConsoleProxyServlet extends HttpServlet { tag = _identityService.getIdentityUuid("vm_instance", tag); String ticket = genAccessTicket(host, String.valueOf(portInfo.second()), sid, tag); - ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(_ms.getHashKey()); + ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(getEncryptorPassword()); ConsoleProxyClientParam param = new ConsoleProxyClientParam(); param.setClientHostAddress(parsedHostInfo.first()); param.setClientHostPort(portInfo.second()); @@ -376,7 +388,7 @@ public class ConsoleProxyServlet extends HttpServlet { String tag = String.valueOf(vm.getId()); tag = _identityService.getIdentityUuid("vm_instance", tag); String ticket = genAccessTicket(host, String.valueOf(portInfo.second()), sid, tag); - ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(_ms.getHashKey()); + ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(getEncryptorPassword()); ConsoleProxyClientParam param = new ConsoleProxyClientParam(); param.setClientHostAddress(parsedHostInfo.first()); param.setClientHostPort(portInfo.second()); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/34f8f795/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java ---------------------------------------------------------------------- diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java index 29826f0..5a7241a 100644 --- a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java @@ -16,13 +16,16 @@ // under the License. package com.cloud.consoleproxy; +import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; @@ -33,110 +36,162 @@ import com.google.gson.GsonBuilder; /** * - * A simple password based encyrptor based on DES. It can serialize simple POJO object into URL safe string + * @author Kelven Yang + * A simple password based encyrptor based on AES/CBC. It can serialize simple POJO object into URL safe string * and deserialize it back. * */ public class ConsoleProxyPasswordBasedEncryptor { - private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class); - - private String password; - private Gson gson; - - public ConsoleProxyPasswordBasedEncryptor(String password) { - this.password = password; - gson = new GsonBuilder().create(); - } - - public String encryptText(String text) { - if(text == null || text.isEmpty()) - return text; - - assert(password != null); - assert(!password.isEmpty()); - - try { - Cipher cipher = Cipher.getInstance("DES"); - int maxKeySize = 8; - SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES"); - cipher.init(Cipher.ENCRYPT_MODE, keySpec); - byte[] encryptedBytes = cipher.doFinal(text.getBytes()); - return Base64.encodeBase64URLSafeString(encryptedBytes); - } catch (NoSuchAlgorithmException e) { - s_logger.error("Unexpected exception ", e); - return null; - } catch (NoSuchPaddingException e) { - s_logger.error("Unexpected exception ", e); - return null; - } catch (IllegalBlockSizeException e) { - s_logger.error("Unexpected exception ", e); - return null; - } catch (BadPaddingException e) { - s_logger.error("Unexpected exception ", e); - return null; - } catch (InvalidKeyException e) { - s_logger.error("Unexpected exception ", e); - return null; - } - } + private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class); + + private Gson gson; + + // key/IV will be set in 128 bit strength + private KeyIVPair keyIvPair; + + public ConsoleProxyPasswordBasedEncryptor(String password) { + gson = new GsonBuilder().create(); + keyIvPair = gson.fromJson(password, KeyIVPair.class); + } + + public String encryptText(String text) { + if(text == null || text.isEmpty()) + return text; + + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKeySpec keySpec = new SecretKeySpec(keyIvPair.getKeyBytes(), "AES"); - public String decryptText(String encryptedText) { - if(encryptedText == null || encryptedText.isEmpty()) - return encryptedText; + cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(keyIvPair.getIvBytes())); + + byte[] encryptedBytes = cipher.doFinal(text.getBytes()); + return Base64.encodeBase64URLSafeString(encryptedBytes); + } catch (NoSuchAlgorithmException e) { + s_logger.error("Unexpected exception ", e); + return null; + } catch (NoSuchPaddingException e) { + s_logger.error("Unexpected exception ", e); + return null; + } catch (IllegalBlockSizeException e) { + s_logger.error("Unexpected exception ", e); + return null; + } catch (BadPaddingException e) { + s_logger.error("Unexpected exception ", e); + return null; + } catch (InvalidKeyException e) { + s_logger.error("Unexpected exception ", e); + return null; + } catch (InvalidAlgorithmParameterException e) { + s_logger.error("Unexpected exception ", e); + return null; + } + } - assert(password != null); - assert(!password.isEmpty()); + public String decryptText(String encryptedText) { + if(encryptedText == null || encryptedText.isEmpty()) + return encryptedText; - try { - Cipher cipher = Cipher.getInstance("DES"); - int maxKeySize = 8; - SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES"); - cipher.init(Cipher.DECRYPT_MODE, keySpec); - - byte[] encryptedBytes = Base64.decodeBase64(encryptedText); - return new String(cipher.doFinal(encryptedBytes)); - } catch (NoSuchAlgorithmException e) { - s_logger.error("Unexpected exception ", e); - return null; - } catch (NoSuchPaddingException e) { - s_logger.error("Unexpected exception ", e); - return null; - } catch (IllegalBlockSizeException e) { - s_logger.error("Unexpected exception ", e); - return null; - } catch (BadPaddingException e) { - s_logger.error("Unexpected exception ", e); - return null; - } catch (InvalidKeyException e) { - s_logger.error("Unexpected exception ", e); - return null; - } - } - - public String encryptObject(Class clz, T obj) { - if(obj == null) - return null; - - String json = gson.toJson(obj); - return encryptText(json); - } - - @SuppressWarnings("unchecked") - public T decryptObject(Class clz, String encrypted) { - if(encrypted == null || encrypted.isEmpty()) - return null; - - String json = decryptText(encrypted); - return (T)gson.fromJson(json, clz); - } - - private static byte[] normalizeKey(byte[] keyBytes, int keySize) { - assert(keySize > 0); - byte[] key = new byte[keySize]; - - for(int i = 0; i < keyBytes.length; i++) - key[i%keySize] ^= keyBytes[i]; - - return key; - } + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKeySpec keySpec = new SecretKeySpec(keyIvPair.getKeyBytes(), "AES"); + cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(keyIvPair.getIvBytes())); + + byte[] encryptedBytes = Base64.decodeBase64(encryptedText); + return new String(cipher.doFinal(encryptedBytes)); + } catch (NoSuchAlgorithmException e) { + s_logger.error("Unexpected exception ", e); + return null; + } catch (NoSuchPaddingException e) { + s_logger.error("Unexpected exception ", e); + return null; + } catch (IllegalBlockSizeException e) { + s_logger.error("Unexpected exception ", e); + return null; + } catch (BadPaddingException e) { + s_logger.error("Unexpected exception ", e); + return null; + } catch (InvalidKeyException e) { + s_logger.error("Unexpected exception ", e); + return null; + } catch (InvalidAlgorithmParameterException e) { + s_logger.error("Unexpected exception ", e); + return null; + } + } + + public String encryptObject(Class clz, T obj) { + if(obj == null) + return null; + + String json = gson.toJson(obj); + return encryptText(json); + } + + @SuppressWarnings("unchecked") + public T decryptObject(Class clz, String encrypted) { + if(encrypted == null || encrypted.isEmpty()) + return null; + + String json = decryptText(encrypted); + return (T)gson.fromJson(json, clz); + } + + public static class KeyIVPair { + String base64EncodedKeyBytes; + String base64EncodedIvBytes; + + public KeyIVPair() { + } + + public KeyIVPair(String base64EncodedKeyBytes, String base64EncodedIvBytes) { + this.base64EncodedKeyBytes = base64EncodedKeyBytes; + this.base64EncodedIvBytes = base64EncodedIvBytes; + } + + public byte[] getKeyBytes() { + return Base64.decodeBase64(base64EncodedKeyBytes); + } + + public void setKeyBytes(byte[] keyBytes) { + base64EncodedKeyBytes = Base64.encodeBase64URLSafeString(keyBytes); + } + + public byte[] getIvBytes() { + return Base64.decodeBase64(base64EncodedIvBytes); + } + + public void setIvBytes(byte[] ivBytes) { + base64EncodedIvBytes = Base64.encodeBase64URLSafeString(ivBytes); + } + } + + public static void main(String[] args) { + SecureRandom random; + try { + random = SecureRandom.getInstance("SHA1PRNG"); + byte[] keyBytes = new byte[16]; + random.nextBytes(keyBytes); + + byte[] ivBytes = new byte[16]; + random.nextBytes(ivBytes); + + KeyIVPair keyIvPair = new KeyIVPair("8x/xUBgX0Up+3UEo39dSeG277JhVj31+ElHkN5+EC0Q=", "Y2SUiIN6JXTdKNK/ZMDyVtLB7gAM9MCCiyrP1xd3bSQ="); + //keyIvPair.setKeyBytes(keyBytes); + //keyIvPair.setIvBytes(ivBytes); + + Gson gson = new GsonBuilder().create(); + ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(gson.toJson(keyIvPair)); + + String encrypted = encryptor.encryptText("Hello, world"); + + System.out.println("Encrypted result: " + encrypted); + + String decrypted = encryptor.decryptText(encrypted); + + System.out.println("Decrypted result: " + decrypted); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } }