FileDocCategorySizeDatePackage
DbSSLSessionCache.javaAPI DocAndroid 1.5 API10355Wed May 06 22:41:56 BST 2009com.android.internal.net

DbSSLSessionCache

public class DbSSLSessionCache extends Object implements org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache
Hook into harmony SSL cache to persist the SSL sessions. Current implementation is suitable for saving a small number of hosts - like google services. It can be extended with expiration and more features to support more hosts. {@hide}

Fields Summary
private static final String
TAG
public static final String
SSL_CACHE_TABLE
Table where sessions are stored.
private static final String
SSL_CACHE_ID
private static final String
SSL_CACHE_HOSTPORT
Key is host:port - port is not optional.
private static final String
SSL_CACHE_SESSION
Base64-encoded DER value of the session.
private static final String
SSL_CACHE_TIME_SEC
Time when the record was added - should be close to the time of the initial session negotiation.
public static final String
DATABASE_NAME
public static final int
DATABASE_VERSION
public static final int
SSL_CACHE_ID_COL
public for testing
public static final int
SSL_CACHE_HOSTPORT_COL
public static final int
SSL_CACHE_SESSION_COL
public static final int
SSL_CACHE_TIME_SEC_COL
public static final int
MAX_CACHE_SIZE
private final Map
mExternalCache
private DatabaseHelper
mDatabaseHelper
private boolean
mNeedsCacheLoad
public static final String[]
PROJECTION
private static final Map
sInstances
Constructors Summary
private DbSSLSessionCache(android.content.Context activityContext)
Create a SslSessionCache instance, using the specified context to initialize the database. This constructor will use the default database - created for the application context.

param
activityContext

        Context appContext = activityContext.getApplicationContext();
        mDatabaseHelper = new DatabaseHelper(appContext);
    
public DbSSLSessionCache(DatabaseHelper database)
Create a SslSessionCache that uses a specific database.

param
database

        this.mDatabaseHelper = database;
    
Methods Summary
public voidclear()
Reset the database and internal state. Used for testing or to free space.

        synchronized(this) {
            try {
                mExternalCache.clear();
                mNeedsCacheLoad = true;
                mDatabaseHelper.getWritableDatabase().delete(SSL_CACHE_TABLE, 
                        null, null);
            } catch (SQLException ex) {
                Log.d(TAG, "Error removing SSL cached entries ", ex);
                // ignore - nothing we can do about it
            }
        } 
    
public static synchronized com.android.internal.net.DbSSLSessionCachegetInstanceForPackage(android.content.Context context)
Returns a singleton instance of the DbSSLSessionCache that should be used for this context's package.

param
context The context that should be used for getting/creating the singleton instance.
return
The singleton instance for the context's package.


                                             
          
        String packageName = context.getPackageName();
        if (sInstances.containsKey(packageName)) {
            return sInstances.get(packageName);
        }
        DbSSLSessionCache cache = new DbSSLSessionCache(context);
        sInstances.put(packageName, cache);
        return cache;
    
public byte[]getSessionData(java.lang.String host, int port)

        // Current (simple) implementation does a single lookup to DB, then saves 
        // all entries to the cache. 

        // This works for google services - i.e. small number of certs.
        // If we extend this to all processes - we should hold a separate cache 
        // or do lookups to DB each time. 
        if (mDatabaseHelper == null) {
            return null;
        }
        synchronized(this.getClass()) {
            if (mNeedsCacheLoad) {
                // Don't try to load again, if something is wrong on the first 
                // request it'll likely be wrong each time.
                mNeedsCacheLoad = false;
                long t0 = System.currentTimeMillis();

                Cursor cur = null;
                try {
                    cur = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE, 
                            PROJECTION, null, null, null, null, null);
                    if (cur.moveToFirst()) {
                        do {
                            String hostPort = cur.getString(SSL_CACHE_HOSTPORT_COL);
                            String value = cur.getString(SSL_CACHE_SESSION_COL);

                            if (hostPort == null || value == null) {
                                continue;
                            }
                            // TODO: blob support ?
                            byte[] der = Base64.decodeBase64(value.getBytes());
                            mExternalCache.put(hostPort, der);
                        } while (cur.moveToNext());

                    }
                } catch (SQLException ex) {
                    Log.d(TAG, "Error loading SSL cached entries ", ex);
                } finally {
                    if (cur != null) {
                        cur.close();
                    }
                    if (Log.isLoggable(TAG, Log.DEBUG)) {
                        long t1 = System.currentTimeMillis();
                        Log.d(TAG, "LOADED CACHED SSL " + (t1 - t0) + " ms");
                    }
                }
            }

            String key = host + ":" + port;

            return mExternalCache.get(key);
        }
    
public byte[]getSessionData(byte[] id)

        // We support client side only - the cache will do nothing for 
        // server-side sessions. 
        return null;
    
public voidputSessionData(javax.net.ssl.SSLSession session, byte[] der)

        if (mDatabaseHelper == null) {
            return;
        }
        synchronized (this.getClass()) {
            SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
            if (mExternalCache.size() == MAX_CACHE_SIZE) {
                // remove oldest.
                // TODO: check if the new one is in cached already ( i.e. update ).
                Cursor byTime = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE, 
                        PROJECTION, null, null, null, null, SSL_CACHE_TIME_SEC);
                if (byTime.moveToFirst()) {
                    // TODO: can I do byTime.deleteRow() ? 
                    String hostPort = byTime.getString(SSL_CACHE_HOSTPORT_COL);
                    db.delete(SSL_CACHE_TABLE, 
                            SSL_CACHE_HOSTPORT + "= ?" , new String[] { hostPort });
                    mExternalCache.remove(hostPort);
                } else {
                    Log.w(TAG, "No rows found");
                    // something is wrong, clear it
                    clear();
                }
            }
            // Serialize native session to standard DER encoding    
            long t0 = System.currentTimeMillis();

            String b64 = new String(Base64.encodeBase64(der));
            String key = session.getPeerHost() + ":" + session.getPeerPort();

            ContentValues values = new ContentValues();
            values.put(SSL_CACHE_HOSTPORT, key);
            values.put(SSL_CACHE_SESSION, b64);
            values.put(SSL_CACHE_TIME_SEC, System.currentTimeMillis() / 1000);

            mExternalCache.put(key, der);

            try {
                db.insert(SSL_CACHE_TABLE, null /*nullColumnHack */ , values);
            } catch(SQLException ex) {
                // Ignore - nothing we can do to recover, and caller shouldn't 
                // be affected.
                Log.w(TAG, "Ignoring SQL exception when caching session", ex);
            }
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                long t1 = System.currentTimeMillis();
                Log.d(TAG, "New SSL session " + session.getPeerHost() +
                        " DER len: " + der.length + " " + (t1 - t0));
            }
        }