Hardening console proxy AJAX protocol to address security concerns

This commit is contained in:
Kelven Yang 2012-04-19 12:10:33 -07:00
parent e5571480d4
commit c5083787c2
13 changed files with 573 additions and 111 deletions

View File

@ -106,7 +106,7 @@ public class ConsoleProxyResource extends ServerResourceBase implements
}
private Answer execute(StartConsoleProxyAgentHttpHandlerCommand cmd) {
launchConsoleProxy(cmd.getKeystoreBits(), cmd.getKeystorePassword());
launchConsoleProxy(cmd.getKeystoreBits(), cmd.getKeystorePassword(), cmd.getEncryptorPassword());
return new Answer(cmd);
}
@ -347,7 +347,7 @@ public class ConsoleProxyResource extends ServerResourceBase implements
return _name;
}
private void launchConsoleProxy(final byte[] ksBits, final String ksPassword) {
private void launchConsoleProxy(final byte[] ksBits, final String ksPassword, final String encryptorPassword) {
final Object resource = this;
if (_consoleProxyMain == null) {
_consoleProxyMain = new Thread(new Runnable() {
@ -355,6 +355,10 @@ public class ConsoleProxyResource extends ServerResourceBase implements
try {
Class<?> consoleProxyClazz = Class.forName("com.cloud.consoleproxy.ConsoleProxy");
try {
Method methodSetup = consoleProxyClazz.getMethod(
"setEncryptorPassword", String.class);
methodSetup.invoke(null, encryptorPassword);
Method method = consoleProxyClazz.getMethod(
"startWithContext", Properties.class,
Object.class, byte[].class, String.class);
@ -385,7 +389,31 @@ public class ConsoleProxyResource extends ServerResourceBase implements
_consoleProxyMain.setDaemon(true);
_consoleProxyMain.start();
} else {
s_logger.error("com.cloud.consoleproxy.ConsoleProxy is already running");
s_logger.info("com.cloud.consoleproxy.ConsoleProxy is already running");
try {
Class<?> consoleProxyClazz = Class.forName("com.cloud.consoleproxy.ConsoleProxy");
Method methodSetup = consoleProxyClazz.getMethod("setEncryptorPassword", String.class);
methodSetup.invoke(null, encryptorPassword);
} catch (SecurityException e) {
s_logger.error("Unable to launch console proxy due to SecurityException");
System.exit(ExitStatus.Error.value());
} catch (NoSuchMethodException e) {
s_logger.error("Unable to launch console proxy due to NoSuchMethodException");
System.exit(ExitStatus.Error.value());
} catch (IllegalArgumentException e) {
s_logger.error("Unable to launch console proxy due to IllegalArgumentException");
System.exit(ExitStatus.Error.value());
} catch (IllegalAccessException e) {
s_logger.error("Unable to launch console proxy due to IllegalAccessException");
System.exit(ExitStatus.Error.value());
} catch (InvocationTargetException e) {
s_logger.error("Unable to launch console proxy due to InvocationTargetException");
System.exit(ExitStatus.Error.value());
} catch (final ClassNotFoundException e) {
s_logger.error("Unable to launch console proxy due to ClassNotFoundException");
System.exit(ExitStatus.Error.value());
}
}
}

View File

@ -16,12 +16,13 @@ import com.cloud.agent.api.Command;
import com.cloud.agent.api.LogLevel.Log4jLevel;
import com.cloud.agent.api.LogLevel;
public class StartConsoleProxyAgentHttpHandlerCommand extends Command {
@LogLevel(Log4jLevel.Off)
private byte[] keystoreBits;
@LogLevel(Log4jLevel.Off)
private String keystorePassword;
@LogLevel(Log4jLevel.Off)
private String encryptorPassword;
public StartConsoleProxyAgentHttpHandlerCommand() {
super();
@ -52,4 +53,12 @@ public class StartConsoleProxyAgentHttpHandlerCommand extends Command {
public void setKeystorePassword(String keystorePassword) {
this.keystorePassword = keystorePassword;
}
public String getEncryptorPassword() {
return encryptorPassword;
}
public void setEncryptorPassword(String encryptorPassword) {
this.encryptorPassword = encryptorPassword;
}
}

View File

@ -19,11 +19,14 @@ import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import org.apache.axis.encoding.Base64;
import org.apache.log4j.xml.DOMConfigurator;
import com.cloud.consoleproxy.util.Logger;
@ -62,6 +65,23 @@ public class ConsoleProxy {
static String factoryClzName;
static boolean standaloneStart = false;
static String encryptorPassword = genDefaultEncryptorPassword();
private static String genDefaultEncryptorPassword() {
try {
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
byte[] randomBytes = new byte[16];
random.nextBytes(randomBytes);
return Base64.encode(randomBytes);
} catch (NoSuchAlgorithmException e) {
s_logger.error("Unexpected exception ", e);
assert(false);
}
return "Dummy";
}
private static void configLog4j() {
URL configUrl = System.class.getResource("/conf/log4j-cloud.xml");
if(configUrl == null)
@ -442,6 +462,14 @@ public class ConsoleProxy {
}
}
public static String getEncryptorPassword() {
return encryptorPassword;
}
public static void setEncryptorPassword(String password) {
encryptorPassword = password;
}
static class ThreadExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();

View File

@ -62,7 +62,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler {
if(s_logger.isTraceEnabled())
s_logger.trace("Handle AJAX request: " + queries);
Map<String, String> queryMap = getQueryMap(queries);
Map<String, String> queryMap = ConsoleProxyHttpHandlerHelper.getQueryMap(queries);
String host = queryMap.get("host");
String portStr = queryMap.get("port");
@ -179,28 +179,6 @@ public class ConsoleProxyAjaxHandler implements HttpHandler {
}
}
public static Map<String, String> getQueryMap(String query) {
String[] params = query.split("&");
Map<String, String> map = new HashMap<String, String>();
for (String param : params) {
String[] paramTokens = param.split("=");
if(paramTokens != null && paramTokens.length == 2) {
String name = param.split("=")[0];
String value = param.split("=")[1];
map.put(name, value);
} else if (paramTokens.length == 3) {
// very ugly, added for Xen tunneling url
String name = paramTokens[0];
String value = paramTokens[1] + "=" + paramTokens[2];
map.put(name, value);
} else {
if(s_logger.isDebugEnabled())
s_logger.debug("Invalid paramemter in URL found. param: " + param);
}
}
return map;
}
private static String convertStreamToString(InputStream is, boolean closeStreamAfterRead) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();

View File

@ -14,7 +14,6 @@ package com.cloud.consoleproxy;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import com.cloud.consoleproxy.util.Logger;
@ -54,7 +53,7 @@ public class ConsoleProxyAjaxImageHandler implements HttpHandler {
private void doHandle(HttpExchange t) throws Exception, IllegalArgumentException {
String queries = t.getRequestURI().getQuery();
Map<String, String> queryMap = getQueryMap(queries);
Map<String, String> queryMap = ConsoleProxyHttpHandlerHelper.getQueryMap(queries);
String host = queryMap.get("host");
String portStr = queryMap.get("port");
@ -110,15 +109,4 @@ public class ConsoleProxyAjaxImageHandler implements HttpHandler {
t.sendResponseHeaders(404, -1);
}
}
public static Map<String, String> getQueryMap(String query) {
String[] params = query.split("&");
Map<String, String> map = new HashMap<String, String>();
for (String param : params) {
String name = param.split("=")[0];
String value = param.split("=")[1];
map.put(name, value);
}
return map;
}
}

View File

@ -44,14 +44,8 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons
protected TileTracker tracker;
protected AjaxFIFOImageCache ajaxImageCache = new AjaxFIFOImageCache(2);
/*
protected String host;
protected int port;
protected String passwordParam;
protected String tag = "";
protected String ticket = "";
*/
protected ConsoleProxyClientParam clientParam;
protected String clientToken;
protected long createTime = System.currentTimeMillis();
protected long lastFrontEndActivityTime = System.currentTimeMillis();
@ -193,10 +187,11 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons
}
int key = ajaxImageCache.putImage(imgBits);
StringBuffer sb = new StringBuffer("/ajaximg?host=");
sb.append(getClientHostAddress()).append("&port=").append(getClientHostPort()).append("&sid=").append(getClientHostPassword());
sb.append("&key=").append(key).append("&tag=").append(this.getClientTag());
StringBuffer sb = new StringBuffer();
sb.append("/ajaximg?token=").append(clientToken);
sb.append("&key=").append(key);
sb.append("&ts=").append(System.currentTimeMillis());
return sb.toString();
}
@ -208,9 +203,7 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons
}
StringBuffer sb = new StringBuffer();
sb.append("/ajax?host=").append(getClientHostAddress()).append("&port=").append(getClientHostPort());
sb.append("&sid=").append(getClientHostPassword()).append("&tag=").append(getClientTag()).append("&sess=").append(ajaxSessionId);
sb.append("/ajax?token=").append(clientToken).append("&sess=").append(ajaxSessionId);
return sb.toString();
}
@ -455,5 +448,7 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons
public void setClientParam(ConsoleProxyClientParam clientParam) {
this.clientParam = clientParam;
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(ConsoleProxy.getEncryptorPassword());
this.clientToken = encryptor.encryptObject(ConsoleProxyClientParam.class, clientParam);
}
}

View File

@ -28,6 +28,8 @@ public class ConsoleProxyClientParam {
private String clientTunnelUrl;
private String clientTunnelSession;
private String ajaxSessionId;
public ConsoleProxyClientParam() {
clientHostPort = 0;
}
@ -88,6 +90,14 @@ public class ConsoleProxyClientParam {
this.clientTunnelSession = clientTunnelSession;
}
public String getAjaxSessionId() {
return this.ajaxSessionId;
}
public void setAjaxSessionId(String ajaxSessionId) {
this.ajaxSessionId = ajaxSessionId;
}
public String getClientMapKey() {
if(clientTag != null && !clientTag.isEmpty())
return clientTag;

View File

@ -0,0 +1,70 @@
// Copyright 2012 Citrix Systems, Inc. Licensed under the
// Apache License, Version 2.0 (the "License"); you may not use this
// file except in compliance with the License. Citrix Systems, Inc.
// reserves all rights not expressly granted by the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Automatically generated by addcopyright.py at 04/03/2012
package com.cloud.consoleproxy;
import java.util.HashMap;
import java.util.Map;
import com.cloud.consoleproxy.util.Logger;
public class ConsoleProxyHttpHandlerHelper {
private static final Logger s_logger = Logger.getLogger(ConsoleProxyHttpHandlerHelper.class);
public static Map<String, String> getQueryMap(String query) {
String[] params = query.split("&");
Map<String, String> map = new HashMap<String, String>();
for (String param : params) {
String[] paramTokens = param.split("=");
if(paramTokens != null && paramTokens.length == 2) {
String name = param.split("=")[0];
String value = param.split("=")[1];
map.put(name, value);
} else if (paramTokens.length == 3) {
// very ugly, added for Xen tunneling url
String name = paramTokens[0];
String value = paramTokens[1] + "=" + paramTokens[2];
map.put(name, value);
} else {
if(s_logger.isDebugEnabled())
s_logger.debug("Invalid paramemter in URL found. param: " + param);
}
}
// This is a ugly solution for now. We will do encryption/decryption translation
// here to make it transparent to rest of the code.
if(map.get("token") != null) {
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(
ConsoleProxy.getEncryptorPassword());
ConsoleProxyClientParam param = encryptor.decryptObject(ConsoleProxyClientParam.class, map.get("token"));
if(param != null) {
if(param.getClientHostAddress() != null)
map.put("host", param.getClientHostAddress());
if(param.getClientHostPort() != 0)
map.put("port", String.valueOf(param.getClientHostPort()));
if(param.getClientTag() != null)
map.put("tag", param.getClientTag());
if(param.getClientHostPassword() != null)
map.put("sid", param.getClientHostPassword());
if(param.getClientTunnelUrl() != null)
map.put("consoleurl", param.getClientTunnelUrl());
if(param.getClientTunnelSession() != null)
map.put("sessionref", param.getClientTunnelSession());
if(param.getTicket() != null)
map.put("ticket", param.getTicket());
}
}
return map;
}
}

View File

@ -0,0 +1,139 @@
// Copyright 2012 Citrix Systems, Inc. Licensed under the
// Apache License, Version 2.0 (the "License"); you may not use this
// file except in compliance with the License. Citrix Systems, Inc.
// reserves all rights not expressly granted by the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Automatically generated by addcopyright.py at 04/03/2012
package com.cloud.consoleproxy;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
*
* @author Kelven Yang
* A simple password based encyrptor based on DES. 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 = Cipher.getMaxAllowedKeyLength("DES") / 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;
}
}
public String decryptText(String encryptedText) {
if(encryptedText == null || encryptedText.isEmpty())
return encryptedText;
assert(password != null);
assert(!password.isEmpty());
try {
Cipher cipher = Cipher.getInstance("DES");
int maxKeySize = Cipher.getMaxAllowedKeyLength("DES") / 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;
}
}

View File

@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import javax.ejb.Local;
import javax.naming.ConfigurationException;
@ -89,6 +90,7 @@ 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.ConsoleProxyServlet;
@ -229,7 +231,6 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
private int _capacityPerProxy = ConsoleProxyManager.DEFAULT_PROXY_CAPACITY;
private int _standbyCapacity = ConsoleProxyManager.DEFAULT_STANDBY_CAPACITY;
private boolean _use_lvm;
private boolean _use_storage_vm;
private boolean _disable_rp_filter = false;
@ -244,6 +245,8 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
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 final GlobalLock _allocProxyLock = GlobalLock.getInternLock(getAllocProxyLockName());
/*
@ -1664,8 +1667,10 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
s_logger.error("Could not find and construct a valid SSL certificate");
}
cmd = new StartConsoleProxyAgentHttpHandlerCommand(ksBits, storePassword);
cmd.setEncryptorPassword(getHashKey());
} else {
cmd = new StartConsoleProxyAgentHttpHandlerCommand();
cmd.setEncryptorPassword(getHashKey());
}
try {
@ -1906,4 +1911,13 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
sc.addAnd(sc.getEntity().getName(), Op.EQ, name);
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;
}
}

View File

@ -0,0 +1,102 @@
// Copyright 2012 Citrix Systems, Inc. Licensed under the
// Apache License, Version 2.0 (the "License"); you may not use this
// file except in compliance with the License. Citrix Systems, Inc.
// reserves all rights not expressly granted by the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Automatically generated by addcopyright.py at 04/03/2012
package com.cloud.servlet;
// To maintain independency of console proxy project, we duplicate this class from console proxy project
public class ConsoleProxyClientParam {
private String clientHostAddress;
private int clientHostPort;
private String clientHostPassword;
private String clientTag;
private String ticket;
private String clientTunnelUrl;
private String clientTunnelSession;
private String ajaxSessionId;
public ConsoleProxyClientParam() {
clientHostPort = 0;
}
public String getClientHostAddress() {
return clientHostAddress;
}
public void setClientHostAddress(String clientHostAddress) {
this.clientHostAddress = clientHostAddress;
}
public int getClientHostPort() {
return clientHostPort;
}
public void setClientHostPort(int clientHostPort) {
this.clientHostPort = clientHostPort;
}
public String getClientHostPassword() {
return clientHostPassword;
}
public void setClientHostPassword(String clientHostPassword) {
this.clientHostPassword = clientHostPassword;
}
public String getClientTag() {
return clientTag;
}
public void setClientTag(String clientTag) {
this.clientTag = clientTag;
}
public String getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
public String getClientTunnelUrl() {
return clientTunnelUrl;
}
public void setClientTunnelUrl(String clientTunnelUrl) {
this.clientTunnelUrl = clientTunnelUrl;
}
public String getClientTunnelSession() {
return clientTunnelSession;
}
public void setClientTunnelSession(String clientTunnelSession) {
this.clientTunnelSession = clientTunnelSession;
}
public String getAjaxSessionId() {
return this.ajaxSessionId;
}
public void setAjaxSessionId(String ajaxSessionId) {
this.ajaxSessionId = ajaxSessionId;
}
public String getClientMapKey() {
if(clientTag != null && !clientTag.isEmpty())
return clientTag;
return clientHostAddress + ":" + clientHostPort;
}
}

View File

@ -0,0 +1,121 @@
package com.cloud.servlet;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
// To maintain independency of console proxy project, we duplicate this class from console proxy project
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 = Cipher.getMaxAllowedKeyLength("DES") / 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;
}
}
public String decryptText(String encryptedText) {
if(encryptedText == null || encryptedText.isEmpty())
return encryptedText;
assert(password != null);
assert(!password.isEmpty());
try {
Cipher cipher = Cipher.getInstance("DES");
int maxKeySize = Cipher.getMaxAllowedKeyLength("DES") / 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;
}
}

View File

@ -64,7 +64,6 @@ public class ConsoleProxyServlet extends HttpServlet {
private final static AccountManager _accountMgr = ComponentLocator.getLocator(ManagementServer.Name).getManager(AccountManager.class);
private final static VirtualMachineManager _vmMgr = ComponentLocator.getLocator(ManagementServer.Name).getManager(VirtualMachineManager.class);
private final static DomainManager _domainMgr = ComponentLocator.getLocator(ManagementServer.Name).getManager(DomainManager.class);
private final static ManagementServer _ms = (ManagementServer)ComponentLocator.getComponent(ManagementServer.Name);
private final static IdentityService _identityService = (IdentityService)ComponentLocator.getLocator(ManagementServer.Name).getManager(IdentityService.class);
@ -317,30 +316,21 @@ 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);
String consoleurl = null;
String sessionref= null;
sb.append("/getscreen?host=").append(parsedHostInfo.first());
sb.append("&port=").append(portInfo.second());
sb.append("&sid=").append(sid);
sb.append("&w=").append(w).append("&h=").append(h);
sb.append("&tag=").append(tag);
sb.append("&ticket=").append(ticket);
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(_ms.getHashKey());
ConsoleProxyClientParam param = new ConsoleProxyClientParam();
param.setClientHostAddress(parsedHostInfo.first());
param.setClientHostPort(portInfo.second());
param.setClientHostPassword(sid);
param.setClientTag(tag);
param.setTicket(ticket);
if(parsedHostInfo.second() != null && parsedHostInfo.third() != null) {
try {
consoleurl = URLEncoder.encode(parsedHostInfo.second(), "UTF-8");
sessionref = URLEncoder.encode(parsedHostInfo.third(), "UTF-8");
sb.append("&").append("consoleurl=").append(URLDecoder.decode(consoleurl, "UTF-8"));
sb.append("&").append("sessionref=").append(URLDecoder.decode(sessionref, "UTF-8"));
} catch (UnsupportedEncodingException e) {
s_logger.error("Unexpected exception ", e);
param.setClientTunnelUrl(parsedHostInfo.second());
param.setClientTunnelSession(parsedHostInfo.third());
}
}
sb.append("/ajax?token=" + encryptor.encryptObject(ConsoleProxyClientParam.class, param));
sb.append("&w=").append(w).append("&h=").append(h);
if(s_logger.isDebugEnabled()) {
s_logger.debug("Compose thumbnail url: " + sb.toString());
@ -362,29 +352,19 @@ 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);
String consoleurl = null;
String sessionref= null;
sb.append("/ajax?host=").append(parsedHostInfo.first());
sb.append("&port=").append(portInfo.second());
sb.append("&sid=").append(sid);
sb.append("&tag=").append(tag);
sb.append("&ticket=").append(ticket);
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(_ms.getHashKey());
ConsoleProxyClientParam param = new ConsoleProxyClientParam();
param.setClientHostAddress(parsedHostInfo.first());
param.setClientHostPort(portInfo.second());
param.setClientHostPassword(sid);
param.setClientTag(tag);
param.setTicket(ticket);
if(parsedHostInfo.second() != null && parsedHostInfo.third() != null) {
try {
consoleurl = URLEncoder.encode(parsedHostInfo.second(), "UTF-8");
sessionref = URLEncoder.encode(parsedHostInfo.third(), "UTF-8");
sb.append("&").append("consoleurl=").append(URLDecoder.decode(consoleurl, "UTF-8"));
sb.append("&").append("sessionref=").append(URLDecoder.decode(sessionref, "UTF-8"));
} catch (UnsupportedEncodingException e) {
s_logger.error("Unexpected exception ", e);
param.setClientTunnelUrl(parsedHostInfo.second());
param.setClientTunnelSession(parsedHostInfo.third());
}
}
sb.append("/ajax?token=" + encryptor.encryptObject(ConsoleProxyClientParam.class, param));
// for console access, we need guest OS type to help implement keyboard
long guestOs = vm.getGuestOSId();