mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
CLOUDSTACK-2039: Improve console access security with 128-bit AES encryption and securely-randomized key generation
This commit is contained in:
parent
8d33353b40
commit
34f8f795e1
@ -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),
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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<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 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<ConsoleProxyVO> profile) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -95,6 +95,9 @@ public interface ManagementServer extends ManagementService, PluggableService {
|
||||
Pair<List<StoragePoolVO>, Integer> searchForStoragePools(Criteria c);
|
||||
|
||||
String getHashKey();
|
||||
String getEncryptionKey();
|
||||
String getEncryptionIV();
|
||||
void resetEncryptionKeyIV();
|
||||
|
||||
public void enableAdminUser(String password);
|
||||
}
|
||||
|
||||
@ -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,15 +3004,66 @@ 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<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
|
||||
public SSHKeyPair createSSHKeyPair(CreateSSHKeyPairCmd cmd) {
|
||||
Account caller = UserContext.current().getCaller();
|
||||
|
||||
@ -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;
|
||||
|
||||
for(int i = 0; i < keyBytes.length; i++)
|
||||
key[i%keySize] ^= keyBytes[i];
|
||||
public KeyIVPair() {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<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) {
|
||||
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());
|
||||
|
||||
@ -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 <T> String encryptObject(Class<?> clz, T obj) {
|
||||
if(obj == null)
|
||||
return null;
|
||||
|
||||
String json = gson.toJson(obj);
|
||||
return encryptText(json);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> 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 <T> String encryptObject(Class<?> clz, T obj) {
|
||||
if(obj == null)
|
||||
return null;
|
||||
|
||||
String json = gson.toJson(obj);
|
||||
return encryptText(json);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user