/*
* Copyright (C) 2012-2014 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
package org.strongswan.android.logic;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import android.util.Log;
//CMA
import com.swisscom.safeconnect.BuildConfig;
public class TrustedCertificateManager
{
private static final String TAG = TrustedCertificateManager.class.getSimpleName();
private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
private Hashtable<String, X509Certificate> mCACerts = new Hashtable<String, X509Certificate>();
private volatile boolean mReload;
private boolean mLoaded;
private final ArrayList<KeyStore> mKeyStores = new ArrayList<KeyStore>();
public enum TrustedCertificateSource
{
SYSTEM("system:"),
USER("user:"),
LOCAL("local:");
private final String mPrefix;
private TrustedCertificateSource(String prefix)
{
mPrefix = prefix;
}
private String getPrefix()
{
return mPrefix;
}
}
/**
* Private constructor to prevent instantiation from other classes.
*/
private TrustedCertificateManager()
{
//CMA
for (String name : new String[] { /*"LocalCertificateStore", */ "AndroidCAStore" })
{
KeyStore store;
try
{
store = KeyStore.getInstance(name);
store.load(null,null);
mKeyStores.add(store);
}
catch (Exception e)
{
Log.e(TAG, "Unable to load KeyStore: " + name);
e.printStackTrace();
}
}
}
/**
* This is not instantiated until the first call to getInstance()
*/
private static class Singleton {
public static final TrustedCertificateManager mInstance = new TrustedCertificateManager();
}
/**
* Get the single instance of the CA certificate manager.
* @return CA certificate manager
*/
public static TrustedCertificateManager getInstance()
{
return Singleton.mInstance;
}
/**
* Invalidates the current load state so that the next call to load()
* will force a reload of the cached CA certificates.
* @return reference to itself
*/
public TrustedCertificateManager reset()
{
//CMA
if (BuildConfig.DEBUG) Log.d(TAG, "Force reload of cached CA certificates on next load");
this.mReload = true;
return this;
}
/**
* Ensures that the certificates are loaded but does not force a reload.
* As this takes a while if the certificates are not loaded yet it should
* be called asynchronously.
* @return reference to itself
*/
public TrustedCertificateManager load()
{
//CMA
if (BuildConfig.DEBUG) Log.d(TAG, "Ensure cached CA certificates are loaded");
this.mLock.writeLock().lock();
if (!this.mLoaded || this.mReload)
{
this.mReload = false;
loadCertificates();
}
this.mLock.writeLock().unlock();
return this;
}
/**
* Opens the CA certificate KeyStore and loads the cached certificates.
* The lock must be locked when calling this method.
*/
private void loadCertificates()
{
//CMA
if (BuildConfig.DEBUG) Log.d(TAG, "Load cached CA certificates");
Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
for (KeyStore store : this.mKeyStores)
{
fetchCertificates(certs, store);
}
this.mCACerts = certs;
this.mLoaded = true;
//CMA
if (BuildConfig.DEBUG) Log.d(TAG, "Cached CA certificates loaded");
}
/**
* Load all X.509 certificates from the given KeyStore.
* @param certs Hashtable to store certificates in
* @param store KeyStore to load certificates from
*/
private void fetchCertificates(Hashtable<String, X509Certificate> certs, KeyStore store)
{
try
{
Enumeration<String> aliases = store.aliases();
while (aliases.hasMoreElements())
{
String alias = aliases.nextElement();
Certificate cert;
cert = store.getCertificate(alias);
if (cert != null && cert instanceof X509Certificate)
{
certs.put(alias, (X509Certificate)cert);
}
}
}
catch (KeyStoreException ex)
{
ex.printStackTrace();
}
}
/**
* Retrieve the CA certificate with the given alias.
* @param alias alias of the certificate to get
* @return the certificate, null if not found
*/
public X509Certificate getCACertificateFromAlias(String alias)
{
X509Certificate certificate = null;
if (this.mLock.readLock().tryLock())
{
certificate = this.mCACerts.get(alias);
this.mLock.readLock().unlock();
}
else
{ /* if we cannot get the lock load it directly from the KeyStore,
* should be fast for a single certificate */
for (KeyStore store : this.mKeyStores)
{
try
{
Certificate cert = store.getCertificate(alias);
if (cert != null && cert instanceof X509Certificate)
{
certificate = (X509Certificate)cert;
break;
}
}
catch (KeyStoreException e)
{
e.printStackTrace();
}
}
}
return certificate;
}
/**
* Get all CA certificates (from all keystores).
* @return Hashtable mapping aliases to certificates
*/
@SuppressWarnings("unchecked")
public Hashtable<String, X509Certificate> getAllCACertificates()
{
Hashtable<String, X509Certificate> certs;
this.mLock.readLock().lock();
certs = (Hashtable<String, X509Certificate>)this.mCACerts.clone();
this.mLock.readLock().unlock();
return certs;
}
/**
* Get all certificates from the given source.
* @param source type to filter certificates
* @return Hashtable mapping aliases to certificates
*/
public Hashtable<String, X509Certificate> getCACertificates(TrustedCertificateSource source)
{
Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
this.mLock.readLock().lock();
for (String alias : this.mCACerts.keySet())
{
if (alias.startsWith(source.getPrefix()))
{
certs.put(alias, this.mCACerts.get(alias));
}
}
this.mLock.readLock().unlock();
return certs;
}
}