/* * Copyright 2012 The Stanford MobiSocial Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mobisocial.musubi.model.helpers; import gnu.trove.map.hash.TLongLongHashMap; import gnu.trove.procedure.TLongLongProcedure; import java.util.Arrays; import mobisocial.crypto.IBEncryptionScheme; import mobisocial.crypto.IBHashedIdentity; import mobisocial.crypto.IBSignatureScheme; import mobisocial.musubi.encoding.NeedsKey; import mobisocial.musubi.encoding.OutgoingMessage; import mobisocial.musubi.encoding.TransportDataProvider; import mobisocial.musubi.model.MDevice; import mobisocial.musubi.model.MEncodedMessage; import mobisocial.musubi.model.MIdentity; import mobisocial.musubi.model.MIncomingSecret; import mobisocial.musubi.model.MMissingMessage; import mobisocial.musubi.model.MOutgoingSecret; import mobisocial.musubi.model.MSequenceNumber; import mobisocial.musubi.util.Util; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; import android.os.Build; /** * This class provides the database binding for the api that allows * identity based message encoding/decoding to be done using a * persistent store of time-varying IBE user keys and dynamic. * * Aside from things it does using other managers, it manipulates * the incoming and outgoing channel secrets tables and the missing * message tracking table */ public class MessageTransportManager extends ManagerBase implements TransportDataProvider { final IBEncryptionScheme encryptionScheme_; final IBSignatureScheme signatureScheme_; final long myDeviceName_; final DatabaseManager mDbManager; final UserKeyManager userKeyManager_; public MessageTransportManager(SQLiteOpenHelper databaseSource, IBEncryptionScheme encryptionScheme, IBSignatureScheme signatureScheme, long deviceName) { super(databaseSource); encryptionScheme_ = encryptionScheme; signatureScheme_ = signatureScheme; myDeviceName_ = deviceName; mDbManager = new DatabaseManager(databaseSource); userKeyManager_ = new UserKeyManager(encryptionScheme, signatureScheme, databaseSource); } SQLiteStatement sqlInsertIncomingSecret_; SQLiteStatement sqlInsertOutgoingSecret_; SQLiteStatement sqlDeleteMissingSequenceNumber_; SQLiteStatement sqlAddSequenceNumber_; @Override public IBEncryptionScheme getEncryptionScheme() { return encryptionScheme_; } @Override public IBSignatureScheme getSignatureScheme() { return signatureScheme_; } @Override public IBSignatureScheme.UserKey getSignatureKey(MIdentity from, IBHashedIdentity me) throws NeedsKey.Signature { return userKeyManager_.getSignatureKey(from, me); } @Override public IBEncryptionScheme.UserKey getEncryptionKey(MIdentity to, IBHashedIdentity me) throws NeedsKey.Encryption { return userKeyManager_.getEncryptionKey(to, me); } // CALL THESE to get the time for the Identity you use for signature @Override public long getSignatureTime(MIdentity from) { //TODO: consider revocation/online offline status, etc return IdentitiesManager.computeTemporalFrameFromHash(from.principalHash_); } @Override public long getEncryptionTime(MIdentity to) { //TODO: consider revocation/online offline status, etc return IdentitiesManager.computeTemporalFrameFromHash(to.principalHash_); } @Override public long getDeviceName() { return myDeviceName_; } @Override public MOutgoingSecret lookupOutgoingSecret(MIdentity from, MIdentity to, IBHashedIdentity me, IBHashedIdentity you) { SQLiteDatabase db = initializeDatabase(); Cursor c = db.query( MOutgoingSecret.TABLE, new String[] { MOutgoingSecret.COL_ID, MOutgoingSecret.COL_OUTGOING_ENCRYPTED_KEY, MOutgoingSecret.COL_OUTGOING_KEY, MOutgoingSecret.COL_OUTGOING_SIGNATURE}, MOutgoingSecret.COL_MY_IDENTITY_ID + "=? AND " + MOutgoingSecret.COL_OTHER_IDENTITY_ID + "=? AND " + MOutgoingSecret.COL_OUTGOING_ENCRYPTION_WHEN + "=? AND " + MOutgoingSecret.COL_OUTGOING_SIGNATURE_WHEN + "=?", new String[] { String.valueOf(from.id_), String.valueOf(to.id_), String.valueOf(you.temporalFrame_), String.valueOf(me.temporalFrame_)}, null, null, null ); try { while(c.moveToNext()) { MOutgoingSecret os = new MOutgoingSecret(); os.id_ = c.getLong(0); os.encryptedKey_ = c.getBlob(1); os.key_ = c.getBlob(2); os.encryptionWhen_ = you.temporalFrame_; os.signatureWhen_ = me.temporalFrame_; os.myIdentityId_ = from.id_; os.otherIdentityId_ = to.id_; os.signature_ = c.getBlob(3); return os; } return null; } finally { c.close(); } } @Override public void insertOutgoingSecret(IBHashedIdentity me, IBHashedIdentity you, MOutgoingSecret os) { SQLiteDatabase db = initializeDatabase(); if(sqlInsertOutgoingSecret_ == null) { synchronized(this) { if(sqlInsertOutgoingSecret_ == null) { sqlInsertOutgoingSecret_ = db.compileStatement( "INSERT INTO " + MOutgoingSecret.TABLE + " (" + MOutgoingSecret.COL_MY_IDENTITY_ID + ", " + MOutgoingSecret.COL_OTHER_IDENTITY_ID + ", " + MOutgoingSecret.COL_OUTGOING_SIGNATURE_WHEN + ", " + MOutgoingSecret.COL_OUTGOING_ENCRYPTION_WHEN + ", " + MOutgoingSecret.COL_OUTGOING_ENCRYPTED_KEY + ", " + MOutgoingSecret.COL_OUTGOING_SIGNATURE + ", " + MOutgoingSecret.COL_OUTGOING_KEY + ") " + "VALUES (?,?,?,?,?,?,?)"); } } } synchronized (sqlInsertOutgoingSecret_) { sqlInsertOutgoingSecret_.bindLong(1, os.myIdentityId_); sqlInsertOutgoingSecret_.bindLong(2, os.otherIdentityId_); sqlInsertOutgoingSecret_.bindLong(3, os.signatureWhen_); sqlInsertOutgoingSecret_.bindLong(4, os.encryptionWhen_); sqlInsertOutgoingSecret_.bindBlob(5, os.encryptedKey_); sqlInsertOutgoingSecret_.bindBlob(6, os.signature_); sqlInsertOutgoingSecret_.bindBlob(7, os.key_); os.id_ = sqlInsertOutgoingSecret_.executeInsert(); } } @Override public MIncomingSecret lookupIncomingSecret(MIdentity from, MDevice fromDevice, MIdentity to, byte[] signature, IBHashedIdentity you, IBHashedIdentity me) { SQLiteDatabase db = initializeDatabase(); Cursor c = db.query( MIncomingSecret.TABLE, new String[] { MIncomingSecret.COL_ID, MIncomingSecret.COL_INCOMING_ENCRYPTED_KEY, MIncomingSecret.COL_INCOMING_KEY, MIncomingSecret.COL_INCOMING_SIGNATURE }, MIncomingSecret.COL_MY_IDENTITY_ID + "=? AND " + MIncomingSecret.COL_OTHER_IDENTITY_ID + "=? AND " + MIncomingSecret.COL_INCOMING_ENCRYPTION_WHEN + "=? AND " + MIncomingSecret.COL_INCOMING_SIGNATURE_WHEN + "=? AND " + MIncomingSecret.COL_INCOMING_DEVICE_ID + "=?", new String[] { String.valueOf(to.id_), String.valueOf(from.id_), String.valueOf(me.temporalFrame_), String.valueOf(you.temporalFrame_), String.valueOf(fromDevice.id_)}, null, null, null ); try { while(c.moveToNext()) { byte[] cached_signature = c.getBlob(3); //its possible to have different signatures on the same set of parameters if(!Arrays.equals(signature, cached_signature)) continue; MIncomingSecret is = new MIncomingSecret(); is.id_ = c.getLong(0); is.deviceId_ = fromDevice.id_; is.encryptedKey_ = c.getBlob(1); is.key_ = c.getBlob(2); is.encryptionWhen_ = me.temporalFrame_; is.signatureWhen_ = you.temporalFrame_; is.myIdentityId_ = to.id_; is.otherIdentityId_ = from.id_; is.signature_ = cached_signature; return is; } return null; } finally { c.close(); } } @Override public void insertIncomingSecret(IBHashedIdentity you, IBHashedIdentity me, MIncomingSecret is) { SQLiteDatabase db = initializeDatabase(); if(sqlInsertIncomingSecret_ == null) { synchronized(this) { if(sqlInsertIncomingSecret_ == null) { sqlInsertIncomingSecret_ = db.compileStatement( "INSERT INTO " + MIncomingSecret.TABLE + " (" + MIncomingSecret.COL_MY_IDENTITY_ID + ", " + MIncomingSecret.COL_OTHER_IDENTITY_ID + ", " + MIncomingSecret.COL_INCOMING_SIGNATURE_WHEN + ", " + MIncomingSecret.COL_INCOMING_ENCRYPTION_WHEN + ", " + MIncomingSecret.COL_INCOMING_ENCRYPTED_KEY + ", " + MIncomingSecret.COL_INCOMING_DEVICE_ID + ", " + MIncomingSecret.COL_INCOMING_SIGNATURE + ", " + MIncomingSecret.COL_INCOMING_KEY + ") " + "VALUES (?,?,?,?,?,?,?,?)"); } } } synchronized (sqlInsertIncomingSecret_) { sqlInsertIncomingSecret_.bindLong(1, is.myIdentityId_); sqlInsertIncomingSecret_.bindLong(2, is.otherIdentityId_); sqlInsertIncomingSecret_.bindLong(3, is.signatureWhen_); sqlInsertIncomingSecret_.bindLong(4, is.encryptionWhen_); sqlInsertIncomingSecret_.bindBlob(5, is.encryptedKey_); sqlInsertIncomingSecret_.bindLong(6, is.deviceId_); sqlInsertIncomingSecret_.bindBlob(7, is.signature_); sqlInsertIncomingSecret_.bindBlob(8, is.key_); is.id_ = sqlInsertIncomingSecret_.executeInsert(); } } @Override public void incrementSequenceNumber(MIdentity to) { mDbManager.getIdentitiesManager().incrementSequenceNumber(to); } @Override public void receivedSequenceNumber(MDevice from, long sequenceNumber) { SQLiteDatabase db = initializeDatabase(); //TODO:this needs to add sequence numbers based on if message appear to have been missing if(sqlDeleteMissingSequenceNumber_ == null) { synchronized(this) { if(sqlDeleteMissingSequenceNumber_ == null) { sqlDeleteMissingSequenceNumber_ = db.compileStatement( "DELETE FROM " + MMissingMessage.TABLE + " WHERE " + MMissingMessage.COL_DEVICE_ID + "=? AND " + MMissingMessage.COL_SEQUENCE_NUMBER + "=? " ); } } } synchronized (sqlDeleteMissingSequenceNumber_) { sqlDeleteMissingSequenceNumber_.bindLong(1, from.id_); sqlDeleteMissingSequenceNumber_.execute(); } } @Override public boolean haveHash(byte[] hash) { return mDbManager.getEncodedMessageManager().getEncodedIdForHash(hash) != -1; } @Override public void storeSequenceNumbers(final MEncodedMessage encoded, TLongLongHashMap sequence_numbers) { SQLiteDatabase db = initializeDatabase(); if(sqlAddSequenceNumber_ == null) { synchronized (this) { if(sqlAddSequenceNumber_ == null) { sqlAddSequenceNumber_ = db.compileStatement( "INSERT INTO " + MSequenceNumber.TABLE + " (" + MSequenceNumber.COL_RECIPIENT + "," + MSequenceNumber.COL_ENCODED_ID + "," + MSequenceNumber.COL_SEQUENCE_NUMBER + ") " + "VALUES (?,?,?)" ); } } } synchronized (sqlAddSequenceNumber_) { sequence_numbers.forEachEntry(new TLongLongProcedure() { @Override public boolean execute(long identityId, long sequenceNumber) { sqlAddSequenceNumber_.bindLong(1, identityId); sqlAddSequenceNumber_.bindLong(2, encoded.id_); sqlAddSequenceNumber_.bindLong(3, sequenceNumber); sqlAddSequenceNumber_.executeInsert(); return true; } }); } } @Override public boolean isBlacklisted(MIdentity from) { return mDbManager.getIdentitiesManager().isBlacklisted(from); } @Override public boolean isMe(IBHashedIdentity ibHashedIdentity) { return mDbManager.getIdentitiesManager().isMe(ibHashedIdentity); } @Override public MIdentity addClaimedIdentity(IBHashedIdentity hid) { return mDbManager.getIdentitiesManager().ensureClaimedIdentity(hid); } @Override public MIdentity addUnclaimedIdentity(IBHashedIdentity hid) { MIdentity id = mDbManager.getIdentitiesManager().getIdentityForIBHashedIdentity(hid); if(id != null) { return id; } id = new MIdentity(); id.claimed_ = false; id.principalHash_ = hid.hashed_; id.principalShortHash_ = Util.shortHash(hid.hashed_); id.type_ = hid.authority_; id.hasSentEmail_ = false; mDbManager.getIdentitiesManager().insertIdentity(id); return id; } @Override public MDevice addDevice(MIdentity ident, long deviceId) { MDevice dev = mDbManager.getDeviceManager().getDeviceForName(ident.id_, deviceId); if(dev != null) return dev; dev = new MDevice(); dev.deviceName_ = deviceId; dev.identityId_ = ident.id_; dev.maxSequenceNumber_ = 0; mDbManager.getDeviceManager().insertDevice(dev); return dev; } @Override public void updateEncodedMetadata(MEncodedMessage encoded) { mDbManager.getEncodedMessageManager().updateEncodedMetadata(encoded); } @Override public void insertEncodedMessage(OutgoingMessage om, MEncodedMessage encoded) { mDbManager.getEncodedMessageManager().insertEncoded(encoded); } @Override public void setTransactionSuccessful() { SQLiteDatabase db = initializeDatabase(); db.setTransactionSuccessful(); } @Override public void beginTransaction() { SQLiteDatabase db = initializeDatabase(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { db.beginTransactionNonExclusive(); } else { db.beginTransaction(); } } @Override public void endTransaction() { SQLiteDatabase db = initializeDatabase(); db.endTransaction(); } @Override public synchronized void close() { if (sqlInsertIncomingSecret_ != null) { sqlInsertIncomingSecret_.close(); sqlInsertIncomingSecret_ = null; } if (sqlInsertOutgoingSecret_ != null) { sqlInsertOutgoingSecret_.close(); sqlInsertOutgoingSecret_ = null; } if (sqlDeleteMissingSequenceNumber_ != null) { sqlDeleteMissingSequenceNumber_.close(); sqlDeleteMissingSequenceNumber_ = null; } if (sqlAddSequenceNumber_ != null) { sqlAddSequenceNumber_.close(); sqlAddSequenceNumber_ = null; } if (mDbManager != null) { mDbManager.close(); } } }