Merges branches/gsoc10/passwdstrg@7435 which represents the work of Dmitri Melnikov on the "Password storage" GSoC 2010 project into trunk.

cusax-fix
Lyubomir Marinov 16 years ago
parent a16201daf3
commit 04cac3354c

@ -913,7 +913,8 @@
bundle-bouncycastle,bundle-plugin-otr,bundle-plugin-iptelaccregwizz,
bundle-contactsource,bundle-plugin-reconnect,
bundle-plugin-securityconfig,bundle-plugin-advancedconfig,
bundle-plugin-sip2sipaccregwizz"/>
bundle-plugin-sip2sipaccregwizz,
bundle-credentialsstorage,bundle-credentialsstorage-slick"/>
<!--BUNDLE-SC-LAUNCHER-->
<target name="bundle-sc-launcher">
@ -1049,6 +1050,29 @@
</jar>
</target>
<!--BUNDLE-CREDENTIALSSTORAGE-->
<target name="bundle-credentialsstorage">
<jar
compress="false"
destfile="${bundles.dest}/credentialsstorage.jar"
manifest="${src}/net/java/sip/communicator/impl/credentialsstorage/credentialsstorage.manifest.mf" >
<zipfileset dir="${dest}/net/java/sip/communicator/service/credentialsstorage"
prefix="net/java/sip/communicator/service/credentialsstorage"/>
<zipfileset dir="${dest}/net/java/sip/communicator/impl/credentialsstorage"
prefix="net/java/sip/communicator/impl/credentialsstorage" />
</jar>
</target>
<!--BUNDLE-CREDENTIALSSTORAGE-SLICK-->
<target name="bundle-credentialsstorage-slick">
<jar compress="false" destfile="${bundles.dest}/credentialsstorage-slick.jar"
manifest="${testsrc}/net/java/sip/communicator/slick/credentialsstorage/credentialsstorage.slick.manifest.mf">
<zipfileset dir="${dest}/net/java/sip/communicator/slick/credentialsstorage"
prefix="net/java/sip/communicator/slick/credentialsstorage"/>
</jar>
</target>
<!--BUNDLE-JUNIT -->
<target name="bundle-junit">
<jar compress="true" destfile="lib/bundle/junit.jar"

@ -42,6 +42,7 @@ felix.auto.start.40= \
reference:file:sc-bundles/browserlauncher.jar
felix.auto.start.42= \
reference:file:sc-bundles/credentialsstorage.jar \
reference:file:sc-bundles/defaultresources.jar
felix.auto.start.45= \

@ -90,6 +90,7 @@ felix.auto.start.6= \
reference:file:sc-bundles/filehistory.jar \
reference:file:sc-bundles/metahistory.jar \
reference:file:sc-bundles/notification.jar \
reference:file:sc-bundles/credentialsstorage.jar \
reference:file:sc-bundles/osdependent.jar
felix.auto.start.7= \
@ -109,6 +110,7 @@ felix.auto.start.7= \
reference:file:sc-bundles/msghistory-slick.jar \
reference:file:sc-bundles/metahistory-slick.jar \
reference:file:sc-bundles/callhistory-slick.jar \
reference:file:sc-bundles/credentialsstorage-slick.jar \
reference:file:sc-bundles/popupmessagehandler-slick.jar
felix.auto.start.100= \

@ -886,6 +886,39 @@ impl.neomedia.configform.VIDEO=Video
# Security configuration form title
plugin.securityconfig.TITLE=Security
plugin.securityconfig.masterpassword.TITLE=Passwords
plugin.securityconfig.masterpassword.CHANGE_MASTER_PASSWORD=Change master password
plugin.securityconfig.masterpassword.USE_MASTER_PASSWORD=Use a master password
plugin.securityconfig.masterpassword.SAVED_PASSWORDS=Saved passwords
plugin.securityconfig.masterpassword.INFO_TEXT=A Master Password is used to protect saved account passwords. Please make sure you remember it.
plugin.securityconfig.masterpassword.CURRENT_PASSWORD=Current password:
plugin.securityconfig.masterpassword.ENTER_PASSWORD=Enter new password:
plugin.securityconfig.masterpassword.REENTER_PASSWORD=Re-enter password:
plugin.securityconfig.masterpassword.MP_TITLE=Master Password
plugin.securityconfig.masterpassword.MP_NOT_SET=(not set)
plugin.securityconfig.masterpassword.MP_CURRENT_EMPTY=You did not enter the correct current Master Password. Please try again.
plugin.securityconfig.masterpassword.MP_VERIFICATION_FAILURE_MSG=The Master Password is not correct!
plugin.securityconfig.masterpassword.MP_CHANGE_FAILURE=Password Change Failed
plugin.securityconfig.masterpassword.MP_CHANGE_SUCCESS=Password Change Succeeded
plugin.securityconfig.masterpassword.MP_CHANGE_SUCCESS_MSG=Master Password successfully changed.
plugin.securityconfig.masterpassword.MP_CHANGE_FAILURE_MSG=The encrypted passwords cannot be decrypted with the provided Master Password.\nThe password storage may have been modified.
plugin.securityconfig.masterpassword.MP_REMOVE_FAILURE=Password Removing Failed
plugin.securityconfig.masterpassword.MP_REMOVE_SUCCESS=Password Removing Succeeded
plugin.securityconfig.masterpassword.MP_REMOVE_SUCCESS_MSG=Master Password successfully removed.
plugin.securityconfig.masterpassword.COL_TYPE=Type
plugin.securityconfig.masterpassword.COL_NAME=Name
plugin.securityconfig.masterpassword.COL_PASSWORD=Password
plugin.securityconfig.masterpassword.PROTOCOL_UNKNOWN=(unknown)
plugin.securityconfig.masterpassword.CANNOT_DECRYPT=(cannot decrypt)
plugin.securityconfig.masterpassword.STORED_ACCOUNT_PASSWORDS=Accounts with stored passwords
plugin.securityconfig.masterpassword.REMOVE_PASSWORD_BUTTON=Remove
plugin.securityconfig.masterpassword.REMOVE_ALL_PASSWORDS_BUTTON=Remove all
plugin.securityconfig.masterpassword.REMOVE_ALL_CONFIRMATION=Are you sure you wish to remove all passwords?
plugin.securityconfig.masterpassword.REMOVE_ALL_TITLE=Remove all passwords
plugin.securityconfig.masterpassword.SHOW_PASSWORDS_BUTTON=Show Passwords
plugin.securityconfig.masterpassword.HIDE_PASSWORDS_BUTTON=Hide Passwords
plugin.securityconfig.masterpassword.PASSWORD_QUALITY_METER=Password quality meter
plugin.securityconfig.masterpassword.MP_INPUT=Please enter your master password:\n\n
# otr plugin
plugin.otr.menu.TITLE=OTR

@ -19,19 +19,25 @@
import org.osgi.framework.*;
/**
* A straight forward implementation of the ConfigurationService using an xml
* file for storing properties. Currently only String properties are
* meaningfully saved (we should probably consider how and whether we should
* take care of the rest).
* A straightforward implementation of the <tt>ConfigurationService</tt> using
* an XML or a .properties file for storing properties. Currently only
* <tt>String</tt> properties are meaningfully saved (we should probably
* consider how and whether we should take care of the rest).
*
* @author Emil Ivov
* @author Damian Minkov
* @author Lubomir Marinov
* @author Dmitri Melnikov
*/
public class ConfigurationServiceImpl
implements ConfigurationService
{
private final Logger logger = Logger.getLogger(ConfigurationServiceImpl.class);
/**
* The <tt>Logger</tt> used by this <tt>ConfigurationServiceImpl</tt>
* instance for logging output.
*/
private final Logger logger
= Logger.getLogger(ConfigurationServiceImpl.class);
private static final String SYS_PROPS_FILE_NAME_PROPERTY
= "net.java.sip.communicator.SYS_PROPS_FILE_NAME";
@ -365,6 +371,43 @@ public List<String> getPropertyNamesByPrefix(String prefix, boolean exactPrefixM
return resultKeySet;
}
/**
* Returns a <tt>List</tt> of <tt>String</tt>s containing the property names
* that have the specified suffix. A suffix is considered to be everything
* after the last dot in the property name.
* <p>
* For example, imagine a configuration service instance containing two
* properties only:
* </p>
* <code>
* net.java.sip.communicator.PROP1=value1
* net.java.sip.communicator.service.protocol.PROP1=value2
* </code>
* <p>
* A call to this method with <tt>suffix</tt> equal to "PROP1" will return
* both properties, whereas the call with <tt>suffix</tt> equal to
* "communicator.PROP1" or "PROP2" will return an empty <tt>List</tt>. Thus,
* if the <tt>suffix</tt> argument contains a dot, nothing will be found.
* </p>
*
* @param suffix the suffix for the property names to be returned
* @return a <tt>List</tt> of <tt>String</tt>s containing the property names
* which contain the specified <tt>suffix</tt>
*/
public List<String> getPropertyNamesBySuffix(String suffix)
{
List<String> resultKeySet = new LinkedList<String>();
for (String key : store.getPropertyNames())
{
int ix = key.lastIndexOf('.');
if ((ix != -1) && suffix.equals(key.substring(ix+1)))
resultKeySet.add(key);
}
return resultKeySet;
}
/**
* Adds a PropertyChangeListener to the listener list.
*

@ -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

@ -7,8 +7,8 @@
import java.util.*;
import net.java.sip.communicator.plugin.otr.*;
import net.java.sip.communicator.service.configuration.*;
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.*;
@ -17,8 +17,8 @@
import org.osgi.framework.*;
/**
*
* @author Yana Stamcheva
* @author Dmitri Melnikov
*/
public class SecurityConfigActivator
implements BundleActivator
@ -35,19 +35,31 @@ public class SecurityConfigActivator
public static BundleContext bundleContext;
/**
* The {@link ResourceManagementService} of the {@link OtrActivator}. Can
* also be obtained from the {@link OtrActivator#bundleContext} on demand,
* but we add it here for convinience.
* The {@link ResourceManagementService} of the
* {@link SecurityConfigActivator}. Can also be obtained from the
* {@link SecurityConfigActivator#bundleContext} on demand, but we add it
* here for convenience.
*/
private static ResourceManagementService resourceService;
private static ResourceManagementService resources;
/**
* The <tt>ConfigurationService</tt> registered in {@link #bundleContext}
* and used by the <tt>NeomediaActivator</tt> instance to read and write
* configuration properties.
* and used by the <tt>SecurityConfigActivator</tt> instance to read and
* write configuration properties.
*/
private static ConfigurationService configurationService;
/**
* The <tt>CredentialsStorageService</tt> registered in
* {@link #bundleContext}.
*/
private static CredentialsStorageService credentialsStorageService;
/**
* The <tt>UIService</tt> registered in {@link #bundleContext}.
*/
private static UIService uiService;
/**
* Starts this plugin.
* @param bc the BundleContext
@ -59,15 +71,33 @@ public void start(BundleContext bc) throws Exception
bundleContext = bc;
// Register the configuration form.
Dictionary<String, String> properties = new Hashtable<String, String>();
Dictionary<String, String> properties;
properties = new Hashtable<String, String>();
properties.put( ConfigurationForm.FORM_TYPE,
ConfigurationForm.GENERAL_TYPE);
bundleContext.registerService(ConfigurationForm.class.getName(),
bundleContext.registerService(
ConfigurationForm.class.getName(),
new LazyConfigurationForm(
"net.java.sip.communicator.plugin.securityconfig.SecurityConfigurationPanel",
getClass().getClassLoader(),
"plugin.securityconfig.ICON",
"plugin.securityconfig.TITLE", 20), properties);
"plugin.securityconfig.TITLE",
20),
properties);
properties = new Hashtable<String, String>();
properties.put( ConfigurationForm.FORM_TYPE,
ConfigurationForm.SECURITY_TYPE);
bundleContext.registerService(
ConfigurationForm.class.getName(),
new LazyConfigurationForm(
"net.java.sip.communicator.plugin.securityconfig.masterpassword.ConfigurationPanel",
getClass().getClassLoader(),
null /* iconID */,
"plugin.securityconfig.masterpassword.TITLE",
20),
properties);
}
/**
@ -87,19 +117,12 @@ public void stop(BundleContext bc) throws Exception {}
*/
public static ResourceManagementService getResources()
{
if (resourceService == null)
if (resources == null)
{
ServiceReference resReference
= bundleContext
.getServiceReference(
ResourceManagementService.class.getName());
if (resReference != null)
resourceService
= (ResourceManagementService)
bundleContext.getService(resReference);
resources
= ResourceManagementServiceUtils.getService(bundleContext);
}
return resourceService;
return resources;
}
/**
@ -125,6 +148,46 @@ public static ConfigurationService getConfigurationService()
return configurationService;
}
/**
* Returns the <tt>CredentialsStorageService</tt> obtained from the bundle
* context.
* @return the <tt>CredentialsStorageService</tt> obtained from the bundle
* context
*/
public static CredentialsStorageService getCredentialsStorageService()
{
if (credentialsStorageService == null)
{
ServiceReference credentialsReference
= bundleContext.getServiceReference(
CredentialsStorageService.class.getName());
credentialsStorageService
= (CredentialsStorageService)
bundleContext.getService(credentialsReference);
}
return credentialsStorageService;
}
/**
* Gets the <tt>UIService</tt> instance registered in the
* <tt>BundleContext</tt> of the <tt>SecurityConfigActivator</tt>.
*
* @return the <tt>UIService</tt> instance registered in the
* <tt>BundleContext</tt> of the <tt>SecurityConfigActivator</tt>
*/
public static UIService getUIService()
{
if (uiService == null)
{
ServiceReference serviceReference
= bundleContext.getServiceReference(UIService.class.getName());
uiService = (UIService) bundleContext.getService(serviceReference);
}
return uiService;
}
/**
* Gets all the available accounts in SIP Communicator.
*
@ -189,4 +252,51 @@ public static List<AccountID> getAllAccountIDs()
}
return providerFactoriesMap;
}
/**
* Finds all accounts with saved encrypted passwords.
*
* @return a {@link List} of {@link AccountID} with the saved encrypted password.
*/
public static Map<AccountID, String> getAccountIDsWithSavedPasswords()
{
Map<?, ProtocolProviderFactory> providerFactoriesMap
= getProtocolProviderFactories();
if (providerFactoriesMap == null)
return null;
CredentialsStorageService credentialsStorageService
= getCredentialsStorageService();
Map<AccountID, String> accountIDs = new HashMap<AccountID, String>();
for (ProtocolProviderFactory providerFactory
: providerFactoriesMap.values())
{
String sourcePackageName
= getFactoryImplPackageName(providerFactory);
for (AccountID accountID : providerFactory.getRegisteredAccounts())
{
String accountPrefix
= ProtocolProviderFactory.findAccountPrefix(
bundleContext,
accountID,
sourcePackageName);
if (credentialsStorageService.isStoredEncrypted(accountPrefix))
accountIDs.put(accountID, accountPrefix);
}
}
return accountIDs;
}
/**
* @return a String containing the package name of the concrete factory
* class that extends the abstract factory.
*/
private static String getFactoryImplPackageName(
ProtocolProviderFactory providerFactory)
{
String className = providerFactory.getClass().getName();
return className.substring(0, className.lastIndexOf('.'));
}
}

@ -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);
}
}

@ -5,23 +5,26 @@ 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.configuration,
net.java.sip.communicator.service.contactlist,
net.java.sip.communicator.service.credentialsstorage,
net.java.sip.communicator.service.gui,
net.java.sip.communicator.service.protocol,
net.java.sip.communicator.service.protocol.event,
net.java.sip.communicator.service.resources,
net.java.sip.communicator.util,
net.java.sip.communicator.util.swing,
net.java.sip.communicator.service.contactlist,
gnu.java.zrtp,
gnu.java.zrtp,
javax.crypto,
javax.crypto.interfaces,
javax.crypto.spec,
javax.imageio,
javax.swing,
javax.swing.border,
javax.swing.table,
javax.swing.table,
javax.swing.text,
javax.swing.text.html,
javax.swing.event,
javax.crypto,
javax.crypto.interfaces,
javax.crypto.spec,
org.bouncycastle.crypto,
org.bouncycastle.crypto.generators,
org.bouncycastle.crypto.params,

@ -18,6 +18,7 @@
*
* @author Emil Ivov
* @author Lubomir Marinov
* @author Dmitri Melnikov
*/
public interface ConfigurationService
{
@ -161,6 +162,31 @@ public void setProperty(String propertyName,
public List<String> getPropertyNamesByPrefix(String prefix,
boolean exactPrefixMatch);
/**
* Returns a <tt>List</tt> of <tt>String</tt>s containing the property names
* that have the specified suffix. A suffix is considered to be everything
* after the last dot in the property name.
* <p>
* For example, imagine a configuration service instance containing two
* properties only:
* </p>
* <code>
* net.java.sip.communicator.PROP1=value1
* net.java.sip.communicator.service.protocol.PROP1=value2
* </code>
* <p>
* A call to this method with <tt>suffix</tt> equal to "PROP1" will return
* both properties, whereas the call with <tt>suffix</tt> equal to
* "communicator.PROP1" or "PROP2" will return an empty <tt>List</tt>. Thus,
* if the <tt>suffix</tt> argument contains a dot, nothing will be found.
* </p>
*
* @param suffix the suffix for the property names to be returned
* @return a <tt>List</tt> of <tt>String</tt>s containing the property names
* which contain the specified <tt>suffix</tt>
*/
public List<String> getPropertyNamesBySuffix(String suffix);
/**
* Returns the String value of the specified property and null in case no
* property value was mapped against the specified propertyName, or in

@ -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;
}
}

@ -8,11 +8,12 @@
import java.util.*;
import org.osgi.framework.*;
import net.java.sip.communicator.service.configuration.*;
import net.java.sip.communicator.service.credentialsstorage.*;
import net.java.sip.communicator.util.*;
import org.osgi.framework.*;
/**
* The ProtocolProviderFactory is what actually creates instances of a
* ProtocolProviderService implementation. A provider factory would register,
@ -27,8 +28,13 @@
*/
public abstract class ProtocolProviderFactory
{
private static final Logger logger =
Logger.getLogger(ProtocolProviderFactory.class);
/**
* The <tt>Logger</tt> used by the <tt>ProtocolProviderFactory</tt> class
* and its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(ProtocolProviderFactory.class);
/**
* Then name of a property which represents a password.
*/
@ -473,12 +479,12 @@ public void storePassword(AccountID accountID, String password)
* </p>
*
* @param bundleContext a currently valid bundle context.
* @param accountID the AccountID for the account whose password we're
* storing.
* @param password the password itself.
* @param accountID the <tt>AccountID</tt> of the account whose password is
* to be stored
* @param password the password to be stored
*
* @throws IllegalArgumentException if no account corresponding to
* <tt>accountID</tt> has been previously stored.
* <tt>accountID</tt> has been previously stored.
*/
protected void storePassword(BundleContext bundleContext,
AccountID accountID,
@ -486,7 +492,7 @@ protected void storePassword(BundleContext bundleContext,
throws IllegalArgumentException
{
String accountPrefix = findAccountPrefix(
bundleContext, accountID);
bundleContext, accountID, getFactoryImplPackageName());
if (accountPrefix == null)
throw new IllegalArgumentException(
@ -494,24 +500,14 @@ protected void storePassword(BundleContext bundleContext,
+ accountID.getAccountUniqueID()
+ " in package" + getFactoryImplPackageName());
//obscure the password
String mangledPassword = null;
//if password is null then the caller simply wants the current password
//removed from the cache. make sure they don't get a null pointer
//instead.
if(password != null)
mangledPassword = new String(Base64.encode(password.getBytes()));
//get a reference to the config service and store it.
ServiceReference confReference
ServiceReference credentialsReference
= bundleContext.getServiceReference(
ConfigurationService.class.getName());
ConfigurationService configurationService
= (ConfigurationService) bundleContext.getService(confReference);
CredentialsStorageService.class.getName());
CredentialsStorageService credentialsService
= (CredentialsStorageService)
bundleContext.getService(credentialsReference);
configurationService.setProperty(
accountPrefix + "." + PASSWORD, mangledPassword);
credentialsService.storePassword(accountPrefix, password);
}
/**
@ -545,27 +541,19 @@ protected String loadPassword(BundleContext bundleContext,
AccountID accountID)
{
String accountPrefix = findAccountPrefix(
bundleContext, accountID);
bundleContext, accountID, getFactoryImplPackageName());
if (accountPrefix == null)
return null;
//get a reference to the config service and store it.
ServiceReference confReference
ServiceReference credentialsReference
= bundleContext.getServiceReference(
ConfigurationService.class.getName());
ConfigurationService configurationService
= (ConfigurationService) bundleContext.getService(confReference);
//obscure the password
String mangledPassword
= configurationService.getString(
accountPrefix + "." + PASSWORD);
if(mangledPassword == null)
return null;
CredentialsStorageService.class.getName());
CredentialsStorageService credentialsService
= (CredentialsStorageService)
bundleContext.getService(credentialsReference);
return new String(Base64.decode(mangledPassword));
return credentialsService.loadPassword(accountPrefix);
}
/**
@ -784,15 +772,16 @@ protected boolean removeStoredAccount(AccountID accountID)
* @param bundleContext a currently valid bundle context.
* @param accountID the AccountID of the account whose properties we're
* looking for.
* @param a String containing the package name of the concrete factory
* class that extends us.
* @return a String indicating the ConfigurationService property name
* prefix under which all account properties are stored or null if no
* account corresponding to the specified id was found.
*/
protected String findAccountPrefix(BundleContext bundleContext,
AccountID accountID)
public static String findAccountPrefix(BundleContext bundleContext,
AccountID accountID,
String sourcePackageName)
{
String sourcePackageName = getFactoryImplPackageName();
ServiceReference confReference
= bundleContext.getServiceReference(
ConfigurationService.class.getName());

@ -7,6 +7,7 @@ System-Bundle: yes
Import-Package: org.osgi.framework,
net.java.sip.communicator.service.configuration,
net.java.sip.communicator.service.configuration.event,
net.java.sip.communicator.service.credentialsstorage,
net.java.sip.communicator.util
Export-Package: net.java.sip.communicator.service.protocol,
net.java.sip.communicator.service.protocol.aimconstants,

@ -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…
Cancel
Save