2013-12-02 14:07:57 -05:00
|
|
|
package com.fsck.k9.security;
|
2013-12-02 14:04:40 -05:00
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
import java.io.FileNotFoundException;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.security.KeyStore;
|
|
|
|
import java.security.KeyStoreException;
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
import java.security.cert.Certificate;
|
|
|
|
import java.security.cert.CertificateException;
|
|
|
|
import java.security.cert.X509Certificate;
|
|
|
|
|
|
|
|
import org.apache.commons.io.IOUtils;
|
|
|
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
import com.fsck.k9.K9;
|
|
|
|
|
|
|
|
public class LocalKeyStore {
|
2013-12-02 14:37:07 -05:00
|
|
|
private static final int KEY_STORE_FILE_VERSION = 1;
|
2013-12-02 14:04:40 -05:00
|
|
|
|
2013-12-03 19:20:20 -05:00
|
|
|
private static String sKeyStoreLocation;
|
2013-12-03 07:28:48 -05:00
|
|
|
|
2013-12-03 19:20:20 -05:00
|
|
|
public static void setKeyStoreLocation(String directory) {
|
|
|
|
sKeyStoreLocation = directory;
|
|
|
|
}
|
2013-12-03 07:28:48 -05:00
|
|
|
|
2013-12-03 19:20:20 -05:00
|
|
|
private static class LocalKeyStoreHolder {
|
|
|
|
static final LocalKeyStore INSTANCE = new LocalKeyStore();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static LocalKeyStore getInstance() {
|
|
|
|
return LocalKeyStoreHolder.INSTANCE;
|
2013-12-02 14:04:40 -05:00
|
|
|
}
|
|
|
|
|
2013-12-03 07:28:48 -05:00
|
|
|
|
|
|
|
private File mKeyStoreFile;
|
|
|
|
private KeyStore mKeyStore;
|
|
|
|
|
|
|
|
|
2013-12-03 19:20:20 -05:00
|
|
|
private LocalKeyStore() {
|
2013-12-04 12:10:12 -05:00
|
|
|
try {
|
2013-12-03 19:20:20 -05:00
|
|
|
upgradeKeyStoreFile();
|
|
|
|
setKeyStoreFile(null);
|
2013-12-04 12:10:12 -05:00
|
|
|
} catch (CertificateException e) {
|
|
|
|
/*
|
|
|
|
* Can happen if setKeyStoreLocation(String directory) has not been
|
|
|
|
* called before the first call to getInstance(). Not necessarily an
|
|
|
|
* error, presuming setKeyStoreFile(File) is called next with a
|
|
|
|
* non-null File.
|
|
|
|
*/
|
|
|
|
Log.w(K9.LOG_TAG, "Local key store has not been initialized");
|
2013-12-03 19:20:20 -05:00
|
|
|
}
|
2013-12-02 14:04:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reinitialize the local key store with certificates contained in
|
|
|
|
* {@code file}
|
|
|
|
*
|
|
|
|
* @param file
|
|
|
|
* {@link File} containing locally saved certificates. May be 0
|
|
|
|
* length, in which case it is deleted and recreated. May be
|
|
|
|
* {@code null}, in which case a default file location is used.
|
2013-12-04 12:10:12 -05:00
|
|
|
* @throws CertificateException
|
|
|
|
* Occurs if {@code file == null} and
|
|
|
|
* {@code setKeyStoreLocation(directory)} was not called previously.
|
2013-12-02 14:04:40 -05:00
|
|
|
*/
|
2013-12-04 12:10:12 -05:00
|
|
|
public synchronized void setKeyStoreFile(File file) throws CertificateException {
|
2013-12-02 14:04:40 -05:00
|
|
|
if (file == null) {
|
2013-12-02 14:37:07 -05:00
|
|
|
file = new File(getKeyStoreFilePath(KEY_STORE_FILE_VERSION));
|
2013-12-02 14:04:40 -05:00
|
|
|
}
|
|
|
|
if (file.length() == 0) {
|
2013-12-02 14:37:07 -05:00
|
|
|
/*
|
|
|
|
* The file may be empty (e.g., if it was created with
|
|
|
|
* File.createTempFile). We can't pass an empty file to
|
|
|
|
* Keystore.load. Instead, we let it be created anew.
|
|
|
|
*/
|
2013-12-02 14:04:40 -05:00
|
|
|
file.delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
FileInputStream fis = null;
|
|
|
|
try {
|
|
|
|
fis = new FileInputStream(file);
|
|
|
|
} catch (FileNotFoundException e) {
|
|
|
|
// If the file doesn't exist, that's fine, too
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
|
|
|
|
store.load(fis, "".toCharArray());
|
|
|
|
mKeyStore = store;
|
|
|
|
mKeyStoreFile = file;
|
|
|
|
} catch (Exception e) {
|
|
|
|
Log.e(K9.LOG_TAG, "Failed to initialize local key store", e);
|
|
|
|
// Use of the local key store is effectively disabled.
|
|
|
|
mKeyStore = null;
|
|
|
|
mKeyStoreFile = null;
|
|
|
|
} finally {
|
|
|
|
IOUtils.closeQuietly(fis);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized void addCertificate(String host, int port,
|
|
|
|
X509Certificate certificate) throws CertificateException {
|
|
|
|
if (mKeyStore == null) {
|
|
|
|
throw new CertificateException(
|
|
|
|
"Certificate not added because key store not initialized");
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
mKeyStore.setCertificateEntry(getCertKey(host, port), certificate);
|
2013-12-02 14:33:01 -05:00
|
|
|
} catch (KeyStoreException e) {
|
|
|
|
throw new CertificateException(
|
|
|
|
"Failed to add certificate to local key store", e);
|
|
|
|
}
|
|
|
|
writeCertificateFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void writeCertificateFile() throws CertificateException {
|
|
|
|
java.io.OutputStream keyStoreStream = null;
|
|
|
|
try {
|
2013-12-02 14:04:40 -05:00
|
|
|
keyStoreStream = new java.io.FileOutputStream(mKeyStoreFile);
|
|
|
|
mKeyStore.store(keyStoreStream, "".toCharArray());
|
|
|
|
} catch (FileNotFoundException e) {
|
|
|
|
throw new CertificateException("Unable to write KeyStore: "
|
|
|
|
+ e.getMessage());
|
|
|
|
} catch (CertificateException e) {
|
|
|
|
throw new CertificateException("Unable to write KeyStore: "
|
|
|
|
+ e.getMessage());
|
|
|
|
} catch (IOException e) {
|
|
|
|
throw new CertificateException("Unable to write KeyStore: "
|
|
|
|
+ e.getMessage());
|
|
|
|
} catch (NoSuchAlgorithmException e) {
|
|
|
|
throw new CertificateException("Unable to write KeyStore: "
|
|
|
|
+ e.getMessage());
|
|
|
|
} catch (KeyStoreException e) {
|
|
|
|
throw new CertificateException("Unable to write KeyStore: "
|
|
|
|
+ e.getMessage());
|
|
|
|
} finally {
|
|
|
|
IOUtils.closeQuietly(keyStoreStream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-02 14:33:01 -05:00
|
|
|
public synchronized boolean isValidCertificate(Certificate certificate,
|
|
|
|
String host, int port) {
|
2013-12-02 14:04:40 -05:00
|
|
|
if (mKeyStore == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Certificate storedCert = null;
|
|
|
|
try {
|
|
|
|
storedCert = mKeyStore.getCertificate(getCertKey(host, port));
|
|
|
|
return (storedCert != null && storedCert.equals(certificate));
|
|
|
|
} catch (KeyStoreException e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static String getCertKey(String host, int port) {
|
|
|
|
return host + ":" + port;
|
|
|
|
}
|
2013-12-02 14:33:01 -05:00
|
|
|
|
|
|
|
public synchronized void deleteCertificate(String oldHost, int oldPort) {
|
|
|
|
if (mKeyStore == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
mKeyStore.deleteEntry(getCertKey(oldHost, oldPort));
|
|
|
|
writeCertificateFile();
|
|
|
|
} catch (KeyStoreException e) {
|
|
|
|
// Ignore: most likely there was no cert. found
|
|
|
|
} catch (CertificateException e) {
|
|
|
|
Log.e(K9.LOG_TAG, "Error updating the local key store file", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-04 12:10:12 -05:00
|
|
|
private void upgradeKeyStoreFile() throws CertificateException {
|
2013-12-02 14:37:07 -05:00
|
|
|
if (KEY_STORE_FILE_VERSION > 0) {
|
|
|
|
// Blow away version "0" because certificate aliases have changed.
|
|
|
|
new File(getKeyStoreFilePath(0)).delete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-04 12:10:12 -05:00
|
|
|
private String getKeyStoreFilePath(int version) throws CertificateException {
|
|
|
|
if (sKeyStoreLocation == null) {
|
|
|
|
throw new CertificateException("Local key store location has not been initialized");
|
|
|
|
}
|
2013-12-02 14:37:07 -05:00
|
|
|
if (version < 1) {
|
2013-12-03 19:20:20 -05:00
|
|
|
return sKeyStoreLocation + File.separator + "KeyStore.bks";
|
2013-12-02 14:37:07 -05:00
|
|
|
} else {
|
2013-12-03 19:20:20 -05:00
|
|
|
return sKeyStoreLocation + File.separator + "KeyStore_v" + version + ".bks";
|
2013-12-02 14:37:07 -05:00
|
|
|
}
|
|
|
|
}
|
2013-12-02 14:04:40 -05:00
|
|
|
}
|