mirror of https://github.com/sipwise/jitsi.git
Merges branches/gsoc10/passwdstrg@7435 which represents the work of Dmitri Melnikov on the "Password storage" GSoC 2010 project into trunk.
parent
a16201daf3
commit
04cac3354c
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.credentialsstorage;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.spec.*;
|
||||
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.*;
|
||||
|
||||
import net.java.sip.communicator.service.credentialsstorage.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
|
||||
/**
|
||||
* Performs encryption and decryption of text using AES algorithm.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public class AESCrypto
|
||||
implements Crypto
|
||||
{
|
||||
/**
|
||||
* The algorithm associated with the key.
|
||||
*/
|
||||
private static final String KEY_ALGORITHM = "AES";
|
||||
|
||||
/**
|
||||
* AES in ECB mode with padding.
|
||||
*/
|
||||
private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5PADDING";
|
||||
|
||||
/**
|
||||
* Salt used when creating the key.
|
||||
*/
|
||||
private static byte[] SALT =
|
||||
{ 0x0C, 0x0A, 0x0F, 0x0E, 0x0B, 0x0E, 0x0E, 0x0F };
|
||||
|
||||
/**
|
||||
* Length of the key in bits.
|
||||
*/
|
||||
private static int KEY_LENGTH = 256;
|
||||
|
||||
/**
|
||||
* Number of iterations to use when creating the key.
|
||||
*/
|
||||
private static int ITERATION_COUNT = 1024;
|
||||
|
||||
/**
|
||||
* Key derived from the master password to use for encryption/decryption.
|
||||
*/
|
||||
private Key key;
|
||||
|
||||
/**
|
||||
* Decryption object.
|
||||
*/
|
||||
private Cipher decryptCipher;
|
||||
|
||||
/**
|
||||
* Encryption object.
|
||||
*/
|
||||
private Cipher encryptCipher;
|
||||
|
||||
/**
|
||||
* Creates the encryption and decryption objects and the key.
|
||||
*
|
||||
* @param masterPassword used to derive the key. Can be null.
|
||||
*/
|
||||
public AESCrypto(String masterPassword)
|
||||
{
|
||||
// if the password is empty, we get an exception constructing the key
|
||||
if (masterPassword == null)
|
||||
{
|
||||
// here a default password can be set,
|
||||
// cannot be an empty string
|
||||
masterPassword = " ";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
encryptCipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
|
||||
// Password-Based Key Derivation Function found in PKCS5 v2.0.
|
||||
// This is only available with java 6.
|
||||
SecretKeyFactory factory =
|
||||
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
|
||||
// Make a key from the master password
|
||||
KeySpec spec =
|
||||
new PBEKeySpec(masterPassword.toCharArray(), SALT,
|
||||
ITERATION_COUNT, KEY_LENGTH);
|
||||
SecretKey tmp = factory.generateSecret(spec);
|
||||
// Make an algorithm specific key
|
||||
key = new SecretKeySpec(tmp.getEncoded(), KEY_ALGORITHM);
|
||||
}
|
||||
catch (InvalidKeySpecException e)
|
||||
{
|
||||
throw new RuntimeException("Invalid key specification", e);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e)
|
||||
{
|
||||
throw new RuntimeException("Algorithm not found", e);
|
||||
}
|
||||
catch (NoSuchPaddingException e)
|
||||
{
|
||||
throw new RuntimeException("Padding not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the cyphertext using the key.
|
||||
*
|
||||
* @param ciphertext base64 encoded encrypted data
|
||||
* @return decrypted data
|
||||
* @throws CryptoException when the ciphertext cannot be decrypted with the
|
||||
* key or on decryption error.
|
||||
*/
|
||||
public String decrypt(String ciphertext) throws CryptoException
|
||||
{
|
||||
try
|
||||
{
|
||||
decryptCipher.init(Cipher.DECRYPT_MODE, key);
|
||||
return new String(decryptCipher.doFinal(Base64.decode(ciphertext)));
|
||||
}
|
||||
catch (BadPaddingException e)
|
||||
{
|
||||
throw new CryptoException(CryptoException.WRONG_KEY, e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new CryptoException(CryptoException.DECRYPTION_ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the plaintext using the key.
|
||||
*
|
||||
* @param plaintext data to be encrypted
|
||||
* @return base64 encoded encrypted data
|
||||
* @throws CryptoException on encryption error
|
||||
*/
|
||||
public String encrypt(String plaintext) throws CryptoException
|
||||
{
|
||||
try
|
||||
{
|
||||
encryptCipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
return new String(Base64.encode(encryptCipher.doFinal(plaintext
|
||||
.getBytes("UTF-8"))));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new CryptoException(CryptoException.ENCRYPTION_ERROR, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.credentialsstorage;
|
||||
|
||||
import net.java.sip.communicator.service.credentialsstorage.*;
|
||||
import net.java.sip.communicator.service.resources.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
|
||||
import org.osgi.framework.*;
|
||||
|
||||
/**
|
||||
* Activator for the @link{CredentialsStorageService}.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public class CredentialsStorageActivator
|
||||
implements BundleActivator
|
||||
{
|
||||
/**
|
||||
* The <tt>Logger</tt> used by the <tt>CredentialsStorageActivator</tt>
|
||||
* class and its instances.
|
||||
*/
|
||||
private static final Logger logger
|
||||
= Logger.getLogger(CredentialsStorageActivator.class);
|
||||
|
||||
/**
|
||||
* The {@link CredentialsStorageService} implementation.
|
||||
*/
|
||||
private CredentialsStorageServiceImpl impl;
|
||||
|
||||
/**
|
||||
* The {@link BundleContext}.
|
||||
*/
|
||||
private static BundleContext bundleContext;
|
||||
|
||||
/**
|
||||
* The resources service.
|
||||
*/
|
||||
private static ResourceManagementService resourcesService;
|
||||
|
||||
/**
|
||||
* Starts the credentials storage service
|
||||
*
|
||||
* @param bundleContext the <tt>BundleContext</tt> as provided from the OSGi
|
||||
* framework
|
||||
* @throws Exception if anything goes wrong
|
||||
*/
|
||||
public void start(BundleContext bundleContext) throws Exception
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(
|
||||
"Service Impl: " + getClass().getName() + " [ STARTED ]");
|
||||
}
|
||||
|
||||
CredentialsStorageActivator.bundleContext = bundleContext;
|
||||
|
||||
impl = new CredentialsStorageServiceImpl();
|
||||
impl.start(bundleContext);
|
||||
|
||||
bundleContext.registerService(
|
||||
CredentialsStorageService.class.getName(), impl, null);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(
|
||||
"Service Impl: " + getClass().getName() + " [REGISTERED]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the credentials storage service.
|
||||
*
|
||||
* @param bundleContext BundleContext
|
||||
* @throws Exception if anything goes wrong
|
||||
*/
|
||||
public void stop(BundleContext bundleContext) throws Exception
|
||||
{
|
||||
logger.logEntry();
|
||||
impl.stop();
|
||||
logger
|
||||
.info("The CredentialsStorageService stop method has been called.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an internationalized string corresponding to the given key.
|
||||
*
|
||||
* @param key The key of the string.
|
||||
* @return An internationalized string corresponding to the given key.
|
||||
*/
|
||||
public static String getString(String key)
|
||||
{
|
||||
return getResources().getI18NString(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of {@link ResourceManagementService}.
|
||||
*
|
||||
* @return an instance of {@link ResourceManagementService}.
|
||||
*/
|
||||
private static ResourceManagementService getResources()
|
||||
{
|
||||
if (resourcesService == null)
|
||||
{
|
||||
resourcesService
|
||||
= ResourceManagementServiceUtils.getService(bundleContext);
|
||||
}
|
||||
return resourcesService;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,522 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.credentialsstorage;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import net.java.sip.communicator.service.configuration.*;
|
||||
import net.java.sip.communicator.service.credentialsstorage.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
|
||||
import org.osgi.framework.*;
|
||||
|
||||
/**
|
||||
* Implements {@link CredentialsStorageService} to load and store user
|
||||
* credentials from/to the {@link ConfigurationService}.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public class CredentialsStorageServiceImpl
|
||||
implements CredentialsStorageService
|
||||
{
|
||||
/**
|
||||
* The logger for this class.
|
||||
*/
|
||||
private final Logger logger =
|
||||
Logger.getLogger(CredentialsStorageServiceImpl.class);
|
||||
|
||||
/**
|
||||
* The name of a property which represents an encrypted password.
|
||||
*/
|
||||
public static final String ACCOUNT_ENCRYPTED_PASSWORD =
|
||||
"ENCRYPTED_PASSWORD";
|
||||
|
||||
/**
|
||||
* The name of a property which represents an unencrypted password.
|
||||
*/
|
||||
public static final String ACCOUNT_UNENCRYPTED_PASSWORD = "PASSWORD";
|
||||
|
||||
/**
|
||||
* The property in the configuration that we use to verify master password
|
||||
* existence and correctness.
|
||||
*/
|
||||
private static final String MASTER_PROP =
|
||||
"net.java.sip.communicator.impl.credentialsstorage.MASTER";
|
||||
|
||||
/**
|
||||
* This value will be encrypted and saved in MASTER_PROP and
|
||||
* will be used to verify the key's correctness.
|
||||
*/
|
||||
private static final String MASTER_PROP_VALUE = "true";
|
||||
|
||||
/**
|
||||
* The configuration service.
|
||||
*/
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
/**
|
||||
* A {@link Crypto} instance that does the actual encryption and decryption.
|
||||
*/
|
||||
private Crypto crypto;
|
||||
|
||||
/**
|
||||
* Initializes the credentials service by fetching the configuration service
|
||||
* reference from the bundle context.
|
||||
*
|
||||
* @param bc bundle context
|
||||
*/
|
||||
void start(BundleContext bc)
|
||||
{
|
||||
ServiceReference confServiceReference =
|
||||
bc.getServiceReference(ConfigurationService.class.getName());
|
||||
this.configurationService =
|
||||
(ConfigurationService) bc.getService(confServiceReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget the encryption/decryption key when stopping the service.
|
||||
*/
|
||||
void stop()
|
||||
{
|
||||
crypto = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the password for the specified account. When password is
|
||||
* null the property is cleared.
|
||||
*
|
||||
* Many threads can call this method at the same time, and the
|
||||
* first thread may present the user with the master password prompt and
|
||||
* create a <tt>Crypto</tt> instance based on the input
|
||||
* (<tt>createCrypto</tt> method). This instance will be used later by all
|
||||
* other threads.
|
||||
*
|
||||
* @see CredentialsStorageServiceImpl#createCrypto()
|
||||
*/
|
||||
public synchronized void storePassword(String accountPrefix, String password)
|
||||
{
|
||||
boolean createdOrExisting = createCrypto();
|
||||
if (createdOrExisting)
|
||||
{
|
||||
String encryptedPassword = null;
|
||||
try
|
||||
{
|
||||
if (password != null)
|
||||
encryptedPassword = crypto.encrypt(password);
|
||||
setEncrypted(accountPrefix, encryptedPassword);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.warn("Encryption failed, password not saved", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the password for the specified account.
|
||||
* First check if the password is stored in the configuration unencrypted
|
||||
* and if so, encrypt it and store in the new property. Otherwise, if the
|
||||
* password is stored encrypted, decrypt it with the master password.
|
||||
*
|
||||
* Many threads can call this method at the same time, and the
|
||||
* first thread may present the user with the master password prompt and
|
||||
* create a <tt>Crypto</tt> instance based on the input
|
||||
* (<tt>createCrypto</tt> method). This instance will be used later by all
|
||||
* other threads.
|
||||
*
|
||||
* @see CredentialsStorageServiceImpl#createCrypto()
|
||||
*/
|
||||
public synchronized String loadPassword(String accountPrefix)
|
||||
{
|
||||
String password = null;
|
||||
if (isStoredUnencrypted(accountPrefix))
|
||||
{
|
||||
password = new String(Base64.decode(getUnencrypted(accountPrefix)));
|
||||
movePasswordProperty(accountPrefix, password);
|
||||
}
|
||||
|
||||
if (password == null && isStoredEncrypted(accountPrefix))
|
||||
{
|
||||
boolean createdOrExisting = createCrypto();
|
||||
if (createdOrExisting)
|
||||
{
|
||||
try
|
||||
{
|
||||
String ciphertext = getEncrypted(accountPrefix);
|
||||
password = crypto.decrypt(ciphertext);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.warn("Decryption with master password failed", e);
|
||||
// password stays null
|
||||
}
|
||||
}
|
||||
}
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the password for the account that starts with the given prefix by
|
||||
* setting its value in the configuration to null.
|
||||
*/
|
||||
public void removePassword(String accountPrefix)
|
||||
{
|
||||
setEncrypted(accountPrefix, null);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Password for '" + accountPrefix + "' removed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if master password is used to encrypt saved account passwords.
|
||||
*
|
||||
* @return true if used, false if not
|
||||
*/
|
||||
public boolean isUsingMasterPassword()
|
||||
{
|
||||
return null != configurationService.getString(MASTER_PROP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the correctness of the master password.
|
||||
* Since we do not store the MP itself, if MASTER_PROP_VALUE
|
||||
* is equal to the decrypted MASTER_PROP value, then
|
||||
* the MP is considered correct.
|
||||
*
|
||||
* @param master master password
|
||||
* @return true if the password is correct, false otherwise
|
||||
*/
|
||||
public boolean verifyMasterPassword(String master)
|
||||
{
|
||||
Crypto localCrypto = new AESCrypto(master);
|
||||
try
|
||||
{
|
||||
// use this value to verify master password correctness
|
||||
String encryptedValue = getEncryptedMasterPropValue();
|
||||
return MASTER_PROP_VALUE
|
||||
.equals(localCrypto.decrypt(encryptedValue));
|
||||
}
|
||||
catch (CryptoException e)
|
||||
{
|
||||
if (e.getErrorCode() == CryptoException.WRONG_KEY)
|
||||
{
|
||||
logger.debug("Incorrect master pass", e);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// this should not happen, so just in case it does..
|
||||
throw new RuntimeException("Decryption failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the master password from the old to the new one.
|
||||
* Decrypts all encrypted password properties from the configuration
|
||||
* with the oldPassword and encrypts them again with newPassword.
|
||||
*
|
||||
* @param oldPassword old master password
|
||||
* @param newPassword new master password
|
||||
*/
|
||||
public boolean changeMasterPassword(String oldPassword, String newPassword)
|
||||
{
|
||||
// get all encrypted account password properties
|
||||
List<String> encryptedAccountProps =
|
||||
configurationService
|
||||
.getPropertyNamesBySuffix(ACCOUNT_ENCRYPTED_PASSWORD);
|
||||
|
||||
// this map stores propName -> password
|
||||
Map<String, String> passwords = new HashMap<String, String>();
|
||||
try
|
||||
{
|
||||
// read from the config and decrypt with the old MP..
|
||||
setMasterPassword(oldPassword);
|
||||
for (String propName : encryptedAccountProps)
|
||||
{
|
||||
String propValue = configurationService.getString(propName);
|
||||
if (propValue != null)
|
||||
{
|
||||
String decrypted = crypto.decrypt(propValue);
|
||||
passwords.put(propName, decrypted);
|
||||
}
|
||||
}
|
||||
// ..and encrypt again with the new, write to the config
|
||||
setMasterPassword(newPassword);
|
||||
for (Map.Entry<String, String> entry : passwords.entrySet())
|
||||
{
|
||||
String encrypted = crypto.encrypt(entry.getValue());
|
||||
configurationService.setProperty(entry.getKey(), encrypted);
|
||||
}
|
||||
// save the verification value, encrypted with the new MP,
|
||||
// or remove it if the newPassword is null (we are unsetting MP)
|
||||
writeVerificationValue(newPassword == null);
|
||||
}
|
||||
catch (CryptoException ce)
|
||||
{
|
||||
logger.debug(ce);
|
||||
crypto = null;
|
||||
passwords = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the master password to the argument value.
|
||||
*
|
||||
* @param master master password
|
||||
*/
|
||||
private void setMasterPassword(String master)
|
||||
{
|
||||
crypto = new AESCrypto(master);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks for master password if needed, encrypts the password, saves it to
|
||||
* the new property and removes the old property.
|
||||
*
|
||||
* @param accountPrefix prefix of the account
|
||||
* @param password unencrypted password
|
||||
*/
|
||||
private void movePasswordProperty(String accountPrefix, String password)
|
||||
{
|
||||
boolean createdOrExisting = createCrypto();
|
||||
if (createdOrExisting)
|
||||
{
|
||||
try
|
||||
{
|
||||
String encryptedPassword = crypto.encrypt(password);
|
||||
setEncrypted(accountPrefix, encryptedPassword);
|
||||
setUnencrypted(accountPrefix, null);
|
||||
}
|
||||
catch (CryptoException e)
|
||||
{
|
||||
logger.debug("Encryption failed", e);
|
||||
// properties are not moved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the verification value to the configuration for later use or
|
||||
* removes it completely depending on the remove flag argument.
|
||||
*
|
||||
* @param remove to remove the verification value or just overwrite it.
|
||||
*/
|
||||
private void writeVerificationValue(boolean remove)
|
||||
{
|
||||
if (remove)
|
||||
{
|
||||
configurationService.removeProperty(MASTER_PROP);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
String encryptedValue = crypto.encrypt(MASTER_PROP_VALUE);
|
||||
configurationService.setProperty(MASTER_PROP, encryptedValue);
|
||||
}
|
||||
catch (CryptoException e)
|
||||
{
|
||||
logger.warn("Failed to encrypt and write verification value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Crypto instance only when it's null, either with a user input
|
||||
* master password or with null. If the user decided not to input anything,
|
||||
* the instance is not created.
|
||||
*
|
||||
* @return true if Crypto instance was created, false otherwise
|
||||
*/
|
||||
private boolean createCrypto()
|
||||
{
|
||||
if (crypto == null)
|
||||
{
|
||||
logger.debug("Crypto instance is null, creating.");
|
||||
if (isUsingMasterPassword())
|
||||
{
|
||||
String master = showPasswordPrompt();
|
||||
if (master == null)
|
||||
{
|
||||
// user clicked cancel button in the prompt
|
||||
crypto = null;
|
||||
return false;
|
||||
}
|
||||
// at this point the master password must be correct,
|
||||
// so we set the crypto instance to use it
|
||||
setMasterPassword(master);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("Master password not set");
|
||||
|
||||
// setting the master password to null means
|
||||
// we shall still be using encryption/decryption
|
||||
// but using some default value, not something specified
|
||||
// by the user
|
||||
setMasterPassword(null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a password prompt to the user in a loop until it is correct or
|
||||
* the user presses the cancel button.
|
||||
*
|
||||
* @return the entered password or null if none was provided.
|
||||
*/
|
||||
private String showPasswordPrompt()
|
||||
{
|
||||
String master = null;
|
||||
JPasswordField passwordField = new JPasswordField();
|
||||
String inputMsg =
|
||||
CredentialsStorageActivator
|
||||
.getString("plugin.securityconfig.masterpassword.MP_INPUT");
|
||||
String errorMsg =
|
||||
"<html><font color=\"red\">"
|
||||
+ CredentialsStorageActivator
|
||||
.getString("plugin.securityconfig.masterpassword.MP_VERIFICATION_FAILURE_MSG")
|
||||
+ "</font></html>";
|
||||
|
||||
// Ask for master password until the input is correct or
|
||||
// cancel button is pressed
|
||||
boolean correct = true;
|
||||
do
|
||||
{
|
||||
Object[] msg = null;
|
||||
if (correct)
|
||||
{
|
||||
msg = new Object[]
|
||||
{ inputMsg, passwordField };
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = new Object[]
|
||||
{ errorMsg, inputMsg, passwordField };
|
||||
}
|
||||
// clear the password field
|
||||
passwordField.setText("");
|
||||
|
||||
if (JOptionPane
|
||||
.showOptionDialog(
|
||||
null,
|
||||
msg,
|
||||
CredentialsStorageActivator
|
||||
.getString("plugin.securityconfig.masterpassword.MP_TITLE"),
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE,
|
||||
null,
|
||||
new String[]
|
||||
{
|
||||
CredentialsStorageActivator.getString("service.gui.OK"),
|
||||
CredentialsStorageActivator
|
||||
.getString("service.gui.CANCEL") },
|
||||
CredentialsStorageActivator.getString("service.gui.OK")) == JOptionPane.YES_OPTION)
|
||||
{
|
||||
|
||||
master = new String(passwordField.getPassword());
|
||||
correct = !master.isEmpty() && verifyMasterPassword(master);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
while (!correct);
|
||||
return master;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the property for the master password from the configuration
|
||||
* service.
|
||||
*
|
||||
* @return the property for the master password
|
||||
*/
|
||||
private String getEncryptedMasterPropValue()
|
||||
{
|
||||
return configurationService.getString(MASTER_PROP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the encrypted account password using configuration service.
|
||||
*
|
||||
* @param accountPrefix account prefix
|
||||
* @return the encrypted account password.
|
||||
*/
|
||||
private String getEncrypted(String accountPrefix)
|
||||
{
|
||||
return configurationService.getString(accountPrefix + "."
|
||||
+ ACCOUNT_ENCRYPTED_PASSWORD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the encrypted account password using configuration service.
|
||||
*
|
||||
* @param accountPrefix account prefix
|
||||
* @param value the encrypted account password.
|
||||
*/
|
||||
private void setEncrypted(String accountPrefix, String value)
|
||||
{
|
||||
configurationService.setProperty(accountPrefix + "."
|
||||
+ ACCOUNT_ENCRYPTED_PASSWORD, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if encrypted account password is saved in the configuration.
|
||||
*
|
||||
* @return true if saved, false if not
|
||||
*/
|
||||
public boolean isStoredEncrypted(String accountPrefix)
|
||||
{
|
||||
return null != configurationService.getString(accountPrefix + "."
|
||||
+ ACCOUNT_ENCRYPTED_PASSWORD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the unencrypted account password using configuration service.
|
||||
*
|
||||
* @param accountPrefix account prefix
|
||||
* @return the unencrypted account password
|
||||
*/
|
||||
private String getUnencrypted(String accountPrefix)
|
||||
{
|
||||
return configurationService.getString(accountPrefix + "."
|
||||
+ ACCOUNT_UNENCRYPTED_PASSWORD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the unencrypted account password using configuration service.
|
||||
*
|
||||
* @param accountPrefix account prefix
|
||||
* @param value the unencrypted account password
|
||||
*/
|
||||
private void setUnencrypted(String accountPrefix, String value)
|
||||
{
|
||||
configurationService.setProperty(accountPrefix + "."
|
||||
+ ACCOUNT_UNENCRYPTED_PASSWORD, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if unencrypted account password is saved in the configuration.
|
||||
*
|
||||
* @param accountPrefix account prefix
|
||||
* @return true if saved, false if not
|
||||
*/
|
||||
private boolean isStoredUnencrypted(String accountPrefix)
|
||||
{
|
||||
configurationService.getPropertyNamesByPrefix("", false);
|
||||
return null != configurationService.getString(accountPrefix + "."
|
||||
+ ACCOUNT_UNENCRYPTED_PASSWORD);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.credentialsstorage;
|
||||
|
||||
import net.java.sip.communicator.service.credentialsstorage.*;
|
||||
|
||||
/**
|
||||
* Allows to encrypt and decrypt text using a symmetric algorithm.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public interface Crypto
|
||||
{
|
||||
/**
|
||||
* Decrypts the cipher text and returns the result.
|
||||
*
|
||||
* @param ciphertext base64 encoded encrypted data
|
||||
* @return decrypted data
|
||||
* @throws CryptoException when the ciphertext cannot be decrypted with the
|
||||
* key or on decryption error.
|
||||
*/
|
||||
public String decrypt(String ciphertext) throws CryptoException;
|
||||
|
||||
/**
|
||||
* Encrypts the plain text and returns the result.
|
||||
*
|
||||
* @param plaintext data to be encrypted
|
||||
* @return base64 encoded encrypted data
|
||||
* @throws CryptoException on encryption error
|
||||
*/
|
||||
public String encrypt(String plaintext) throws CryptoException;
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
Bundle-Activator: net.java.sip.communicator.impl.credentialsstorage.CredentialsStorageActivator
|
||||
Bundle-Name: Credentials Storage Service Implementation
|
||||
Bundle-Description: A bundle that handles credentials
|
||||
Bundle-Vendor: sip-communicator.org
|
||||
Bundle-Version: 0.0.1
|
||||
System-Bundle: yes
|
||||
Import-Package: org.osgi.framework,
|
||||
net.java.sip.communicator.service.configuration,
|
||||
net.java.sip.communicator.service.resources,
|
||||
net.java.sip.communicator.util,
|
||||
javax.crypto,
|
||||
javax.crypto.spec,
|
||||
javax.swing
|
||||
Export-Package: net.java.sip.communicator.service.credentialsstorage
|
||||
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.plugin.securityconfig.masterpassword;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import net.java.sip.communicator.util.swing.*;
|
||||
|
||||
/**
|
||||
* Implements a Swing <tt>Component</tt> to represent the user interface of the
|
||||
* Passwords <tt>ConfigurationForm</tt>.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
* @author Lubomir Marinov
|
||||
*/
|
||||
public class ConfigurationPanel
|
||||
extends TransparentPanel
|
||||
{
|
||||
/**
|
||||
* Initializes a new <tt>ConfigurationPanel</tt> instance.
|
||||
*/
|
||||
public ConfigurationPanel()
|
||||
{
|
||||
add(new MasterPasswordPanel());
|
||||
add(Box.createVerticalStrut(10));
|
||||
add(new SavedPasswordsPanel());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,343 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.plugin.securityconfig.masterpassword;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.*;
|
||||
|
||||
import net.java.sip.communicator.plugin.securityconfig.*;
|
||||
import net.java.sip.communicator.service.credentialsstorage.*;
|
||||
import net.java.sip.communicator.service.gui.*;
|
||||
import net.java.sip.communicator.service.resources.*;
|
||||
import net.java.sip.communicator.util.swing.*;
|
||||
|
||||
/**
|
||||
* UI dialog to change the master password.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public class MasterPasswordChangeDialog
|
||||
extends SIPCommDialog
|
||||
implements ActionListener,
|
||||
KeyListener
|
||||
{
|
||||
/**
|
||||
* Callback interface. Implementing classes know how to change the master
|
||||
* password from the old to the new one.
|
||||
*/
|
||||
interface MasterPasswordExecutable
|
||||
{
|
||||
/**
|
||||
* The actions to execute to change the master password.
|
||||
*
|
||||
* @param masterPassword old master password
|
||||
* @param newMasterPassword new master password
|
||||
* @return true on success, false on failure.
|
||||
*/
|
||||
public boolean execute(String masterPassword, String newMasterPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog instance of this class.
|
||||
*/
|
||||
private static MasterPasswordChangeDialog dialog;
|
||||
|
||||
/**
|
||||
* Password quality meter.
|
||||
*/
|
||||
private static PasswordQualityMeter passwordMeter =
|
||||
new PasswordQualityMeter();
|
||||
|
||||
/**
|
||||
* Callback to execute on password change.
|
||||
*/
|
||||
private MasterPasswordExecutable callback;
|
||||
|
||||
/**
|
||||
* UI components.
|
||||
*/
|
||||
private JTextComponent currentPasswdField;
|
||||
private JPasswordField newPasswordField;
|
||||
private JPasswordField newAgainPasswordField;
|
||||
private JButton okButton;
|
||||
private JButton cancelButton;
|
||||
private JTextArea infoTextArea;
|
||||
private JProgressBar passwordQualityBar;
|
||||
private JPanel textFieldsPanel;
|
||||
private JPanel labelsPanel;
|
||||
private JPanel buttonsPanel;
|
||||
private JPanel qualityPanel;
|
||||
private JPanel mainPanel;
|
||||
|
||||
/**
|
||||
* The <tt>ResourceManagementService</tt> used by this instance to access
|
||||
* the localized and internationalized resources of the application.
|
||||
*/
|
||||
private final ResourceManagementService resources
|
||||
= SecurityConfigActivator.getResources();
|
||||
|
||||
/**
|
||||
* Builds the dialog.
|
||||
*/
|
||||
private MasterPasswordChangeDialog()
|
||||
{
|
||||
super(false);
|
||||
initComponents();
|
||||
|
||||
this.setTitle(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.MP_TITLE"));
|
||||
this.setMinimumSize(new Dimension(350, 300));
|
||||
this.setPreferredSize(new Dimension(350, 300));
|
||||
this.setResizable(false);
|
||||
|
||||
this.getContentPane().add(mainPanel);
|
||||
|
||||
this.pack();
|
||||
Toolkit toolkit = Toolkit.getDefaultToolkit();
|
||||
Dimension screenSize = toolkit.getScreenSize();
|
||||
|
||||
int x = (screenSize.width - this.getWidth()) / 2;
|
||||
int y = (screenSize.height - this.getHeight()) / 2;
|
||||
|
||||
this.setLocation(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the UI components.
|
||||
*/
|
||||
private void initComponents()
|
||||
{
|
||||
mainPanel = new TransparentPanel(new BorderLayout(10, 10));
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
|
||||
// info text
|
||||
infoTextArea = new JTextArea();
|
||||
infoTextArea.setEditable(false);
|
||||
infoTextArea.setOpaque(false);
|
||||
infoTextArea.setLineWrap(true);
|
||||
infoTextArea.setWrapStyleWord(true);
|
||||
infoTextArea.setFont(infoTextArea.getFont().deriveFont(Font.BOLD));
|
||||
infoTextArea.setText(
|
||||
resources.getI18NString("plugin.securityconfig.masterpassword.INFO_TEXT"));
|
||||
|
||||
// label fields
|
||||
labelsPanel = new TransparentPanel(new GridLayout(0, 1, 8, 8));
|
||||
|
||||
labelsPanel.add(
|
||||
new JLabel(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.CURRENT_PASSWORD")));
|
||||
labelsPanel.add(
|
||||
new JLabel(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.ENTER_PASSWORD")));
|
||||
labelsPanel.add(
|
||||
new JLabel(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.REENTER_PASSWORD")));
|
||||
|
||||
// password fields
|
||||
if (!SecurityConfigActivator
|
||||
.getCredentialsStorageService()
|
||||
.isUsingMasterPassword())
|
||||
{
|
||||
currentPasswdField
|
||||
= new JTextField(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.MP_NOT_SET"));
|
||||
currentPasswdField.setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentPasswdField = new JPasswordField(15);
|
||||
}
|
||||
newPasswordField = new JPasswordField(15);
|
||||
newPasswordField.addKeyListener(this);
|
||||
newAgainPasswordField = new JPasswordField(15);
|
||||
newAgainPasswordField.addKeyListener(this);
|
||||
|
||||
textFieldsPanel = new TransparentPanel(new GridLayout(0, 1, 8, 8));
|
||||
textFieldsPanel.add(currentPasswdField);
|
||||
textFieldsPanel.add(newPasswordField);
|
||||
textFieldsPanel.add(newAgainPasswordField);
|
||||
|
||||
// OK and cancel buttons
|
||||
okButton = new JButton(resources.getI18NString("service.gui.OK"));
|
||||
okButton.addActionListener(this);
|
||||
okButton.setEnabled(false);
|
||||
this.getRootPane().setDefaultButton(okButton);
|
||||
cancelButton
|
||||
= new JButton(resources.getI18NString("service.gui.CANCEL"));
|
||||
cancelButton.addActionListener(this);
|
||||
|
||||
passwordQualityBar =
|
||||
new JProgressBar(0, PasswordQualityMeter.TOTAL_POINTS);
|
||||
passwordQualityBar.setValue(0);
|
||||
|
||||
qualityPanel = new TransparentPanel();
|
||||
qualityPanel.setLayout(new BoxLayout(qualityPanel, BoxLayout.Y_AXIS));
|
||||
qualityPanel.add(
|
||||
new JLabel(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.PASSWORD_QUALITY_METER")));
|
||||
qualityPanel.add(passwordQualityBar);
|
||||
qualityPanel.add(Box.createVerticalStrut(15));
|
||||
|
||||
buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER));
|
||||
buttonsPanel.add(okButton);
|
||||
buttonsPanel.add(cancelButton);
|
||||
qualityPanel.add(buttonsPanel);
|
||||
|
||||
mainPanel.add(infoTextArea, BorderLayout.NORTH);
|
||||
mainPanel.add(labelsPanel, BorderLayout.WEST);
|
||||
mainPanel.add(textFieldsPanel, BorderLayout.CENTER);
|
||||
mainPanel.add(qualityPanel, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* OK and Cancel button event handler.
|
||||
*/
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
JButton sourceButton = (JButton) e.getSource();
|
||||
boolean close = false;
|
||||
if (sourceButton.equals(okButton)) // ok button
|
||||
{
|
||||
CredentialsStorageService credentialsStorageService
|
||||
= SecurityConfigActivator.getCredentialsStorageService();
|
||||
String oldMasterPassword = null;
|
||||
|
||||
if (credentialsStorageService.isUsingMasterPassword())
|
||||
{
|
||||
oldMasterPassword =
|
||||
new String(((JPasswordField) currentPasswdField)
|
||||
.getPassword());
|
||||
if (oldMasterPassword.isEmpty())
|
||||
{
|
||||
displayPopupError(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.MP_CURRENT_EMPTY"));
|
||||
return;
|
||||
}
|
||||
boolean verified =
|
||||
credentialsStorageService
|
||||
.verifyMasterPassword(oldMasterPassword);
|
||||
if (!verified)
|
||||
{
|
||||
displayPopupError(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.MP_VERIFICATION_FAILURE_MSG"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if the callback executes OK, we close the dialog
|
||||
if (callback != null)
|
||||
{
|
||||
String newPassword = new String(newPasswordField.getPassword());
|
||||
close = callback.execute(oldMasterPassword, newPassword);
|
||||
}
|
||||
}
|
||||
else // cancel button
|
||||
{
|
||||
close = true;
|
||||
}
|
||||
|
||||
if (close)
|
||||
{
|
||||
dialog = null;
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an error pop-up.
|
||||
*
|
||||
* @param message the message to display
|
||||
*/
|
||||
protected void displayPopupError(String message)
|
||||
{
|
||||
SecurityConfigActivator
|
||||
.getUIService()
|
||||
.getPopupDialog()
|
||||
.showMessagePopupDialog(
|
||||
message,
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.MP_CHANGE_FAILURE"),
|
||||
PopupDialog.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an info pop-up.
|
||||
*
|
||||
* @param message the message to display.
|
||||
*/
|
||||
protected void displayPopupInfo(String message)
|
||||
{
|
||||
SecurityConfigActivator
|
||||
.getUIService()
|
||||
.getPopupDialog()
|
||||
.showMessagePopupDialog(
|
||||
message,
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.MP_CHANGE_SUCCESS"),
|
||||
PopupDialog.INFORMATION_MESSAGE);
|
||||
}
|
||||
|
||||
protected void close(boolean isEscaped)
|
||||
{
|
||||
cancelButton.doClick();
|
||||
}
|
||||
|
||||
public void keyReleased(KeyEvent event)
|
||||
{
|
||||
JPasswordField source = (JPasswordField) event.getSource();
|
||||
if (newPasswordField.equals(source)
|
||||
|| newAgainPasswordField.equals(source))
|
||||
{
|
||||
String password1 = new String(newPasswordField.getPassword());
|
||||
String password2 = new String(newAgainPasswordField.getPassword());
|
||||
// measure password quality
|
||||
passwordQualityBar
|
||||
.setValue(passwordMeter.assessPassword(password1));
|
||||
// enable OK button if passwords are equal
|
||||
boolean eq = !password1.isEmpty() && password1.equals(password2);
|
||||
okButton.setEnabled(eq);
|
||||
password1 = null;
|
||||
password2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void keyPressed(KeyEvent arg0)
|
||||
{
|
||||
}
|
||||
|
||||
public void keyTyped(KeyEvent arg0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return dialog instance
|
||||
*/
|
||||
public static MasterPasswordChangeDialog getInstance()
|
||||
{
|
||||
if (dialog == null)
|
||||
dialog = new MasterPasswordChangeDialog();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callbackInstance callback instance.
|
||||
*/
|
||||
public void setCallback(MasterPasswordExecutable callbackInstance)
|
||||
{
|
||||
this.callback = callbackInstance;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.plugin.securityconfig.masterpassword;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import net.java.sip.communicator.plugin.securityconfig.*;
|
||||
import net.java.sip.communicator.plugin.securityconfig.masterpassword.MasterPasswordChangeDialog.MasterPasswordExecutable;
|
||||
import net.java.sip.communicator.service.gui.*;
|
||||
import net.java.sip.communicator.service.resources.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
import net.java.sip.communicator.util.swing.*;
|
||||
|
||||
/**
|
||||
* Panel containing the master password checkbox and change button.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public class MasterPasswordPanel
|
||||
extends TransparentPanel
|
||||
implements ActionListener
|
||||
{
|
||||
/**
|
||||
* The logger for this class.
|
||||
*/
|
||||
private static final Logger logger
|
||||
= Logger.getLogger(MasterPasswordPanel.class);
|
||||
|
||||
/**
|
||||
* UI components.
|
||||
*/
|
||||
private JCheckBox useMasterPasswordCheckBox;
|
||||
private JButton changeMasterPasswordButton;
|
||||
|
||||
/**
|
||||
* The <tt>ResourceManagementService</tt> used by this instance to access
|
||||
* the localized and internationalized resources of the application.
|
||||
*/
|
||||
private final ResourceManagementService resources
|
||||
= SecurityConfigActivator.getResources();
|
||||
|
||||
/**
|
||||
* Builds the panel.
|
||||
*/
|
||||
public MasterPasswordPanel()
|
||||
{
|
||||
this.setLayout(new BorderLayout(10, 10));
|
||||
this.setAlignmentX(0.0f);
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the UI components.
|
||||
*/
|
||||
private void initComponents()
|
||||
{
|
||||
useMasterPasswordCheckBox
|
||||
= new SIPCommCheckBox(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.USE_MASTER_PASSWORD"));
|
||||
useMasterPasswordCheckBox.addActionListener(this);
|
||||
useMasterPasswordCheckBox.setSelected(
|
||||
SecurityConfigActivator
|
||||
.getCredentialsStorageService()
|
||||
.isUsingMasterPassword());
|
||||
this.add(useMasterPasswordCheckBox, BorderLayout.WEST);
|
||||
|
||||
changeMasterPasswordButton = new JButton();
|
||||
changeMasterPasswordButton.setText(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.CHANGE_MASTER_PASSWORD"));
|
||||
changeMasterPasswordButton.addActionListener(new ActionListener()
|
||||
{
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
showMasterPasswordChangeDialog();
|
||||
}
|
||||
});
|
||||
changeMasterPasswordButton.setEnabled(useMasterPasswordCheckBox
|
||||
.isSelected());
|
||||
this.add(changeMasterPasswordButton, BorderLayout.EAST);
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
boolean isSelected = useMasterPasswordCheckBox.isSelected();
|
||||
// do not change the check box yet
|
||||
useMasterPasswordCheckBox.setSelected(!isSelected);
|
||||
if (isSelected)
|
||||
showMasterPasswordChangeDialog();
|
||||
else
|
||||
{
|
||||
// the checkbox is unselected only when this method finishes ok
|
||||
removeMasterPassword();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the dialog to change master password.
|
||||
*/
|
||||
private void showMasterPasswordChangeDialog()
|
||||
{
|
||||
MasterPasswordChangeDialog dialog = MasterPasswordChangeDialog.getInstance();
|
||||
dialog.setCallback(new ChangeMasterPasswordCallback());
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a master password prompt to the user, verifies the entered
|
||||
* password and then executes <tt>ChangeMasterPasswordCallback.execute</tt>
|
||||
* method with null as the new password, thus removing it.
|
||||
*/
|
||||
private void removeMasterPassword()
|
||||
{
|
||||
String master = null;
|
||||
JPasswordField passwordField = new JPasswordField();
|
||||
String inputMsg
|
||||
= resources.getI18NString("plugin.securityconfig.masterpassword.MP_INPUT");
|
||||
String errorMsg =
|
||||
"<html><font color=\"red\">"
|
||||
+ resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.MP_VERIFICATION_FAILURE_MSG")
|
||||
+ "</font></html>";
|
||||
|
||||
boolean correct = true;
|
||||
do
|
||||
{
|
||||
Object[] msg = null;
|
||||
if (correct)
|
||||
{
|
||||
msg = new Object[]
|
||||
{ inputMsg, passwordField };
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = new Object[]
|
||||
{ errorMsg, inputMsg, passwordField };
|
||||
}
|
||||
//clear the password field
|
||||
passwordField.setText("");
|
||||
|
||||
if (JOptionPane.showOptionDialog(
|
||||
null,
|
||||
msg,
|
||||
resources.getI18NString("plugin.securityconfig.masterpassword.MP_TITLE"),
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE,
|
||||
null,
|
||||
new String[]
|
||||
{
|
||||
resources.getI18NString("service.gui.OK"),
|
||||
resources.getI18NString("service.gui.CANCEL")
|
||||
},
|
||||
resources.getI18NString("service.gui.OK"))
|
||||
== JOptionPane.YES_OPTION)
|
||||
{
|
||||
master = new String(passwordField.getPassword());
|
||||
correct =
|
||||
!master.isEmpty()
|
||||
&& SecurityConfigActivator
|
||||
.getCredentialsStorageService()
|
||||
.verifyMasterPassword(master);
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
while (!correct);
|
||||
// remove the master password by setting it to null
|
||||
new ChangeMasterPasswordCallback().execute(master, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback implementation that changes or removes the master password.
|
||||
* When the new password is null, the master password is removed.
|
||||
*/
|
||||
class ChangeMasterPasswordCallback
|
||||
implements MasterPasswordExecutable
|
||||
{
|
||||
public boolean execute(String masterPassword, String newMasterPassword)
|
||||
{
|
||||
boolean remove = newMasterPassword == null;
|
||||
// update all passwords with new master pass
|
||||
boolean changed
|
||||
= SecurityConfigActivator
|
||||
.getCredentialsStorageService()
|
||||
.changeMasterPassword(
|
||||
masterPassword,
|
||||
newMasterPassword);
|
||||
if (!changed)
|
||||
{
|
||||
String titleKey
|
||||
= remove
|
||||
? "plugin.securityconfig.masterpassword.MP_REMOVE_FAILURE"
|
||||
: "plugin.securityconfig.masterpassword.MP_CHANGE_FAILURE";
|
||||
SecurityConfigActivator
|
||||
.getUIService()
|
||||
.getPopupDialog()
|
||||
.showMessagePopupDialog(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.MP_CHANGE_FAILURE_MSG"),
|
||||
resources.getI18NString(titleKey),
|
||||
PopupDialog.ERROR_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
String title = null;
|
||||
String msg = null;
|
||||
if (remove)
|
||||
{
|
||||
title = "plugin.securityconfig.masterpassword.MP_REMOVE_SUCCESS";
|
||||
msg = "plugin.securityconfig.masterpassword.MP_REMOVE_SUCCESS_MSG";
|
||||
//disable the checkbox and change button
|
||||
useMasterPasswordCheckBox.setSelected(false);
|
||||
changeMasterPasswordButton.setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
title = "plugin.securityconfig.masterpassword.MP_CHANGE_SUCCESS";
|
||||
msg = "plugin.securityconfig.masterpassword.MP_CHANGE_SUCCESS_MSG";
|
||||
// Enable the checkbox and change button.
|
||||
useMasterPasswordCheckBox.setSelected(true);
|
||||
changeMasterPasswordButton.setEnabled(true);
|
||||
}
|
||||
logger.debug("Master password successfully changed");
|
||||
SecurityConfigActivator
|
||||
.getUIService()
|
||||
.getPopupDialog()
|
||||
.showMessagePopupDialog(
|
||||
resources.getI18NString(msg),
|
||||
resources.getI18NString(title),
|
||||
PopupDialog.INFORMATION_MESSAGE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.plugin.securityconfig.masterpassword;
|
||||
|
||||
import java.util.regex.*;
|
||||
|
||||
/**
|
||||
* Simple password quality meter. The JavaScript version found at
|
||||
* http://www.geekwisdom.com/js/passwordmeter.js was used as a base. Provides a
|
||||
* method to compute the relative score of the password that can be used to
|
||||
* model a progress bar, for example.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public class PasswordQualityMeter
|
||||
{
|
||||
/**
|
||||
* Maximum possible points.
|
||||
*/
|
||||
public static final int TOTAL_POINTS = 42;
|
||||
|
||||
/**
|
||||
* Assesses the strength of the password.
|
||||
*
|
||||
* @param pass the password to assess
|
||||
* @return the score for this password between 0 and <tt>TOTAL_POINTS</tt>
|
||||
*/
|
||||
public int assessPassword(String pass)
|
||||
{
|
||||
int score = 0;
|
||||
|
||||
if (pass == null || pass.isEmpty())
|
||||
return score;
|
||||
score += assessLength(pass);
|
||||
score += assessLetters(pass);
|
||||
score += assessNumbers(pass);
|
||||
score += assessSpecials(pass);
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assesses password length:
|
||||
* level 0 (3 point): less than 5 characters
|
||||
* level 1 (6 points): between 5 and 7 characters
|
||||
* level 2 (12 points): between 8 and 15 characters
|
||||
* level 3 (18 points): 16 or more characters
|
||||
*
|
||||
* @param pass the password to assess
|
||||
* @return the score based on the length
|
||||
*/
|
||||
private int assessLength(String pass)
|
||||
{
|
||||
int len = pass.length();
|
||||
|
||||
if (len < 5)
|
||||
return 3;
|
||||
else if (len >= 5 && len < 8)
|
||||
return 6;
|
||||
else if (len >= 8 && len < 16)
|
||||
return 12;
|
||||
// len >= 16
|
||||
return 18;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assesses letter cases:
|
||||
* level 0 (0 points): no letters
|
||||
* level 1 (5 points): all letters are either lower or upper case
|
||||
* level 2 (7 points): letters are mixed case
|
||||
*
|
||||
* @param pass the password to assess
|
||||
* @return the score based on the letters
|
||||
*/
|
||||
private int assessLetters(String pass)
|
||||
{
|
||||
boolean lower = matches(pass, "[a-z]+");
|
||||
boolean upper = matches(pass, "[A-Z]+");
|
||||
|
||||
if (lower && upper)
|
||||
return 7;
|
||||
if (lower || upper)
|
||||
return 5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assesses number count:
|
||||
* level 0 (0 points): no numbers exist
|
||||
* level 1 (5 points): one or two number exists
|
||||
* level 1 (7 points): 3 or more numbers exists
|
||||
*
|
||||
* @param pass the password to assess
|
||||
* @return the score based on the numbers
|
||||
*/
|
||||
private int assessNumbers(String pass)
|
||||
{
|
||||
int found = countMatches(pass, "\\d");
|
||||
|
||||
if (found < 1)
|
||||
return 0;
|
||||
else if (found >= 1 && found < 3)
|
||||
return 5;
|
||||
return 7;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assesses special character count.
|
||||
* Here special characters are non-word and non-space ones.
|
||||
* level 0 (0 points): no special characters
|
||||
* level 1 (5 points): one special character exists
|
||||
* level 2 (10 points): more than one special character exists
|
||||
*
|
||||
* @param pass the password to assess
|
||||
* @return the score based on special characters
|
||||
*/
|
||||
private int assessSpecials(String pass)
|
||||
{
|
||||
int found = countMatches(pass, "[^\\w\\s]");
|
||||
|
||||
if (found < 1)
|
||||
return 0;
|
||||
else if (found <= 1 && found < 2)
|
||||
return 5;
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of matches of a given pattern in a given string.
|
||||
*
|
||||
* @param str the string to search in
|
||||
* @param pattern the pattern to search for
|
||||
* @return number of matches of <tt>patter</tt> in <tt>str</tt>
|
||||
*/
|
||||
private int countMatches(String str, String pattern)
|
||||
{
|
||||
Pattern p = Pattern.compile(pattern);
|
||||
Matcher matcher = p.matcher(str);
|
||||
int found = 0;
|
||||
|
||||
while (matcher.find())
|
||||
found++;
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around @link{Pattern} and @link{Matcher} classes.
|
||||
*
|
||||
* @param str the string to search in
|
||||
* @param pattern the pattern to search for
|
||||
* @return true if <tt>pattern</tt> has been found in <tt>str</tt>.
|
||||
*/
|
||||
private boolean matches(String str, String pattern)
|
||||
{
|
||||
return Pattern.compile(pattern).matcher(str).find();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,552 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.plugin.securityconfig.masterpassword;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.util.List; // disambiguation
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.*;
|
||||
import javax.swing.event.*;
|
||||
import javax.swing.table.*;
|
||||
|
||||
import net.java.sip.communicator.plugin.securityconfig.*;
|
||||
import net.java.sip.communicator.service.credentialsstorage.*;
|
||||
import net.java.sip.communicator.service.gui.*;
|
||||
import net.java.sip.communicator.service.protocol.*;
|
||||
import net.java.sip.communicator.service.resources.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
import net.java.sip.communicator.util.swing.*;
|
||||
|
||||
/**
|
||||
* The dialog that displays all saved account passwords.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public class SavedPasswordsDialog
|
||||
extends SIPCommDialog
|
||||
{
|
||||
/**
|
||||
* The logger for this class.
|
||||
*/
|
||||
private static final Logger logger
|
||||
= Logger.getLogger(SavedPasswordsDialog.class);
|
||||
|
||||
/**
|
||||
* UI components.
|
||||
*/
|
||||
private JPanel mainPanel;
|
||||
private JButton closeButton;
|
||||
|
||||
/**
|
||||
* The {@link CredentialsStorageService}.
|
||||
*/
|
||||
private static final CredentialsStorageService credentialsStorageService
|
||||
= SecurityConfigActivator.getCredentialsStorageService();
|
||||
|
||||
/**
|
||||
* The <tt>ResourceManagementService</tt> used by this instance to access
|
||||
* the localized and internationalized resources of the application.
|
||||
*/
|
||||
private static final ResourceManagementService resources
|
||||
= SecurityConfigActivator.getResources();
|
||||
|
||||
/**
|
||||
* Instance of this dialog.
|
||||
*/
|
||||
private static SavedPasswordsDialog dialog;
|
||||
|
||||
/**
|
||||
* Builds the dialog.
|
||||
*/
|
||||
private SavedPasswordsDialog()
|
||||
{
|
||||
super(false);
|
||||
initComponents();
|
||||
|
||||
this.setTitle(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.SAVED_PASSWORDS"));
|
||||
this.setMinimumSize(new Dimension(550, 300));
|
||||
this.setPreferredSize(new Dimension(550, 300));
|
||||
this.setResizable(false);
|
||||
|
||||
this.getContentPane().add(mainPanel);
|
||||
|
||||
this.pack();
|
||||
Toolkit toolkit = Toolkit.getDefaultToolkit();
|
||||
Dimension screenSize = toolkit.getScreenSize();
|
||||
|
||||
int x = (screenSize.width - this.getWidth()) / 2;
|
||||
int y = (screenSize.height - this.getHeight()) / 2;
|
||||
|
||||
this.setLocation(x,y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the UI components.
|
||||
*/
|
||||
private void initComponents()
|
||||
{
|
||||
this.setLayout(new GridBagLayout());
|
||||
mainPanel = new TransparentPanel(new BorderLayout(10, 10));
|
||||
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.gridy = 0;
|
||||
c.fill = GridBagConstraints.BOTH;
|
||||
c.weightx = 1.0;
|
||||
c.weighty = 1.0;
|
||||
c.insets = new Insets(5, 5, 5, 5);
|
||||
c.anchor = GridBagConstraints.PAGE_START;
|
||||
|
||||
AccountPasswordsPanel accPassPanel = new AccountPasswordsPanel();
|
||||
this.add(accPassPanel, c);
|
||||
|
||||
c.gridy = 1;
|
||||
c.weighty = 0.0;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
closeButton
|
||||
= new JButton(resources.getI18NString("service.gui.CLOSE"));
|
||||
closeButton.addActionListener(new ActionListener()
|
||||
{
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
dialog = null;
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
this.add(closeButton, c);
|
||||
}
|
||||
|
||||
protected void close(boolean isEscaped)
|
||||
{
|
||||
closeButton.doClick();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link SavedPasswordsDialog} instance
|
||||
*/
|
||||
public static SavedPasswordsDialog getInstance()
|
||||
{
|
||||
if (dialog == null) {
|
||||
dialog = new SavedPasswordsDialog();
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel containing the accounts table and buttons.
|
||||
*/
|
||||
private static class AccountPasswordsPanel
|
||||
extends TransparentPanel
|
||||
{
|
||||
|
||||
/**
|
||||
* The table model for the accounts table.
|
||||
*/
|
||||
private class AccountsTableModel
|
||||
extends AbstractTableModel
|
||||
{
|
||||
/**
|
||||
* Index of the first column.
|
||||
*/
|
||||
public static final int ACCOUNT_TYPE_INDEX = 0;
|
||||
/**
|
||||
* Index of the second column.
|
||||
*/
|
||||
public static final int ACCOUNT_NAME_INDEX = 1;
|
||||
/**
|
||||
* Index of the third column.
|
||||
*/
|
||||
public static final int PASSWORD_INDEX = 2;
|
||||
|
||||
/**
|
||||
* List of accounts with saved passwords.
|
||||
*/
|
||||
public final List<AccountID> savedPasswordAccounts =
|
||||
new ArrayList<AccountID>();
|
||||
/**
|
||||
* Map that associates an {@link AccountID} with its prefix.
|
||||
*/
|
||||
public final Map<AccountID, String> accountIdPrefixes =
|
||||
new HashMap<AccountID, String>();
|
||||
|
||||
/**
|
||||
* Loads accounts.
|
||||
*/
|
||||
public AccountsTableModel()
|
||||
{
|
||||
accountIdPrefixes.putAll(
|
||||
SecurityConfigActivator
|
||||
.getAccountIDsWithSavedPasswords());
|
||||
savedPasswordAccounts.addAll(new ArrayList<AccountID>(
|
||||
accountIdPrefixes.keySet()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name for the given column.
|
||||
*/
|
||||
public String getColumnName(int column)
|
||||
{
|
||||
String key;
|
||||
|
||||
switch (column)
|
||||
{
|
||||
case ACCOUNT_TYPE_INDEX:
|
||||
key = "plugin.securityconfig.masterpassword.COL_TYPE";
|
||||
break;
|
||||
case ACCOUNT_NAME_INDEX:
|
||||
key = "plugin.securityconfig.masterpassword.COL_NAME";
|
||||
break;
|
||||
case PASSWORD_INDEX:
|
||||
key = "plugin.securityconfig.masterpassword.COL_PASSWORD";
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return resources.getI18NString(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value for the given row and column.
|
||||
*/
|
||||
public Object getValueAt(int row, int column)
|
||||
{
|
||||
if (row < 0)
|
||||
return null;
|
||||
|
||||
AccountID accountID = savedPasswordAccounts.get(row);
|
||||
switch (column)
|
||||
{
|
||||
case ACCOUNT_TYPE_INDEX:
|
||||
String protocol
|
||||
= accountID
|
||||
.getAccountPropertyString(
|
||||
ProtocolProviderFactory.PROTOCOL);
|
||||
return
|
||||
(protocol == null)
|
||||
? resources
|
||||
.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.PROTOCOL_UNKNOWN")
|
||||
: protocol;
|
||||
case ACCOUNT_NAME_INDEX:
|
||||
return accountID.getUserID();
|
||||
case PASSWORD_INDEX:
|
||||
String pass =
|
||||
credentialsStorageService
|
||||
.loadPassword(accountIdPrefixes.get(accountID));
|
||||
return
|
||||
(pass == null)
|
||||
? resources
|
||||
.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.CANNOT_DECRYPT")
|
||||
: pass;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of rows in the table.
|
||||
*/
|
||||
public int getRowCount()
|
||||
{
|
||||
return savedPasswordAccounts.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of columns depends on whether we are showing passwords or
|
||||
* not.
|
||||
*/
|
||||
public int getColumnCount()
|
||||
{
|
||||
return showPasswords ? 3 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we showing the passwords column or not.
|
||||
*/
|
||||
private boolean showPasswords = false;
|
||||
|
||||
/**
|
||||
* The button to remove the saved password for the selected account.
|
||||
*/
|
||||
private JButton removeButton;
|
||||
|
||||
/**
|
||||
* The button to remove saved passwords for all accounts.
|
||||
*/
|
||||
private JButton removeAllButton;
|
||||
|
||||
/**
|
||||
* The button to show the saved passwords for all accounts in plain text.
|
||||
*/
|
||||
private JButton showPasswordsButton;
|
||||
|
||||
/**
|
||||
* The table itself.
|
||||
*/
|
||||
private JTable accountsTable;
|
||||
|
||||
/**
|
||||
* Builds the panel.
|
||||
*/
|
||||
public AccountPasswordsPanel()
|
||||
{
|
||||
this.initComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link AccountID} object for the selected row.
|
||||
* @return the selected account
|
||||
*/
|
||||
private AccountID getSelectedAccountID()
|
||||
{
|
||||
AccountsTableModel model =
|
||||
(AccountsTableModel) accountsTable.getModel();
|
||||
int index = accountsTable.getSelectedRow();
|
||||
if (index < 0 || index > model.savedPasswordAccounts.size())
|
||||
return null;
|
||||
|
||||
return model.savedPasswordAccounts.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the table's components.
|
||||
*/
|
||||
private void initComponents()
|
||||
{
|
||||
setBorder(
|
||||
BorderFactory.createTitledBorder(
|
||||
BorderFactory.createEtchedBorder(EtchedBorder.LOWERED),
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.STORED_ACCOUNT_PASSWORDS")));
|
||||
this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
||||
|
||||
accountsTable = new JTable();
|
||||
accountsTable.setModel(new AccountsTableModel());
|
||||
accountsTable
|
||||
.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
|
||||
accountsTable.setCellSelectionEnabled(false);
|
||||
accountsTable.setColumnSelectionAllowed(false);
|
||||
accountsTable.setRowSelectionAllowed(true);
|
||||
accountsTable.getColumnModel().getColumn(
|
||||
AccountsTableModel.ACCOUNT_NAME_INDEX).setPreferredWidth(270);
|
||||
accountsTable.getSelectionModel().addListSelectionListener(
|
||||
new ListSelectionListener()
|
||||
{
|
||||
public void valueChanged(ListSelectionEvent e)
|
||||
{
|
||||
if (e.getValueIsAdjusting())
|
||||
return;
|
||||
// activate remove button on select
|
||||
removeButton.setEnabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
JScrollPane pnlAccounts = new JScrollPane(accountsTable);
|
||||
this.add(pnlAccounts);
|
||||
|
||||
JPanel pnlButtons = new TransparentPanel();
|
||||
pnlButtons.setLayout(new BorderLayout());
|
||||
this.add(pnlButtons);
|
||||
|
||||
JPanel leftButtons = new TransparentPanel();
|
||||
pnlButtons.add(leftButtons, BorderLayout.WEST);
|
||||
|
||||
removeButton
|
||||
= new JButton(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.REMOVE_PASSWORD_BUTTON"));
|
||||
// enabled on row selection
|
||||
removeButton.setEnabled(false);
|
||||
removeButton.addActionListener(new ActionListener()
|
||||
{
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
AccountID selectedAccountID = getSelectedAccountID();
|
||||
if (selectedAccountID != null)
|
||||
{
|
||||
AccountsTableModel model =
|
||||
(AccountsTableModel) accountsTable.getModel();
|
||||
String accountPrefix = model.accountIdPrefixes.get(selectedAccountID);
|
||||
|
||||
removeSavedPassword(accountPrefix, selectedAccountID);
|
||||
model.savedPasswordAccounts.remove(selectedAccountID);
|
||||
model.accountIdPrefixes.remove(accountPrefix);
|
||||
|
||||
int selectedRow = accountsTable.getSelectedRow();
|
||||
model.fireTableRowsDeleted(selectedRow, selectedRow);
|
||||
}
|
||||
}
|
||||
});
|
||||
leftButtons.add(removeButton);
|
||||
|
||||
removeAllButton
|
||||
= new JButton(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.REMOVE_ALL_PASSWORDS_BUTTON"));
|
||||
removeAllButton.addActionListener(new ActionListener()
|
||||
{
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
AccountsTableModel model =
|
||||
(AccountsTableModel) accountsTable.getModel();
|
||||
if (model.savedPasswordAccounts.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int answer
|
||||
= SecurityConfigActivator
|
||||
.getUIService()
|
||||
.getPopupDialog()
|
||||
.showConfirmPopupDialog(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.REMOVE_ALL_CONFIRMATION"),
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.REMOVE_ALL_TITLE"),
|
||||
PopupDialog.YES_NO_OPTION);
|
||||
|
||||
if (answer == PopupDialog.YES_OPTION)
|
||||
{
|
||||
for (AccountID accountID : model.savedPasswordAccounts)
|
||||
{
|
||||
String accountPrefix =
|
||||
model.accountIdPrefixes.get(accountID);
|
||||
removeSavedPassword(accountPrefix, accountID);
|
||||
}
|
||||
model.savedPasswordAccounts.clear();
|
||||
model.accountIdPrefixes.clear();
|
||||
model.fireTableDataChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
leftButtons.add(removeAllButton);
|
||||
|
||||
JPanel rightButtons = new TransparentPanel();
|
||||
pnlButtons.add(rightButtons, BorderLayout.EAST);
|
||||
showPasswordsButton
|
||||
= new JButton(
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.SHOW_PASSWORDS_BUTTON"));
|
||||
showPasswordsButton.addActionListener(new ActionListener()
|
||||
{
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
// show the master password input only when it's set and the
|
||||
// passwords column is hidden
|
||||
if (credentialsStorageService.isUsingMasterPassword()
|
||||
&& !showPasswords)
|
||||
{
|
||||
showOrHidePasswordsProtected();
|
||||
}
|
||||
else
|
||||
{
|
||||
showOrHidePasswords();
|
||||
}
|
||||
}
|
||||
});
|
||||
rightButtons.add(showPasswordsButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the password from the storage.
|
||||
*
|
||||
* @param accountPrefix account prefix
|
||||
* @param accountID AccountID object
|
||||
*/
|
||||
private void removeSavedPassword(String accountPrefix, AccountID accountID)
|
||||
{
|
||||
credentialsStorageService.removePassword(accountPrefix);
|
||||
|
||||
logger.debug(accountID + " removed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the passwords column.
|
||||
*/
|
||||
private void showOrHidePasswords()
|
||||
{
|
||||
showPasswords = !showPasswords;
|
||||
showPasswordsButton.setText(
|
||||
resources.getI18NString(
|
||||
showPasswords
|
||||
? "plugin.securityconfig.masterpassword.HIDE_PASSWORDS_BUTTON"
|
||||
: "plugin.securityconfig.masterpassword.SHOW_PASSWORDS_BUTTON"));
|
||||
AccountsTableModel model =
|
||||
(AccountsTableModel) accountsTable.getModel();
|
||||
model.fireTableStructureChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a master password prompt to the user, verifies the entered
|
||||
* password and then executes <tt>showOrHidePasswords</tt> method.
|
||||
*/
|
||||
private void showOrHidePasswordsProtected()
|
||||
{
|
||||
String master = null;
|
||||
JPasswordField passwordField = new JPasswordField();
|
||||
String inputMsg
|
||||
= resources.getI18NString("plugin.securityconfig.masterpassword.MP_INPUT");
|
||||
String errorMsg =
|
||||
"<html><font color=\"red\">"
|
||||
+ resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.MP_VERIFICATION_FAILURE_MSG")
|
||||
+ "</font></html>";
|
||||
|
||||
boolean correct = true;
|
||||
do
|
||||
{
|
||||
Object[] msg = null;
|
||||
if (correct)
|
||||
{
|
||||
msg = new Object[]
|
||||
{ inputMsg, passwordField };
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = new Object[]
|
||||
{ errorMsg, inputMsg, passwordField };
|
||||
}
|
||||
//clear the password field
|
||||
passwordField.setText("");
|
||||
|
||||
if (JOptionPane.showOptionDialog(
|
||||
null,
|
||||
msg,
|
||||
resources.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.MP_TITLE"),
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE,
|
||||
null,
|
||||
new String[]
|
||||
{
|
||||
resources.getI18NString("service.gui.OK"),
|
||||
resources.getI18NString("service.gui.CANCEL")
|
||||
},
|
||||
resources.getI18NString("service.gui.OK"))
|
||||
== JOptionPane.YES_OPTION)
|
||||
{
|
||||
master = new String(passwordField.getPassword());
|
||||
correct =
|
||||
!master.isEmpty()
|
||||
&& credentialsStorageService
|
||||
.verifyMasterPassword(master);
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
while (!correct);
|
||||
showOrHidePasswords();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.plugin.securityconfig.masterpassword;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import net.java.sip.communicator.plugin.securityconfig.*;
|
||||
import net.java.sip.communicator.util.swing.*;
|
||||
|
||||
/**
|
||||
* Panel containing the saved passwords button.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public class SavedPasswordsPanel
|
||||
extends TransparentPanel
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the panel.
|
||||
*/
|
||||
public SavedPasswordsPanel() {
|
||||
this.setLayout(new BorderLayout(10, 10));
|
||||
this.setAlignmentX(0.0f);
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the UI components.
|
||||
*/
|
||||
private void initComponents()
|
||||
{
|
||||
JButton savedPasswordsButton = new JButton();
|
||||
savedPasswordsButton.setText(
|
||||
SecurityConfigActivator
|
||||
.getResources()
|
||||
.getI18NString(
|
||||
"plugin.securityconfig.masterpassword.SAVED_PASSWORDS"));
|
||||
savedPasswordsButton.addActionListener(new ActionListener()
|
||||
{
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
SavedPasswordsDialog.getInstance().setVisible(true);
|
||||
}
|
||||
});
|
||||
this.add(savedPasswordsButton, BorderLayout.EAST);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.service.credentialsstorage;
|
||||
|
||||
/**
|
||||
* Loads and saves user credentials from/to the persistent storage
|
||||
* (configuration file in the default implementation).
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public interface CredentialsStorageService
|
||||
{
|
||||
/**
|
||||
* Store the password for the account that starts with the given prefix.
|
||||
*
|
||||
* @param accountPrefix
|
||||
* @param password
|
||||
*/
|
||||
public void storePassword(String accountPrefix, String password);
|
||||
|
||||
/**
|
||||
* Load the password for the account that starts with the given prefix.
|
||||
*
|
||||
* @param accountPrefix
|
||||
* @return
|
||||
*/
|
||||
public String loadPassword(String accountPrefix);
|
||||
|
||||
/**
|
||||
* Remove the password for the account that starts with the given prefix.
|
||||
*
|
||||
* @param accountPrefix
|
||||
*/
|
||||
public void removePassword(String accountPrefix);
|
||||
|
||||
/**
|
||||
* Checks if master password was set by the user and
|
||||
* it is used to encrypt saved account passwords.
|
||||
*
|
||||
* @return true if used, false if not
|
||||
*/
|
||||
public boolean isUsingMasterPassword();
|
||||
|
||||
/**
|
||||
* Changes the old master password to the new one.
|
||||
* For all saved account passwords it decrypts them with the old MP and then
|
||||
* encrypts them with the new MP.
|
||||
* @param oldPassword
|
||||
* @param newPassword
|
||||
* @return true if MP was changed successfully, false otherwise
|
||||
*/
|
||||
public boolean changeMasterPassword(String oldPassword, String newPassword);
|
||||
|
||||
/**
|
||||
* Verifies the correctness of the master password.
|
||||
* @param master
|
||||
* @return true if the password is correct, false otherwise
|
||||
*/
|
||||
public boolean verifyMasterPassword(String master);
|
||||
|
||||
/**
|
||||
* Checks if the account password that starts with the given prefix is saved in encrypted form.
|
||||
*
|
||||
* @return true if saved, false if not
|
||||
*/
|
||||
public boolean isStoredEncrypted(String accountPrefix);
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.service.credentialsstorage;
|
||||
|
||||
/**
|
||||
* Exception thrown by the Crypto encrypt/decrypt interface methods.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public class CryptoException
|
||||
extends Exception
|
||||
{
|
||||
private static final long serialVersionUID = -5424208764356198091L;
|
||||
|
||||
/**
|
||||
* Set when encryption fails.
|
||||
*/
|
||||
public static final int ENCRYPTION_ERROR = 1;
|
||||
|
||||
/**
|
||||
* Set when decryption fails.
|
||||
*/
|
||||
public static final int DECRYPTION_ERROR = 2;
|
||||
|
||||
/**
|
||||
* Set when a decryption fail is caused by the wrong key.
|
||||
*/
|
||||
public static final int WRONG_KEY = 3;
|
||||
|
||||
/**
|
||||
* The error code of this exception.
|
||||
*/
|
||||
private final int errorCode;
|
||||
|
||||
public CryptoException(int code, Exception ex) {
|
||||
super(ex);
|
||||
this.errorCode = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the error code for the exception.
|
||||
*/
|
||||
public int getErrorCode()
|
||||
{
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.slick.credentialsstorage;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import junit.framework.*;
|
||||
|
||||
import net.java.sip.communicator.service.credentialsstorage.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
|
||||
import org.osgi.framework.*;
|
||||
|
||||
/**
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public class CredentialsStorageServiceLick
|
||||
extends TestSuite
|
||||
implements BundleActivator
|
||||
{
|
||||
private Logger logger = Logger.getLogger(getClass().getName());
|
||||
|
||||
protected static CredentialsStorageService credentialsService = null;
|
||||
protected static BundleContext bc = null;
|
||||
public static TestCase tcase = new TestCase(){};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param bundleContext BundleContext
|
||||
* @throws Exception
|
||||
*/
|
||||
public void start(BundleContext bundleContext) throws Exception
|
||||
{
|
||||
CredentialsStorageServiceLick.bc = bundleContext;
|
||||
setName("CredentialsStorageServiceLick");
|
||||
Hashtable<String, String> properties = new Hashtable<String, String>();
|
||||
properties.put("service.pid", getName());
|
||||
|
||||
addTestSuite(TestCredentialsStorageService.class);
|
||||
bundleContext.registerService(getClass().getName(), this, properties);
|
||||
|
||||
logger.debug("Successfully registered " + getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* stop
|
||||
*
|
||||
* @param bundlecontext BundleContext
|
||||
* @throws Exception
|
||||
*/
|
||||
public void stop(BundleContext bundlecontext) throws Exception
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.slick.credentialsstorage;
|
||||
|
||||
import junit.framework.*;
|
||||
import net.java.sip.communicator.service.credentialsstorage.*;
|
||||
|
||||
import org.osgi.framework.*;
|
||||
|
||||
/**
|
||||
* Tests for the @link{CredentialsStorageService}.
|
||||
*
|
||||
* @author Dmitri Melnikov
|
||||
*/
|
||||
public class TestCredentialsStorageService
|
||||
extends TestCase
|
||||
{
|
||||
/**
|
||||
* The service we are testing.
|
||||
*/
|
||||
private CredentialsStorageService credentialsService = null;
|
||||
|
||||
/**
|
||||
* Prefix for the test account.
|
||||
*/
|
||||
private final String accountPrefix = "my.account.prefix";
|
||||
|
||||
/**
|
||||
* Password for the test account.
|
||||
*/
|
||||
private final String accountPassword = "pa$$W0rt123.";
|
||||
|
||||
/**
|
||||
* The master password.
|
||||
*/
|
||||
private final String masterPassword = "MasterPazz321";
|
||||
|
||||
/**
|
||||
* Another master password.
|
||||
*/
|
||||
private final String otherMasterPassword = "123$ecretPSWRD";
|
||||
|
||||
|
||||
/**
|
||||
* Generic JUnit Constructor.
|
||||
*
|
||||
* @param name the name of the test
|
||||
*/
|
||||
public TestCredentialsStorageService(String name)
|
||||
{
|
||||
super(name);
|
||||
BundleContext context = CredentialsStorageServiceLick.bc;
|
||||
ServiceReference ref =
|
||||
context.getServiceReference(CredentialsStorageService.class
|
||||
.getName());
|
||||
credentialsService =
|
||||
(CredentialsStorageService) context.getService(ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic JUnit setUp method.
|
||||
* @throws Exception if anything goes wrong.
|
||||
*/
|
||||
protected void setUp() throws Exception
|
||||
{
|
||||
// set the master password
|
||||
boolean passSet =
|
||||
credentialsService.changeMasterPassword(null, masterPassword);
|
||||
if (!passSet)
|
||||
{
|
||||
throw new Exception("Failed to set the master password");
|
||||
}
|
||||
credentialsService.storePassword(accountPrefix, accountPassword);
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic JUnit tearDown method.
|
||||
* @throws Exception if anything goes wrong.
|
||||
*/
|
||||
protected void tearDown() throws Exception
|
||||
{
|
||||
// remove the password
|
||||
boolean passRemoved =
|
||||
credentialsService.changeMasterPassword(masterPassword, null);
|
||||
if (!passRemoved)
|
||||
{
|
||||
throw new Exception("Failed to remove the master password");
|
||||
}
|
||||
credentialsService.removePassword(accountPrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a master password can be verified.
|
||||
*/
|
||||
public void testIsVerified()
|
||||
{
|
||||
// try to verify a wrong password
|
||||
boolean verify1 =
|
||||
credentialsService.verifyMasterPassword(otherMasterPassword);
|
||||
assertFalse("Wrong password cannot be correct", verify1);
|
||||
|
||||
// try to verify a correct password
|
||||
boolean verify2 =
|
||||
credentialsService.verifyMasterPassword(masterPassword);
|
||||
assertTrue("Correct password cannot be wrong", verify2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the loaded password is the same as the stored one.
|
||||
*/
|
||||
public void testLoadPassword()
|
||||
{
|
||||
String loadedPassword = credentialsService.loadPassword(accountPrefix);
|
||||
|
||||
assertEquals("Loaded and stored passwords do not match", accountPassword,
|
||||
loadedPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the service knows that we are using a master password.
|
||||
*/
|
||||
public void testIsUsingMasterPassword()
|
||||
{
|
||||
boolean isUsing = credentialsService.isUsingMasterPassword();
|
||||
|
||||
assertTrue("Master password is used, true expected", isUsing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the master password to the new value and back again.
|
||||
*/
|
||||
public void testChangeMasterPassword()
|
||||
{
|
||||
// change MP to a new value
|
||||
boolean change1 =
|
||||
credentialsService.changeMasterPassword(masterPassword,
|
||||
otherMasterPassword);
|
||||
assertTrue("Changing master password failed", change1);
|
||||
|
||||
// account passwords must remain the same
|
||||
String loadedPassword = credentialsService.loadPassword(accountPrefix);
|
||||
assertEquals("Account passwords must not differ", loadedPassword,
|
||||
accountPassword);
|
||||
|
||||
// change MP back
|
||||
boolean change2 =
|
||||
credentialsService.changeMasterPassword(otherMasterPassword,
|
||||
masterPassword);
|
||||
assertTrue("Changing master password back failed", change2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the service is aware that the account password is stored in an
|
||||
* encrypted form.
|
||||
*/
|
||||
public void testIsStoredEncrypted()
|
||||
{
|
||||
boolean storedEncrypted =
|
||||
credentialsService.isStoredEncrypted(accountPrefix);
|
||||
assertTrue("Account password is not stored encrypted", storedEncrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether removing the saved password really removes it.
|
||||
*/
|
||||
public void testRemoveSavedPassword()
|
||||
{
|
||||
// remove the saved password
|
||||
credentialsService.removePassword(accountPrefix);
|
||||
|
||||
// try to load the password
|
||||
String loadedPassword = credentialsService.loadPassword(accountPrefix);
|
||||
assertNull("Password was not removed", loadedPassword);
|
||||
|
||||
// save it back again
|
||||
credentialsService.storePassword(accountPrefix, accountPassword);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
Bundle-Activator: net.java.sip.communicator.slick.credentialsstorage.CredentialsStorageServiceLick
|
||||
Bundle-Name: Credentials Storage Service Implementation Compatibility Kit
|
||||
Bundle-Description: A Service Implementation Compatibility Kit for the Credentials Storage Service
|
||||
Bundle-Vendor: sip-communicator.org
|
||||
Bundle-Version: 0.0.1
|
||||
System-Bundle: yes
|
||||
Import-Package: junit.framework,
|
||||
net.java.sip.communicator.service.credentialsstorage,
|
||||
net.java.sip.communicator.util,
|
||||
org.osgi.framework
|
||||
Export-Package: net.java.sip.communicator.slick.credentialsstorage
|
||||
Loading…
Reference in new issue