19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project// Copyright 2009 The Android Open Source Project
29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage android.core;
49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.database.Cursor;
69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.database.SQLException;
79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.database.sqlite.SQLiteDatabase;
89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.database.sqlite.SQLiteOpenHelper;
99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.util.Log;
109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.content.ContentValues;
119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.content.Context;
129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport org.apache.commons.codec.binary.Base64;
149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.LinkedHashMap;
179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Map;
189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport javax.net.ssl.SSLSession;
209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/**
229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Hook into harmony SSL cache to persist the SSL sessions.
239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Current implementation is suitable for saving a small number of hosts -
259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * like google services. It can be extended with expiration and more features
269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * to support more hosts.
279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * {@hide}
299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpublic class DatabaseSessionCache implements SSLClientSessionCache {
319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String TAG = "SslSessionCache";
329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    static DatabaseHelper sDefaultDatabaseHelper;
339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private DatabaseHelper mDatabaseHelper;
359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Table where sessions are stored.
389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final String SSL_CACHE_TABLE = "ssl_sessions";
409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String SSL_CACHE_ID = "_id";
429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Key is host:port - port is not optional.
459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String SSL_CACHE_HOSTPORT = "hostport";
479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Base64-encoded DER value of the session.
509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String SSL_CACHE_SESSION = "session";
529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Time when the record was added - should be close to the time
559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * of the initial session negotiation.
569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String SSL_CACHE_TIME_SEC = "time_sec";
589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final String DATABASE_NAME = "ssl_sessions.db";
609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int DATABASE_VERSION = 1;
629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /** public for testing
649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int SSL_CACHE_ID_COL = 0;
669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int SSL_CACHE_HOSTPORT_COL = 1;
679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int SSL_CACHE_SESSION_COL = 2;
689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int SSL_CACHE_TIME_SEC_COL = 3;
699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String SAVE_ON_ADD = "save_on_add";
719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    static boolean sHookInitializationDone = false;
739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int MAX_CACHE_SIZE = 256;
759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final Map<String, byte[]> mExternalCache =
779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        new LinkedHashMap<String, byte[]>(MAX_CACHE_SIZE, 0.75f, true) {
789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        @Override
799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        public boolean removeEldestEntry(
809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                Map.Entry<String, byte[]> eldest) {
819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            boolean shouldDelete = this.size() > MAX_CACHE_SIZE;
829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // TODO: delete from DB
849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            return shouldDelete;
859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    };
879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    static boolean mNeedsCacheLoad = true;
889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final String[] PROJECTION = new String[] {
909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project      SSL_CACHE_ID,
919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project      SSL_CACHE_HOSTPORT,
929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project      SSL_CACHE_SESSION,
939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project      SSL_CACHE_TIME_SEC
949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    };
959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * This class needs to be installed as a hook, if the security property
989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * is set. Getting the right classloader may be fun since we don't use
999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Provider to get its classloader, but in android this is in same
1009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * loader with AndroidHttpClient.
1019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     *
1029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * This constructor will use the default database. You must
1039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * call init() before to specify the context used for the database and
1049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * check settings.
1059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
1069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public DatabaseSessionCache() {
1079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        Log.v(TAG, "Instance created.");
1089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // May be null if caching is disabled - no sessions will be persisted.
1099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        this.mDatabaseHelper = sDefaultDatabaseHelper;
1109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
1139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Create a SslSessionCache instance, using the specified context to
1149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * initialize the database.
1159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     *
1169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * This constructor will use the default database - created the first
1179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * time.
1189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     *
1199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @param activityContext
1209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
1219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public DatabaseSessionCache(Context activityContext) {
1229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // Static init - only one initialization will happen.
1239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // Each SslSessionCache is using the same DB.
1249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        init(activityContext);
1259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // May be null if caching is disabled - no sessions will be persisted.
1269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        this.mDatabaseHelper = sDefaultDatabaseHelper;
1279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
1309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Create a SslSessionCache that uses a specific database.
1319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     *
1329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @param database
1339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
1349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public DatabaseSessionCache(DatabaseHelper database) {
1359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        this.mDatabaseHelper = database;
1369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//    public static boolean enabled(Context androidContext) {
139edc5189c33de03f3e2f5f73edc0e007992b933c9Doug Zongker//        String sslCache = Settings.Secure.getString(androidContext.getContentResolver(),
140edc5189c33de03f3e2f5f73edc0e007992b933c9Doug Zongker//                Settings.Secure.SSL_SESSION_CACHE);
1419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//
1429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//        if (Log.isLoggable(TAG, Log.DEBUG)) {
1439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//            Log.d(TAG, "enabled " + sslCache + " " + androidContext.getPackageName());
1449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//        }
1459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//
1469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//        return SAVE_ON_ADD.equals(sslCache);
1479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//    }
1489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
1509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * You must call this method to enable SSL session caching for an app.
1519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
1529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public synchronized static void init(Context activityContext) {
1539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // It is possible that multiple provider will try to install this hook.
1549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // We want a single db per VM.
1559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        if (sHookInitializationDone) {
1569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            return;
1579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
1589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//        // More values can be added in future to provide different
1619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//        // behaviours, like 'batch save'.
1629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//        if (enabled(activityContext)) {
1639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            Context appContext = activityContext.getApplicationContext();
1649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            sDefaultDatabaseHelper = new DatabaseHelper(appContext);
1659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // Set default SSLSocketFactory
1679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // The property is defined in the javadocs for javax.net.SSLSocketFactory
1689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // (no constant defined there)
1699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // This should cover all code using SSLSocketFactory.getDefault(),
1709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // including native http client and apache httpclient.
1719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // MCS is using its own custom factory - will need special code.
1729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//            Security.setProperty("ssl.SocketFactory.provider",
1739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//                    SslSocketFactoryWithCache.class.getName());
1749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project//        }
1759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // Won't try again.
1779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        sHookInitializationDone = true;
1789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void putSessionData(SSLSession session, byte[] der) {
1819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        if (mDatabaseHelper == null) {
1829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            return;
1839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
1849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        if (mExternalCache.size() > MAX_CACHE_SIZE) {
1859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // remove oldest.
1869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            Cursor byTime = mDatabaseHelper.getWritableDatabase().query(SSL_CACHE_TABLE,
1879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    PROJECTION, null, null, null, null, SSL_CACHE_TIME_SEC);
1889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            byTime.moveToFirst();
1899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            // TODO: can I do byTime.deleteRow() ?
1909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            String hostPort = byTime.getString(SSL_CACHE_HOSTPORT_COL);
1919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            mDatabaseHelper.getWritableDatabase().delete(SSL_CACHE_TABLE,
1939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    SSL_CACHE_HOSTPORT + "= ?" , new String[] { hostPort });
1949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
1959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // Serialize native session to standard DER encoding
1969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        long t0 = System.currentTimeMillis();
1979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        String b64 = new String(Base64.encodeBase64(der));
1999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        String key = session.getPeerHost() + ":" + session.getPeerPort();
2009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        ContentValues values = new ContentValues();
2029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        values.put(SSL_CACHE_HOSTPORT, key);
2039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        values.put(SSL_CACHE_SESSION, b64);
2049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        values.put(SSL_CACHE_TIME_SEC, System.currentTimeMillis() / 1000);
2059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        synchronized (this.getClass()) {
2079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            mExternalCache.put(key, der);
2089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            try {
2109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                mDatabaseHelper.getWritableDatabase().insert(SSL_CACHE_TABLE, null /*nullColumnHack */ , values);
2119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } catch(SQLException ex) {
2129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                // Ignore - nothing we can do to recover, and caller shouldn't
2139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                // be affected.
2149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                Log.w(TAG, "Ignoring SQL exception when caching session", ex);
2159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
2169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
2179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        if (Log.isLoggable(TAG, Log.DEBUG)) {
2189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            long t1 = System.currentTimeMillis();
2199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            Log.d(TAG, "New SSL session " + session.getPeerHost() +
2209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    " DER len: " + der.length + " " + (t1 - t0));
2219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
2229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
2249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public byte[] getSessionData(String host, int port) {
2269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // Current (simple) implementation does a single lookup to DB, then saves
2279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // all entries to the cache.
2289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // This works for google services - i.e. small number of certs.
2309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // If we extend this to all processes - we should hold a separate cache
2319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // or do lookups to DB each time.
2329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        if (mDatabaseHelper == null) {
2339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            return null;
2349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
2359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        synchronized(this.getClass()) {
2369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            if (mNeedsCacheLoad) {
2379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                // Don't try to load again, if something is wrong on the first
2389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                // request it'll likely be wrong each time.
2399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                mNeedsCacheLoad = false;
2409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                long t0 = System.currentTimeMillis();
2419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                Cursor cur = null;
2439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                try {
2449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    cur = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE, PROJECTION, null,
2459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                            null, null, null, null);
2469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if (cur.moveToFirst()) {
2479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        do {
2489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                            String hostPort = cur.getString(SSL_CACHE_HOSTPORT_COL);
2499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                            String value = cur.getString(SSL_CACHE_SESSION_COL);
2509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                            if (hostPort == null || value == null) {
2529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                                continue;
2539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                            }
2549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                            // TODO: blob support ?
2559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                            byte[] der = Base64.decodeBase64(value.getBytes());
2569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                            mExternalCache.put(hostPort, der);
2579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        } while (cur.moveToNext());
2589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    }
2609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                } catch (SQLException ex) {
2619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    Log.d(TAG, "Error loading SSL cached entries ", ex);
2629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                } finally {
2639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if (cur != null) {
2649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        cur.close();
2659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    }
2669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        long t1 = System.currentTimeMillis();
2689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                        Log.d(TAG, "LOADED CACHED SSL " + (t1 - t0) + " ms");
2699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    }
2709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
2719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
2729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            String key = host + ":" + port;
2749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            return mExternalCache.get(key);
2769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
2779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
2789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public byte[] getSessionData(byte[] id) {
2809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // We support client side only - the cache will do nothing on client.
2819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        return null;
2829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
2839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /** Visible for testing.
2859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
2869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static class DatabaseHelper extends SQLiteOpenHelper {
2879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        public DatabaseHelper(Context context) {
2899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            super(context, DATABASE_NAME, null /* factory */, DATABASE_VERSION);
2909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
2919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        @Override
2939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        public void onCreate(SQLiteDatabase db) {
2949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            db.execSQL("CREATE TABLE " + SSL_CACHE_TABLE + " (" +
2959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    SSL_CACHE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
2969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    SSL_CACHE_HOSTPORT + " TEXT UNIQUE ON CONFLICT REPLACE," +
2979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    SSL_CACHE_SESSION + " TEXT," +
2989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    SSL_CACHE_TIME_SEC + " INTEGER" +
2999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            ");");
3009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            db.execSQL("CREATE INDEX ssl_sessions_idx1 ON ssl_sessions (" +
3019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    SSL_CACHE_HOSTPORT + ");");
3029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
3039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
3049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        @Override
3059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
3069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            db.execSQL("DROP TABLE IF EXISTS " + SSL_CACHE_TABLE );
3079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            onCreate(db);
3089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
3099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
3109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
3119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
312edc5189c33de03f3e2f5f73edc0e007992b933c9Doug Zongker}
313