package com.hecticant.thinpass.persistence;
import java.util.List;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
/**
* A singleton that manages the default store.
*
* @author Pedro Fonseca
*/
public class StoreController {
private static final String TAG = "StoreController";
private static final String SKEY_KEY = "SECRETKEY";
private static final String CHAL_KEY = "CHALLENGE";
private static final String SSAL_KEY = "SECRETSALT";
private static final String MSAL_KEY = "MASTERSALT";
private static StoreController scSingleton;
private Store store;
private StoreController(Object context) {
this.store = new DefaultStore(context);
}
public static synchronized StoreController getInstance(Object context) {
if (scSingleton == null) {
scSingleton = new StoreController(context);
} else {
((DefaultStore) scSingleton.store).checkState();
}
return scSingleton;
}
@Override
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public long rowCount() {
return store.countAccounts();
}
public List<Account> accountsInRange(long offset, long limit) {
return store.accountsInRange(offset, limit);
}
public List<Account> accounts() {
return ((DefaultStore) store).accounts();
}
/**
* Adds a new account to the store. The account id for this account is
* automatically generated by the <code>Store</code>.
* <p>
* The underlying store saves the data "as is" therefore the caller is
* responsible for encrypting sensitive information before passing it
* to this <code>StoreController</code>.
*
* @param username an optional username for the account.
* @param password a password for the account.
* @param description a description for the account.
* @return the new account.
*
* @see Store
* @see Account
*/
public Account addAccount(String username, byte[] password,
byte[] description)
{
if (password == null || description == null) {
throw new NullPointerException();
}
long count;
try {
count = store.countAccounts();
store.addAccount(username, password, description);
}
catch (SQLException e) {
return null;
}
Account a = null;
List<Account> list = accountsInRange(count, 1);
try {
a = list.get(0);
}
catch (IndexOutOfBoundsException e) {}
return a;
}
/**
*
* @param acc
*/
public void updateAccount(Account acc) {
if (acc == null) {
throw new NullPointerException();
}
try {
store.updateAccount(acc);
}
catch (SQLException e) {}
}
public byte[] key() {
return store.valueForKey(SKEY_KEY);
}
public byte[] passwordSalt() {
return store.valueForKey(MSAL_KEY);
}
public void setPasswordSalt(byte[] salt) {
store.setValueForKey(MSAL_KEY, salt, true);
}
public byte[] keySalt() {
return store.valueForKey(SSAL_KEY);
}
public byte[] challenge() {
return store.valueForKey(CHAL_KEY);
}
/**
* Checks if a master key exists. The following conditions must be met:
* <ul>
* <li>The salt used to generate the key must exist in the application data
* store
* <li>The random key, generated when the master key is created, must be
* hashed in the application data store
* </ul>
*
* @return
*
* @see #storeKey(byte[], byte[], byte[])
* @see #passwordSalt()
* @see #challenge()
*/
public boolean hasKey() {
return passwordSalt() != null && challenge() != null;
}
/**
* Saves a new key to the store. The key to be stored is a random symmetric
* key that is used to encrypt sensitive user data saved on the store. The
* key itself is protected by the master key which is derivated from a
* password. Thus the password can be changed without re-encrypting the
* store and the random key can be changed if necessary.
*
* <p>
* Master and random keys are related one-to-one. However, that can be
* easily extended to a one-to-many relationship.
*
* @param encryptedKey a key encrypted with the master key.
* @param salt the salt used when generating <code>check</check>
* @param check a cryptographic checksum to verify if the
* <code>encryptedKey</code> is correctly decrypted.
* @return if the new key was successfully stored.
*/
public boolean storeKey(byte[] encryptedKey, byte[] salt, byte[] check) {
if (encryptedKey == null || check == null) {
return false;
}
SQLiteDatabase db = ((DefaultStore) store).getRawStore();
if (!db.isOpen()) {
return false;
}
boolean didSet = true;
db.beginTransaction();
try {
store.setValueForKey(SKEY_KEY, encryptedKey, false);
store.setValueForKey(CHAL_KEY, check, false);
store.setValueForKey(SSAL_KEY, salt, false);
db.setTransactionSuccessful();
}
catch (Exception e) {
Log.e(TAG, e.getLocalizedMessage());
didSet = false;
}
finally {
db.endTransaction();
}
return didSet;
}
/**
* Updates the encrypted text of the key when the master key is changed.
* The key itself must remain unchanged.
*
* @param encryptedKey
*/
public void updateKey(byte[] encryptedKey) {
if (encryptedKey == null) {
throw new NullPointerException();
}
store.setValueForKey(SKEY_KEY, encryptedKey, true);
}
/* (non-javadoc) @see #storeKey(byte[], byte[], byte[]) */
public boolean replaceKey(byte[] encryptedKey, byte[] salt, byte[] check) {
/* TO BE IMPLEMENTED */
return false;
}
public void obliterateStore() {
store.obliterate();
}
public void close() {
store.close();
}
}
|