From 3b75edb715b37c98ff918c44e7f96bec8e021e59 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Thu, 21 Jul 2016 17:21:58 +0200 Subject: [PATCH 01/26] =?UTF-8?q?PasswordSafe=20=E2=80=94=20do=20not=20ask?= =?UTF-8?q?=20master=20password?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/intellij/mock/MockApplicationEx.java | 7 +- .../openapi/application/ex/ApplicationEx.java | 3 +- .../MasterPasswordUnavailableException.java | 25 -- .../passwordSafe/PasswordSafeException.java | 13 - .../ide/passwordSafe/PasswordStorage.java | 29 +- .../com/intellij/util/EncryptionSupport.kt | 67 ++++ .../proxy/PropertiesEncryptionSupport.java | 26 +- .../intellij/ide/ApplicationLoadListener.java | 8 +- .../config/PasswordSafeConfigurable.java | 28 +- .../config/PasswordSafeOptionsPanel.form | 37 +- .../config/PasswordSafeOptionsPanel.java | 42 +-- .../passwordSafe/impl/PasswordSafeImpl.java | 29 +- .../impl/PasswordSafeProvider.java | 4 - .../providers/BasePasswordSafeProvider.java | 26 +- .../impl/providers/EncryptionUtil.java | 17 +- .../masterKey/ChangePasswordComponent.java | 43 --- .../masterKey/EnterPasswordComponent.java | 30 +- .../masterKey/FilePasswordSafeProvider.kt | 182 ++++++++++ .../masterKey/MasterKeyPasswordSafe.java | 335 ------------------ .../masterKey/MasterPasswordDialog.java | 71 +--- .../masterKey/PasswordComponentBase.form | 12 +- .../masterKey/PasswordComponentBase.java | 36 +- .../providers/masterKey/PasswordDatabase.java | 221 ++---------- .../masterKey/ResetPasswordComponent.java | 68 ---- .../impl/providers/masterKey/dbV1Convertor.kt | 124 +++++++ .../providers/memory/MemoryPasswordSafe.java | 8 +- .../impl/providers/nil/NilProvider.java | 13 +- .../application/impl/ApplicationImpl.java | 14 +- .../src/META-INF/PlatformExtensions.xml | 2 + .../masterKey/MasterKeyPasswordSafeTest.java | 48 --- .../masterKey/MasterPasswordMigrationTest.kt | 93 +++++ .../util/resources/misc/registry.properties | 1 - .../commands/GitHttpGuiAuthenticator.java | 5 +- 33 files changed, 639 insertions(+), 1028 deletions(-) delete mode 100644 platform/platform-api/src/com/intellij/ide/passwordSafe/MasterPasswordUnavailableException.java create mode 100644 platform/platform-api/src/com/intellij/util/EncryptionSupport.kt delete mode 100644 platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/ChangePasswordComponent.java create mode 100644 platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/FilePasswordSafeProvider.kt delete mode 100644 platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java delete mode 100644 platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/ResetPasswordComponent.java create mode 100644 platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/dbV1Convertor.kt delete mode 100644 platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafeTest.java create mode 100644 platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordMigrationTest.kt diff --git a/platform/core-impl/src/com/intellij/mock/MockApplicationEx.java b/platform/core-impl/src/com/intellij/mock/MockApplicationEx.java index 9d53126fbc8e..361371a2eeba 100644 --- a/platform/core-impl/src/com/intellij/mock/MockApplicationEx.java +++ b/platform/core-impl/src/com/intellij/mock/MockApplicationEx.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 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. @@ -24,7 +24,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.io.IOException; public class MockApplicationEx extends MockApplication implements ApplicationEx { public MockApplicationEx(@NotNull Disposable parentDisposable) { @@ -43,11 +42,11 @@ public class MockApplicationEx extends MockApplication implements ApplicationEx } @Override - public void load(String path) { + public void load(@Nullable String path) { } @Override - public void load() throws IOException { + public void load() { load(null); } diff --git a/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java b/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java index 8c4c57946bc6..337dd04eb83a 100644 --- a/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java +++ b/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java @@ -22,7 +22,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.io.IOException; /** * @author max @@ -37,7 +36,7 @@ public interface ApplicationEx extends Application { */ void load(@Nullable String configPath); - void load() throws IOException; + void load(); boolean isLoaded(); diff --git a/platform/platform-api/src/com/intellij/ide/passwordSafe/MasterPasswordUnavailableException.java b/platform/platform-api/src/com/intellij/ide/passwordSafe/MasterPasswordUnavailableException.java deleted file mode 100644 index 5617c9c11072..000000000000 --- a/platform/platform-api/src/com/intellij/ide/passwordSafe/MasterPasswordUnavailableException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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.ide.passwordSafe; - -/** - * This exception is thrown when master password is not available (process of entering password is cancelled, or IDEA is running headless mode) - */ -public class MasterPasswordUnavailableException extends RuntimeException { - public MasterPasswordUnavailableException(String message) { - super(message); - } -} 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 74bdd1902c56..583b457bbfbf 100644 --- a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafeException.java +++ b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafeException.java @@ -19,10 +19,6 @@ package com.intellij.ide.passwordSafe; * The exception that is thrown when password safe is not available (unable to ask for master password) */ public class PasswordSafeException extends RuntimeException { - private static final long MIN_INTERVAL = 1000L; - - private long myTimeMillis = System.currentTimeMillis(); - public PasswordSafeException(String message, Throwable cause) { super(message, cause); } @@ -30,13 +26,4 @@ public class PasswordSafeException extends RuntimeException { public PasswordSafeException(String message) { super(message); } - - public boolean justHappened() { - long timeMillis = System.currentTimeMillis(); - if (timeMillis - myTimeMillis < MIN_INTERVAL) { - myTimeMillis = timeMillis; - return true; - } - return false; - } } 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 7ee62a58cb7f..c8c2431fcb1d 100644 --- a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordStorage.java +++ b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordStorage.java @@ -15,8 +15,6 @@ */ package com.intellij.ide.passwordSafe; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,41 +26,34 @@ public interface PasswordStorage { /** *

Get password stored in a password safe.

* - *

NB: - * This method may be called from the background, - * and it may need to ask user to enter the master password to access the database by calling - * {@link Application#invokeAndWait(Runnable, ModalityState) invokeAndWait()} to show a modal dialog. - * So make sure not to call it from the read action. - * Calling this method from the dispatch thread is allowed.

- * - * @param project the project, that is used to ask for the master password if this is the first access to password safe * @param requestor the requestor class * @param key the key for the password * @return the stored password or null if the password record was not found or was removed - * @throws PasswordSafeException if password safe cannot be accessed - * @throws IllegalStateException if the method is called from the read action. */ @Nullable - String getPassword(@Nullable Project project, @NotNull Class requestor, String key); + String getPassword(@Nullable Project project, @Nullable Class requestor, @NotNull String key); + + @Nullable + default String getPassword(@NotNull String key) { + return getPassword(null, null, key); + } /** * Remove password stored in a password safe * - * @param project the project, that is used to ask for the master password if this is the first access to password safe * @param requestor the requestor class * @param key the key for the password - * @throws PasswordSafeException if password safe cannot be accessed */ - void removePassword(@Nullable Project project, @NotNull Class requestor, String key); + default void removePassword(@Nullable Project project, @Nullable Class requestor, String key) { + storePassword(project, requestor, key, null); + } /** * Store password in password safe * - * @param project the project, that is used to ask for the master password if this is the first access to password safe * @param requestor the requestor class * @param key the key for the password * @param value the value to store - * @throws PasswordSafeException if password safe cannot be accessed */ - void storePassword(@Nullable Project project, @NotNull Class requestor, String key, String value); + void storePassword(@Nullable Project project, @Nullable Class requestor, String key, @Nullable String value); } diff --git a/platform/platform-api/src/com/intellij/util/EncryptionSupport.kt b/platform/platform-api/src/com/intellij/util/EncryptionSupport.kt new file mode 100644 index 000000000000..c1d664b63de1 --- /dev/null +++ b/platform/platform-api/src/com/intellij/util/EncryptionSupport.kt @@ -0,0 +1,67 @@ +/* + * 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 + +import java.security.Key +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +open class EncryptionSupport(private val key: Key = SecretKeySpec(generateAesKey(), "AES")) { + open fun encrypt(data: ByteArray) = encrypt(data, key) + + open fun decrypt(data: ByteArray) = decrypt(data, key) +} + +fun generateAesKey(): ByteArray { + // http://security.stackexchange.com/questions/14068/why-most-people-use-256-bit-encryption-instead-of-128-bit + val bytes = ByteArray(16) + SecureRandom().nextBytes(bytes) + return bytes +} + +private fun encrypt(msgBytes: ByteArray, key: Key): ByteArray { + val ciph = Cipher.getInstance("AES/CBC/PKCS5Padding") + ciph.init(Cipher.ENCRYPT_MODE, key) + val body = ciph.doFinal(msgBytes) + val iv = ciph.iv + + val data = ByteArray(4 + iv.size + body.size) + + val length = body.size + data[0] = (length shr 24 and 0xFF).toByte() + data[1] = (length shr 16 and 0xFF).toByte() + data[2] = (length shr 8 and 0xFF).toByte() + data[3] = (length and 0xFF).toByte() + + System.arraycopy(iv, 0, data, 4, iv.size) + System.arraycopy(body, 0, data, 4 + iv.size, body.size) + return data +} + +private fun decrypt(data: ByteArray, key: Key): ByteArray { + var bodyLength = data[0].toInt() and 0xFF.toInt() + bodyLength = (bodyLength shl 8) + data[1] and 0xFF + bodyLength = (bodyLength shl 8) + data[2] and 0xFF + bodyLength = (bodyLength shl 8) + data[3] and 0xFF + + val ivlength = data.size - 4 - bodyLength + + val ciph = Cipher.getInstance("AES/CBC/PKCS5Padding") + ciph.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(data, 4, ivlength)) + return ciph.doFinal(data, 4 + ivlength, bodyLength) +} \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/util/proxy/PropertiesEncryptionSupport.java b/platform/platform-api/src/com/intellij/util/proxy/PropertiesEncryptionSupport.java index 0fc4e8478e6d..c186f918633b 100644 --- a/platform/platform-api/src/com/intellij/util/proxy/PropertiesEncryptionSupport.java +++ b/platform/platform-api/src/com/intellij/util/proxy/PropertiesEncryptionSupport.java @@ -16,16 +16,13 @@ package com.intellij.util.proxy; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.EncryptionSupport; 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; /** @@ -33,25 +30,24 @@ import java.util.Properties; * Date: 08-Jul-16 */ public class PropertiesEncryptionSupport { - private final Key myKey; + private final EncryptionSupport myEncryptionSupport; public PropertiesEncryptionSupport(Key key) { - myKey = key; + myEncryptionSupport = new EncryptionSupport(key); } public PropertiesEncryptionSupport() { - this(generateKey()); - } - - public static Key generateKey() { - final byte[] bytes = new byte[16]; - new SecureRandom().nextBytes(bytes); - return new SecretKeySpec(bytes, "AES"); + myEncryptionSupport = new EncryptionSupport(); } @NotNull public Properties load(@NotNull File file) throws Exception { - final byte[] bytes = decrypt(FileUtil.loadFileBytes(file)); + return load(FileUtil.loadFileBytes(file)); + } + + @NotNull + public Properties load(@NotNull byte[] data) throws Exception { + final byte[] bytes = myEncryptionSupport.decrypt(data); final Properties props = new Properties(); props.load(new ByteArrayInputStream(bytes)); return props; @@ -61,7 +57,7 @@ public class PropertiesEncryptionSupport { final ByteArrayOutputStream out = new ByteArrayOutputStream(); props.store(out, comments); out.close(); - FileUtil.writeToFile(file, encrypt(out.toByteArray())); + FileUtil.writeToFile(file, myEncryptionSupport.encrypt(out.toByteArray())); } public byte[] encrypt(byte[] bytes) throws Exception { diff --git a/platform/platform-impl/src/com/intellij/ide/ApplicationLoadListener.java b/platform/platform-impl/src/com/intellij/ide/ApplicationLoadListener.java index 47b11480857f..fe81280cb51b 100644 --- a/platform/platform-impl/src/com/intellij/ide/ApplicationLoadListener.java +++ b/platform/platform-impl/src/com/intellij/ide/ApplicationLoadListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 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. @@ -22,5 +22,9 @@ import org.jetbrains.annotations.NotNull; public interface ApplicationLoadListener { ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.ApplicationLoadListener"); - void beforeApplicationLoaded(@NotNull Application application, @NotNull String configPath); + default void beforeApplicationLoaded(@NotNull Application application, @NotNull String configPath) { + } + + default void beforeComponentsCreated() { + } } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeConfigurable.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeConfigurable.java index b4b9b1a78106..6091b8c5bc3f 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeConfigurable.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeConfigurable.java @@ -1,6 +1,20 @@ +/* + * 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.ide.passwordSafe.config; -import com.intellij.ide.passwordSafe.PasswordSafe; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SearchableConfigurable; @@ -17,10 +31,7 @@ public class PasswordSafeConfigurable implements SearchableConfigurable, Configu * The settings for the password safe */ final PasswordSafeSettings mySettings; - /** - * The password safe service - */ - private final PasswordSafe myPasswordSafe; + /** * The option panel to use */ @@ -31,9 +42,8 @@ public class PasswordSafeConfigurable implements SearchableConfigurable, Configu * * @param settings the password safe settings */ - public PasswordSafeConfigurable(@NotNull PasswordSafeSettings settings, @NotNull PasswordSafe passwordSafe) { + public PasswordSafeConfigurable(@NotNull PasswordSafeSettings settings) { mySettings = settings; - myPasswordSafe = passwordSafe; } /** @@ -55,9 +65,9 @@ public class PasswordSafeConfigurable implements SearchableConfigurable, Configu * {@inheritDoc} */ public JComponent createComponent() { - myPanel = new PasswordSafeOptionsPanel(myPasswordSafe); + myPanel = new PasswordSafeOptionsPanel(); myPanel.reset(mySettings); - return myPanel.getRoot(); //To change body of implemented methods use File | Settings | File Templates. + return myPanel.getRoot(); } /** diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.form b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.form index b2db444be071..2f1bcd8086e1 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.form +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.form @@ -1,6 +1,6 @@
- + @@ -56,42 +56,9 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.java index d8b52bdead19..44ce8e4aa563 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.java @@ -15,21 +15,12 @@ */ package com.intellij.ide.passwordSafe.config; -import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; -import com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterPasswordDialog; -import com.intellij.ui.JBColor; -import com.intellij.ui.components.JBLabel; - import javax.swing.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; /** * The option panel for password safe */ public class PasswordSafeOptionsPanel { - private final PasswordSafeImpl myPasswordSafe; /** * The password storage policy option */ @@ -42,46 +33,17 @@ public class PasswordSafeOptionsPanel { * The password storage policy option */ private JRadioButton mySaveOnDiskRadioButton; - /** - * The change password button - */ - private JButton myManagePasswordButton; + /** * The root panel */ private JPanel myRoot; - private JBLabel myMasterPasswordStateLabel; - /** - * The constructor - * - * @param passwordSafe the password safe service instance - */ - public PasswordSafeOptionsPanel(PasswordSafe passwordSafe) { - myPasswordSafe = (PasswordSafeImpl)passwordSafe; - myMasterPasswordStateLabel.setForeground(JBColor.BLUE); - updateMasterPasswordState(); - myManagePasswordButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (myPasswordSafe.getMasterKeyProvider().isEmpty()) { - MasterPasswordDialog.resetMasterPasswordDialog(null, myPasswordSafe.getMasterKeyProvider()).show(); - } - else { - MasterPasswordDialog.changeMasterPasswordDialog(null, myPasswordSafe.getMasterKeyProvider()).show(); - } - updateMasterPasswordState(); - } - }); - } - - private void updateMasterPasswordState() { - boolean empty = myPasswordSafe.getMasterKeyProvider().isMasterPasswordEnabled(); - myMasterPasswordStateLabel.setText(empty ? "Disabled" : "Enabled"); + public PasswordSafeOptionsPanel() { } public void reset(PasswordSafeSettings settings) { PasswordSafeSettings.ProviderType t = settings.getProviderType(); - updateMasterPasswordState(); switch (t) { case DO_NOT_STORE: myDoNotRememberPasswordsRadioButton.setSelected(true); diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeImpl.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeImpl.java index 39efd68b0113..561b956121ea 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeImpl.java @@ -18,26 +18,27 @@ package com.intellij.ide.passwordSafe.impl; import com.intellij.ide.passwordSafe.PasswordSafe; import com.intellij.ide.passwordSafe.PasswordSafeException; import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; -import com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterKeyPasswordSafe; -import com.intellij.ide.passwordSafe.impl.providers.masterKey.PasswordDatabase; import com.intellij.ide.passwordSafe.impl.providers.memory.MemoryPasswordSafe; import com.intellij.ide.passwordSafe.impl.providers.nil.NilProvider; +import com.intellij.ide.passwordSafe.masterKey.DbV1ConvertorKt; +import com.intellij.ide.passwordSafe.masterKey.FilePasswordSafeProvider; +import com.intellij.openapi.components.SettingsSavingComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class PasswordSafeImpl extends PasswordSafe { +public class PasswordSafeImpl extends PasswordSafe implements SettingsSavingComponent { private static final Logger LOG = Logger.getInstance(PasswordSafeImpl.class); private final PasswordSafeSettings mySettings; - private final MasterKeyPasswordSafe myMasterKeyProvider; + private final FilePasswordSafeProvider myMasterKeyProvider; private final NilProvider myNilProvider; private final MemoryPasswordSafe myMemoryProvider; - public PasswordSafeImpl(PasswordSafeSettings settings, PasswordDatabase database) { + public PasswordSafeImpl(@NotNull PasswordSafeSettings settings) { mySettings = settings; - myMasterKeyProvider = new MasterKeyPasswordSafe(database); + myMasterKeyProvider = new FilePasswordSafeProvider(DbV1ConvertorKt.convertOldDb()); myNilProvider = new NilProvider(); myMemoryProvider = new MemoryPasswordSafe(); } @@ -71,7 +72,7 @@ public class PasswordSafeImpl extends PasswordSafe { } @Nullable - public String getPassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { + public String getPassword(@Nullable Project project, @Nullable Class requester, @NotNull String key) throws PasswordSafeException { if (mySettings.getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) { String password = getMemoryProvider().getPassword(project, requester, key); if (password == null) { @@ -86,28 +87,30 @@ public class PasswordSafeImpl extends PasswordSafe { return provider().getPassword(project, requester, key); } - public void removePassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { + public void removePassword(@Nullable Project project, @Nullable Class requester, String key) throws PasswordSafeException { if (mySettings.getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) { getMemoryProvider().removePassword(project, requester, key); } provider().removePassword(project, requester, key); } - public void storePassword(@Nullable Project project, @NotNull Class requester, String key, String value) throws PasswordSafeException { + public void storePassword(@Nullable Project project, @Nullable Class requester, String key, String value) throws PasswordSafeException { if (mySettings.getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) { getMemoryProvider().storePassword(project, requester, key, value); } provider().storePassword(project, requester, key, value); } - /** - * @return get master key provider instance (used for configuration specific to this provider) - */ - public MasterKeyPasswordSafe getMasterKeyProvider() { + public PasswordSafeProvider getMasterKeyProvider() { return myMasterKeyProvider; } public MemoryPasswordSafe getMemoryProvider() { return myMemoryProvider; } + + @Override + public void save() { + myMasterKeyProvider.save(); + } } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeProvider.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeProvider.java index 26ef98ec1ed3..f0f076764c81 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeProvider.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeProvider.java @@ -27,10 +27,6 @@ public abstract class PasswordSafeProvider implements PasswordStorage { public boolean isSupported() { return true; } - /** - * @return the description of the provider - */ - public abstract String getDescription(); /** * @return the name of provider diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java index cde1dd1288d8..fa1baeb5f733 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java @@ -37,19 +37,18 @@ public abstract class BasePasswordSafeProvider extends PasswordSafeProvider { * So make sure not to call it from the read action. * Calling this method from the dispatch thread is allowed.

* - * @param project the project to use - * @param requestor * @return the secret key to use * @throws PasswordSafeException in case of problems with access to the password database. * @throws IllegalStateException if the method is called from the read action. */ @NotNull - protected abstract byte[] key(@Nullable Project project, @NotNull Class requestor); + protected abstract byte[] key(); @Nullable - public String getPassword(@Nullable Project project, @NotNull Class requestor, String key) { - byte[] ct = getEncryptedPassword(dbKey(project, requestor, key)); - return ct == null ? null : EncryptionUtil.decryptText(key(project, requestor), ct); + public String getPassword(@Nullable Project project, @Nullable Class requestor, @NotNull String key) { + byte[] masterKey = key(); + byte[] encryptedPassword = getEncryptedPassword(EncryptionUtil.dbKey(masterKey, requestor, key)); + return encryptedPassword == null ? null : EncryptionUtil.decryptText(masterKey, encryptedPassword); } /** @@ -63,18 +62,17 @@ public abstract class BasePasswordSafeProvider extends PasswordSafeProvider { /** * Get database key * - * @param project * @param requestor the requestor class * @param key the key to use * @return the key to use for map */ @NotNull - private byte[] dbKey(@Nullable Project project, Class requestor, String key) { - return EncryptionUtil.dbKey(key(project, requestor), requestor, key); + private byte[] dbKey(@Nullable Class requestor, String key) { + return EncryptionUtil.dbKey(key(), requestor, key); } - public void removePassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { - byte[] k = dbKey(project, requester, key); + public void removePassword(@Nullable Project project, @Nullable Class requestor, String key) throws PasswordSafeException { + byte[] k = dbKey(requestor, key); removeEncryptedPassword(k); } @@ -85,9 +83,9 @@ public abstract class BasePasswordSafeProvider extends PasswordSafeProvider { */ protected abstract void removeEncryptedPassword(byte[] key); - public void storePassword(@Nullable Project project, @NotNull Class requestor, String key, String value) throws PasswordSafeException { - byte[] k = dbKey(project, requestor, key); - byte[] ct = EncryptionUtil.encryptText(key(project, requestor), value); + public void storePassword(@Nullable Project project, @Nullable Class requestor, String key, String value) throws PasswordSafeException { + byte[] k = dbKey(requestor, key); + byte[] ct = EncryptionUtil.encryptText(key(), value); storeEncryptedPassword(k, ct); } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java index 35c1ee376eea..986857db1c59 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java @@ -17,6 +17,7 @@ package com.intellij.ide.passwordSafe.impl.providers; import com.intellij.openapi.vfs.CharsetToolkit; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; @@ -81,8 +82,8 @@ public class EncryptionUtil { * @param key the key * @return the raw key bytes */ - static byte[] rawKey(Class requester, String key) { - return hash(getUTF8Bytes(requester.getName() + "/" + key)); + static byte[] rawKey(@Nullable Class requester, String key) { + return hash(getUTF8Bytes((requester == null ? "" : (requester.getName() + "/")) + key)); } /** @@ -105,7 +106,7 @@ public class EncryptionUtil { * @param password the password to use * @return the generated key */ - public static byte[] genPasswordKey(String password) { + public static byte[] genPasswordKey(@NotNull String password) { return genKey(hash(getUTF8Bytes(password))); } @@ -138,7 +139,7 @@ public class EncryptionUtil { * @return the key to use in the database */ @NotNull - public static byte[] dbKey(@NotNull byte[] password, Class requestor, String key) { + public static byte[] dbKey(@NotNull byte[] password, @Nullable Class requestor, String key) { return encryptKey(password, rawKey(requestor, key)); } @@ -211,14 +212,6 @@ public class EncryptionUtil { } } - - /** - * Encrypt text - * - * @param password the secret key to use - * @param data the bytes to decrypt - * @return encrypted text - */ @NotNull public static String decryptText(byte[] password, byte[] data) { byte[] plain = decryptData(password, data); diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/ChangePasswordComponent.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/ChangePasswordComponent.java deleted file mode 100644 index 6e4ef44c232b..000000000000 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/ChangePasswordComponent.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2000-2014 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.ide.passwordSafe.impl.providers.masterKey; - -import com.intellij.ide.passwordSafe.HelpID; - -/** - * @author gregsh - */ -public class ChangePasswordComponent extends PasswordComponentBase { - - - public ChangePasswordComponent(MasterKeyPasswordSafe safe) { - super(safe, "Change"); - myPromptLabel.setText("Enter the current password and the new password
" + - "in order to change the master password."); - } - - @Override - public String getHelpId() { - return HelpID.CHANGE_PASSWORD; - } - - @Override - public boolean apply() { - String o = new String(myPasswordField.getPassword()); - String n = new String(myNewPasswordField.getPassword()); - return mySafe.changeMasterPassword(o, n, myEncryptCheckBox.isSelected()); - } -} diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/EnterPasswordComponent.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/EnterPasswordComponent.java index d5f164c5108a..dc0b91381664 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/EnterPasswordComponent.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/EnterPasswordComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 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. @@ -16,24 +16,34 @@ package com.intellij.ide.passwordSafe.impl.providers.masterKey; import com.intellij.ide.passwordSafe.HelpID; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; +import java.util.function.Function; + /** * @author gregsh */ public class EnterPasswordComponent extends PasswordComponentBase { + @NotNull + private final Function myPasswordConsumer; + + public EnterPasswordComponent(@NotNull Function passwordConsumer) { + super("Enter"); + + myPasswordConsumer = passwordConsumer; - public EnterPasswordComponent(@NotNull MasterKeyPasswordSafe safe, @NotNull Class requestor) { - super(safe, "Enter"); - String requestorName = getRequestorTitle(requestor); myPromptLabel.setText("
Master password is required to unlock the password database.
" + "The password database will be unlocked during this session
" + - "for all subsystems.
" + - "
Requested by: " + requestorName + ""); + "for all subsystems."); UIUtil.setEnabled(myNewPasswordPanel, false, true); myNewPasswordPanel.setVisible(false); + + if (ApplicationManager.getApplication().isUnitTestMode()) { + myPasswordField.setText("pass"); + } } @Override @@ -42,9 +52,13 @@ public class EnterPasswordComponent extends PasswordComponentBase { } @Override - public boolean apply() { + public ValidationInfo apply() { + // enter password — only and only old key, so, we use EncryptionUtil.genPasswordKey String password = new String(myPasswordField.getPassword()); - return mySafe.changeMasterPassword(password, password, myEncryptCheckBox.isSelected()); + if (!myPasswordConsumer.apply(password)) { + return new ValidationInfo("Password is incorrect", myPasswordField); + } + return null; } @Override diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/FilePasswordSafeProvider.kt b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/FilePasswordSafeProvider.kt new file mode 100644 index 000000000000..18661dc9b670 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/FilePasswordSafeProvider.kt @@ -0,0 +1,182 @@ +/* + * 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.ide.passwordSafe.masterKey + +import com.intellij.ide.passwordSafe.impl.PasswordSafeProvider +import com.intellij.ide.passwordSafe.impl.providers.masterKey.windows.WindowsCryptUtils +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.io.setOwnerPermissions +import com.intellij.util.* +import java.io.DataInputStream +import java.io.DataOutputStream +import java.nio.file.Files +import java.nio.file.NoSuchFileException +import java.nio.file.Paths +import java.nio.file.StandardCopyOption +import java.security.Key +import java.security.MessageDigest +import java.security.SecureRandom +import java.util.Base64 +import java.util.concurrent.ConcurrentHashMap +import javax.crypto.spec.SecretKeySpec + +internal val LOG = Logger.getInstance(FilePasswordSafeProvider::class.java) + +class FilePasswordSafeProvider(keyToValue: Map? = null) : PasswordSafeProvider() { + private val db = ConcurrentHashMap() + + private val dbFile = Paths.get(PathManager.getConfigPath(), "pdb") + private val masterKeyStorage = MasterKeyFileStorage() + + private var encryptionSupport: EncryptionSupport? = null + + private var isNeedToSave = false + + init { + if (keyToValue == null) { + init() + } + else { + db.putAll(keyToValue) + isNeedToSave = true + } + } + + @Synchronized + private fun init() { + val masterKey = masterKeyStorage.get() ?: return + encryptionSupport = EncryptionSupport(SecretKeySpec(masterKey, "AES")) + + val data: ByteArray + try { + data = encryptionSupport!!.decrypt(dbFile.readBytes()) + } + catch (e: NoSuchFileException) { + LOG.warn("key file exists, but db file not") + return + } + + val input = DataInputStream(data.inputStream()) + while (input.available() > 0) { + db.put(input.readUTF(), input.readUTF()) + } + } + + @Synchronized + fun save() { + if (!isNeedToSave) { + return + } + + if (encryptionSupport == null) { + val masterKey = generateAesKey() + encryptionSupport = EncryptionSupport(SecretKeySpec(masterKey, "AES")) + masterKeyStorage.set(masterKey) + } + + val tempFile = Paths.get(PathManager.getConfigPath(), "pdb.pwd.tmp") + DataOutputStream(tempFile.outputStream()).use { out -> + for ((key, value) in db) { + out.writeUTF(key) + out.writeUTF(value) + } + } + + Files.move(tempFile, dbFile, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING) + dbFile.setOwnerPermissions() + } + + override fun getPassword(project: Project?, requestor: Class<*>?, key: String): String? { + val rawKey = getRawKey(key, requestor) + // try old key - as hash + val value = db.get(rawKey) ?: db.remove(toOldKey(MessageDigest.getInstance("SHA-256").digest(rawKey.toByteArray()))) + return value + } + + override fun storePassword(project: Project?, requestor: Class<*>?, key: String?, value: String?) { + val rawKey = getRawKey(key, requestor) + if (value == null) { + if (db.remove(rawKey) != null) { + isNeedToSave = true + } + } + else { + db.put(rawKey, value) + } + } + + override fun getName() = "File PasswordSafe" +} + +private fun getRawKey(key: String?, requestor: Class<*>?) = "${if (requestor == null) "" else "${requestor.name}/"}$key" + +internal fun generate(): ByteArray { + val bytes = ByteArray(16) + SecureRandom().nextBytes(bytes) + return bytes +} + +interface MasterKeyStorage { + fun get(): ByteArray? + + fun set(key: ByteArray) +} + +class WindowsEncryptionSupport(key: Key): EncryptionSupport(key) { + override fun encrypt(data: ByteArray) = WindowsCryptUtils.protect(super.encrypt(data)) + + override fun decrypt(data: ByteArray) = WindowsCryptUtils.unprotect(super.decrypt(data)) +} + +class MasterKeyFileStorage : MasterKeyStorage { + private val encryptionSupport: EncryptionSupport + private val passwordFile = Paths.get(PathManager.getConfigPath(), "pdb.pwd") + + init { + val key = SecretKeySpec(byteArrayOf( + 0x50, 0x72, 0x6f.toByte(), 0x78.toByte(), 0x79.toByte(), 0x20.toByte(), + 0x43.toByte(), 0x6f.toByte(), 0x6e.toByte(), 0x66.toByte(), 0x69.toByte(), 0x67.toByte(), + 0x20.toByte(), 0x53.toByte(), 0x65.toByte(), 0x63.toByte()), "AES") + + encryptionSupport = if (SystemInfo.isWindows) WindowsEncryptionSupport(key) else EncryptionSupport(key) + } + + override fun get(): ByteArray? { + val data: ByteArray + try { + data = passwordFile.readBytes() + } + catch (e: NoSuchFileException) { + return null + } + + try { + return encryptionSupport.decrypt(data) + } + catch (e: Exception) { + LOG.warn("Cannot decrypt master key, file content: ${Base64.getEncoder().encodeToString(data)}", e) + return null + } + } + + override fun set(key: ByteArray) { + passwordFile.write(encryptionSupport.encrypt(key)) + passwordFile.setOwnerPermissions() + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java deleted file mode 100644 index 42fc03bfd22a..000000000000 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * 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.ide.passwordSafe.impl.providers.masterKey; - -import com.intellij.concurrency.AsyncFutureFactory; -import com.intellij.concurrency.AsyncFutureResult; -import com.intellij.ide.passwordSafe.MasterPasswordUnavailableException; -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.ide.passwordSafe.impl.PasswordSafeTimed; -import com.intellij.ide.passwordSafe.impl.providers.BasePasswordSafeProvider; -import com.intellij.ide.passwordSafe.impl.providers.ByteArrayWrapper; -import com.intellij.ide.passwordSafe.impl.providers.EncryptionUtil; -import com.intellij.ide.passwordSafe.impl.providers.masterKey.windows.WindowsCryptUtils; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressIndicatorProvider; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.util.ArrayUtil; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutionException; - -/** - * The password safe that stores information in configuration file encrypted by master password - */ -public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { - private static final String TEST_PASSWORD_KEY = "TEST_PASSWORD:"; - private static final String TEST_PASSWORD_VALUE = "test password"; - - private final PasswordDatabase myDatabase; - private final transient PasswordSafeTimed> myKey = new PasswordSafeTimed>() { - @Override - protected Ref compute() { - return Ref.create(); - } - - @Override - protected int getMinutesToLive() { - return Registry.intValue("passwordSafe.masterPassword.ttl"); - } - }; - - public MasterKeyPasswordSafe(PasswordDatabase database) { - myDatabase = database; - } - - /** - * Reset password for the password safe (clears password database). The method is used from plugin's UI. - * - * @param password the password to set - * @param encrypt if the password should be encrypted an stored is master database - */ - void resetMasterPassword(String password, boolean encrypt) { - myKey.get().set(EncryptionUtil.genPasswordKey(password)); - myDatabase.clear(); - try { - storePassword(null, MasterKeyPasswordSafe.class, testKey(password), TEST_PASSWORD_VALUE); - if (encrypt) { - myDatabase.setPasswordInfo(encryptPassword(password)); - } - else { - myDatabase.setPasswordInfo(ArrayUtil.EMPTY_BYTE_ARRAY); - } - } - catch (PasswordSafeException e) { - throw new IllegalStateException("There should be no problem with password at this point", e); - } - } - - /** - * Set password to use (used from plugin's UI) - * - * @param password the password - * @return true, if password is a correct one - */ - boolean setMasterPassword(String password) { - Object savedKey = myKey.get().get(); - myKey.get().set(EncryptionUtil.genPasswordKey(password)); - String rc; - try { - rc = getPassword(null, MasterKeyPasswordSafe.class, testKey(password)); - } - catch (PasswordSafeException e) { - throw new IllegalStateException("There should be no problem with password at this point", e); - } - if (!TEST_PASSWORD_VALUE.equals(rc)) { - myKey.get().set(savedKey); - return false; - } - else { - return true; - } - } - - /** - * Encrypt database with new password - * - * @param oldPassword the old password - * @param newPassword the new password - * @return re-encrypted database - */ - boolean changeMasterPassword(String oldPassword, String newPassword, boolean encrypt) { - if (!setMasterPassword(oldPassword)) { - return false; - } - byte[] oldKey = (byte[])myKey.get().get(); // set right in the previous call - byte[] newKey = EncryptionUtil.genPasswordKey(newPassword); - ByteArrayWrapper testKey = new ByteArrayWrapper(EncryptionUtil.dbKey(oldKey, MasterKeyPasswordSafe.class, testKey(oldPassword))); - HashMap oldDb = new HashMap(); - myDatabase.copyTo(oldDb); - HashMap newDb = new HashMap(); - for (Map.Entry e : oldDb.entrySet()) { - if (testKey.equals(e.getKey())) { - continue; - } - byte[] decryptedKey = EncryptionUtil.decryptKey(oldKey, e.getKey().unwrap()); - String decryptedText = EncryptionUtil.decryptText(oldKey, e.getValue()); - newDb.put(new ByteArrayWrapper(EncryptionUtil.encryptKey(newKey, decryptedKey)), EncryptionUtil.encryptText(newKey, decryptedText)); - } - synchronized (myDatabase.getDbLock()) { - resetMasterPassword(newPassword, encrypt); - myDatabase.putAll(newDb); - } - return true; - } - - - private static String testKey(String password) { - return TEST_PASSWORD_KEY + password; - } - - @NotNull - @Override - protected byte[] key(@Nullable final Project project, @NotNull final Class requestor) { - Object key = myKey.get().get(); - if (key instanceof byte[]) { - return (byte[])key; - } - - if (key instanceof PasswordSafeException && ((PasswordSafeException)key).justHappened()) { - throw (PasswordSafeException)key; - } - - if (SystemInfo.isWindows) { - try { - setMasterPassword(new String(WindowsCryptUtils.unprotect(myDatabase.getPasswordInfo()), CharsetToolkit.UTF8_CHARSET)); - key = myKey.get().get(); - if (key instanceof byte[]) { - return (byte[])key; - } - } - catch (Exception ignored) { - // ignore exception and ask password - } - } - - key = invokeAndWait(() -> { - Object key1 = myKey.get().get(); - if (key1 instanceof byte[] || key1 instanceof PasswordSafeException && ((PasswordSafeException)key1).justHappened()) { - return key1; - } - try { - if (!myDatabase.isEmpty()) { - MasterPasswordDialog.askPassword(project, this, requestor); - } - else if (!MasterPasswordDialog.resetMasterPasswordDialog(project, this).showAndGet()) { - throw new MasterPasswordUnavailableException("Master password is required to store passwords in the database."); - } - } - catch (PasswordSafeException e) { - myKey.get().set(e); - throw e; - } - return myKey.get().get(); - }, project == null ? Conditions.alwaysFalse() : project.getDisposed()); - if (key instanceof byte[]) { - return (byte[])key; - } - if (key instanceof PasswordSafeException) { - throw (PasswordSafeException)key; - } - - throw new AssertionError(); - } - - private static final Object ourEDTLock = new Object(); - public T invokeAndWait(@NotNull final ThrowableComputable computable, @NotNull final Condition expired) throws E { - if (ApplicationManager.getApplication().isDispatchThread()) { - return computable.compute(); - } - - final AsyncFutureResult future = AsyncFutureFactory.getInstance().createAsyncFutureResult(); - final ExpirableRunnable runnable = new ExpirableRunnable() { - @Override - public boolean isExpired() { - boolean b = expired.value(null); - if (b) future.setException(new ProcessCanceledException()); - return b; - } - - @Override - public void run() { - try { - future.set(computable.compute()); - } - catch (Throwable e) { - future.setException(e); - } - } - }; - ProgressIndicator indicator = ProgressIndicatorProvider.getGlobalProgressIndicator(); - synchronized (ourEDTLock) { - if (indicator != null && indicator.isModal()) { - UIUtil.invokeLaterIfNeeded(() -> { - if (!runnable.isExpired()) { - runnable.run(); - } - }); - } - else { - IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(runnable); - } - try { - return future.get(); - } - catch (InterruptedException e) { - throw new ProcessCanceledException(e); - } - catch (ExecutionException e) { - throw (E) e.getCause(); - } - } - } - - @Override - public String getPassword(@Nullable Project project, @NotNull Class requestor, String key) throws PasswordSafeException { - return myDatabase.isEmpty() ? null : super.getPassword(project, requestor, key); - } - - @Override - public void removePassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { - if (myDatabase.isEmpty()) { - return; - } - super.removePassword(project, requester, key); - } - - @Override - protected byte[] getEncryptedPassword(@NotNull byte[] key) { - return myDatabase.get(key); - } - - @Override - protected void removeEncryptedPassword(byte[] key) { - myDatabase.remove(key); - } - - @Override - protected void storeEncryptedPassword(byte[] key, byte[] encryptedPassword) { - myDatabase.put(key, encryptedPassword); - } - - @Override - public boolean isSupported() { - return !ApplicationManager.getApplication().isHeadlessEnvironment(); - } - - @Override - public String getDescription() { - return "This provider stores passwords in IDEA config and uses master password to encrypt other passwords. " + - "The passwords for the same resources are shared between different projects."; - } - - @Override - public String getName() { - return "Master Key PasswordSafe"; - } - - - public boolean isMasterPasswordEnabled() { - return setMasterPassword(""); - } - - public boolean isOsProtectedPasswordSupported() { - return SystemInfo.isWindows; - } - - /** - * Encrypt master password - * - * @param pw the password to encrypt - * @return the encrypted password - * @throws MasterPasswordUnavailableException - * if encryption fails - */ - private static byte[] encryptPassword(String pw) throws MasterPasswordUnavailableException { - assert SystemInfo.isWindows; - return WindowsCryptUtils.protect(EncryptionUtil.getUTF8Bytes(pw)); - } - - public boolean isPasswordEncrypted() { - if (!isOsProtectedPasswordSupported()) { - return false; - } - - byte[] info = myDatabase.getPasswordInfo(); - return info != null && info.length > 0; - } - - public boolean isEmpty() { - return myDatabase.isEmpty(); - } -} diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordDialog.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordDialog.java index 8478ab7701d7..3f7b52470185 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordDialog.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordDialog.java @@ -15,9 +15,6 @@ */ package com.intellij.ide.passwordSafe.impl.providers.masterKey; -import com.intellij.ide.passwordSafe.MasterPasswordUnavailableException; -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.text.StringUtil; @@ -29,7 +26,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; -import java.util.Arrays; import java.util.List; /** @@ -43,51 +39,12 @@ public class MasterPasswordDialog extends DialogWrapper { private final DialogWrapperAction myCardAction; private int myRetriesCount; - /** - * Ask password from user and set it to password safe instance - * - * @param project the current project - * @param safe the password safe - * @param requestor - * @throws PasswordSafeException if the master password is not provided. - */ - public static void askPassword(@Nullable Project project, @NotNull MasterKeyPasswordSafe safe, @NotNull Class requestor) - throws PasswordSafeException { - // trying empty password: people who have set up empty password, don't want to get disturbed by the prompt. - if (safe.setMasterPassword("")) { - return; - } - - if (!enterMasterPasswordDialog(project, safe, requestor).showAndGet()) { - throw new MasterPasswordUnavailableException(PasswordComponentBase.getRequestorTitle(requestor) + ": Cancelled by user"); - } - } - - public static MasterPasswordDialog resetMasterPasswordDialog(@Nullable Project project, - @NotNull MasterKeyPasswordSafe safe) { - return new MasterPasswordDialog(project, new ResetPasswordComponent(safe, true)); - } - - public static MasterPasswordDialog changeMasterPasswordDialog(@Nullable Project project, - @NotNull MasterKeyPasswordSafe safe) { - return new MasterPasswordDialog(project, new ChangePasswordComponent(safe), new ResetPasswordComponent(safe, false)); - } - - public static MasterPasswordDialog enterMasterPasswordDialog(@Nullable Project project, - @NotNull MasterKeyPasswordSafe safe, - @NotNull Class requestor) { - return new MasterPasswordDialog(project, new EnterPasswordComponent(safe, requestor), new ResetPasswordComponent(safe, false)); - } - - protected MasterPasswordDialog(@Nullable Project project, PasswordComponentBase... components) { - super(project, false); + public MasterPasswordDialog(@NotNull PasswordComponentBase component) { + super(false); setResizable(false); - assert components.length > 0; - myComponents.addAll(Arrays.asList(components)); - for (PasswordComponentBase component : myComponents) { - myRootPanel.add(component.getComponent(), component.getTitle()); - } + myComponents.add(component); + myRootPanel.add(component.getComponent(), component.getTitle()); myCardAction = new DialogWrapperAction("") { @Override protected void doAction(ActionEvent e) { @@ -160,21 +117,19 @@ public class MasterPasswordDialog extends DialogWrapper { } @Override - protected void doOKAction() { + public void doOKAction() { PasswordComponentBase component = getSelectedComponent(); - if (component.apply()) { + ValidationInfo info = component.apply(); + if (info == null) { super.doOKAction(); } else { - ValidationInfo info = component.validatePassword(); - if (info != null) { - setErrorText(info.message + " " + StringUtil.repeat(".", myRetriesCount)); - if (info.component != null) { - info.component.requestFocus(); - } - if (++myRetriesCount > NUMBER_OF_RETRIES) { - super.doCancelAction(); - } + setErrorText(info.message + " " + StringUtil.repeat(".", myRetriesCount)); + if (info.component != null) { + info.component.requestFocus(); + } + if (++myRetriesCount > NUMBER_OF_RETRIES) { + super.doCancelAction(); } } } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.form b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.form index d1a28d249433..f088f62aa219 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.form +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.form @@ -41,7 +41,7 @@ - + @@ -83,16 +83,6 @@ - - - - - - - - - - diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.java index 453fd0f133ac..ca3a8ceb8759 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 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. @@ -16,7 +16,6 @@ package com.intellij.ide.passwordSafe.impl.providers.masterKey; import com.intellij.icons.AllIcons; -import com.intellij.ide.TypePresentationService; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -29,7 +28,6 @@ import java.util.Arrays; * @author gregsh */ public abstract class PasswordComponentBase { - protected final MasterKeyPasswordSafe mySafe; private final String myTitle; private JPanel myRootPanel; @@ -41,27 +39,16 @@ public abstract class PasswordComponentBase { protected JPasswordField myPasswordField; protected JPasswordField myNewPasswordField; protected JPasswordField myConfirmPasswordField; - protected JCheckBox myEncryptCheckBox; protected JLabel myPasswordLabel; protected JLabel myNewPasswordLabel; - - public PasswordComponentBase(@NotNull MasterKeyPasswordSafe safe, @NotNull String title) { - mySafe = safe; + public PasswordComponentBase(@NotNull String title) { myTitle = title; myIconLabel.setText(""); myIconLabel.setIcon(AllIcons.General.PasswordLock); myIconLabel.setDisabledIcon(AllIcons.General.PasswordLock); //myPromptLabel.setUI(new MultiLineLabelUI()); myPromptLabel.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL)); - - if (!safe.isOsProtectedPasswordSupported()) { - myEncryptCheckBox.setSelected(false); - myEncryptCheckBox.setVisible(false); - } - else { - myEncryptCheckBox.setSelected(safe.isPasswordEncrypted()); - } } public JComponent getComponent() { @@ -85,25 +72,10 @@ public abstract class PasswordComponentBase { return null; } - public abstract boolean apply(); + @Nullable + public abstract ValidationInfo apply(); public String getHelpId() { return null; } - - @Nullable - protected ValidationInfo validatePassword() { - if (myPasswordField.isEnabled()) { - String oldPassword = new String(myPasswordField.getPassword()); - if (!mySafe.setMasterPassword(oldPassword)) { - return new ValidationInfo("Password is incorrect", myPasswordField); - } - } - return null; - } - - @NotNull - public static String getRequestorTitle(@NotNull Class requestor) { - return TypePresentationService.getDefaultTypeName(requestor); - } } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordDatabase.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordDatabase.java index 1f2260168c5d..68c632bd2f0e 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordDatabase.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordDatabase.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.intellij.ide.passwordSafe.impl.providers.masterKey; import com.intellij.ide.passwordSafe.impl.providers.ByteArrayWrapper; @@ -22,219 +21,55 @@ import com.intellij.openapi.components.RoamingType; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.diagnostic.Logger; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.binary.Hex; import org.jetbrains.annotations.Nullable; +import javax.xml.bind.DatatypeConverter; import java.util.HashMap; import java.util.Map; -import java.util.TreeMap; -/** - * The password database. The internal component for {@link MasterKeyPasswordSafe}. - */ -@State( - name = "PasswordDatabase", - storages = {@Storage(value = "security.xml", roamingType = RoamingType.DISABLED)} -) +@Deprecated +@State(name = "PasswordDatabase", storages = @Storage(value = "security.xml", roamingType = RoamingType.DISABLED, deprecated = true)) public class PasswordDatabase implements PersistentStateComponent { - /** - * The name of logger - */ private final static Logger LOG = Logger.getInstance(PasswordDatabase.class.getName()); - /** - * The password database - */ - private transient final Map myDatabase = new HashMap(); - /** - * OS-specific information about master password - */ - private transient byte[] myMasterPasswordInfo; - /** - * Clear the password database - */ - public void clear() { - synchronized (myDatabase) { - myDatabase.clear(); - } - } + public transient final Map myDatabase = new HashMap(); + public transient byte[] myMasterPassword; - /** - * @return true if the database is empty - */ - public boolean isEmpty() { - synchronized (myDatabase) { - return myDatabase.isEmpty(); - } - } - - /** - * Put password in the database - * - * @param key the encrypted key - * @param value the encrypted value - */ - public void put(byte[] key, byte[] value) { - synchronized (myDatabase) { - myDatabase.put(new ByteArrayWrapper(key), value); - } - } - - /** - * Get all entries in the database - * - * @param copy the copy to use - */ - public void copyTo(Map copy) { - synchronized (myDatabase) { - copy.putAll(myDatabase); - } - } - - /** - * Put all entries to the database - * - * @param copy the copy to use - */ - public void putAll(Map copy) { - synchronized (myDatabase) { - myDatabase.putAll(copy); - } - } - - - /** - * Get password from the database - * - * @param key the encrypted key - * @return the encrypted value or null - */ - public byte[] get(byte[] key) { - synchronized (myDatabase) { - return myDatabase.get(new ByteArrayWrapper(key)); - } - - } - - /** - * Remove password from the database - * - * @param key the encrypted key - */ - public void remove(byte[] key) { - synchronized (myDatabase) { - myDatabase.remove(new ByteArrayWrapper(key)); - } - } - - /** - * {@inheritDoc} - */ + @Override public State getState() { - TreeMap sorted; - String pi; - synchronized (myDatabase) { - pi = toHex(myMasterPasswordInfo); - sorted = new TreeMap(myDatabase); - } - String[][] db = new String[2][sorted.size()]; - int i = 0; - for (Map.Entry e : sorted.entrySet()) { - db[0][i] = toHex(e.getKey().unwrap()); - db[1][i] = toHex(e.getValue()); - i++; - } - State s = new State(); - s.PASSWORDS = db; - s.MASTER_PASSWORD_INFO = pi; - return s; + return new State(); } - - /** - * Covert bytes to hex - * - * @param bytes bytes to convert - * @return hex representation - */ @Nullable - private static String toHex(byte[] bytes) { - return bytes == null ? null : new String(Hex.encodeHex(bytes)); + private static byte[] fromHex(@Nullable String hex) { + return hex == null ? null : DatatypeConverter.parseHexBinary(hex); } - /** - * Covert hex to bytes - * - * @param hex string to convert - * @return bytes representation - * @throws DecoderException if invalid data encountered - */ - @Nullable - private static byte[] fromHex(String hex) throws DecoderException { - return hex == null ? null : Hex.decodeHex(hex.toCharArray()); - } - - /** - * {@inheritDoc} - */ + @Override public void loadState(State state) { String[][] db = state.PASSWORDS; String pi = state.MASTER_PASSWORD_INFO; - synchronized (myDatabase) { + try { + myMasterPassword = fromHex(pi); + } + catch (Exception e) { + myMasterPassword = null; + } + myDatabase.clear(); + if (db[0].length != db[1].length) { + LOG.warn("The password database is in inconsistent state, ignoring it: " + db[0].length + " != " + db[1].length); + } + int n = db[0].length; + for (int i = 0; i < n; i++) { try { - myMasterPasswordInfo = fromHex(pi); - if (myMasterPasswordInfo == null) { - myMasterPasswordInfo = new byte[0]; + byte[] key = fromHex(db[0][i]); + byte[] value = fromHex(db[1][i]); + if (key != null && value != null) { + myDatabase.put(new ByteArrayWrapper(key), value); } } - catch (DecoderException e) { - myMasterPasswordInfo = new byte[0]; + catch (Exception ignored) { } - myDatabase.clear(); - if (db[0].length != db[1].length) { - LOG.warn("The password database is in inconsistent state, ignoring it: " + db[0].length + " != " + db[1].length); - } - int n = db[0].length; - for (int i = 0; i < n; i++) { - try { - byte[] key = fromHex(db[0][i]); - byte[] value = fromHex(db[1][i]); - if (key != null && value != null) { - myDatabase.put(new ByteArrayWrapper(key), value); - } - } - catch (DecoderException e) { - // skip the entry - } - } - } - } - - /** - * @return the object over which database is synchronized - */ - Object getDbLock() { - return myDatabase; - } - - /** - * @return master password information - */ - byte[] getPasswordInfo() { - synchronized (myDatabase) { - return myMasterPasswordInfo; - } - } - - /** - * Set master password information - * - * @param bytes the bytes for the master password - */ - public void setPasswordInfo(byte[] bytes) { - synchronized (myDatabase) { - myMasterPasswordInfo = bytes; } } @@ -245,7 +80,7 @@ public class PasswordDatabase implements PersistentStateComponent
Specify the new password for the password database.
" + - "Leave blank to disable the master password protection."); - } - else { - myPromptLabel.setText("
The password for the password database will be reset.
" + - "All previously stored passwords will be removed!"); - } - } - - @Override - public String getHelpId() { - return myFirstTime ? HelpID.INIT_PASSWORD : HelpID.RESET_PASSWORD; - } - - @Override - public boolean apply() { - if (myFirstTime || - Messages.showYesNoDialog((Project)null, "All stored passwords will be removed! Are you sure you want to proceed?", - "Confirm Master Password Reset", Messages.getWarningIcon()) == Messages.YES) { - mySafe.resetMasterPassword(new String(myNewPasswordField.getPassword()), myEncryptCheckBox.isSelected()); - ((PasswordSafeImpl)PasswordSafe.getInstance()).getMemoryProvider().clear(); - return true; - } - return false; - } -} diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/dbV1Convertor.kt b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/dbV1Convertor.kt new file mode 100644 index 000000000000..318f58087dba --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/dbV1Convertor.kt @@ -0,0 +1,124 @@ +/* + * 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.ide.passwordSafe.masterKey + +import com.intellij.ide.ApplicationLoadListener +import com.intellij.ide.passwordSafe.PasswordSafe +import com.intellij.ide.passwordSafe.impl.providers.ByteArrayWrapper +import com.intellij.ide.passwordSafe.impl.providers.EncryptionUtil +import com.intellij.ide.passwordSafe.impl.providers.masterKey.EnterPasswordComponent +import com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterPasswordDialog +import com.intellij.ide.passwordSafe.impl.providers.masterKey.PasswordDatabase +import com.intellij.ide.passwordSafe.impl.providers.masterKey.windows.WindowsCryptUtils +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.util.SystemInfo +import gnu.trove.THashMap +import java.util.* +import java.util.function.Function + +private val TEST_PASSWORD_VALUE = "test password" + +internal fun isMasterPasswordValid(password: String, @Suppress("DEPRECATION") db: PasswordDatabase): Boolean { + val key = EncryptionUtil.genPasswordKey(password) + val value = db.myDatabase.get(ByteArrayWrapper(EncryptionUtil.encryptKey(key, rawTestKey(password)))) + if (value != null) { + return EncryptionUtil.decryptText(key, value) == TEST_PASSWORD_VALUE + } + return false +} + +internal fun checkPassAndConvertOldDb(password: String, @Suppress("DEPRECATION") db: PasswordDatabase): Map? { + if (isMasterPasswordValid(password, db)) { + return convertOldDb(password, db) + } + else { + return null + } +} + +fun convertOldDb(@Suppress("DEPRECATION") db: PasswordDatabase): Map? { + if (db.myDatabase.isEmpty()) { + return null + } + + // trying empty password: people who have set up empty password, don't want to get disturbed by the prompt + checkPassAndConvertOldDb("", db)?.let { return it } + + if (SystemInfo.isWindows) { + db.myMasterPassword?.let { + try { + WindowsCryptUtils.unprotect(it).toString(Charsets.UTF_8) + } + catch (e: Exception) { + LOG.warn(e) + null + } + }?.let { + checkPassAndConvertOldDb(it, db)?.let { return it } + } + } + + var result: Map? = null + val dialog = MasterPasswordDialog(EnterPasswordComponent(Function { + result = checkPassAndConvertOldDb(it, db) + result != null + })) + + if (ApplicationManager.getApplication().isUnitTestMode) { + dialog.doOKAction() + dialog.close(DialogWrapper.OK_EXIT_CODE) + } + else if (!dialog.showAndGet()) { + LOG.warn("User cancelled master password dialog, will be recreated") + } + return result +} + +internal fun convertOldDb(oldKey: String, @Suppress("DEPRECATION") db: PasswordDatabase): Map { + val oldKeyB = EncryptionUtil.genPasswordKey(oldKey) + val testKey = ByteArrayWrapper(EncryptionUtil.encryptKey(oldKeyB, rawTestKey(oldKey))) + val newDb = THashMap(db.myDatabase.size) + for ((key, value) in db.myDatabase) { + if (testKey == key) { + continue + } + + // in old db we cannot get key value - it is hashed, so, we store it as a base64 encoded in the new DB + newDb.put(toOldKey(EncryptionUtil.decryptKey(oldKeyB, key.unwrap())), EncryptionUtil.decryptText(oldKeyB, value)) + } + return newDb +} + +fun toOldKey(hash: ByteArray) = "old-hashed-key|" + Base64.getEncoder().encodeToString(hash) + +private fun rawTestKey(oldKey: String) = EncryptionUtil.hash("com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterKeyPasswordSafe/TEST_PASSWORD:${oldKey}".toByteArray()) + +fun convertOldDb(): Map? { + @Suppress("DEPRECATION") + val oldDb = ServiceManager.getService(PasswordDatabase::class.java) + if (oldDb.myDatabase.isNotEmpty()) { + return convertOldDb(oldDb) + } + return null +} + +internal class PasswordDatabaseConvertor : ApplicationLoadListener { + override fun beforeComponentsCreated() { + PasswordSafe.getInstance() + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/memory/MemoryPasswordSafe.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/memory/MemoryPasswordSafe.java index fc0a57441688..e50877515cb9 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/memory/MemoryPasswordSafe.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/memory/MemoryPasswordSafe.java @@ -19,7 +19,6 @@ import com.intellij.ide.passwordSafe.impl.PasswordSafeTimed; import com.intellij.ide.passwordSafe.impl.providers.BasePasswordSafeProvider; import com.intellij.ide.passwordSafe.impl.providers.ByteArrayWrapper; import com.intellij.ide.passwordSafe.impl.providers.EncryptionUtil; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.registry.Registry; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -60,7 +59,7 @@ public class MemoryPasswordSafe extends BasePasswordSafeProvider { @NotNull @Override - protected byte[] key(Project project, @NotNull Class requestor) { + protected byte[] key() { if (key.get() == null) { byte[] rnd = new byte[EncryptionUtil.SECRET_KEY_SIZE_BYTES * 16]; new SecureRandom().nextBytes(rnd); @@ -84,11 +83,6 @@ public class MemoryPasswordSafe extends BasePasswordSafeProvider { database.get().put(new ByteArrayWrapper(key), encryptedPassword); } - @Override - public String getDescription() { - return "Memory-based password safe provider. The passwords are stored only for the duration of IDEA process."; - } - @Override public String getName() { return "Memory PasswordSafe"; diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/nil/NilProvider.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/nil/NilProvider.java index ed63e3691cbb..a9e7f62bc318 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/nil/NilProvider.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/nil/NilProvider.java @@ -25,26 +25,17 @@ import org.jetbrains.annotations.Nullable; * The most secure provider that does not store anything, so it cannot be cracked */ public final class NilProvider extends PasswordSafeProvider { - @Override - public String getDescription() { - return "The provider that does not remembers password."; - } - @Override public String getName() { return "Do not Store"; } - public String getPassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { + public String getPassword(@Nullable Project project, @Nullable Class requester, @NotNull String key) throws PasswordSafeException { // nothing is stored return null; } - public void removePassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { - // do nothing - } - - public void storePassword(@Nullable Project project, @NotNull Class requester, String key, String value) throws PasswordSafeException { + public void storePassword(@Nullable Project project, @Nullable Class requester, String key, String value) throws PasswordSafeException { // just forget about password } } diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index 128711d081e1..1e13f87558be 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -381,7 +381,7 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App } @Override - public void load() throws IOException { + public void load() { load(null); } @@ -401,7 +401,8 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App getPicoContainer().getComponentInstance(ServiceManagerImpl.class); String effectiveConfigPath = FileUtilRt.toSystemIndependentName(configPath == null ? PathManager.getConfigPath() : configPath); - for (ApplicationLoadListener listener : ApplicationLoadListener.EP_NAME.getExtensions()) { + ApplicationLoadListener[] applicationLoadListeners = ApplicationLoadListener.EP_NAME.getExtensions(); + for (ApplicationLoadListener listener : applicationLoadListeners) { try { listener.beforeApplicationLoaded(this, effectiveConfigPath); } @@ -412,6 +413,15 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App // we set it after beforeApplicationLoaded call, because app store can depends on stream provider state ServiceKt.getStateStore(this).setPath(effectiveConfigPath); + + for (ApplicationLoadListener listener : applicationLoadListeners) { + try { + listener.beforeComponentsCreated(); + } + catch (Throwable e) { + LOG.error(e); + } + } }); LOG.info(getComponentConfigCount() + " application components initialized in " + (System.currentTimeMillis() - start) + "ms"); } diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index de606a9e0669..3e0339a6d8eb 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -163,6 +163,8 @@ serviceImplementation="com.intellij.ide.passwordSafe.config.PasswordSafeSettings"/> + + + + """)) + assertThat(passwordSafe).isNotEmpty + + val provider = FilePasswordSafeProvider(passwordSafe) + assertThat(provider.getPassword("com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterKeyPasswordSafeTest/TEST")).isEqualTo("test") + } + + @Test + fun nonEmptyPass() { + var passwordSafe: Map? = null + runInEdtAndWait { + passwordSafe = convertOldDb(getDb(""" + + """)) + } + assertThat(passwordSafe).isNotEmpty + val provider = FilePasswordSafeProvider(passwordSafe) + assertThat(provider.getPassword("com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterKeyPasswordSafeTest/TEST")).isEqualTo("test") + } + + @Suppress("DEPRECATION") + private fun getDb(data: String): PasswordDatabase { + val passwordDatabase = PasswordDatabase() + val state = PasswordDatabase.State() + XmlSerializer.deserializeInto(state, JDOMUtil.load(data.reader())) + passwordDatabase.loadState(state) + return passwordDatabase + } +} \ No newline at end of file diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index e46d70db7e6e..5a4cfaff6229 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -92,7 +92,6 @@ ide.windowSystem.showListItemsPopup=true ide.windowSystem.asyncSplitters=true ide.windowSystem.showTooWindowButtonsSwitcher=true -passwordSafe.masterPassword.ttl=360 passwordSafe.memorySafe.ttl=-1 ide.tree.yieldingUiUpdate=true diff --git a/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java b/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java index 397c4782ad16..a4a56b69a10a 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java +++ b/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 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. @@ -15,7 +15,6 @@ */ package git4idea.commands; -import com.intellij.ide.passwordSafe.MasterPasswordUnavailableException; import com.intellij.ide.passwordSafe.PasswordSafe; import com.intellij.ide.passwordSafe.PasswordSafeException; import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; @@ -179,8 +178,6 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { passwordSafe.getMasterKeyProvider().storePassword(myProject, PASS_REQUESTER, myPasswordKey, myPassword); } } - catch (MasterPasswordUnavailableException ignored) { - } catch (PasswordSafeException e) { LOG.error("Couldn't remember password for " + myPasswordKey, e); } From cd1a4bda81c5ef785aa68073df1a7a7ed67e004c Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Fri, 22 Jul 2016 12:56:51 +0200 Subject: [PATCH 02/26] =?UTF-8?q?cleanup=20=E2=80=94=20reduce=20usage=20of?= =?UTF-8?q?=20PasswordSafeException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../providers/BasePasswordSafeProvider.java | 10 ++--- .../impl/providers/nil/NilProvider.java | 5 +-- .../ui/PasswordSafePromptDialog.java | 44 +++++-------------- .../commands/GitHttpGuiAuthenticator.java | 30 +++---------- .../plugins/github/util/GithubSettings.java | 31 ++++--------- .../facet/AppEngineAccountDialog.java | 19 ++------ .../execution/HgCommandAuthenticator.java | 37 ++++++---------- .../edu/learning/stepic/StepicUser.java | 20 +-------- 8 files changed, 49 insertions(+), 147 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java index fa1baeb5f733..08ebbcb27137 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java @@ -15,7 +15,6 @@ */ package com.intellij.ide.passwordSafe.impl.providers; -import com.intellij.ide.passwordSafe.PasswordSafeException; import com.intellij.ide.passwordSafe.impl.PasswordSafeProvider; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ModalityState; @@ -38,8 +37,6 @@ public abstract class BasePasswordSafeProvider extends PasswordSafeProvider { * Calling this method from the dispatch thread is allowed.

* * @return the secret key to use - * @throws PasswordSafeException in case of problems with access to the password database. - * @throws IllegalStateException if the method is called from the read action. */ @NotNull protected abstract byte[] key(); @@ -71,9 +68,8 @@ public abstract class BasePasswordSafeProvider extends PasswordSafeProvider { return EncryptionUtil.dbKey(key(), requestor, key); } - public void removePassword(@Nullable Project project, @Nullable Class requestor, String key) throws PasswordSafeException { - byte[] k = dbKey(requestor, key); - removeEncryptedPassword(k); + public void removePassword(@Nullable Project project, @Nullable Class requestor, String key) { + removeEncryptedPassword(dbKey(requestor, key)); } /** @@ -83,7 +79,7 @@ public abstract class BasePasswordSafeProvider extends PasswordSafeProvider { */ protected abstract void removeEncryptedPassword(byte[] key); - public void storePassword(@Nullable Project project, @Nullable Class requestor, String key, String value) throws PasswordSafeException { + public void storePassword(@Nullable Project project, @Nullable Class requestor, String key, String value) { byte[] k = dbKey(requestor, key); byte[] ct = EncryptionUtil.encryptText(key(), value); storeEncryptedPassword(k, ct); diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/nil/NilProvider.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/nil/NilProvider.java index a9e7f62bc318..b010f543e529 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/nil/NilProvider.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/nil/NilProvider.java @@ -15,7 +15,6 @@ */ package com.intellij.ide.passwordSafe.impl.providers.nil; -import com.intellij.ide.passwordSafe.PasswordSafeException; import com.intellij.ide.passwordSafe.impl.PasswordSafeProvider; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; @@ -30,12 +29,12 @@ public final class NilProvider extends PasswordSafeProvider { return "Do not Store"; } - public String getPassword(@Nullable Project project, @Nullable Class requester, @NotNull String key) throws PasswordSafeException { + public String getPassword(@Nullable Project project, @Nullable Class requester, @NotNull String key) { // nothing is stored return null; } - public void storePassword(@Nullable Project project, @Nullable Class requester, String key, String value) throws PasswordSafeException { + public void storePassword(@Nullable Project project, @Nullable Class requester, String key, String value) { // just forget about password } } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java index 16b53889ca9c..b7a1013f0d19 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 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. @@ -16,15 +16,12 @@ package com.intellij.ide.passwordSafe.ui; import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.PasswordSafeException; import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Ref; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -36,8 +33,6 @@ import javax.swing.*; * The generic password dialog. Use it to ask a password from user with option to remember it. */ public class PasswordSafePromptDialog extends DialogWrapper { - private static final Logger LOG = Logger.getInstance(PasswordSafePromptDialog.class.getName()); - private final PasswordPromptComponent myComponent; /** @@ -160,23 +155,16 @@ public class PasswordSafePromptDialog extends DialogWrapper { final String promptLabel, final String checkboxLabel) { final PasswordSafeImpl ps = (PasswordSafeImpl)PasswordSafe.getInstance(); - try { - if (resetPassword) { - ps.removePassword(project, requestor, key); - } - else { - String pw = ps.getPassword(project, requestor, key); - if (pw != null) { - return pw; - } - } + if (resetPassword) { + ps.removePassword(project, requestor, key); } - catch (PasswordSafeException ex) { - // ignore exception on get/reset phase - if (LOG.isDebugEnabled()) { - LOG.debug("Failed to retrieve or reset password", ex); + else { + String pw = ps.getPassword(project, requestor, key); + if (pw != null) { + return pw; } } + final Ref ref = Ref.create(); ApplicationManager.getApplication().invokeAndWait(() -> { PasswordSafeSettings.ProviderType type = ps.getSettings().getProviderType(); @@ -186,19 +174,11 @@ public class PasswordSafePromptDialog extends DialogWrapper { d.setErrorText(error); if (d.showAndGet()) { ref.set(new String(component.getPassword())); - try { - if (component.isRememberSelected()) { - ps.storePassword(project, requestor, key, ref.get()); - } - else if (!type.equals(PasswordSafeSettings.ProviderType.DO_NOT_STORE)) { - ps.getMemoryProvider().storePassword(project, requestor, key, ref.get()); - } + if (component.isRememberSelected()) { + ps.storePassword(project, requestor, key, ref.get()); } - catch (PasswordSafeException e) { - Messages.showErrorDialog(project, e.getMessage(), "Failed to Store Password"); - if (LOG.isDebugEnabled()) { - LOG.debug("Failed to store password", e); - } + else if (!type.equals(PasswordSafeSettings.ProviderType.DO_NOT_STORE)) { + ps.getMemoryProvider().storePassword(project, requestor, key, ref.get()); } } }, ModalityState.any()); diff --git a/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java b/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java index a4a56b69a10a..1b922bf454ac 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java +++ b/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java @@ -16,7 +16,6 @@ package git4idea.commands; import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.PasswordSafeException; import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; import com.intellij.ide.passwordSafe.ui.PasswordSafePromptDialog; import com.intellij.openapi.application.ApplicationManager; @@ -172,14 +171,9 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { // save password if (myPasswordKey != null && myPassword != null) { PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance(); - try { - passwordSafe.getMemoryProvider().storePassword(myProject, PASS_REQUESTER, myPasswordKey, myPassword); - if (mySaveOnDisk) { - passwordSafe.getMasterKeyProvider().storePassword(myProject, PASS_REQUESTER, myPasswordKey, myPassword); - } - } - catch (PasswordSafeException e) { - LOG.error("Couldn't remember password for " + myPasswordKey, e); + passwordSafe.getMemoryProvider().storePassword(myProject, PASS_REQUESTER, myPasswordKey, myPassword); + if (mySaveOnDisk) { + passwordSafe.getMasterKeyProvider().storePassword(myProject, PASS_REQUESTER, myPasswordKey, myPassword); } } } @@ -292,14 +286,8 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { String userName = getUsername(url); String key = makeKey(url, userName); final PasswordSafe passwordSafe = PasswordSafe.getInstance(); - try { - String password = passwordSafe.getPassword(myProject, PASS_REQUESTER, key); - return new AuthData(StringUtil.notNullize(userName), password); - } - catch (PasswordSafeException e) { - LOG.info("Couldn't get the password for key [" + key + "]", e); - return null; - } + String password = passwordSafe.getPassword(myProject, PASS_REQUESTER, key); + return new AuthData(StringUtil.notNullize(userName), password); } @Nullable @@ -311,13 +299,7 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { public void forgetPassword(@NotNull String url) { String key = myPasswordKey != null ? myPasswordKey : makeKey(url, getUsername(url)); LOG.debug("forgetPassword. key=" + key); - try { - PasswordSafe.getInstance().removePassword(myProject, PASS_REQUESTER, key); - } - catch (PasswordSafeException e) { - LOG.info("Couldn't forget the password for " + myPasswordKey); - } + PasswordSafe.getInstance().removePassword(myProject, PASS_REQUESTER, key); } } - } diff --git a/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java b/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java index bf9bb2299262..e8c97626e01f 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java +++ b/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java @@ -16,7 +16,6 @@ package org.jetbrains.plugins.github.util; import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.PasswordSafeException; import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; import com.intellij.openapi.components.PersistentStateComponent; @@ -172,32 +171,18 @@ public class GithubSettings implements PersistentStateComponent Date: Fri, 22 Jul 2016 13:00:25 +0200 Subject: [PATCH 03/26] do not ask master pass to convert if < 2 entries --- .../passwordSafe/impl/PasswordSafeImpl.java | 12 ++++--- .../impl/providers/masterKey/dbV1Convertor.kt | 34 +++++++++++++------ .../masterKey/MasterPasswordMigrationTest.kt | 8 +++++ 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeImpl.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeImpl.java index 561b956121ea..4d620b2a4418 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeImpl.java @@ -16,12 +16,13 @@ package com.intellij.ide.passwordSafe.impl; import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.PasswordSafeException; import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; +import com.intellij.ide.passwordSafe.impl.providers.masterKey.PasswordDatabase; import com.intellij.ide.passwordSafe.impl.providers.memory.MemoryPasswordSafe; import com.intellij.ide.passwordSafe.impl.providers.nil.NilProvider; import com.intellij.ide.passwordSafe.masterKey.DbV1ConvertorKt; import com.intellij.ide.passwordSafe.masterKey.FilePasswordSafeProvider; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.components.SettingsSavingComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; @@ -38,7 +39,8 @@ public class PasswordSafeImpl extends PasswordSafe implements SettingsSavingComp public PasswordSafeImpl(@NotNull PasswordSafeSettings settings) { mySettings = settings; - myMasterKeyProvider = new FilePasswordSafeProvider(DbV1ConvertorKt.convertOldDb()); + //noinspection deprecation + myMasterKeyProvider = new FilePasswordSafeProvider(DbV1ConvertorKt.convertOldDb(ServiceManager.getService(PasswordDatabase.class))); myNilProvider = new NilProvider(); myMemoryProvider = new MemoryPasswordSafe(); } @@ -72,7 +74,7 @@ public class PasswordSafeImpl extends PasswordSafe implements SettingsSavingComp } @Nullable - public String getPassword(@Nullable Project project, @Nullable Class requester, @NotNull String key) throws PasswordSafeException { + public String getPassword(@Nullable Project project, @Nullable Class requester, @NotNull String key) { if (mySettings.getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) { String password = getMemoryProvider().getPassword(project, requester, key); if (password == null) { @@ -87,14 +89,14 @@ public class PasswordSafeImpl extends PasswordSafe implements SettingsSavingComp return provider().getPassword(project, requester, key); } - public void removePassword(@Nullable Project project, @Nullable Class requester, String key) throws PasswordSafeException { + public void removePassword(@Nullable Project project, @Nullable Class requester, String key) { if (mySettings.getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) { getMemoryProvider().removePassword(project, requester, key); } provider().removePassword(project, requester, key); } - public void storePassword(@Nullable Project project, @Nullable Class requester, String key, String value) throws PasswordSafeException { + public void storePassword(@Nullable Project project, @Nullable Class requester, String key, String value) { if (mySettings.getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) { getMemoryProvider().storePassword(project, requester, key, value); } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/dbV1Convertor.kt b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/dbV1Convertor.kt index 318f58087dba..8050c8ca52e8 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/dbV1Convertor.kt +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/dbV1Convertor.kt @@ -24,10 +24,13 @@ import com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterPasswordDial import com.intellij.ide.passwordSafe.impl.providers.masterKey.PasswordDatabase import com.intellij.ide.passwordSafe.impl.providers.masterKey.windows.WindowsCryptUtils import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.PathManager import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.util.SystemInfo import gnu.trove.THashMap +import java.nio.file.Files +import java.nio.file.Paths import java.util.* import java.util.function.Function @@ -52,7 +55,7 @@ internal fun checkPassAndConvertOldDb(password: String, @Suppress("DEPRECATION") } fun convertOldDb(@Suppress("DEPRECATION") db: PasswordDatabase): Map? { - if (db.myDatabase.isEmpty()) { + if (db.myDatabase.size <= 1) { return null } @@ -73,6 +76,11 @@ fun convertOldDb(@Suppress("DEPRECATION") db: PasswordDatabase): Map? = null val dialog = MasterPasswordDialog(EnterPasswordComponent(Function { result = checkPassAndConvertOldDb(it, db) @@ -108,17 +116,21 @@ fun toOldKey(hash: ByteArray) = "old-hashed-key|" + Base64.getEncoder().encodeTo private fun rawTestKey(oldKey: String) = EncryptionUtil.hash("com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterKeyPasswordSafe/TEST_PASSWORD:${oldKey}".toByteArray()) -fun convertOldDb(): Map? { - @Suppress("DEPRECATION") - val oldDb = ServiceManager.getService(PasswordDatabase::class.java) - if (oldDb.myDatabase.isNotEmpty()) { - return convertOldDb(oldDb) - } - return null -} - internal class PasswordDatabaseConvertor : ApplicationLoadListener { override fun beforeComponentsCreated() { - PasswordSafe.getInstance() + try { + val oldDbFile = Paths.get(PathManager.getConfigPath(), "options", "security.xml") + if (Files.exists(oldDbFile)) { + @Suppress("DEPRECATION") + val oldDb = ServiceManager.getService(PasswordDatabase::class.java) + // old db contains at least one test key - skip it + if (oldDb.myDatabase.size > 1) { + PasswordSafe.getInstance() + } + } + } + catch (e: Throwable) { + LOG.warn("Cannot check old password safe DB", e) + } } } \ No newline at end of file diff --git a/platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordMigrationTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordMigrationTest.kt index ccd7b7b84376..0065c238efe7 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordMigrationTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordMigrationTest.kt @@ -41,12 +41,16 @@ class MasterPasswordMigrationTest { @@ -66,10 +70,14 @@ class MasterPasswordMigrationTest {