Bug 222074 Streamline password management
diff --git a/bundles/org.eclipse.equinox.security.tests/src/org/eclipse/equinox/internal/security/tests/storage/HighPriorityModule.java b/bundles/org.eclipse.equinox.security.tests/src/org/eclipse/equinox/internal/security/tests/storage/HighPriorityModule.java
index bee36a2..8274c51 100644
--- a/bundles/org.eclipse.equinox.security.tests/src/org/eclipse/equinox/internal/security/tests/storage/HighPriorityModule.java
+++ b/bundles/org.eclipse.equinox.security.tests/src/org/eclipse/equinox/internal/security/tests/storage/HighPriorityModule.java
@@ -21,16 +21,11 @@
public final static PBEKeySpec PASSWORD = new PBEKeySpec("HighPriorityPassword".toCharArray());
- public PBEKeySpec login(IPreferencesContainer controller) {
+ public PBEKeySpec getPassword(IPreferencesContainer container, int passwordType) {
return PASSWORD;
}
- public boolean changePassword(Exception e, IPreferencesContainer container) {
+ public boolean retryOnError(Exception e, IPreferencesContainer container) {
return false;
}
-
- public void logout(IPreferencesContainer container) {
- // noting to do
- }
-
}
diff --git a/bundles/org.eclipse.equinox.security.tests/src/org/eclipse/equinox/internal/security/tests/storage/LowPriorityModule.java b/bundles/org.eclipse.equinox.security.tests/src/org/eclipse/equinox/internal/security/tests/storage/LowPriorityModule.java
index 1573917..3b44391 100644
--- a/bundles/org.eclipse.equinox.security.tests/src/org/eclipse/equinox/internal/security/tests/storage/LowPriorityModule.java
+++ b/bundles/org.eclipse.equinox.security.tests/src/org/eclipse/equinox/internal/security/tests/storage/LowPriorityModule.java
@@ -21,15 +21,11 @@
public final static PBEKeySpec PASSWORD = new PBEKeySpec("LowPriorityPassword".toCharArray());
- public PBEKeySpec login(IPreferencesContainer controller) {
+ public PBEKeySpec getPassword(IPreferencesContainer container, int passwordType) {
return PASSWORD;
}
- public boolean changePassword(Exception e, IPreferencesContainer container) {
+ public boolean retryOnError(Exception e, IPreferencesContainer container) {
return false;
}
-
- public void logout(IPreferencesContainer container) {
- // noting to do
- }
}
diff --git a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/nls/SecUIMessages.java b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/nls/SecUIMessages.java
index 148208d..8f0d606 100644
--- a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/nls/SecUIMessages.java
+++ b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/nls/SecUIMessages.java
@@ -27,11 +27,13 @@
public static String buttonLogin;
public static String buttonExit;
public static String messageLogin;
+ public static String messageLoginChange;
public static String messageEmptyPassword;
public static String messageNoMatch;
public static String labelPassword;
public static String labelConfirm;
public static String dialogTitle;
+ public static String passwordChangeTitle;
public static String showPassword;
public static String noDigestPassword;
@@ -130,6 +132,7 @@
public static String wizardEncode;
public static String wizardEncodeLabel;
public static String wizardDecodeWarning;
+ public static String wizardSwitchError;
public static String wizardDoneTitle;
public static String wizardDone;
diff --git a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/nls/messages.properties b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/nls/messages.properties
index d7556be..6376aa5 100644
--- a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/nls/messages.properties
+++ b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/nls/messages.properties
@@ -19,6 +19,7 @@
## Login dialog
messageLogin = Please enter the secure storage password
+messageLoginChange = Password change: please enter the NEW secure storage password
messageEmptyPassword = Password can not be empty
messageNoMatch = Password and confirmation characters did not match
labelPassword = &Password:
@@ -26,6 +27,7 @@
buttonLogin = Login
buttonExit = Exit
dialogTitle = Secure storage login
+passwordChangeTitle = Enter new password for the secure storage
showPassword = &Show password
noDigestPassword = The message digest algorithm \"{0}\" is not available.
@@ -36,7 +38,7 @@
## Properties pages
selectCipher = &Select encryption algorithm to use in new storages:
-changePasswordButton = &Change password
+changePasswordButton = &Change password / Re-encrypt
deleteButton = D&elete
logoutButton = &Logout
providersTable = Available password providers:
@@ -124,6 +126,7 @@
wizardEncode = NEW password: the values will be encrypted.
wizardEncodeLabel = At this point if you are prompted for a password, enter the NEW password.
wizardDecodeWarning = A error occurred while decrypting stored values (see log for details), those values might become inaccessible.\nDo you want to cancel password change?
+wizardSwitchError = A error occurred while creating new password. See error log for details.
wizardDoneTitle = Complete
wizardDone = Password change complete.
diff --git a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/ChangePasswordWizardDialog.java b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/ChangePasswordWizardDialog.java
index a3c05fc..797b9a6 100644
--- a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/ChangePasswordWizardDialog.java
+++ b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/ChangePasswordWizardDialog.java
@@ -44,7 +44,7 @@
if (currentPage instanceof ChangePasswordWizard.DecodePage) { // decrypt
if (!reEncrypter.decrypt()) {
MessageBox messageBox = new MessageBox(getShell(), SWT.YES | SWT.NO | SWT.ICON_WARNING);
- messageBox.setText(SecUIMessages.wizardDecodeLabel);
+ messageBox.setText(SecUIMessages.changePasswordWizardTitle);
messageBox.setMessage(SecUIMessages.wizardDecodeWarning);
if (messageBox.open() == SWT.YES) {
setReturnCode(CANCEL);
@@ -53,6 +53,14 @@
}
}
} else if (currentPage instanceof ChangePasswordWizard.EncodePage) { // encrypt
+ if (!reEncrypter.switchToNewPassword()) {
+ MessageBox messageBox = new MessageBox(getShell(), SWT.OK | SWT.ICON_ERROR);
+ messageBox.setText(SecUIMessages.changePasswordWizardTitle);
+ messageBox.setMessage(SecUIMessages.wizardSwitchError);
+ messageBox.open();
+ close();
+ return;
+ }
reEncrypter.encrypt();
recodeDone = true;
}
diff --git a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/DefaultPasswordProvider.java b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/DefaultPasswordProvider.java
index ca6bd7a..c3d1912 100644
--- a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/DefaultPasswordProvider.java
+++ b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/DefaultPasswordProvider.java
@@ -24,35 +24,18 @@
*/
public class DefaultPasswordProvider extends PasswordProvider {
- private PBEKeySpec password = null;
+ public PBEKeySpec getPassword(IPreferencesContainer container, int passwordType) {
+ boolean newPassword = ((passwordType & CREATE_NEW_PASSWORD) != 0);
+ boolean passwordChange = ((passwordType & PASSWORD_CHANGE) != 0);
- synchronized public PBEKeySpec login(IPreferencesContainer container) {
- if (password != null)
- return password;
String location = container.getLocation().getFile();
- StorageLoginDialog loginDialog = new StorageLoginDialog(confirmPassword(container), location);
+ StorageLoginDialog loginDialog = new StorageLoginDialog(newPassword, passwordChange, location);
if (loginDialog.open() == Window.OK)
- password = loginDialog.getGeneratedPassword();
- return password;
+ return loginDialog.getGeneratedPassword();
+ return null;
}
- synchronized public void logout(IPreferencesContainer container) {
- if (password == null)
- return;
- password.clearPassword();
- password = null;
- }
-
- private boolean confirmPassword(IPreferencesContainer container) {
- if (!container.hasOption(IProviderHints.NEW_PASSWORD))
- return false;
- Object confirmationHint = container.getOption(IProviderHints.NEW_PASSWORD);
- if (confirmationHint != null && confirmationHint instanceof Boolean)
- return ((Boolean) confirmationHint).booleanValue();
- return false;
- }
-
- public boolean changePassword(Exception e, IPreferencesContainer container) {
+ public boolean retryOnError(Exception e, IPreferencesContainer container) {
boolean canPrompt = true;
if (container.hasOption(IProviderHints.PROMPT_USER)) {
Object promptHint = container.getOption(IProviderHints.PROMPT_USER);
diff --git a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/StorageLoginDialog.java b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/StorageLoginDialog.java
index d3556a2..811ec52 100644
--- a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/StorageLoginDialog.java
+++ b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/StorageLoginDialog.java
@@ -41,11 +41,13 @@
protected PBEKeySpec generatedPassword;
final protected boolean confirmPassword;
+ final protected boolean passwordChange;
final protected String location;
- public StorageLoginDialog(boolean confirmPassword, String location) {
+ public StorageLoginDialog(boolean confirmPassword, boolean passwordChange, String location) {
super(null);
this.confirmPassword = confirmPassword;
+ this.passwordChange = passwordChange;
this.location = location;
}
@@ -74,7 +76,7 @@
protected void configureShell(Shell shell) {
super.configureShell(shell);
if (location == null)
- shell.setText(SecUIMessages.dialogTitle);
+ shell.setText(passwordChange ? SecUIMessages.passwordChangeTitle : SecUIMessages.dialogTitle);
else
shell.setText(location);
@@ -84,7 +86,7 @@
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite) super.createDialogArea(parent);
- setMessage(SecUIMessages.messageLogin);
+ setMessage(passwordChange ? SecUIMessages.messageLoginChange : SecUIMessages.messageLogin);
new Label(composite, SWT.LEFT).setText(SecUIMessages.labelPassword);
password = new Text(composite, SWT.LEFT | SWT.BORDER);
@@ -153,7 +155,7 @@
return false;
}
}
- setMessage(SecUIMessages.messageLogin, IMessageProvider.NONE);
+ setMessage(passwordChange ? SecUIMessages.messageLoginChange : SecUIMessages.messageLogin, IMessageProvider.NONE);
return true;
}
diff --git a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/view/ValuesView.java b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/view/ValuesView.java
index 151c037..09e0549 100644
--- a/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/view/ValuesView.java
+++ b/bundles/org.eclipse.equinox.security.ui/src/org/eclipse/equinox/internal/security/ui/storage/view/ValuesView.java
@@ -32,7 +32,7 @@
/**
* Line to show for encrypted values
*/
- private final static String ENCRYPTED_SUBSTITUTE = ""; //$NON-NLS-1$
+ private final static String ENCRYPTED_SUBSTITUTE = "**********"; //$NON-NLS-1$
protected SecurePreferencesView parentView;
protected TableViewer tableViewer;
diff --git a/bundles/org.eclipse.equinox.security.win32.x86/src/org/eclipse/equinox/internal/security/win32/WinCrypto.java b/bundles/org.eclipse.equinox.security.win32.x86/src/org/eclipse/equinox/internal/security/win32/WinCrypto.java
index 724fe80..bc8811e 100644
--- a/bundles/org.eclipse.equinox.security.win32.x86/src/org/eclipse/equinox/internal/security/win32/WinCrypto.java
+++ b/bundles/org.eclipse.equinox.security.win32.x86/src/org/eclipse/equinox/internal/security/win32/WinCrypto.java
@@ -47,7 +47,7 @@
*/
private final static int PASSWORD_LENGTH = 250;
- public PBEKeySpec login(IPreferencesContainer container) {
+ public PBEKeySpec getPassword(IPreferencesContainer container, int passwordType) {
byte[] encryptedPassord = getEncryptedPassword(container);
if (encryptedPassord != null) {
byte[] decryptedPassword = windecrypt(encryptedPassord);
@@ -128,7 +128,7 @@
return true;
}
- public boolean changePassword(Exception e, IPreferencesContainer container) {
+ public boolean retryOnError(Exception e, IPreferencesContainer container) {
// It would be rather dangerous to allow this password to be changed
// as it would permanently trash all entries in the secure storage.
// Rather applications using get...() should handle exceptions and offer to overwrite
@@ -136,8 +136,4 @@
return false;
}
- public void logout(IPreferencesContainer container) {
- // nothing to do
- }
-
}
diff --git a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/PasswordProviderModuleExt.java b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/PasswordProviderModuleExt.java
index 1ae8370..cd22598 100644
--- a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/PasswordProviderModuleExt.java
+++ b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/PasswordProviderModuleExt.java
@@ -31,16 +31,12 @@
return moduleID;
}
- public PBEKeySpec login(IPreferencesContainer container) {
- return providerModule.login(container);
- }
-
- public void logout(IPreferencesContainer container) {
- providerModule.logout(container);
+ public PBEKeySpec getPassword(IPreferencesContainer container, int passwordType) {
+ return providerModule.getPassword(container, passwordType);
}
public boolean changePassword(Exception e, IPreferencesContainer controller) {
- return providerModule.changePassword(e, controller);
+ return providerModule.retryOnError(e, controller);
}
}
diff --git a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/PasswordProviderSelector.java b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/PasswordProviderSelector.java
index 7d0fef0..6661777 100644
--- a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/PasswordProviderSelector.java
+++ b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/PasswordProviderSelector.java
@@ -115,21 +115,6 @@
return allAvailableModules;
}
- /**
- * Let's all instantiated modules to clear cached passwords. Passwords
- * are cached in open preferences and might be cached in the password
- * providers.
- */
- public void logout() {
- SecurePreferencesMapper.clearCaches();
- synchronized (modules) {
- for (Iterator i = modules.values().iterator(); i.hasNext();) {
- PasswordProviderModuleExt module = (PasswordProviderModuleExt) i.next();
- module.logout(null);
- }
- }
- }
-
public PasswordProviderModuleExt findStorageModule(String expectedID) throws StorageException {
if (expectedID != null)
expectedID = expectedID.toLowerCase(); // ID is case-insensitive
@@ -203,7 +188,11 @@
private void clearCaches() {
synchronized (modules) {
modules.clear();
- SecurePreferencesMapper.clearCaches();
+ // If module was removed, clear its entry from the password cache.
+ // The code below clears all entries for simplicity, in future this
+ // can be made more limiting if a scenario exists where module
+ // removal/addition is a frequent event.
+ SecurePreferencesMapper.clearPasswordCache();
}
}
}
diff --git a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferences.java b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferences.java
index 837e23d..976bbdd 100644
--- a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferences.java
+++ b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferences.java
@@ -14,26 +14,13 @@
import java.util.*;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.spec.PBEKeySpec;
import org.eclipse.core.runtime.IPath;
import org.eclipse.equinox.internal.security.auth.nls.SecAuthMessages;
import org.eclipse.equinox.security.storage.StorageException;
-import org.eclipse.equinox.security.storage.provider.IPreferencesContainer;
-import org.eclipse.equinox.security.storage.provider.IProviderHints;
import org.eclipse.osgi.util.NLS;
public class SecurePreferences {
- /**
- * Pseudo-module ID to use when encryption is done with the default password.
- */
- protected final static String DEFAULT_PASSWORD_ID = "org.eclipse.equinox.security.noModule"; //$NON-NLS-1$
-
- /**
- * Maximum unsuccessful decryption attempts per operation
- */
- static protected final int MAX_ATTEMPTS = 20;
-
private static final String PATH_SEPARATOR = String.valueOf(IPath.SEPARATOR);
private static final String[] EMPTY_STRING_ARRAY = new String[0];
@@ -227,16 +214,11 @@
return;
}
- PasswordExt passwordExt = getPassword(container);
+ PasswordExt passwordExt = getRoot().getPassword(null, container, true);
+ if (passwordExt == null)
+ throw new StorageException(StorageException.NO_PASSWORD, SecAuthMessages.loginNoPassword);
- CryptoData encryptedValue;
- try {
- encryptedValue = getRoot().getCipher().encrypt(passwordExt, value.getBytes());
- } catch (StorageException e) {
- RuntimeException exception = new IllegalStateException();
- exception.initCause(e);
- throw exception;
- }
+ CryptoData encryptedValue = getRoot().getCipher().encrypt(getRoot().getPassword(null, container, true), value.getBytes());
internalPut(key, encryptedValue.toString());
markModified();
}
@@ -255,8 +237,12 @@
return new String(data.getData());
}
+ PasswordExt passwordExt = getRoot().getPassword(moduleID, container, false);
+ if (passwordExt == null)
+ throw new StorageException(StorageException.NO_PASSWORD, SecAuthMessages.loginNoPassword);
+
try {
- byte[] clearText = getRoot().getCipher().decrypt(getPassword(moduleID, container), data);
+ byte[] clearText = getRoot().getCipher().decrypt(passwordExt, data);
return new String(clearText);
} catch (IllegalBlockSizeException e) { // invalid password?
throw new StorageException(StorageException.DECRYPTION_ERROR, e);
@@ -265,59 +251,6 @@
}
}
- ///////////////////////////////////////////////////////////////////////////////////////////////////////
- // Password handling routines
-
- /**
- * Provides password for a new entry using:
- * 1) default password, if any
- * 2a) if options specify usage of specific module, that module is polled to produce password
- * 2b) otherwise, password provider with highest priority is used to produce password
- */
- private PasswordExt getPassword(SecurePreferencesContainer container) throws StorageException {
- PasswordExt defaultPassword = getDefaultPassword(container);
- if (defaultPassword != null)
- return defaultPassword;
-
- String moduleID = null;
- if (container.hasOption(IProviderHints.REQUIRED_MODULE_ID)) {
- Object idHint = container.getOption(IProviderHints.REQUIRED_MODULE_ID);
- if (idHint instanceof String)
- moduleID = (String) idHint;
- }
- return getRoot().getModulePassword(moduleID, container);
- }
-
- /**
- * Provides password using specified password provider module
- */
- private PasswordExt getPassword(String moduleID, SecurePreferencesContainer container) throws StorageException {
- if (moduleID == null)
- throw new StorageException(StorageException.NO_SECURE_MODULE, SecAuthMessages.invalidEntryFormat);
- if (DEFAULT_PASSWORD_ID.equals(moduleID)) {
- PasswordExt defaultPassword = getDefaultPassword(container);
- if (defaultPassword != null)
- return defaultPassword;
- throw new StorageException(StorageException.NO_SECURE_MODULE, SecAuthMessages.noDefaultPassword);
- }
-
- return getRoot().getModulePassword(moduleID, container);
- }
-
- /**
- * Retrieves default password from options, if any
- */
- private PasswordExt getDefaultPassword(IPreferencesContainer container) {
- if (container.hasOption(IProviderHints.DEFAULT_PASSWORD)) {
- Object passwordHint = container.getOption(IProviderHints.DEFAULT_PASSWORD);
- if (passwordHint instanceof PBEKeySpec)
- return new PasswordExt((PBEKeySpec) passwordHint, DEFAULT_PASSWORD_ID);
- }
- return null;
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////////////////
-
synchronized protected void internalPut(String key, String value) {
if (values == null)
values = new HashMap(5);
@@ -525,17 +458,7 @@
return (moduleID != null);
}
- public void clearPasswordVerification(SecurePreferencesContainer container) {
- PasswordExt defaultPassword = getDefaultPassword(container);
- if (defaultPassword != null)
- return;
-
- String moduleID = null;
- if (container.hasOption(IProviderHints.REQUIRED_MODULE_ID)) {
- Object idHint = container.getOption(IProviderHints.REQUIRED_MODULE_ID);
- if (idHint instanceof String)
- moduleID = (String) idHint;
- }
- getRoot().clearPasswordVerification(moduleID, container);
+ public boolean passwordChanging(SecurePreferencesContainer container) {
+ return getRoot().onChangePassword(container);
}
}
diff --git a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesMapper.java b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesMapper.java
index 32c9744..062bf1f 100644
--- a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesMapper.java
+++ b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesMapper.java
@@ -85,8 +85,7 @@
}
}
- // Not an API; links to the dynamic bundles
- static public void clearCaches() {
+ static public void clearPasswordCache() {
synchronized (preferences) {
for (Iterator i = preferences.values().iterator(); i.hasNext();) {
SecurePreferencesRoot provider = (SecurePreferencesRoot) i.next();
diff --git a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesRoot.java b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesRoot.java
index b4404ae..541b75b 100644
--- a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesRoot.java
+++ b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesRoot.java
@@ -16,10 +16,12 @@
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.PBEKeySpec;
+import org.eclipse.equinox.internal.security.auth.AuthPlugin;
import org.eclipse.equinox.internal.security.auth.nls.SecAuthMessages;
import org.eclipse.equinox.internal.security.storage.friends.IStorageConstants;
import org.eclipse.equinox.security.storage.StorageException;
-import org.eclipse.equinox.security.storage.provider.IProviderHints;
+import org.eclipse.equinox.security.storage.provider.*;
+import org.eclipse.osgi.util.NLS;
/**
* Root secure preference node. In addition to usual things it stores location, modified
@@ -55,6 +57,16 @@
*/
private final static String PASSWORD_VERIFICATION_SAMPLE = "-> brown fox jumped over lazy dog <-"; //$NON-NLS-1$
+ /**
+ * Pseudo-module ID to use when encryption is done with the default password.
+ */
+ protected final static String DEFAULT_PASSWORD_ID = "org.eclipse.equinox.security.noModule"; //$NON-NLS-1$
+
+ /**
+ * Maximum unsuccessful decryption attempts per operation
+ */
+ static protected final int MAX_ATTEMPTS = 20;
+
final private URL location;
private boolean modified = false;
@@ -164,7 +176,32 @@
}
}
- public PasswordExt getModulePassword(String moduleID, SecurePreferencesContainer container) throws StorageException {
+ /**
+ * Provides password for a new entry using:
+ * 1) default password, if any
+ * 2a) if options specify usage of specific module, that module is polled to produce password
+ * 2b) otherwise, password provider with highest priority is used to produce password
+ */
+ public PasswordExt getPassword(String moduleID, IPreferencesContainer container, boolean encryption) throws StorageException {
+ if (encryption) { // provides password for a new entry
+ PasswordExt defaultPassword = getDefaultPassword(container);
+ if (defaultPassword != null)
+ return defaultPassword;
+ moduleID = getDefaultModuleID(container);
+ } else { // provides password for previously encrypted entry using its specified password provider module
+ if (moduleID == null)
+ throw new StorageException(StorageException.NO_SECURE_MODULE, SecAuthMessages.invalidEntryFormat);
+ if (DEFAULT_PASSWORD_ID.equals(moduleID)) { // was default password used?
+ PasswordExt defaultPassword = getDefaultPassword(container);
+ if (defaultPassword != null)
+ return defaultPassword;
+ throw new StorageException(StorageException.NO_SECURE_MODULE, SecAuthMessages.noDefaultPassword);
+ }
+ }
+ return getModulePassword(moduleID, container);
+ }
+
+ private PasswordExt getModulePassword(String moduleID, IPreferencesContainer container) throws StorageException {
if (DEFAULT_PASSWORD_ID.equals(moduleID)) // this should never happen but add this check just in case
throw new StorageException(StorageException.NO_PASSWORD, SecAuthMessages.loginNoPassword);
@@ -177,13 +214,13 @@
// is there password verification string already?
SecurePreferences node = node(PASSWORD_VERIFICATION_NODE);
boolean newPassword = !node.hasKey(key);
- container.setOption(IProviderHints.NEW_PASSWORD, new Boolean(newPassword));
+ int passwordType = newPassword ? PasswordProvider.CREATE_NEW_PASSWORD : 0;
boolean validPassword = false;
PasswordExt passwordExt = null;
for (int i = 0; i < MAX_ATTEMPTS; i++) {
- PBEKeySpec password = moduleExt.login(container);
+ PBEKeySpec password = moduleExt.getPassword(container, passwordType);
if (password == null)
return null;
passwordExt = new PasswordExt(password, key);
@@ -206,11 +243,9 @@
} catch (IllegalBlockSizeException e) {
if (!moduleExt.changePassword(e, container))
break;
- moduleExt.logout(container);
} catch (BadPaddingException e) {
if (!moduleExt.changePassword(e, container))
break;
- moduleExt.logout(container);
}
}
if (validPassword) {
@@ -221,22 +256,78 @@
}
}
+ /**
+ * Retrieves default password from options, if any
+ */
+ private PasswordExt getDefaultPassword(IPreferencesContainer container) {
+ if (container.hasOption(IProviderHints.DEFAULT_PASSWORD)) {
+ Object passwordHint = container.getOption(IProviderHints.DEFAULT_PASSWORD);
+ if (passwordHint instanceof PBEKeySpec)
+ return new PasswordExt((PBEKeySpec) passwordHint, DEFAULT_PASSWORD_ID);
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves requested module ID from options, if any
+ */
+ private String getDefaultModuleID(IPreferencesContainer container) {
+ if (container.hasOption(IProviderHints.REQUIRED_MODULE_ID)) {
+ Object idHint = container.getOption(IProviderHints.REQUIRED_MODULE_ID);
+ if (idHint instanceof String)
+ return (String) idHint;
+ }
+ return null;
+ }
+
+ public boolean onChangePassword(IPreferencesContainer container) {
+ // validation: can't change externally supplied password
+ PasswordExt defaultPassword = getDefaultPassword(container);
+ if (defaultPassword != null)
+ return false;
+ String moduleID = getDefaultModuleID(container);
+
+ // validation: must have a password module
+ PasswordProviderModuleExt moduleExt;
+ try {
+ moduleExt = PasswordProviderSelector.getInstance().findStorageModule(moduleID);
+ } catch (StorageException e) {
+ return false; // no module -> nothing to do
+ }
+
+ // obtain new password first
+ int passwordType = PasswordProvider.CREATE_NEW_PASSWORD | PasswordProvider.PASSWORD_CHANGE;
+ PBEKeySpec password = moduleExt.getPassword(container, passwordType);
+ if (password == null)
+ return false;
+
+ synchronized (passwordCache) { // we are good to go, lock into single processing thread
+ // create verification node
+ String key = moduleExt.getID();
+ PasswordExt passwordExt = new PasswordExt(password, key);
+ CryptoData encryptedValue;
+ try {
+ encryptedValue = getCipher().encrypt(passwordExt, PASSWORD_VERIFICATION_SAMPLE.getBytes());
+ } catch (StorageException e) {
+ String msg = NLS.bind(SecAuthMessages.encryptingError, key, PASSWORD_VERIFICATION_NODE);
+ AuthPlugin.getDefault().logError(msg, e);
+ return false;
+ }
+
+ SecurePreferences node = node(PASSWORD_VERIFICATION_NODE);
+ node.internalPut(key, encryptedValue.toString());
+ markModified();
+
+ // store password in the memory cache
+ passwordCache.put(key, passwordExt);
+ }
+ return true;
+ }
+
public void clearPasswordCache() {
synchronized (passwordCache) {
passwordCache.clear();
}
}
- public void clearPasswordVerification(String moduleID, SecurePreferencesContainer container) {
- PasswordProviderModuleExt moduleExt;
- try {
- moduleExt = PasswordProviderSelector.getInstance().findStorageModule(moduleID);
- } catch (StorageException e) {
- return; // no module -> nothing to do
- }
- String key = moduleExt.getID();
- SecurePreferences node = node(PASSWORD_VERIFICATION_NODE);
- if (node.hasKey(key))
- node.remove(key);
- }
}
diff --git a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesWrapper.java b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesWrapper.java
index fe29024..931dc20 100644
--- a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesWrapper.java
+++ b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/SecurePreferencesWrapper.java
@@ -162,9 +162,7 @@
return node.isEncrypted(key);
}
- /////////////////////////////////////////////////////////////////////////////////////
- // really internal; used for password reset; don't use
- public void clearPasswordVerification() {
- node.clearPasswordVerification(container);
+ public boolean passwordChanging() {
+ return node.passwordChanging(container);
}
}
diff --git a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/friends/InternalExchangeUtils.java b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/friends/InternalExchangeUtils.java
index b529dd3..02341f2 100644
--- a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/friends/InternalExchangeUtils.java
+++ b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/friends/InternalExchangeUtils.java
@@ -38,7 +38,7 @@
* Clears cached passwords from the open storages and password providers.
*/
static public void passwordProvidersLogout() {
- PasswordProviderSelector.getInstance().logout();
+ SecurePreferencesMapper.clearPasswordCache();
}
/**
diff --git a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/friends/ReEncrypter.java b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/friends/ReEncrypter.java
index 2bf08c6..f20c5e9 100644
--- a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/friends/ReEncrypter.java
+++ b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/internal/security/storage/friends/ReEncrypter.java
@@ -86,15 +86,22 @@
}
/**
- * The method will encrypt all data from the memory structure created by
- * decrypt using current passwords and providers. The original encrypted
- * data will be overwritten.
+ * The method try to create new password.
+ * <p>
+ * <strong>Note</strong> that after the successful completion of this method the secure storage has
+ * new verification string and previously decoded values <b>must</b> be added via encrypt() method
+ * or they will become unavailable via conventional APIs.
+ * </p>
+ */
+ public boolean switchToNewPassword() {
+ return ((SecurePreferencesWrapper) root).passwordChanging();
+ }
+
+ /**
+ * The method will encrypt all data from the memory structure created by decrypt using current
+ * passwords and providers. The original encrypted data will be overwritten.
*/
public boolean encrypt() {
- InternalExchangeUtils.passwordProvidersLogout();
- // TBD let providers know that this is a new password?
- ((SecurePreferencesWrapper) root).clearPasswordVerification();
-
boolean result = true;
for (Iterator i = elements.iterator(); i.hasNext();) {
TmpElement element = (TmpElement) i.next();
diff --git a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/security/storage/provider/IProviderHints.java b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/security/storage/provider/IProviderHints.java
index e401ac0..44148bf 100644
--- a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/security/storage/provider/IProviderHints.java
+++ b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/security/storage/provider/IProviderHints.java
@@ -48,10 +48,4 @@
* Storage will use this password. Expected value: {@link PBEKeySpec}.
*/
static final public String DEFAULT_PASSWORD = "org.eclipse.equinox.security.storage.defaultPassword"; //$NON-NLS-1$
-
- /**
- * Specifies if a new password is being requested. Expected value: {@link Boolean}.
- */
- static final public String NEW_PASSWORD = "org.eclipse.equinox.security.storage.newPassword"; //$NON-NLS-1$
-
}
diff --git a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/security/storage/provider/PasswordProvider.java b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/security/storage/provider/PasswordProvider.java
index f832231..d330d8a 100644
--- a/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/security/storage/provider/PasswordProvider.java
+++ b/bundles/org.eclipse.equinox.security/src/org/eclipse/equinox/security/storage/provider/PasswordProvider.java
@@ -28,26 +28,31 @@
abstract public class PasswordProvider {
/**
+ * Bit mask for the password type field of the {@link #getPassword(IPreferencesContainer, int)}
+ * method. If value at this bit set to <code>1</code>, it indicates that a new
+ * password should be created; otherwise this is a request for the password previously
+ * used for this secure storage.
+ */
+ final public static int CREATE_NEW_PASSWORD = 1 << 0;
+
+ /**
+ * Bit mask for the password type field of the {@link #getPassword(IPreferencesContainer, int)}
+ * method. If value at this bit set to <code>1</code>, it indicates that a new password
+ * is requested as a part of the password change operation.
+ */
+ final public static int PASSWORD_CHANGE = 1 << 1;
+
+ /**
* This method should return the password used to encrypt entries in the secure
* preferences.
* @param container container of the secure preferences
+ * @param passwordType the collection of bits that describes password type requested. See
+ * {@link #CREATE_NEW_PASSWORD} and {@link #PASSWORD_CHANGE}. When evaluating value of this
+ * field use bit-wise filters as additional bits might be used in future versions
* @return password used to encrypt entries in the secure preferences, <code>null</code>
* if unable to obtain password
*/
- abstract public PBEKeySpec login(IPreferencesContainer container);
-
- /**
- * A logical equivalent of "logout" for the password providers.
- * <p>
- * The module should clear its cached password if it is the password that can be
- * re-obtained from some third party (such as asking user to type it in). Modules
- * should not discard auto generated passwords that are not available from other
- * sources.
- * </p>
- * @param container container of the secure preferences, might be <code>null</code>
- * if logout request is not related to a specific container
- */
- abstract public void logout(IPreferencesContainer container);
+ abstract public PBEKeySpec getPassword(IPreferencesContainer container, int passwordType);
/**
* Constructor.
@@ -64,7 +69,7 @@
* @return <code>true</code> if a different password might be provided; <code>false</code>
* otherwise. If in doubt, return <code>false</code>
*/
- public boolean changePassword(Exception e, IPreferencesContainer container) {
+ public boolean retryOnError(Exception e, IPreferencesContainer container) {
return false;
}