session-android/src/org/thoughtcrime/securesms/database/IdentityDatabase.java
Moxie Marlinspike 24fc93e9ae Switch to a more heavily TOFU model for identity keys.
1) There is no longer a concept of "verified" or "unverified."
   Only "what we saw last time" and "different from last time."

2) Let's eliminate "verify session," since we're all about
   identity keys now.

3) Mark manually processed key exchanges as processed.
2013-05-23 16:36:24 -07:00

191 lines
6.9 KiB
Java

/**
* Copyright (C) 2011 Whisper Systems
*
* 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 3 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.IdentityKey;
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Base64;
import java.io.IOException;
public class IdentityDatabase extends Database {
private static final Uri CHANGE_URI = Uri.parse("content://textsecure/identities");
private static final String TABLE_NAME = "identities";
private static final String ID = "_id";
public static final String RECIPIENT = "recipient";
public static final String IDENTITY_KEY = "key";
public static final String MAC = "mac";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
" (" + ID + " INTEGER PRIMARY KEY, " +
RECIPIENT + " INTEGER UNIQUE, " +
IDENTITY_KEY + " TEXT, " +
MAC + " TEXT);";
public IdentityDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public Cursor getIdentities() {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = database.query(TABLE_NAME, null, null, null, null, null, null);
if (cursor != null)
cursor.setNotificationUri(context.getContentResolver(), CHANGE_URI);
return cursor;
}
public boolean isValidIdentity(MasterSecret masterSecret,
Recipient recipient,
IdentityKey theirIdentity)
{
SQLiteDatabase database = databaseHelper.getReadableDatabase();
String number = recipient.getNumber();
long recipientId = DatabaseFactory.getAddressDatabase(context).getCanonicalAddress(number);
MasterCipher masterCipher = new MasterCipher(masterSecret);
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, RECIPIENT + " = ?",
new String[] {recipientId+""}, null, null,null);
if (cursor != null && cursor.moveToFirst()) {
String serializedIdentity = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
String mac = cursor.getString(cursor.getColumnIndexOrThrow(MAC));
if (!masterCipher.verifyMacFor(recipientId + serializedIdentity, Base64.decode(mac))) {
Log.w("IdentityDatabase", "MAC failed");
return false;
}
IdentityKey ourIdentity = new IdentityKey(Base64.decode(serializedIdentity), 0);
return ourIdentity.equals(theirIdentity);
} else {
return true;
}
} catch (IOException e) {
Log.w("IdentityDatabase", e);
return false;
} catch (InvalidKeyException e) {
Log.w("IdentityDatabase", e);
return false;
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public void saveIdentity(MasterSecret masterSecret, Recipient recipient, IdentityKey identityKey)
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
String number = recipient.getNumber();
long recipientId = DatabaseFactory.getAddressDatabase(context).getCanonicalAddress(number);
MasterCipher masterCipher = new MasterCipher(masterSecret);
String identityKeyString = Base64.encodeBytes(identityKey.serialize());
String macString = Base64.encodeBytes(masterCipher.getMacFor(recipientId +
identityKeyString));
ContentValues contentValues = new ContentValues();
contentValues.put(RECIPIENT, recipientId);
contentValues.put(IDENTITY_KEY, identityKeyString);
contentValues.put(MAC, macString);
database.replace(TABLE_NAME, null, contentValues);
context.getContentResolver().notifyChange(CHANGE_URI, null);
}
public void deleteIdentity(long id) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, ID_WHERE, new String[] {id+""});
context.getContentResolver().notifyChange(CHANGE_URI, null);
}
public Reader readerFor(MasterSecret masterSecret, Cursor cursor) {
return new Reader(masterSecret, cursor);
}
public class Reader {
private final Cursor cursor;
private final MasterCipher cipher;
public Reader(MasterSecret masterSecret, Cursor cursor) {
this.cursor = cursor;
this.cipher = new MasterCipher(masterSecret);
}
public Identity getCurrent() {
long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT));
Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientId + "", true);
try {
String identityKeyString = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
String mac = cursor.getString(cursor.getColumnIndexOrThrow(MAC));
if (!cipher.verifyMacFor(recipientId + identityKeyString, Base64.decode(mac))) {
return new Identity(recipients, null);
}
IdentityKey identityKey = new IdentityKey(Base64.decode(identityKeyString), 0);
return new Identity(recipients, identityKey);
} catch (IOException e) {
Log.w("IdentityDatabase", e);
return new Identity(recipients, null);
} catch (InvalidKeyException e) {
Log.w("IdentityDatabase", e);
return new Identity(recipients, null);
}
}
}
public class Identity {
private final Recipients recipients;
private final IdentityKey identityKey;
public Identity(Recipients recipients, IdentityKey identityKey) {
this.recipients = recipients;
this.identityKey = identityKey;
}
public Recipients getRecipients() {
return recipients;
}
public IdentityKey getIdentityKey() {
return identityKey;
}
}
}