licensing: connection via authenticated proxy supported (IDEA-149370)

This commit is contained in:
Eugene Zhuravlev
2016-05-04 14:26:04 +02:00
parent d2c8eba5a6
commit f6e393fd4d
3 changed files with 187 additions and 0 deletions
@@ -40,6 +40,8 @@ import com.intellij.util.WaitForProgressToShow;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.proxy.CommonProxy;
import com.intellij.util.proxy.JavaProxyProperty;
import com.intellij.util.proxy.SharedProxyConfig;
import com.intellij.util.xmlb.SkipDefaultsSerializationFilter;
import com.intellij.util.xmlb.XmlSerializer;
import com.intellij.util.xmlb.XmlSerializerUtil;
import com.intellij.util.xmlb.annotations.Transient;
@@ -129,6 +131,32 @@ public class HttpConfigurable implements PersistentStateComponent<HttpConfigurab
@Override
public void initComponent() {
final HttpConfigurable currentState = getState();
if (currentState != null) {
final Element serialized = XmlSerializer.serializeIfNotDefault(currentState, new SkipDefaultsSerializationFilter());
if (serialized == null) {
// all settings are defaults
// trying user's proxy configuration entered while obtaining the license
final SharedProxyConfig.ProxyParameters cfg = SharedProxyConfig.load();
if (cfg != null) {
SharedProxyConfig.clear();
if (cfg.host != null) {
USE_HTTP_PROXY = true;
PROXY_HOST = cfg.host;
PROXY_PORT = cfg.port;
if (cfg.login != null) {
setPlainProxyPassword(new String(cfg.password));
PROXY_LOGIN = cfg.login;
PROXY_AUTHENTICATION = true;
KEEP_PROXY_PASSWORD = true;
}
}
}
}
}
mySelector = new IdeaWideProxySelector(this);
String name = getClass().getName();
CommonProxy.getInstance().setCustom(name, mySelector);
@@ -377,4 +377,8 @@ public class CommonProxy extends ProxySelector {
return result;
}
}
}
@@ -0,0 +1,155 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.proxy;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.security.Key;
import java.security.SecureRandom;
import java.util.Properties;
public class SharedProxyConfig {
private static final File CONFIG_FILE = new File(PathManager.getConfigPath(), "proxy_config");
private static final String HOST = "host";
private static final String PORT = "port";
private static final String LOGIN = "login";
private static final String PASSWORD = "password";
private static final Key ENCRYPTION_KEY; // the key is valid for the same session only
static {
final byte[] bytes = new byte[16];
new SecureRandom().nextBytes(bytes);
ENCRYPTION_KEY = new SecretKeySpec(bytes, "AES");
}
public static final class ProxyParameters {
@Nullable
public final String host;
public final int port;
@Nullable
public final String login;
@NotNull
public final char[] password;
public ProxyParameters(@Nullable String host, int port) {
this(host, port, null, new char[0]);
}
public ProxyParameters(@Nullable String host, int port, @Nullable String login, @NotNull char[] password) {
this.host = host;
this.port = port;
this.login = login;
this.password = password;
}
}
public static boolean clear() {
return FileUtil.delete(CONFIG_FILE);
}
@Nullable
public static ProxyParameters load() {
try {
final byte[] bytes = decrypt(FileUtil.loadFileBytes(CONFIG_FILE));
final Properties props = new Properties();
props.load(new ByteArrayInputStream(bytes));
final String password = props.getProperty(PASSWORD, "");
return new ProxyParameters(
props.getProperty(HOST, null),
Integer.parseInt(props.getProperty(PORT, "0")),
props.getProperty(LOGIN, null),
password.toCharArray()
);
}
catch (Exception ignored) {
}
return null;
}
public static boolean store(@NotNull ProxyParameters params) {
if (params.host != null) {
try {
final Properties props = new Properties();
props.setProperty(HOST, params.host);
props.setProperty(PORT, String.valueOf(params.port));
if (params.login != null) {
props.setProperty(LOGIN, params.login);
props.setProperty(PASSWORD, new String(params.password));
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
props.store(out, "Proxy Configuration");
out.close();
FileUtil.writeToFile(CONFIG_FILE, encrypt(out.toByteArray()));
return true;
}
catch (Exception ignored) {
}
}
else {
FileUtil.delete(CONFIG_FILE);
}
return false;
}
private static byte[] encrypt(byte[] bytes) throws Exception {
return encrypt(bytes, ENCRYPTION_KEY);
}
private static byte[] decrypt(byte[] bytes) throws Exception {
return decrypt(bytes, ENCRYPTION_KEY);
}
private static byte[] encrypt(byte[] msgBytes, Key key) throws Exception {
final Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciph.init(Cipher.ENCRYPT_MODE, key);
final byte[] body = ciph.doFinal(msgBytes);
final byte[] iv = ciph.getIV();
final byte[] data = new byte[4 + iv.length + body.length];
final int length = body.length;
data[0] = (byte)((length >> 24)& 0xFF);
data[1] = (byte)((length >> 16)& 0xFF);
data[2] = (byte)((length >> 8)& 0xFF);
data[3] = (byte)(length & 0xFF);
System.arraycopy(iv, 0, data, 4, iv.length);
System.arraycopy(body, 0, data, 4 + iv.length, body.length);
return data;
}
private static byte[] decrypt(byte[] data, Key key) throws Exception {
int bodyLength = data[0] & 0xFF;
bodyLength = (bodyLength << 8) + data[1] & 0xFF;
bodyLength = (bodyLength << 8) + data[2] & 0xFF;
bodyLength = (bodyLength << 8) + data[3] & 0xFF;
final int ivlength = data.length - 4 - bodyLength;
final Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciph.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(data, 4, ivlength));
return ciph.doFinal(data, 4 + ivlength, bodyLength);
}
}