CLOUDSTACK-2039: Improve console access security with 128-bit AES encryption and securely-randomized key generation

This commit is contained in:
Kelven Yang 2013-04-15 16:51:50 -07:00
parent 8d33353b40
commit 34f8f795e1
9 changed files with 358 additions and 157 deletions

View File

@ -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), 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"), //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), 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), 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), 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),

View File

@ -33,6 +33,7 @@ import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao; import com.cloud.host.dao.HostDao;
import com.cloud.info.ConsoleProxyInfo; import com.cloud.info.ConsoleProxyInfo;
import com.cloud.keystore.KeystoreManager; import com.cloud.keystore.KeystoreManager;
import com.cloud.server.ManagementServer;
import com.cloud.utils.NumbersUtil; import com.cloud.utils.NumbersUtil;
import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.ManagerBase;
import com.cloud.vm.ConsoleProxyVO; import com.cloud.vm.ConsoleProxyVO;
@ -69,12 +70,13 @@ public class AgentBasedConsoleProxyManager extends ManagerBase implements Consol
protected KeystoreManager _ksMgr; protected KeystoreManager _ksMgr;
@Inject ConfigurationDao _configDao; @Inject ConfigurationDao _configDao;
@Inject ManagementServer _ms;
public class AgentBasedAgentHook extends AgentHookBase { public class AgentBasedAgentHook extends AgentHookBase {
public AgentBasedAgentHook(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, public AgentBasedAgentHook(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao,
KeystoreManager ksMgr, AgentManager agentMgr) { KeystoreManager ksMgr, AgentManager agentMgr, ManagementServer ms) {
super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr); super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr, ms);
} }
@Override @Override
@ -120,7 +122,7 @@ public class AgentBasedConsoleProxyManager extends ManagerBase implements Consol
_consoleProxyUrlDomain = configs.get("consoleproxy.url.domain"); _consoleProxyUrlDomain = configs.get("consoleproxy.url.domain");
_listener = _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); _agentMgr.registerForHostEvents(_listener, true, true, false);
if (s_logger.isInfoEnabled()) { if (s_logger.isInfoEnabled()) {

View File

@ -42,10 +42,14 @@ import com.cloud.host.HostVO;
import com.cloud.host.Status; import com.cloud.host.Status;
import com.cloud.host.dao.HostDao; import com.cloud.host.dao.HostDao;
import com.cloud.keystore.KeystoreManager; import com.cloud.keystore.KeystoreManager;
import com.cloud.server.ManagementServer;
import com.cloud.servlet.ConsoleProxyPasswordBasedEncryptor;
import com.cloud.servlet.ConsoleProxyServlet; import com.cloud.servlet.ConsoleProxyServlet;
import com.cloud.utils.Ternary; import com.cloud.utils.Ternary;
import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.VMInstanceDao; 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 * Utility class to manage interactions with agent-based console access
@ -60,28 +64,19 @@ public abstract class AgentHookBase implements AgentHook {
ConfigurationDao _configDao; ConfigurationDao _configDao;
AgentManager _agentMgr; AgentManager _agentMgr;
KeystoreManager _ksMgr; KeystoreManager _ksMgr;
ManagementServer _ms;
final Random _random = new Random(System.currentTimeMillis()); final Random _random = new Random(System.currentTimeMillis());
private String _hashKey; private String _hashKey;
public AgentHookBase(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr, public AgentHookBase(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr,
AgentManager agentMgr) { AgentManager agentMgr, ManagementServer ms) {
this._instanceDao = instanceDao; this._instanceDao = instanceDao;
this._hostDao = hostDao; this._hostDao = hostDao;
this._agentMgr = agentMgr; this._agentMgr = agentMgr;
this._configDao = cfgDao; this._configDao = cfgDao;
this._ksMgr = ksMgr; this._ksMgr = ksMgr;
} this._ms = ms;
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;
} }
public AgentControlAnswer onConsoleAccessAuthentication(ConsoleAccessAuthenticationCommand cmd) { 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"); s_logger.error("Could not find and construct a valid SSL certificate");
} }
cmd = new StartConsoleProxyAgentHttpHandlerCommand(ksBits, storePassword); cmd = new StartConsoleProxyAgentHttpHandlerCommand(ksBits, storePassword);
cmd.setEncryptorPassword(getHashKey()); cmd.setEncryptorPassword(getEncryptorPassword());
} else { } else {
cmd = new StartConsoleProxyAgentHttpHandlerCommand(); cmd = new StartConsoleProxyAgentHttpHandlerCommand();
cmd.setEncryptorPassword(getHashKey()); cmd.setEncryptorPassword(getEncryptorPassword());
} }
try { try {
@ -247,6 +242,33 @@ public abstract class AgentHookBase implements AgentHook {
} }
} }
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); protected abstract HostVO findConsoleProxyHost(StartupProxyCommand cmd);
@Override @Override

View File

@ -95,8 +95,10 @@ import com.cloud.resource.ResourceManager;
import com.cloud.resource.ResourceStateAdapter; import com.cloud.resource.ResourceStateAdapter;
import com.cloud.resource.ServerResource; import com.cloud.resource.ServerResource;
import com.cloud.resource.UnableDeleteHostException; import com.cloud.resource.UnableDeleteHostException;
import com.cloud.server.ManagementServer;
import com.cloud.service.ServiceOfferingVO; import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.servlet.ConsoleProxyPasswordBasedEncryptor;
import com.cloud.storage.StorageManager; import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePoolStatus; import com.cloud.storage.StoragePoolStatus;
import com.cloud.storage.VMTemplateHostVO; import com.cloud.storage.VMTemplateHostVO;
@ -220,6 +222,8 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
TemplateManager templateMgr; TemplateManager templateMgr;
@Inject @Inject
IPAddressDao _ipAddressDao; IPAddressDao _ipAddressDao;
@Inject
ManagementServer _ms;
private ConsoleProxyListener _listener; private ConsoleProxyListener _listener;
@ -254,7 +258,6 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
private Map<Long, ConsoleProxyLoadInfo> _zoneProxyCountMap; // map <zone id, info about proxy VMs count in zone> private Map<Long, ConsoleProxyLoadInfo> _zoneProxyCountMap; // map <zone id, info about proxy VMs count in zone>
private Map<Long, ConsoleProxyLoadInfo> _zoneVmCountMap; // map <zone id, info about running VMs count in zone> private Map<Long, ConsoleProxyLoadInfo> _zoneVmCountMap; // map <zone id, info about running VMs count in zone>
private String _hashKey;
private String _staticPublicIp; private String _staticPublicIp;
private int _staticPort; private int _staticPort;
@ -448,8 +451,8 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
public class VmBasedAgentHook extends AgentHookBase { public class VmBasedAgentHook extends AgentHookBase {
public VmBasedAgentHook(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, public VmBasedAgentHook(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao,
KeystoreManager ksMgr, AgentManager agentMgr) { KeystoreManager ksMgr, AgentManager agentMgr, ManagementServer ms) {
super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr); super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr, ms);
} }
@Override @Override
@ -1439,7 +1442,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
_listener = _listener =
new ConsoleProxyListener(new VmBasedAgentHook(_instanceDao, _hostDao, _configDao, _ksMgr, new ConsoleProxyListener(new VmBasedAgentHook(_instanceDao, _hostDao, _configDao, _ksMgr,
_agentMgr)); _agentMgr, _ms));
_agentMgr.registerForHostEvents(_listener, true, true, false); _agentMgr.registerForHostEvents(_listener, true, true, false);
_itMgr.registerGuru(VirtualMachine.Type.ConsoleProxy, this); _itMgr.registerGuru(VirtualMachine.Type.ConsoleProxy, this);
@ -1884,15 +1887,6 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
return sc.find(); 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 @Override
public boolean plugNic(Network network, NicTO nic, VirtualMachineTO vm, public boolean plugNic(Network network, NicTO nic, VirtualMachineTO vm,
ReservationContext context, DeployDestination dest) throws ConcurrentOperationException, ResourceUnavailableException, ReservationContext context, DeployDestination dest) throws ConcurrentOperationException, ResourceUnavailableException,
@ -1912,4 +1906,5 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
@Override @Override
public void prepareStop(VirtualMachineProfile<ConsoleProxyVO> profile) { public void prepareStop(VirtualMachineProfile<ConsoleProxyVO> profile) {
} }
} }

View File

@ -95,6 +95,9 @@ public interface ManagementServer extends ManagementService, PluggableService {
Pair<List<StoragePoolVO>, Integer> searchForStoragePools(Criteria c); Pair<List<StoragePoolVO>, Integer> searchForStoragePools(Criteria c);
String getHashKey(); String getHashKey();
String getEncryptionKey();
String getEncryptionIV();
void resetEncryptionKeyIV();
public void enableAdminUser(String password); public void enableAdminUser(String password);
} }

View File

@ -22,6 +22,8 @@ import java.net.InetAddress;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Comparator; import java.util.Comparator;
@ -400,6 +402,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
@Inject ClusterManager _clusterMgr; @Inject ClusterManager _clusterMgr;
private String _hashKey = null; private String _hashKey = null;
private String _encryptionKey = null;
private String _encryptionIV = null;
@Inject @Inject
protected AffinityGroupVMMapDao _affinityGroupVMMapDao; protected AffinityGroupVMMapDao _affinityGroupVMMapDao;
@ -3000,15 +3004,66 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
@Override @Override
public String getHashKey() { public String getHashKey() {
// although we may have race conditioning here, database transaction // although we may have race conditioning here, database transaction serialization should
// serialization should
// give us the same key // give us the same key
if (_hashKey == null) { 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; 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<ConfigurationVO> sb = _configDao.createSearchBuilder();
sb.and("name1", sb.entity().getName(), SearchCriteria.Op.EQ);
sb.or("name2", sb.entity().getName(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<ConfigurationVO> 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 @Override
public SSHKeyPair createSSHKeyPair(CreateSSHKeyPairCmd cmd) { public SSHKeyPair createSSHKeyPair(CreateSSHKeyPairCmd cmd) {
Account caller = UserContext.current().getCaller(); Account caller = UserContext.current().getCaller();

View File

@ -16,13 +16,16 @@
// under the License. // under the License.
package com.cloud.servlet; package com.cloud.servlet;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
@ -35,26 +38,26 @@ import com.google.gson.GsonBuilder;
public class ConsoleProxyPasswordBasedEncryptor { public class ConsoleProxyPasswordBasedEncryptor {
private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class); private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class);
private String password;
private Gson gson; private Gson gson;
// key/IV will be set in 128 bit strength
private KeyIVPair keyIvPair;
public ConsoleProxyPasswordBasedEncryptor(String password) { public ConsoleProxyPasswordBasedEncryptor(String password) {
this.password = password;
gson = new GsonBuilder().create(); gson = new GsonBuilder().create();
keyIvPair = gson.fromJson(password, KeyIVPair.class);
} }
public String encryptText(String text) { public String encryptText(String text) {
if(text == null || text.isEmpty()) if(text == null || text.isEmpty())
return text; return text;
assert(password != null);
assert(!password.isEmpty());
try { try {
Cipher cipher = Cipher.getInstance("DES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
int maxKeySize = 8; SecretKeySpec keySpec = new SecretKeySpec(keyIvPair.getKeyBytes(), "AES");
SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec); cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(keyIvPair.getIvBytes()));
byte[] encryptedBytes = cipher.doFinal(text.getBytes()); byte[] encryptedBytes = cipher.doFinal(text.getBytes());
return Base64.encodeBase64URLSafeString(encryptedBytes); return Base64.encodeBase64URLSafeString(encryptedBytes);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
@ -72,6 +75,9 @@ public class ConsoleProxyPasswordBasedEncryptor {
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
s_logger.error("Unexpected exception ", e); s_logger.error("Unexpected exception ", e);
return null; 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()) if(encryptedText == null || encryptedText.isEmpty())
return encryptedText; return encryptedText;
assert(password != null);
assert(!password.isEmpty());
try { try {
Cipher cipher = Cipher.getInstance("DES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
int maxKeySize = 8; SecretKeySpec keySpec = new SecretKeySpec(keyIvPair.getKeyBytes(), "AES");
SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES"); cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(keyIvPair.getIvBytes()));
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] encryptedBytes = Base64.decodeBase64(encryptedText); byte[] encryptedBytes = Base64.decodeBase64(encryptedText);
return new String(cipher.doFinal(encryptedBytes)); return new String(cipher.doFinal(encryptedBytes));
@ -105,6 +107,9 @@ public class ConsoleProxyPasswordBasedEncryptor {
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
s_logger.error("Unexpected exception ", e); s_logger.error("Unexpected exception ", e);
return null; 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); return (T)gson.fromJson(json, clz);
} }
private static byte[] normalizeKey(byte[] keyBytes, int keySize) { public static class KeyIVPair {
assert(keySize > 0); String base64EncodedKeyBytes;
byte[] key = new byte[keySize]; String base64EncodedIvBytes;
for(int i = 0; i < keyBytes.length; i++) public KeyIVPair() {
key[i%keySize] ^= keyBytes[i]; }
return key; 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();
}
}
}

View File

@ -55,6 +55,8 @@ import com.cloud.utils.db.Transaction;
import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineManager; 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 * Thumbnail access : /console?cmd=thumbnail&vm=xxx&w=xxx&h=xxx
@ -75,6 +77,8 @@ public class ConsoleProxyServlet extends HttpServlet {
static ManagementServer s_ms; static ManagementServer s_ms;
private Gson _gson = new GsonBuilder().create();
public ConsoleProxyServlet() { public ConsoleProxyServlet() {
} }
@ -327,6 +331,14 @@ public class ConsoleProxyServlet extends HttpServlet {
return new Ternary<String, String, String>(host, tunnelUrl, tunnelSession); return new Ternary<String, String, String>(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) { private String composeThumbnailUrl(String rootUrl, VMInstanceVO vm, HostVO hostVo, int w, int h) {
StringBuffer sb = new StringBuffer(rootUrl); StringBuffer sb = new StringBuffer(rootUrl);
@ -340,7 +352,7 @@ public class ConsoleProxyServlet extends HttpServlet {
tag = _identityService.getIdentityUuid("vm_instance", tag); tag = _identityService.getIdentityUuid("vm_instance", tag);
String ticket = genAccessTicket(host, String.valueOf(portInfo.second()), sid, 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(); ConsoleProxyClientParam param = new ConsoleProxyClientParam();
param.setClientHostAddress(parsedHostInfo.first()); param.setClientHostAddress(parsedHostInfo.first());
param.setClientHostPort(portInfo.second()); param.setClientHostPort(portInfo.second());
@ -376,7 +388,7 @@ public class ConsoleProxyServlet extends HttpServlet {
String tag = String.valueOf(vm.getId()); String tag = String.valueOf(vm.getId());
tag = _identityService.getIdentityUuid("vm_instance", tag); tag = _identityService.getIdentityUuid("vm_instance", tag);
String ticket = genAccessTicket(host, String.valueOf(portInfo.second()), sid, 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(); ConsoleProxyClientParam param = new ConsoleProxyClientParam();
param.setClientHostAddress(parsedHostInfo.first()); param.setClientHostAddress(parsedHostInfo.first());
param.setClientHostPort(portInfo.second()); param.setClientHostPort(portInfo.second());

View File

@ -16,13 +16,16 @@
// under the License. // under the License.
package com.cloud.consoleproxy; package com.cloud.consoleproxy;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
@ -33,33 +36,34 @@ 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. * and deserialize it back.
* *
*/ */
public class ConsoleProxyPasswordBasedEncryptor { public class ConsoleProxyPasswordBasedEncryptor {
private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class); private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class);
private String password;
private Gson gson; private Gson gson;
// key/IV will be set in 128 bit strength
private KeyIVPair keyIvPair;
public ConsoleProxyPasswordBasedEncryptor(String password) { public ConsoleProxyPasswordBasedEncryptor(String password) {
this.password = password;
gson = new GsonBuilder().create(); gson = new GsonBuilder().create();
keyIvPair = gson.fromJson(password, KeyIVPair.class);
} }
public String encryptText(String text) { public String encryptText(String text) {
if(text == null || text.isEmpty()) if(text == null || text.isEmpty())
return text; return text;
assert(password != null);
assert(!password.isEmpty());
try { try {
Cipher cipher = Cipher.getInstance("DES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
int maxKeySize = 8; SecretKeySpec keySpec = new SecretKeySpec(keyIvPair.getKeyBytes(), "AES");
SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec); cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(keyIvPair.getIvBytes()));
byte[] encryptedBytes = cipher.doFinal(text.getBytes()); byte[] encryptedBytes = cipher.doFinal(text.getBytes());
return Base64.encodeBase64URLSafeString(encryptedBytes); return Base64.encodeBase64URLSafeString(encryptedBytes);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
@ -77,6 +81,9 @@ public class ConsoleProxyPasswordBasedEncryptor {
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
s_logger.error("Unexpected exception ", e); s_logger.error("Unexpected exception ", e);
return null; return null;
} catch (InvalidAlgorithmParameterException e) {
s_logger.error("Unexpected exception ", e);
return null;
} }
} }
@ -84,14 +91,10 @@ public class ConsoleProxyPasswordBasedEncryptor {
if(encryptedText == null || encryptedText.isEmpty()) if(encryptedText == null || encryptedText.isEmpty())
return encryptedText; return encryptedText;
assert(password != null);
assert(!password.isEmpty());
try { try {
Cipher cipher = Cipher.getInstance("DES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
int maxKeySize = 8; SecretKeySpec keySpec = new SecretKeySpec(keyIvPair.getKeyBytes(), "AES");
SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES"); cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(keyIvPair.getIvBytes()));
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] encryptedBytes = Base64.decodeBase64(encryptedText); byte[] encryptedBytes = Base64.decodeBase64(encryptedText);
return new String(cipher.doFinal(encryptedBytes)); return new String(cipher.doFinal(encryptedBytes));
@ -110,6 +113,9 @@ public class ConsoleProxyPasswordBasedEncryptor {
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
s_logger.error("Unexpected exception ", e); s_logger.error("Unexpected exception ", e);
return null; return null;
} catch (InvalidAlgorithmParameterException e) {
s_logger.error("Unexpected exception ", e);
return null;
} }
} }
@ -130,13 +136,62 @@ public class ConsoleProxyPasswordBasedEncryptor {
return (T)gson.fromJson(json, clz); return (T)gson.fromJson(json, clz);
} }
private static byte[] normalizeKey(byte[] keyBytes, int keySize) { public static class KeyIVPair {
assert(keySize > 0); String base64EncodedKeyBytes;
byte[] key = new byte[keySize]; String base64EncodedIvBytes;
for(int i = 0; i < keyBytes.length; i++) public KeyIVPair() {
key[i%keySize] ^= keyBytes[i]; }
return key; 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();
}
} }
} }