diff options
Diffstat (limited to 'platform/platform-impl/src/com/intellij/ide/passwordSafe')
6 files changed, 129 insertions, 92 deletions
diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/PasswordSafeException.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/PasswordSafeException.java index 00b529933f68..20d7d5188efb 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/PasswordSafeException.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/PasswordSafeException.java @@ -19,22 +19,29 @@ 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 Exception { - /** - * The constructor - * - * @param message the message - * @param cause the cause - */ + + private static final long MIN_INTERVAL = 1000L; + + private long myTimeMillis = System.currentTimeMillis(); + public PasswordSafeException(String message, Throwable cause) { super(message, cause); } - /** - * The constructor - * - * @param message the message - */ public PasswordSafeException(String message) { super(message); } + + public long getTimeMillis() { + return myTimeMillis; + } + + public boolean justHappened() { + long timeMillis = System.currentTimeMillis(); + if (timeMillis - myTimeMillis < MIN_INTERVAL) { + myTimeMillis = timeMillis; + return true; + } + return false; + } } 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 a0b001824d90..8bfa859d5ceb 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 @@ -43,6 +43,7 @@ public abstract class BasePasswordSafeProvider extends PasswordSafeProvider { * @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) throws PasswordSafeException; @Nullable 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 01fef01cecb1..7bbb8bc80789 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 @@ -15,6 +15,8 @@ */ package com.intellij.ide.passwordSafe.impl.providers; +import org.jetbrains.annotations.NotNull; + import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; @@ -115,7 +117,7 @@ public class EncryptionUtil { * @param rawKey the raw key to encrypt * @return the encrypted key */ - public static byte[] encryptKey(byte[] password, byte[] rawKey) { + public static byte[] encryptKey(@NotNull byte[] password, byte[] rawKey) { try { Cipher c = Cipher.getInstance(ENCRYPT_KEY_ALGORITHM); c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(password, SECRET_KEY_ALGORITHM), CBC_SALT_KEY); @@ -130,12 +132,12 @@ public class EncryptionUtil { * Create encrypted db key * * @param password the password to protect the key - * @param requester the requester for the key - * @param key the key within requester + * @param requestor the requestor for the key + * @param key the key within requestor * @return the key to use in the database */ - public static byte[] dbKey(byte[] password, Class requester, String key) { - return encryptKey(password, rawKey(requester, key)); + public static byte[] dbKey(@NotNull byte[] password, Class requestor, String key) { + return encryptKey(password, rawKey(requestor, key)); } 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 index 840159270a8d..6b0b9814698b 100644 --- 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 @@ -22,12 +22,14 @@ 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.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.ThrowableComputable; import com.intellij.openapi.util.registry.Registry; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; @@ -36,7 +38,6 @@ import org.jetbrains.annotations.Nullable; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; /** * The password safe that stores information in configuration file encrypted by master password @@ -44,13 +45,11 @@ import java.util.concurrent.atomic.AtomicReference; public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { private static final String TEST_PASSWORD_KEY = "TEST_PASSWORD:"; private static final String TEST_PASSWORD_VALUE = "test password"; - final PasswordDatabase database; - /** - * The key to use to encrypt data - */ - private transient final PasswordSafeTimed<AtomicReference<byte[]>> key = new PasswordSafeTimed<AtomicReference<byte[]>>() { - protected AtomicReference<byte[]> compute() { - return new AtomicReference<byte[]>(); + + private final PasswordDatabase myDatabase; + private transient final PasswordSafeTimed<Ref<Object>> myKey = new PasswordSafeTimed<Ref<Object>>() { + protected Ref<Object> compute() { + return Ref.create(); } @Override @@ -60,11 +59,7 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { }; public MasterKeyPasswordSafe(PasswordDatabase database) { - this.database = database; - } - - protected boolean isTestMode() { - return false; + this.myDatabase = database; } /** @@ -74,15 +69,15 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { * @param encrypt if the password should be encrypted an stored is master database */ void resetMasterPassword(String password, boolean encrypt) { - key.get().set(EncryptionUtil.genPasswordKey(password)); - database.clear(); + myKey.get().set(EncryptionUtil.genPasswordKey(password)); + myDatabase.clear(); try { storePassword(null, MasterKeyPasswordSafe.class, testKey(password), TEST_PASSWORD_VALUE); if (encrypt) { - database.setPasswordInfo(encryptPassword(password)); + myDatabase.setPasswordInfo(encryptPassword(password)); } else { - database.setPasswordInfo(ArrayUtil.EMPTY_BYTE_ARRAY); + myDatabase.setPasswordInfo(ArrayUtil.EMPTY_BYTE_ARRAY); } } catch (PasswordSafeException e) { @@ -97,8 +92,8 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { * @return true, if password is a correct one */ boolean setMasterPassword(String password) { - byte[] savedKey = key.get().get(); - key.get().set(EncryptionUtil.genPasswordKey(password)); + Object savedKey = myKey.get().get(); + myKey.get().set(EncryptionUtil.genPasswordKey(password)); String rc; try { rc = getPassword(null, MasterKeyPasswordSafe.class, testKey(password)); @@ -107,7 +102,7 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { throw new IllegalStateException("There should be no problem with password at this point", e); } if (!TEST_PASSWORD_VALUE.equals(rc)) { - key.get().set(savedKey); + myKey.get().set(savedKey); return false; } else { @@ -127,11 +122,11 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { if (!setMasterPassword(oldPassword)) { return false; } - byte[] oldKey = key.get().get(); // set right in the previous call + 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<ByteArrayWrapper, byte[]> oldDb = new HashMap<ByteArrayWrapper, byte[]>(); - database.copyTo(oldDb); + myDatabase.copyTo(oldDb); HashMap<ByteArrayWrapper, byte[]> newDb = new HashMap<ByteArrayWrapper, byte[]>(); for (Map.Entry<ByteArrayWrapper, byte[]> e : oldDb.entrySet()) { if (testKey.equals(e.getKey())) { @@ -141,9 +136,9 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { String decryptedText = EncryptionUtil.decryptText(oldKey, e.getValue()); newDb.put(new ByteArrayWrapper(EncryptionUtil.encryptKey(newKey, decryptedKey)), EncryptionUtil.encryptText(newKey, decryptedText)); } - synchronized (database.getDbLock()) { + synchronized (myDatabase.getDbLock()) { resetMasterPassword(newPassword, encrypt); - database.putAll(newDb); + myDatabase.putAll(newDb); } return true; } @@ -153,65 +148,92 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { return TEST_PASSWORD_KEY + password; } + @NotNull @Override protected byte[] key(@Nullable final Project project, @NotNull final Class requestor) throws PasswordSafeException { - Application application = ApplicationManager.getApplication(); - if (!isTestMode() && application.isHeadlessEnvironment()) { + Object key = myKey.get().get(); + if (key instanceof byte[]) return (byte[])key; + if (key instanceof PasswordSafeException && ((PasswordSafeException)key).justHappened()) throw (PasswordSafeException)key; + + if (isPasswordEncrypted()) { + try { + setMasterPassword(decryptPassword(myDatabase.getPasswordInfo())); + key = myKey.get().get(); + if (key instanceof byte[]) return (byte[])key; + } + catch (PasswordSafeException e) { + // ignore exception and ask password + } + } + + if (ApplicationManager.getApplication().isHeadlessEnvironment()) { throw new MasterPasswordUnavailableException("The provider is not available in headless environment"); } - final Ref<byte[]> result = Ref.create(key.get().get()); - if (result.isNull()) { - if (isPasswordEncrypted()) { + + if (myDatabase.isEmpty()) { + if (!MasterPasswordDialog.resetMasterPasswordDialog(project, this, requestor).showAndGet()) { + throw new MasterPasswordUnavailableException("Master password is required to store passwords in the database."); + } + } + + key = invokeAndWait(new ThrowableComputable<Object, PasswordSafeException>() { + @Override + public Object compute() throws PasswordSafeException { + Object key = myKey.get().get(); + if (key instanceof byte[] || key instanceof PasswordSafeException && ((PasswordSafeException)key).justHappened()) { + return key; + } try { - setMasterPassword(decryptPassword(database.getPasswordInfo())); - result.set(key.get().get()); + MasterPasswordDialog.askPassword(project, MasterKeyPasswordSafe.this, requestor); } catch (PasswordSafeException e) { - // ignore exception and ask password + myKey.get().set(e); + throw e; } + return myKey.get().get(); + } + }, project == null ? Condition.FALSE : 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, E extends Throwable> T invokeAndWait(@NotNull final ThrowableComputable<T, E> computable, @NotNull final Condition expired) throws E { + if (ApplicationManager.getApplication().isDispatchThread()) { + return computable.compute(); + } + final Ref<Throwable> exRef = Ref.create(); + final Ref<T> ref = Ref.create(); + synchronized (ourEDTLock) { + if (expired.value(null)) { + throw new ProcessCanceledException(); } - if (result.isNull()) { - final Ref<PasswordSafeException> ex = new Ref<PasswordSafeException>(); - application.invokeAndWait(new Runnable() { - public void run() { - result.set(key.get().get()); - if (result.isNull()) { - try { - if (isTestMode()) { - throw new MasterPasswordUnavailableException("Master password must be specified in test mode."); - } - if (database.isEmpty()) { - if (!MasterPasswordDialog.resetMasterPasswordDialog(project, MasterKeyPasswordSafe.this, requestor).showAndGet()) { - throw new MasterPasswordUnavailableException("Master password is required to store passwords in the database."); - } - } - else { - MasterPasswordDialog.askPassword(project, MasterKeyPasswordSafe.this, requestor); - } - result.set(key.get().get()); - } - catch (PasswordSafeException e) { - ex.set(e); - } - catch (Exception e) { - //noinspection ThrowableInstanceNeverThrown - ex.set(new MasterPasswordUnavailableException("The problem with retrieving the password", e)); - } - } + ApplicationManager.getApplication().invokeAndWait(new Runnable() { + @Override + public void run() { + if (expired.value(null)) { + exRef.set(new ProcessCanceledException()); + return; + } + + try { + ref.set(computable.compute()); + } + catch (Throwable e) { + exRef.set(e); } - }, ModalityState.any()); - //noinspection ThrowableResultOfMethodCallIgnored - if (ex.get() != null) { - throw ex.get(); } - } + }, ModalityState.any()); } - return result.get(); + if (!exRef.isNull()) throw (E)exRef.get(); + return ref.get(); } @Override public String getPassword(@Nullable Project project, @NotNull Class requestor, String key) throws PasswordSafeException { - if (database.isEmpty()) { + if (myDatabase.isEmpty()) { return null; } return super.getPassword(project, requestor, key); @@ -219,7 +241,7 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { @Override public void removePassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { - if (database.isEmpty()) { + if (myDatabase.isEmpty()) { return; } super.removePassword(project, requester, key); @@ -227,17 +249,17 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { @Override protected byte[] getEncryptedPassword(byte[] key) { - return database.get(key); + return myDatabase.get(key); } @Override protected void removeEncryptedPassword(byte[] key) { - database.remove(key); + myDatabase.remove(key); } @Override protected void storeEncryptedPassword(byte[] key, byte[] encryptedPassword) { - database.put(key, encryptedPassword); + myDatabase.put(key, encryptedPassword); } @Override @@ -303,11 +325,11 @@ public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { public boolean isPasswordEncrypted() { if (!isOsProtectedPasswordSupported()) return false; - byte[] i = database.getPasswordInfo(); - return i != null && i.length > 0; + byte[] info = myDatabase.getPasswordInfo(); + return info != null && info.length > 0; } public boolean isEmpty() { - return database.isEmpty(); + return myDatabase.isEmpty(); } } 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 4989789b4ec0..5057b0ee48d6 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 @@ -57,6 +57,7 @@ public class MemoryPasswordSafe extends BasePasswordSafeProvider { return Registry.intValue("passwordSafe.memorySafe.ttl"); } + @NotNull @Override protected byte[] key(Project project, @NotNull Class requestor) { if (key.get() == null) { diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordPromptComponent.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordPromptComponent.java index 6ea43344bda4..b31599816435 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordPromptComponent.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordPromptComponent.java @@ -18,6 +18,7 @@ package com.intellij.ide.passwordSafe.ui; import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ui.DialogUtil; import com.intellij.util.ui.UIUtil; import javax.swing.*; @@ -49,7 +50,10 @@ public class PasswordPromptComponent { setTargetProviderType(type); setUserInputVisible(showUserName); if (passwordPrompt != null) myPasswordLabel.setText(passwordPrompt); - if (rememberPrompt != null) myRememberCheckBox.setText(rememberPrompt); + if (rememberPrompt != null) { + myRememberCheckBox.setText(rememberPrompt); + DialogUtil.registerMnemonic(myRememberCheckBox); + } } public JComponent getComponent() { |