935 lines
36 KiB
Java
935 lines
36 KiB
Java
/*
|
|
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
|
*
|
|
* 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.sufficientlysecure.keychain.provider;
|
|
|
|
import android.content.ContentProviderOperation;
|
|
import android.content.ContentResolver;
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.content.OperationApplicationException;
|
|
import android.database.Cursor;
|
|
import android.database.DatabaseUtils;
|
|
import android.net.Uri;
|
|
import android.os.RemoteException;
|
|
|
|
import org.spongycastle.bcpg.ArmoredOutputStream;
|
|
import org.spongycastle.openpgp.PGPKeyRing;
|
|
import org.spongycastle.openpgp.PGPPublicKey;
|
|
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
|
import org.spongycastle.openpgp.PGPSecretKey;
|
|
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
|
import org.spongycastle.openpgp.PGPSignature;
|
|
import org.sufficientlysecure.keychain.Constants;
|
|
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
|
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
|
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
|
import org.sufficientlysecure.keychain.remote.AppSettings;
|
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
|
import org.sufficientlysecure.keychain.util.Log;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Date;
|
|
import java.util.HashSet;
|
|
import java.util.Set;
|
|
|
|
public class ProviderHelper {
|
|
|
|
/**
|
|
* Private helper method to get PGPKeyRing from database
|
|
*/
|
|
public static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) {
|
|
Cursor cursor = context.getContentResolver().query(queryUri,
|
|
new String[]{KeyRings._ID, KeyRings.KEY_RING_DATA}, null, null, null);
|
|
|
|
PGPKeyRing keyRing = null;
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
int keyRingDataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA);
|
|
|
|
byte[] data = cursor.getBlob(keyRingDataCol);
|
|
if (data != null) {
|
|
keyRing = PgpConversionHelper.BytesToPGPKeyRing(data);
|
|
}
|
|
}
|
|
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
|
|
return keyRing;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the actual PGPPublicKeyRing object from the database blob based on the rowId
|
|
*/
|
|
public static PGPPublicKeyRing getPGPPublicKeyRingByRowId(Context context, long rowId) {
|
|
Uri queryUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
|
|
return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the actual PGPPublicKeyRing object from the database blob based on the masterKeyId
|
|
*/
|
|
public static PGPPublicKeyRing getPGPPublicKeyRingByMasterKeyId(Context context,
|
|
long masterKeyId) {
|
|
Uri queryUri = KeyRings.buildPublicKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
|
|
return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the actual PGPPublicKeyRing object from the database blob associated with a key
|
|
* with this keyId
|
|
*/
|
|
public static PGPPublicKeyRing getPGPPublicKeyRingByKeyId(Context context, long keyId) {
|
|
Uri queryUri = KeyRings.buildPublicKeyRingsByKeyIdUri(Long.toString(keyId));
|
|
return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the actual PGPPublicKey object from the database blob associated with a key with
|
|
* this keyId
|
|
*/
|
|
public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long keyId) {
|
|
PGPPublicKeyRing keyRing = getPGPPublicKeyRingByKeyId(context, keyId);
|
|
|
|
return (keyRing == null) ? null : keyRing.getPublicKey(keyId);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the actual PGPSecretKeyRing object from the database blob based on the rowId
|
|
*/
|
|
public static PGPSecretKeyRing getPGPSecretKeyRingByRowId(Context context, long rowId) {
|
|
Uri queryUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
|
|
return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the actual PGPSecretKeyRing object from the database blob based on the maserKeyId
|
|
*/
|
|
public static PGPSecretKeyRing getPGPSecretKeyRingByMasterKeyId(Context context,
|
|
long masterKeyId) {
|
|
Uri queryUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
|
|
return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the actual PGPSecretKeyRing object from the database blob associated with a key
|
|
* with this keyId
|
|
*/
|
|
public static PGPSecretKeyRing getPGPSecretKeyRingByKeyId(Context context, long keyId) {
|
|
Uri queryUri = KeyRings.buildSecretKeyRingsByKeyIdUri(Long.toString(keyId));
|
|
return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the actual PGPSecretKey object from the database blob associated with a key with
|
|
* this keyId
|
|
*/
|
|
public static PGPSecretKey getPGPSecretKeyByKeyId(Context context, long keyId) {
|
|
PGPSecretKeyRing keyRing = getPGPSecretKeyRingByKeyId(context, keyId);
|
|
|
|
return (keyRing == null) ? null : keyRing.getSecretKey(keyId);
|
|
}
|
|
|
|
/**
|
|
* Saves PGPPublicKeyRing with its keys and userIds in DB
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public static void saveKeyRing(Context context, PGPPublicKeyRing keyRing) throws IOException {
|
|
PGPPublicKey masterKey = keyRing.getPublicKey();
|
|
long masterKeyId = masterKey.getKeyID();
|
|
|
|
Uri deleteUri = KeyRings.buildPublicKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
|
|
|
|
// get current _ID of key
|
|
long currentRowId = -1;
|
|
Cursor oldQuery = context.getContentResolver()
|
|
.query(deleteUri, new String[]{KeyRings._ID}, null, null, null);
|
|
if (oldQuery != null && oldQuery.moveToFirst()) {
|
|
currentRowId = oldQuery.getLong(0);
|
|
} else {
|
|
Log.e(Constants.TAG, "Key could not be found! Something wrong is happening!");
|
|
}
|
|
|
|
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
|
|
try {
|
|
context.getContentResolver().delete(deleteUri, null, null);
|
|
} catch (UnsupportedOperationException e) {
|
|
Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e);
|
|
}
|
|
|
|
ContentValues values = new ContentValues();
|
|
// use exactly the same _ID again to replace key in-place.
|
|
// NOTE: If we would not use the same _ID again,
|
|
// getting back to the ViewKeyActivity would result in Nullpointer,
|
|
// because the currently loaded key would be gone from the database
|
|
if (currentRowId != -1) {
|
|
values.put(KeyRings._ID, currentRowId);
|
|
}
|
|
values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
|
|
values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
|
|
|
|
// insert new version of this keyRing
|
|
Uri uri = KeyRings.buildPublicKeyRingsUri();
|
|
Uri insertedUri = context.getContentResolver().insert(uri, values);
|
|
long keyRingRowId = Long.valueOf(insertedUri.getLastPathSegment());
|
|
|
|
// save all keys and userIds included in keyRing object in database
|
|
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
|
|
|
|
int rank = 0;
|
|
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
|
|
operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank));
|
|
++rank;
|
|
}
|
|
|
|
int userIdRank = 0;
|
|
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
|
operations.add(buildPublicUserIdOperations(context, keyRingRowId, userId, userIdRank));
|
|
++userIdRank;
|
|
}
|
|
|
|
for (PGPSignature certification :
|
|
new IterableIterator<PGPSignature>(
|
|
masterKey.getSignaturesOfType(PGPSignature.POSITIVE_CERTIFICATION))) {
|
|
// TODO: how to do this??
|
|
// we need to verify the signatures again and again when they are displayed...
|
|
// if (certification.verify
|
|
// operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank));
|
|
}
|
|
|
|
|
|
try {
|
|
context.getContentResolver().applyBatch(KeychainContract.CONTENT_AUTHORITY, operations);
|
|
} catch (RemoteException e) {
|
|
Log.e(Constants.TAG, "applyBatch failed!", e);
|
|
} catch (OperationApplicationException e) {
|
|
Log.e(Constants.TAG, "applyBatch failed!", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Saves PGPSecretKeyRing with its keys and userIds in DB
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public static void saveKeyRing(Context context, PGPSecretKeyRing keyRing) throws IOException {
|
|
PGPSecretKey masterKey = keyRing.getSecretKey();
|
|
long masterKeyId = masterKey.getKeyID();
|
|
|
|
Uri deleteUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
|
|
|
|
// get current _ID of key
|
|
long currentRowId = -1;
|
|
Cursor oldQuery = context.getContentResolver()
|
|
.query(deleteUri, new String[]{KeyRings._ID}, null, null, null);
|
|
if (oldQuery != null && oldQuery.moveToFirst()) {
|
|
currentRowId = oldQuery.getLong(0);
|
|
} else {
|
|
Log.e(Constants.TAG, "Key could not be found! Something wrong is happening!");
|
|
}
|
|
|
|
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
|
|
try {
|
|
context.getContentResolver().delete(deleteUri, null, null);
|
|
} catch (UnsupportedOperationException e) {
|
|
Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e);
|
|
}
|
|
|
|
ContentValues values = new ContentValues();
|
|
// use exactly the same _ID again to replace key in-place.
|
|
// NOTE: If we would not use the same _ID again,
|
|
// getting back to the ViewKeyActivity would result in Nullpointer,
|
|
// because the currently loaded key would be gone from the database
|
|
if (currentRowId != -1) {
|
|
values.put(KeyRings._ID, currentRowId);
|
|
}
|
|
values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
|
|
values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
|
|
|
|
// insert new version of this keyRing
|
|
Uri uri = KeyRings.buildSecretKeyRingsUri();
|
|
Uri insertedUri = context.getContentResolver().insert(uri, values);
|
|
long keyRingRowId = Long.valueOf(insertedUri.getLastPathSegment());
|
|
|
|
// save all keys and userIds included in keyRing object in database
|
|
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
|
|
|
|
int rank = 0;
|
|
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
|
|
operations.add(buildSecretKeyOperations(context, keyRingRowId, key, rank));
|
|
++rank;
|
|
}
|
|
|
|
int userIdRank = 0;
|
|
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
|
operations.add(buildSecretUserIdOperations(context, keyRingRowId, userId, userIdRank));
|
|
++userIdRank;
|
|
}
|
|
|
|
try {
|
|
context.getContentResolver().applyBatch(KeychainContract.CONTENT_AUTHORITY, operations);
|
|
} catch (RemoteException e) {
|
|
Log.e(Constants.TAG, "applyBatch failed!", e);
|
|
} catch (OperationApplicationException e) {
|
|
Log.e(Constants.TAG, "applyBatch failed!", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
|
|
*/
|
|
private static ContentProviderOperation buildPublicKeyOperations(
|
|
Context context, long keyRingRowId, PGPPublicKey key, int rank) throws IOException {
|
|
ContentValues values = new ContentValues();
|
|
values.put(Keys.KEY_ID, key.getKeyID());
|
|
values.put(Keys.IS_MASTER_KEY, key.isMasterKey());
|
|
values.put(Keys.ALGORITHM, key.getAlgorithm());
|
|
values.put(Keys.KEY_SIZE, key.getBitStrength());
|
|
values.put(Keys.CAN_SIGN, PgpKeyHelper.isSigningKey(key));
|
|
values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key));
|
|
values.put(Keys.IS_REVOKED, key.isRevoked());
|
|
values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000);
|
|
Date expiryDate = PgpKeyHelper.getExpiryDate(key);
|
|
if (expiryDate != null) {
|
|
values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
|
|
}
|
|
values.put(Keys.KEY_RING_ROW_ID, keyRingRowId);
|
|
values.put(Keys.KEY_DATA, key.getEncoded());
|
|
values.put(Keys.RANK, rank);
|
|
values.put(Keys.FINGERPRINT, key.getFingerprint());
|
|
|
|
Uri uri = Keys.buildPublicKeysUri(Long.toString(keyRingRowId));
|
|
|
|
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
|
}
|
|
|
|
/**
|
|
* Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing
|
|
*/
|
|
private static ContentProviderOperation buildPublicUserIdOperations(
|
|
Context context, long keyRingRowId, String userId, int rank) {
|
|
ContentValues values = new ContentValues();
|
|
values.put(UserIds.KEY_RING_ROW_ID, keyRingRowId);
|
|
values.put(UserIds.USER_ID, userId);
|
|
values.put(UserIds.RANK, rank);
|
|
|
|
Uri uri = UserIds.buildPublicUserIdsUri(Long.toString(keyRingRowId));
|
|
|
|
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
|
}
|
|
|
|
/**
|
|
* Build ContentProviderOperation to add PGPSecretKey to database corresponding to a keyRing
|
|
*/
|
|
private static ContentProviderOperation buildSecretKeyOperations(
|
|
Context context, long keyRingRowId, PGPSecretKey key, int rank) throws IOException {
|
|
ContentValues values = new ContentValues();
|
|
|
|
boolean hasPrivate = true;
|
|
if (key.isMasterKey()) {
|
|
if (key.isPrivateKeyEmpty()) {
|
|
hasPrivate = false;
|
|
}
|
|
}
|
|
|
|
values.put(Keys.KEY_ID, key.getKeyID());
|
|
values.put(Keys.IS_MASTER_KEY, key.isMasterKey());
|
|
values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm());
|
|
values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength());
|
|
values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && hasPrivate));
|
|
values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && hasPrivate));
|
|
values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key));
|
|
values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked());
|
|
values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000);
|
|
Date expiryDate = PgpKeyHelper.getExpiryDate(key);
|
|
if (expiryDate != null) {
|
|
values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
|
|
}
|
|
values.put(Keys.KEY_RING_ROW_ID, keyRingRowId);
|
|
values.put(Keys.KEY_DATA, key.getEncoded());
|
|
values.put(Keys.RANK, rank);
|
|
values.put(Keys.FINGERPRINT, key.getPublicKey().getFingerprint());
|
|
|
|
Uri uri = Keys.buildSecretKeysUri(Long.toString(keyRingRowId));
|
|
|
|
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
|
}
|
|
|
|
/**
|
|
* Build ContentProviderOperation to add SecretUserIds to database corresponding to a keyRing
|
|
*/
|
|
private static ContentProviderOperation buildSecretUserIdOperations(
|
|
Context context, long keyRingRowId, String userId, int rank) {
|
|
ContentValues values = new ContentValues();
|
|
values.put(UserIds.KEY_RING_ROW_ID, keyRingRowId);
|
|
values.put(UserIds.USER_ID, userId);
|
|
values.put(UserIds.RANK, rank);
|
|
|
|
Uri uri = UserIds.buildSecretUserIdsUri(Long.toString(keyRingRowId));
|
|
|
|
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
|
}
|
|
|
|
/**
|
|
* Private helper method
|
|
*/
|
|
private static ArrayList<Long> getKeyRingsMasterKeyIds(Context context, Uri queryUri) {
|
|
Cursor cursor = context.getContentResolver().query(queryUri,
|
|
new String[]{KeyRings.MASTER_KEY_ID}, null, null, null);
|
|
|
|
ArrayList<Long> masterKeyIds = new ArrayList<Long>();
|
|
if (cursor != null) {
|
|
int masterKeyIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
|
|
if (cursor.moveToFirst()) {
|
|
do {
|
|
masterKeyIds.add(cursor.getLong(masterKeyIdCol));
|
|
} while (cursor.moveToNext());
|
|
}
|
|
}
|
|
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
|
|
return masterKeyIds;
|
|
}
|
|
|
|
/**
|
|
* Private helper method
|
|
*/
|
|
private static ArrayList<Long> getKeyRingsRowIds(Context context, Uri queryUri) {
|
|
Cursor cursor = context.getContentResolver().query(queryUri,
|
|
new String[]{KeyRings._ID}, null, null, null);
|
|
|
|
ArrayList<Long> rowIds = new ArrayList<Long>();
|
|
if (cursor != null) {
|
|
int idCol = cursor.getColumnIndex(KeyRings._ID);
|
|
if (cursor.moveToFirst()) {
|
|
do {
|
|
rowIds.add(cursor.getLong(idCol));
|
|
} while (cursor.moveToNext());
|
|
}
|
|
}
|
|
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
|
|
return rowIds;
|
|
}
|
|
|
|
/**
|
|
* Retrieves ids of all SecretKeyRings
|
|
*/
|
|
public static ArrayList<Long> getSecretKeyRingsMasterKeyIds(Context context) {
|
|
Uri queryUri = KeyRings.buildSecretKeyRingsUri();
|
|
return getKeyRingsMasterKeyIds(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Retrieves ids of all PublicKeyRings
|
|
*/
|
|
public static ArrayList<Long> getPublicKeyRingsMasterKeyIds(Context context) {
|
|
Uri queryUri = KeyRings.buildPublicKeyRingsUri();
|
|
return getKeyRingsMasterKeyIds(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Retrieves ids of all SecretKeyRings
|
|
*/
|
|
public static ArrayList<Long> getSecretKeyRingsRowIds(Context context) {
|
|
Uri queryUri = KeyRings.buildSecretKeyRingsUri();
|
|
return getKeyRingsRowIds(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Retrieves ids of all PublicKeyRings
|
|
*/
|
|
public static ArrayList<Long> getPublicKeyRingsRowIds(Context context) {
|
|
Uri queryUri = KeyRings.buildPublicKeyRingsUri();
|
|
return getKeyRingsRowIds(context, queryUri);
|
|
}
|
|
|
|
public static void deletePublicKeyRing(Context context, long rowId) {
|
|
ContentResolver cr = context.getContentResolver();
|
|
cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null);
|
|
}
|
|
|
|
public static void deleteSecretKeyRing(Context context, long rowId) {
|
|
ContentResolver cr = context.getContentResolver();
|
|
cr.delete(KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)), null, null);
|
|
}
|
|
|
|
public static void deleteUnifiedKeyRing(Context context, String masterKeyId, boolean isSecretKey) {
|
|
ContentResolver cr = context.getContentResolver();
|
|
cr.delete(KeyRings.buildPublicKeyRingsByMasterKeyIdUri(masterKeyId), null, null);
|
|
if (isSecretKey) {
|
|
cr.delete(KeyRings.buildSecretKeyRingsByMasterKeyIdUri(masterKeyId), null, null);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Get master key id of keyring by its row id
|
|
*/
|
|
public static long getPublicMasterKeyId(Context context, long keyRingRowId) {
|
|
Uri queryUri = KeyRings.buildPublicKeyRingsUri(String.valueOf(keyRingRowId));
|
|
return getMasterKeyId(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Get empty status of master key of keyring by its row id
|
|
*/
|
|
public static boolean getSecretMasterKeyCanCertify(Context context, long keyRingRowId) {
|
|
Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId));
|
|
return getMasterKeyCanCertify(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Private helper method to get master key private empty status of keyring by its row id
|
|
*/
|
|
|
|
public static boolean getMasterKeyCanCertify(Context context, Uri queryUri) {
|
|
String[] projection = new String[] {
|
|
KeyRings.MASTER_KEY_ID,
|
|
"(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
|
+ " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = "
|
|
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
|
+ " AND sign_keys." + Keys.CAN_CERTIFY + " = '1' AND " + Keys.IS_MASTER_KEY
|
|
+ " = 1) AS sign",
|
|
};
|
|
|
|
ContentResolver cr = context.getContentResolver();
|
|
Cursor cursor = cr.query(queryUri, projection, null, null, null);
|
|
|
|
long masterKeyId = -1;
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
int masterKeyIdCol = cursor.getColumnIndex("sign");
|
|
|
|
masterKeyId = cursor.getLong(masterKeyIdCol);
|
|
}
|
|
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
|
|
return (masterKeyId > 0);
|
|
}
|
|
|
|
public static boolean hasSecretKeyByMasterKeyId(Context context, long masterKeyId) {
|
|
Uri queryUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
|
|
// see if we can get our master key id back from the uri
|
|
return getMasterKeyId(context, queryUri) == masterKeyId;
|
|
}
|
|
|
|
/**
|
|
* Get master key id of keyring by its row id
|
|
*/
|
|
public static long getSecretMasterKeyId(Context context, long keyRingRowId) {
|
|
Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId));
|
|
return getMasterKeyId(context, queryUri);
|
|
}
|
|
|
|
/**
|
|
* Get master key id of key
|
|
*/
|
|
public static long getMasterKeyId(Context context, Uri queryUri) {
|
|
String[] projection = new String[]{KeyRings.MASTER_KEY_ID};
|
|
Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
|
|
|
|
long masterKeyId = 0;
|
|
try {
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
int masterKeyIdCol = cursor.getColumnIndexOrThrow(KeyRings.MASTER_KEY_ID);
|
|
|
|
masterKeyId = cursor.getLong(masterKeyIdCol);
|
|
}
|
|
} finally {
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
return masterKeyId;
|
|
}
|
|
|
|
public static long getRowId(Context context, Uri queryUri) {
|
|
String[] projection = new String[]{KeyRings._ID};
|
|
Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
|
|
|
|
long rowId = 0;
|
|
try {
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
int idCol = cursor.getColumnIndexOrThrow(KeyRings._ID);
|
|
|
|
rowId = cursor.getLong(idCol);
|
|
}
|
|
} finally {
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
return rowId;
|
|
}
|
|
|
|
/**
|
|
* Get fingerprint of key
|
|
*/
|
|
public static byte[] getFingerprint(Context context, Uri queryUri) {
|
|
String[] projection = new String[]{Keys.FINGERPRINT};
|
|
Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
|
|
|
|
byte[] fingerprint = null;
|
|
try {
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
int col = cursor.getColumnIndexOrThrow(Keys.FINGERPRINT);
|
|
|
|
fingerprint = cursor.getBlob(col);
|
|
}
|
|
} finally {
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
// FALLBACK: If fingerprint is not in database, get it from key blob!
|
|
// this could happen if the key was saved by a previous version of Keychain!
|
|
if (fingerprint == null) {
|
|
Log.d(Constants.TAG, "FALLBACK: fingerprint is not in database, get it from key blob!");
|
|
|
|
// get master key id
|
|
projection = new String[]{KeyRings.MASTER_KEY_ID};
|
|
cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
|
|
long masterKeyId = 0;
|
|
try {
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
int col = cursor.getColumnIndexOrThrow(KeyRings.MASTER_KEY_ID);
|
|
|
|
masterKeyId = cursor.getLong(col);
|
|
}
|
|
} finally {
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, masterKeyId);
|
|
// if it is no public key get it from your own keys...
|
|
if (key == null) {
|
|
PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, masterKeyId);
|
|
if (secretKey == null) {
|
|
Log.e(Constants.TAG, "Key could not be found!");
|
|
return null;
|
|
}
|
|
key = secretKey.getPublicKey();
|
|
}
|
|
|
|
fingerprint = key.getFingerprint();
|
|
}
|
|
|
|
return fingerprint;
|
|
}
|
|
|
|
public static String getUserId(Context context, Uri queryUri) {
|
|
String[] projection = new String[]{UserIds.USER_ID};
|
|
Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
|
|
|
|
String userId = null;
|
|
try {
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
int col = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
|
|
|
|
userId = cursor.getString(col);
|
|
}
|
|
} finally {
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
return userId;
|
|
}
|
|
|
|
public static ArrayList<String> getKeyRingsAsArmoredString(Context context, Uri uri,
|
|
long[] masterKeyIds) {
|
|
ArrayList<String> output = new ArrayList<String>();
|
|
|
|
if (masterKeyIds != null && masterKeyIds.length > 0) {
|
|
|
|
Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, uri, masterKeyIds);
|
|
|
|
if (cursor != null) {
|
|
int masterIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
|
|
int dataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA);
|
|
if (cursor.moveToFirst()) {
|
|
do {
|
|
Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol));
|
|
|
|
// get actual keyring data blob and write it to ByteArrayOutputStream
|
|
try {
|
|
Object keyRing = null;
|
|
byte[] data = cursor.getBlob(dataCol);
|
|
if (data != null) {
|
|
keyRing = PgpConversionHelper.BytesToPGPKeyRing(data);
|
|
}
|
|
|
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
ArmoredOutputStream aos = new ArmoredOutputStream(bos);
|
|
aos.setHeader("Version", PgpHelper.getFullVersion(context));
|
|
|
|
if (keyRing instanceof PGPSecretKeyRing) {
|
|
aos.write(((PGPSecretKeyRing) keyRing).getEncoded());
|
|
} else if (keyRing instanceof PGPPublicKeyRing) {
|
|
aos.write(((PGPPublicKeyRing) keyRing).getEncoded());
|
|
}
|
|
aos.close();
|
|
|
|
String armoredKey = bos.toString("UTF-8");
|
|
|
|
Log.d(Constants.TAG, "armoredKey:" + armoredKey);
|
|
|
|
output.add(armoredKey);
|
|
} catch (IOException e) {
|
|
Log.e(Constants.TAG, "IOException", e);
|
|
}
|
|
} while (cursor.moveToNext());
|
|
}
|
|
}
|
|
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
|
|
} else {
|
|
Log.e(Constants.TAG, "No master keys given!");
|
|
}
|
|
|
|
if (output.size() > 0) {
|
|
return output;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static byte[] getKeyRingsAsByteArray(Context context, Uri uri, long[] masterKeyIds) {
|
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
|
|
if (masterKeyIds != null && masterKeyIds.length > 0) {
|
|
|
|
Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, uri, masterKeyIds);
|
|
|
|
if (cursor != null) {
|
|
int masterIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID);
|
|
int dataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA);
|
|
if (cursor.moveToFirst()) {
|
|
do {
|
|
Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol));
|
|
|
|
// get actual keyring data blob and write it to ByteArrayOutputStream
|
|
try {
|
|
bos.write(cursor.getBlob(dataCol));
|
|
} catch (IOException e) {
|
|
Log.e(Constants.TAG, "IOException", e);
|
|
}
|
|
} while (cursor.moveToNext());
|
|
}
|
|
}
|
|
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
|
|
} else {
|
|
Log.e(Constants.TAG, "No master keys given!");
|
|
}
|
|
|
|
return bos.toByteArray();
|
|
}
|
|
|
|
private static Cursor getCursorWithSelectedKeyringMasterKeyIds(Context context, Uri baseUri,
|
|
long[] masterKeyIds) {
|
|
Cursor cursor = null;
|
|
if (masterKeyIds != null && masterKeyIds.length > 0) {
|
|
|
|
String inMasterKeyList = KeyRings.MASTER_KEY_ID + " IN (";
|
|
for (int i = 0; i < masterKeyIds.length; ++i) {
|
|
if (i != 0) {
|
|
inMasterKeyList += ", ";
|
|
}
|
|
inMasterKeyList += DatabaseUtils.sqlEscapeString("" + masterKeyIds[i]);
|
|
}
|
|
inMasterKeyList += ")";
|
|
|
|
cursor = context.getContentResolver().query(baseUri,
|
|
new String[]{KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.KEY_RING_DATA},
|
|
inMasterKeyList, null, null);
|
|
}
|
|
|
|
return cursor;
|
|
}
|
|
|
|
public static ArrayList<String> getRegisteredApiApps(Context context) {
|
|
Cursor cursor = context.getContentResolver().query(ApiApps.CONTENT_URI, null, null, null,
|
|
null);
|
|
|
|
ArrayList<String> packageNames = new ArrayList<String>();
|
|
if (cursor != null) {
|
|
int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
|
|
if (cursor.moveToFirst()) {
|
|
do {
|
|
packageNames.add(cursor.getString(packageNameCol));
|
|
} while (cursor.moveToNext());
|
|
}
|
|
}
|
|
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
|
|
return packageNames;
|
|
}
|
|
|
|
private static ContentValues contentValueForApiApps(AppSettings appSettings) {
|
|
ContentValues values = new ContentValues();
|
|
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
|
|
values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature());
|
|
return values;
|
|
}
|
|
|
|
private static ContentValues contentValueForApiAccounts(AccountSettings accSettings) {
|
|
ContentValues values = new ContentValues();
|
|
values.put(KeychainContract.ApiAccounts.ACCOUNT_NAME, accSettings.getAccountName());
|
|
values.put(KeychainContract.ApiAccounts.KEY_ID, accSettings.getKeyId());
|
|
values.put(KeychainContract.ApiAccounts.COMPRESSION, accSettings.getCompression());
|
|
values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM, accSettings.getEncryptionAlgorithm());
|
|
values.put(KeychainContract.ApiAccounts.HASH_ALORITHM, accSettings.getHashAlgorithm());
|
|
return values;
|
|
}
|
|
|
|
public static void insertApiApp(Context context, AppSettings appSettings) {
|
|
context.getContentResolver().insert(KeychainContract.ApiApps.CONTENT_URI,
|
|
contentValueForApiApps(appSettings));
|
|
}
|
|
|
|
public static void insertApiAccount(Context context, Uri uri, AccountSettings accSettings) {
|
|
context.getContentResolver().insert(uri, contentValueForApiAccounts(accSettings));
|
|
}
|
|
|
|
public static void updateApiApp(Context context, AppSettings appSettings, Uri uri) {
|
|
if (context.getContentResolver().update(uri, contentValueForApiApps(appSettings), null,
|
|
null) <= 0) {
|
|
throw new RuntimeException();
|
|
}
|
|
}
|
|
|
|
public static void updateApiAccount(Context context, AccountSettings accSettings, Uri uri) {
|
|
if (context.getContentResolver().update(uri, contentValueForApiAccounts(accSettings), null,
|
|
null) <= 0) {
|
|
throw new RuntimeException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Must be an uri pointing to an account
|
|
*
|
|
* @param context
|
|
* @param uri
|
|
* @return
|
|
*/
|
|
public static AppSettings getApiAppSettings(Context context, Uri uri) {
|
|
AppSettings settings = null;
|
|
|
|
Cursor cur = context.getContentResolver().query(uri, null, null, null, null);
|
|
if (cur != null && cur.moveToFirst()) {
|
|
settings = new AppSettings();
|
|
settings.setPackageName(cur.getString(
|
|
cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
|
|
settings.setPackageSignature(cur.getBlob(
|
|
cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE)));
|
|
}
|
|
|
|
return settings;
|
|
}
|
|
|
|
public static AccountSettings getApiAccountSettings(Context context, Uri accountUri) {
|
|
AccountSettings settings = null;
|
|
|
|
Cursor cur = context.getContentResolver().query(accountUri, null, null, null, null);
|
|
if (cur != null && cur.moveToFirst()) {
|
|
settings = new AccountSettings();
|
|
|
|
settings.setAccountName(cur.getString(
|
|
cur.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME)));
|
|
settings.setKeyId(cur.getLong(
|
|
cur.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID)));
|
|
settings.setCompression(cur.getInt(
|
|
cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.COMPRESSION)));
|
|
settings.setHashAlgorithm(cur.getInt(
|
|
cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.HASH_ALORITHM)));
|
|
settings.setEncryptionAlgorithm(cur.getInt(
|
|
cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM)));
|
|
}
|
|
|
|
return settings;
|
|
}
|
|
|
|
public static Set<Long> getAllKeyIdsForApp(Context context, Uri uri) {
|
|
Set<Long> keyIds = new HashSet<Long>();
|
|
|
|
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
|
|
if (cursor != null) {
|
|
int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID);
|
|
while (cursor.moveToNext()) {
|
|
keyIds.add(cursor.getLong(keyIdColumn));
|
|
}
|
|
}
|
|
|
|
return keyIds;
|
|
}
|
|
|
|
public static byte[] getApiAppSignature(Context context, String packageName) {
|
|
Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
|
|
|
|
String[] projection = new String[]{ApiApps.PACKAGE_SIGNATURE};
|
|
|
|
ContentResolver cr = context.getContentResolver();
|
|
Cursor cursor = cr.query(queryUri, projection, null, null, null);
|
|
|
|
byte[] signature = null;
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
int signatureCol = 0;
|
|
|
|
signature = cursor.getBlob(signatureCol);
|
|
}
|
|
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
|
|
return signature;
|
|
}
|
|
}
|