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