From b0375e6db2d63feb1a8ebbad16091454d155bb08 Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Fri, 8 Jul 2016 18:54:01 +0200 Subject: [PATCH] store proxy login and password in a separate encrypted file --- .../ide/passwordSafe/PasswordSafe.java | 2 +- .../passwordSafe/PasswordSafeException.java | 2 +- .../ide/passwordSafe/PasswordStorage.java | 2 +- .../intellij/util/net/HttpConfigurable.java | 157 ++++++++++++++---- .../util/net/HttpProxySettingsUi.java | 6 +- .../proxy/PropertiesEncryptionSupport.java | 107 ++++++++++++ .../util/proxy/SharedProxyConfig.java | 63 +------ .../util/net/IdeHttpClientHelpers.java | 2 +- .../util/CloudConfigurationBase.java | 2 +- .../src/git4idea/commands/GitHandler.java | 2 +- .../BaseGradleProjectResolverExtension.java | 6 +- .../svn/auth/SvnAuthenticationNotifier.java | 2 +- .../tasks/impl/BaseRepositoryImpl.java | 2 +- .../packaging/PyPackageManagerImpl.java | 2 +- 14 files changed, 251 insertions(+), 106 deletions(-) create mode 100644 platform/platform-api/src/com/intellij/util/proxy/PropertiesEncryptionSupport.java diff --git a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafe.java b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafe.java index da7cd8f93c15..50d503b76411 100644 --- a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafe.java +++ b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafe.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * 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. diff --git a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafeException.java b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafeException.java index 20d7d5188efb..64b0ff9b61c3 100644 --- a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafeException.java +++ b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafeException.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * 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. diff --git a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordStorage.java b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordStorage.java index b1ea1332986e..677e5234f99f 100644 --- a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordStorage.java +++ b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordStorage.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * 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. diff --git a/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java b/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java index 3de4fe9a9ccc..568e9548338f 100644 --- a/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java +++ b/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java @@ -18,10 +18,12 @@ package com.intellij.util.net; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.PathManager; import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; @@ -40,6 +42,7 @@ 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.PropertiesEncryptionSupport; import com.intellij.util.proxy.SharedProxyConfig; import com.intellij.util.xmlb.SkipDefaultsSerializationFilter; import com.intellij.util.xmlb.XmlSerializer; @@ -59,13 +62,13 @@ import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.crypto.spec.SecretKeySpec; import javax.swing.*; +import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.net.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; @State( name = "HttpConfigurable", @@ -76,6 +79,8 @@ import java.util.Set; } ) public class HttpConfigurable implements PersistentStateComponent, ApplicationComponent { + private static final Logger LOG = Logger.getInstance("#com.intellij.util.net.HttpConfigurable"); + private static final File PROXY_CREDENTIALS_FILE = new File(PathManager.getOptionsPath(), "proxy.settings.pwd"); public static final int CONNECTION_TIMEOUT = SystemProperties.getIntProperty("idea.connection.timeout", 10000); public static final int READ_TIMEOUT = SystemProperties.getIntProperty("idea.read.timeout", 60000); public static final int REDIRECT_LIMIT = SystemProperties.getIntProperty("idea.redirect.limit", 10); @@ -88,8 +93,6 @@ public class HttpConfigurable implements PersistentStateComponent myProxyCredentials = NotNullLazyValue.createValue(() -> { + try { + return myEncryptionSupport.load(PROXY_CREDENTIALS_FILE); + } + catch (FileNotFoundException ignored) { + } + catch (Throwable th) { + LOG.info(th); + } + return new Properties(); + }); + @SuppressWarnings("UnusedDeclaration") public transient Getter myTestAuthRunnable = new StaticGetter(null); public transient Getter myTestGenericAuthRunnable = new StaticGetter(null); @@ -123,7 +141,7 @@ public class HttpConfigurable implements PersistentStateComponent> result = new ArrayList>(); if (me.USE_HTTP_PROXY) { - final boolean putCredentials = me.KEEP_PROXY_PASSWORD && StringUtil.isNotEmpty(me.PROXY_LOGIN); + final boolean putCredentials = me.KEEP_PROXY_PASSWORD && StringUtil.isNotEmpty(me.getProxyLogin()); if (me.PROXY_TYPE_IS_SOCKS) { result.add(KeyValue.create(JavaProxyProperty.SOCKS_HOST, me.PROXY_HOST)); result.add(KeyValue.create(JavaProxyProperty.SOCKS_PORT, String.valueOf(me.PROXY_PORT))); if (putCredentials) { - result.add(KeyValue.create(JavaProxyProperty.SOCKS_USERNAME, me.PROXY_LOGIN)); + result.add(KeyValue.create(JavaProxyProperty.SOCKS_USERNAME, me.getProxyLogin())); result.add(KeyValue.create(JavaProxyProperty.SOCKS_PASSWORD, me.getPlainProxyPassword())); } } else { @@ -510,7 +562,7 @@ public class HttpConfigurable implements PersistentStateComponent { settings.PROXY_AUTHENTICATION != myProxyAuthCheckBox.isSelected() || settings.KEEP_PROXY_PASSWORD != myRememberProxyPasswordCheckBox.isSelected() || settings.PROXY_TYPE_IS_SOCKS != mySocks.isSelected() || - !Comparing.strEqual(settings.PROXY_LOGIN, myProxyLoginTextField.getText()) || + !Comparing.strEqual(settings.getProxyLogin(), myProxyLoginTextField.getText()) || !Comparing.strEqual(settings.getPlainProxyPassword(), new String(myProxyPasswordTextField.getPassword())) || settings.PROXY_PORT != myProxyPortTextField.getNumber() || !Comparing.strEqual(settings.PROXY_HOST, myProxyHostTextField.getText()); @@ -231,7 +231,7 @@ class HttpProxySettingsUi implements ConfigurableUi { enableProxy(settings.USE_HTTP_PROXY); - myProxyLoginTextField.setText(settings.PROXY_LOGIN); + myProxyLoginTextField.setText(settings.getProxyLogin()); myProxyPasswordTextField.setText(settings.getPlainProxyPassword()); myProxyPortTextField.setNumber(settings.PROXY_PORT); @@ -312,7 +312,7 @@ class HttpProxySettingsUi implements ConfigurableUi { settings.PROXY_AUTHENTICATION = myProxyAuthCheckBox.isSelected(); settings.KEEP_PROXY_PASSWORD = myRememberProxyPasswordCheckBox.isSelected(); - settings.PROXY_LOGIN = getText(myProxyLoginTextField); + settings.setProxyLogin(getText(myProxyLoginTextField)); settings.setPlainProxyPassword(new String(myProxyPasswordTextField.getPassword())); settings.PROXY_EXCEPTIONS = StringUtil.nullize(myProxyExceptions.getText(), true); diff --git a/platform/platform-api/src/com/intellij/util/proxy/PropertiesEncryptionSupport.java b/platform/platform-api/src/com/intellij/util/proxy/PropertiesEncryptionSupport.java new file mode 100644 index 000000000000..302fffedc8cd --- /dev/null +++ b/platform/platform-api/src/com/intellij/util/proxy/PropertiesEncryptionSupport.java @@ -0,0 +1,107 @@ +/* + * 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.util.io.FileUtil; +import org.jetbrains.annotations.NotNull; + +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; + +/** + * @author Eugene Zhuravlev + * Date: 08-Jul-16 + */ +public class PropertiesEncryptionSupport { + private final Key myKey; + + public PropertiesEncryptionSupport(Key key) { + myKey = key; + } + + public PropertiesEncryptionSupport() { + this(generateKey()); + } + + public static Key generateKey() { + final byte[] bytes = new byte[16]; + new SecureRandom().nextBytes(bytes); + return new SecretKeySpec(bytes, "AES"); + } + + @NotNull + public Properties load(@NotNull File file) throws Exception { + final byte[] bytes = decrypt(FileUtil.loadFileBytes(file)); + final Properties props = new Properties(); + props.load(new ByteArrayInputStream(bytes)); + return props; + } + + public void store(@NotNull Properties props, @NotNull String comments, @NotNull File file) throws Exception { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + props.store(out, comments); + out.close(); + FileUtil.writeToFile(file, encrypt(out.toByteArray())); + } + + private byte[] encrypt(byte[] bytes) throws Exception { + return encrypt(bytes, myKey); + } + + private byte[] decrypt(byte[] bytes) throws Exception { + return decrypt(bytes, myKey); + } + + 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); + } + +} diff --git a/platform/platform-api/src/com/intellij/util/proxy/SharedProxyConfig.java b/platform/platform-api/src/com/intellij/util/proxy/SharedProxyConfig.java index 4de4831cf6ba..0837dc1c1d70 100644 --- a/platform/platform-api/src/com/intellij/util/proxy/SharedProxyConfig.java +++ b/platform/platform-api/src/com/intellij/util/proxy/SharedProxyConfig.java @@ -20,14 +20,7 @@ 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 { @@ -36,12 +29,7 @@ public class SharedProxyConfig { 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"); - } + private static final PropertiesEncryptionSupport ourEncryptionSupport = new PropertiesEncryptionSupport(); // the key is valid for the same session only public static final class ProxyParameters { @Nullable @@ -71,9 +59,7 @@ public class SharedProxyConfig { @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 Properties props = ourEncryptionSupport.load(CONFIG_FILE); final String password = props.getProperty(PASSWORD, ""); return new ProxyParameters( props.getProperty(HOST, null), @@ -97,10 +83,7 @@ public class SharedProxyConfig { 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())); + ourEncryptionSupport.store(props, "Proxy Configuration", CONFIG_FILE); return true; } catch (Exception ignored) { @@ -112,44 +95,4 @@ public class SharedProxyConfig { 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); - } - } diff --git a/platform/platform-impl/src/com/intellij/util/net/IdeHttpClientHelpers.java b/platform/platform-impl/src/com/intellij/util/net/IdeHttpClientHelpers.java index 1747461225d4..07502cbe5081 100644 --- a/platform/platform-impl/src/com/intellij/util/net/IdeHttpClientHelpers.java +++ b/platform/platform-impl/src/com/intellij/util/net/IdeHttpClientHelpers.java @@ -57,7 +57,7 @@ public class IdeHttpClientHelpers { @NotNull private static String getProxyLogin() { - return StringUtil.notNullize(getHttpConfigurable().PROXY_LOGIN); + return StringUtil.notNullize(getHttpConfigurable().getProxyLogin()); } @NotNull diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/CloudConfigurationBase.java b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/CloudConfigurationBase.java index 8b4877443abe..e55511a4cb3e 100644 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/util/CloudConfigurationBase.java +++ b/platform/remote-servers/impl/src/com/intellij/remoteServer/util/CloudConfigurationBase.java @@ -64,7 +64,7 @@ public class CloudConfigurationBase> @Override public String getLogin() { - return httpConfigurable.PROXY_LOGIN; + return httpConfigurable.getProxyLogin(); } @Override diff --git a/plugins/git4idea/src/git4idea/commands/GitHandler.java b/plugins/git4idea/src/git4idea/commands/GitHandler.java index d24185fd0724..b1ea3af68f41 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHandler.java +++ b/plugins/git4idea/src/git4idea/commands/GitHandler.java @@ -496,7 +496,7 @@ public abstract class GitHandler { myEnv.put(GitSSHHandler.SSH_PROXY_AUTHENTICATION_ENV, String.valueOf(proxyAuthentication)); if (proxyAuthentication) { - myEnv.put(GitSSHHandler.SSH_PROXY_USER_ENV, StringUtil.notNullize(httpConfigurable.PROXY_LOGIN)); + myEnv.put(GitSSHHandler.SSH_PROXY_USER_ENV, StringUtil.notNullize(httpConfigurable.getProxyLogin())); myEnv.put(GitSSHHandler.SSH_PROXY_PASSWORD_ENV, StringUtil.notNullize(httpConfigurable.getPlainProxyPassword())); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java index 71b813752edc..4082d697065e 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java @@ -589,9 +589,9 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver extraJvmArgs.add(KeyValue.create("https.nonProxyHosts", nonProxyHosts)); } } - if (httpConfigurable.USE_HTTP_PROXY && StringUtil.isNotEmpty(httpConfigurable.PROXY_LOGIN)) { - extraJvmArgs.add(KeyValue.create("http.proxyUser", httpConfigurable.PROXY_LOGIN)); - extraJvmArgs.add(KeyValue.create("https.proxyUser", httpConfigurable.PROXY_LOGIN)); + if (httpConfigurable.USE_HTTP_PROXY && StringUtil.isNotEmpty(httpConfigurable.getProxyLogin())) { + extraJvmArgs.add(KeyValue.create("http.proxyUser", httpConfigurable.getProxyLogin())); + extraJvmArgs.add(KeyValue.create("https.proxyUser", httpConfigurable.getProxyLogin())); final String plainProxyPassword = httpConfigurable.getPlainProxyPassword(); extraJvmArgs.add(KeyValue.create("http.proxyPassword", plainProxyPassword)); extraJvmArgs.add(KeyValue.create("https.proxyPassword", plainProxyPassword)); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationNotifier.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationNotifier.java index 430ad73509cd..07623991c975 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationNotifier.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationNotifier.java @@ -346,7 +346,7 @@ public class SvnAuthenticationNotifier extends GenericNotifierImpl