integrate download from keyserver into key import operation

This commit is contained in:
Vincent Breitmoser 2014-10-23 23:29:40 +02:00
parent b6c7231a7f
commit 214b60cd00
9 changed files with 165 additions and 126 deletions

View File

@ -21,33 +21,55 @@ package org.sufficientlysecure.keychain.keyimport;
import android.os.Parcel;
import android.os.Parcelable;
/** This is a trivial wrapper around keyring bytes which implements Parcelable. It exists
* for the sole purpose of keeping spongycastle and android imports in separate packages.
/** This class is a parcelable representation of either a keyring as raw data,
* or a (unique) reference to one as a fingerprint, keyid, or keybase name.
*/
public class ParcelableKeyRing implements Parcelable {
final byte[] mBytes;
final String mExpectedFingerprint;
public final byte[] mBytes;
// dual role!
public final String mExpectedFingerprint;
public final String mKeyIdHex;
public final String mKeybaseName;
public ParcelableKeyRing(byte[] bytes) {
mBytes = bytes;
mExpectedFingerprint = null;
mKeyIdHex = null;
mKeybaseName = null;
}
public ParcelableKeyRing(byte[] bytes, String expectedFingerprint) {
public ParcelableKeyRing(String expectedFingerprint, byte[] bytes) {
mBytes = bytes;
mExpectedFingerprint = expectedFingerprint;
mKeyIdHex = null;
mKeybaseName = null;
}
public ParcelableKeyRing(String expectedFingerprint, String keyIdHex, String keybaseName) {
mBytes = null;
mExpectedFingerprint = expectedFingerprint;
mKeyIdHex = keyIdHex;
mKeybaseName = keybaseName;
}
private ParcelableKeyRing(Parcel source) {
mBytes = source.createByteArray();
mExpectedFingerprint = source.readString();
mKeyIdHex = source.readString();
mKeybaseName = source.readString();
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeByteArray(mBytes);
dest.writeString(mExpectedFingerprint);
dest.writeString(mKeyIdHex);
dest.writeString(mKeybaseName);
}
public static final Creator<ParcelableKeyRing> CREATOR = new Creator<ParcelableKeyRing>() {
public ParcelableKeyRing createFromParcel(final Parcel source) {
byte[] bytes = source.createByteArray();
String expectedFingerprint = source.readString();
return new ParcelableKeyRing(bytes, expectedFingerprint);
return new ParcelableKeyRing(source);
}
public ParcelableKeyRing[] newArray(final int size) {
@ -59,11 +81,4 @@ public class ParcelableKeyRing implements Parcelable {
return 0;
}
public byte[] getBytes() {
return mBytes;
}
public String getExpectedFingerprint() {
return mExpectedFingerprint;
}
}

View File

@ -26,6 +26,8 @@ import org.spongycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
import org.sufficientlysecure.keychain.keyimport.Keyserver;
import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
@ -109,7 +111,7 @@ public class ImportExportOperation extends BaseOperation {
}
}
public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num) {
public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num, String keyServerUri) {
updateProgress(R.string.progress_importing, 0, 100);
OperationLog log = new OperationLog();
@ -129,6 +131,9 @@ public class ImportExportOperation extends BaseOperation {
int position = 0;
double progSteps = 100.0 / num;
KeybaseKeyserver keybaseServer = null;
HkpKeyserver keyServer = null;
// iterate over all entries
while (entries.hasNext()) {
ParcelableKeyRing entry = entries.next();
@ -140,13 +145,77 @@ public class ImportExportOperation extends BaseOperation {
}
try {
UncachedKeyRing key = UncachedKeyRing.decodeFromData(entry.getBytes());
String expectedFp = entry.getExpectedFingerprint();
if(expectedFp != null) {
if(!KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint()).equals(expectedFp)) {
UncachedKeyRing key = null;
// If there is already byte data, use that
if (entry.mBytes != null) {
key = UncachedKeyRing.decodeFromData(entry.mBytes);
}
// Otherwise, we need to fetch the data from a server first
else {
// If we have a keybase name, try to fetch from there
if (entry.mKeybaseName != null) {
// Make sure we have this cached
if (keybaseServer == null) {
keybaseServer = new KeybaseKeyserver();
}
try {
byte[] data = keyServer.get(entry.mKeybaseName).getBytes();
key = UncachedKeyRing.decodeFromData(data);
} catch (Keyserver.QueryFailedException e) {
// download failed, too bad. just proceed
}
}
// If we have a keyServerUri and a fingerprint or at least a keyId,
// download from HKP
if (keyServerUri != null
&& (entry.mKeyIdHex != null || entry.mExpectedFingerprint != null)) {
// Make sure we have the keyserver instance cached
if (keyServer == null) {
keyServer = new HkpKeyserver(keyServerUri);
}
try {
byte[] data;
// Download by fingerprint, or keyId - whichever is available
if (entry.mExpectedFingerprint != null) {
data = keyServer.get("0x" + entry.mExpectedFingerprint).getBytes();
} else {
data = keyServer.get(entry.mKeyIdHex).getBytes();
}
// If there already is a key (of keybase origin), merge the two
if (key != null) {
UncachedKeyRing merged = UncachedKeyRing.decodeFromData(data);
// TODO log pollution?
merged = key.merge(merged, log, 2);
// If the merge didn't fail, use the new merged key
if (merged != null) {
key = merged;
}
} else {
key = UncachedKeyRing.decodeFromData(data);
}
} catch (Keyserver.QueryFailedException e) {
break;
}
}
}
if (key == null) {
badKeys += 1;
continue;
}
// If we have an expected fingerprint, make sure it matches
if (entry.mExpectedFingerprint != null) {
if(!KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint()).equals(entry.mExpectedFingerprint)) {
Log.d(Constants.TAG, "fingerprint: " + KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint()));
Log.d(Constants.TAG, "expected fingerprint: " + expectedFp);
Log.d(Constants.TAG, "expected fingerprint: " + entry.mExpectedFingerprint);
Log.e(Constants.TAG, "Actual key fingerprint is not the same as expected!");
badKeys += 1;
continue;
@ -155,6 +224,12 @@ public class ImportExportOperation extends BaseOperation {
}
}
// Another check if we have been cancelled
if (checkCancelled()) {
cancelled = true;
break;
}
SaveKeyringResult result;
mProviderHelper.clearLog();
if (key.isSecret()) {

View File

@ -949,8 +949,8 @@ public class ProviderHelper {
if (cursor.isAfterLast()) {
return false;
}
ring = new ParcelableKeyRing(cursor.getBlob(0),
KeyFormattingUtils.convertFingerprintToHex(cursor.getBlob(1)));
ring = new ParcelableKeyRing(KeyFormattingUtils.convertFingerprintToHex(cursor.getBlob(1)), cursor.getBlob(0)
);
cursor.moveToNext();
return true;
}
@ -1009,8 +1009,8 @@ public class ProviderHelper {
if (cursor.isAfterLast()) {
return false;
}
ring = new ParcelableKeyRing(cursor.getBlob(0),
KeyFormattingUtils.convertFingerprintToHex(cursor.getBlob(1)));
ring = new ParcelableKeyRing(KeyFormattingUtils.convertFingerprintToHex(cursor.getBlob(1)), cursor.getBlob(0)
);
cursor.moveToNext();
return true;
}
@ -1097,7 +1097,7 @@ public class ProviderHelper {
ImportKeyResult result = new ImportExportOperation(mContext, this,
new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport))
.importKeyRings(itSecrets, numSecrets);
.importKeyRings(itSecrets, numSecrets, null);
log.add(result, indent);
} else {
log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent);
@ -1124,7 +1124,7 @@ public class ProviderHelper {
ImportKeyResult result = new ImportExportOperation(mContext, this,
new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport))
.importKeyRings(itPublics, numPublics);
.importKeyRings(itPublics, numPublics, null);
log.add(result, indent);
} else {
log.add(LogType.MSG_CON_REIMPORT_PUBLIC_SKIP, indent);

View File

@ -38,8 +38,6 @@ import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
import org.sufficientlysecure.keychain.keyimport.Keyserver;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
@ -99,15 +97,10 @@ public class KeychainIntentService extends IntentService implements Progressable
public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING";
public static final String ACTION_DELETE_FILE_SECURELY = Constants.INTENT_PREFIX
+ "DELETE_FILE_SECURELY";
public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING";
public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING";
public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
public static final String ACTION_DOWNLOAD_AND_IMPORT_KEYS = Constants.INTENT_PREFIX + "QUERY_KEYRING";
public static final String ACTION_IMPORT_KEYBASE_KEYS = Constants.INTENT_PREFIX + "DOWNLOAD_KEYBASE";
public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
@ -153,16 +146,13 @@ public class KeychainIntentService extends IntentService implements Progressable
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
// delete file securely
public static final String DELETE_FILE = "deleteFile";
// delete keyring(s)
public static final String DELETE_KEY_LIST = "delete_list";
public static final String DELETE_IS_SECRET = "delete_is_secret";
// import key
public static final String IMPORT_KEY_LIST = "import_key_list";
public static final String IMPORT_KEY_FILE = "import_key_file";
public static final String IMPORT_KEY_SERVER = "import_key_server";
// export key
public static final String EXPORT_OUTPUT_STREAM = "export_output_stream";
@ -175,10 +165,6 @@ public class KeychainIntentService extends IntentService implements Progressable
// upload key
public static final String UPLOAD_KEY_SERVER = "upload_key_server";
// query key
public static final String DOWNLOAD_KEY_SERVER = "query_key_server";
public static final String DOWNLOAD_KEY_LIST = "query_key_id";
// certify key
public static final String CERTIFY_PARCEL = "certify_parcel";
@ -358,65 +344,6 @@ public class KeychainIntentService extends IntentService implements Progressable
// Result
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
} else if (ACTION_DOWNLOAD_AND_IMPORT_KEYS.equals(action) || ACTION_IMPORT_KEYBASE_KEYS.equals(action)) {
ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST);
// this downloads the keys and places them into the ImportKeysListEntry entries
String keyServer = data.getString(DOWNLOAD_KEY_SERVER);
ArrayList<ParcelableKeyRing> keyRings = new ArrayList<ParcelableKeyRing>(entries.size());
for (ImportKeysListEntry entry : entries) {
try {
Keyserver server;
ArrayList<String> origins = entry.getOrigins();
if (origins == null) {
origins = new ArrayList<String>();
}
if (origins.isEmpty()) {
origins.add(keyServer);
}
for (String origin : origins) {
if (KeybaseKeyserver.ORIGIN.equals(origin)) {
server = new KeybaseKeyserver();
} else {
server = new HkpKeyserver(origin);
}
Log.d(Constants.TAG, "IMPORTING " + entry.getKeyIdHex() + " FROM: " + server);
// if available use complete fingerprint for get request
byte[] downloadedKeyBytes;
if (KeybaseKeyserver.ORIGIN.equals(origin)) {
downloadedKeyBytes = server.get(entry.getExtraData()).getBytes();
} else if (entry.getFingerprintHex() != null) {
downloadedKeyBytes = server.get("0x" + entry.getFingerprintHex()).getBytes();
} else {
downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes();
}
// save key bytes in entry object for doing the
// actual import afterwards
keyRings.add(new ParcelableKeyRing(downloadedKeyBytes, entry.getFingerprintHex()));
}
} catch (Keyserver.QueryFailedException e) {
sendErrorToHandler(e);
}
}
Intent importIntent = new Intent(this, KeychainIntentService.class);
importIntent.setAction(ACTION_IMPORT_KEYRING);
Bundle importData = new Bundle();
// This is not going through binder, nothing to fear of
importData.putParcelableArrayList(IMPORT_KEY_LIST, keyRings);
importIntent.putExtra(EXTRA_DATA, importData);
importIntent.putExtra(EXTRA_MESSENGER, mMessenger);
// now import it with this service
onHandleIntent(importIntent);
// result is handled in ACTION_IMPORT_KEYRING
} else if (ACTION_EDIT_KEYRING.equals(action)) {
try {
@ -519,6 +446,8 @@ public class KeychainIntentService extends IntentService implements Progressable
try {
// Input
String keyServer = data.getString(IMPORT_KEY_SERVER);
Iterator<ParcelableKeyRing> entries;
int numEntries;
if (data.containsKey(IMPORT_KEY_LIST)) {
@ -535,20 +464,22 @@ public class KeychainIntentService extends IntentService implements Progressable
numEntries = it.getSize();
}
// Operation
ImportExportOperation importExportOperation = new ImportExportOperation(
this, providerHelper, this, mActionCanceled);
ImportKeyResult result = importExportOperation.importKeyRings(entries, numEntries);
ImportKeyResult result = importExportOperation.importKeyRings(entries, numEntries, keyServer);
// we do this even on failure or cancellation!
// Special: consolidate on secret key import (cannot be cancelled!)
if (result.mSecret > 0) {
// cannot cancel from here on out!
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_PREVENT_CANCEL);
providerHelper.consolidateDatabaseStep1(this);
}
// make sure new data is synced into contacts
// Special: make sure new data is synced into contacts
ContactSyncAdapterService.requestSync();
// Result
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
} catch (Exception e) {
sendErrorToHandler(e);

View File

@ -501,16 +501,25 @@ public class ImportKeysActivity extends ActionBarActivity {
// Send all information needed to service to query keys in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_DOWNLOAD_AND_IMPORT_KEYS);
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
// fill values for this action
Bundle data = new Bundle();
data.putString(KeychainIntentService.DOWNLOAD_KEY_SERVER, sls.mCloudPrefs.keyserver);
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, sls.mCloudPrefs.keyserver);
// get selected key entries
ArrayList<ImportKeysListEntry> selectedEntries = mListFragment.getSelectedEntries();
data.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST, selectedEntries);
ArrayList<ParcelableKeyRing> keys = new ArrayList<ParcelableKeyRing>();
{
// change the format into ParcelableKeyRing
ArrayList<ImportKeysListEntry> entries = mListFragment.getSelectedEntries();
for (ImportKeysListEntry entry : entries) {
keys.add(new ParcelableKeyRing(
entry.getFingerprintHex(), entry.getKeyIdHex(), entry.getExtraData())
);
}
}
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keys);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);

View File

@ -32,7 +32,7 @@ import com.google.zxing.integration.android.IntentResult;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
@ -301,20 +301,18 @@ public class KeyListActivity extends DrawerActivity {
// Send all information needed to service to query keys in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_DOWNLOAD_AND_IMPORT_KEYS);
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
// fill values for this action
Bundle data = new Bundle();
data.putString(KeychainIntentService.DOWNLOAD_KEY_SERVER, cloudPrefs.keyserver);
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
final ImportKeysListEntry keyEntry = new ImportKeysListEntry();
keyEntry.setFingerprintHex(fingerprint);
keyEntry.addOrigin(cloudPrefs.keyserver);
ArrayList<ImportKeysListEntry> selectedEntries = new ArrayList<ImportKeysListEntry>();
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<ParcelableKeyRing>();
selectedEntries.add(keyEntry);
data.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST, selectedEntries);
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);

View File

@ -163,7 +163,7 @@ public class KeyListFragment extends LoaderFragment
}
}
};
new KeyUpdateHelper().updateAllKeys(getActivity(), finishedHandler);
// new KeyUpdateHelper().updateAllKeys(getActivity(), finishedHandler);
updateActionbarForSwipe(false);
}
});

View File

@ -25,6 +25,7 @@ import android.os.Messenger;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.Keyserver;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import java.util.ArrayList;
@ -40,14 +41,21 @@ public class EmailKeyHelper {
}
public static void importAll(Context context, Messenger messenger, List<String> mails) {
Set<ImportKeysListEntry> keys = new HashSet<ImportKeysListEntry>();
// Collect all candidates as ImportKeysListEntry (set for deduplication)
Set<ImportKeysListEntry> entries = new HashSet<ImportKeysListEntry>();
for (String mail : mails) {
keys.addAll(getEmailKeys(context, mail));
entries.addAll(getEmailKeys(context, mail));
}
importKeys(context, messenger, new ArrayList<ImportKeysListEntry>(keys));
// Put them in a list and import
ArrayList<ParcelableKeyRing> keys = new ArrayList<ParcelableKeyRing>(entries.size());
for (ImportKeysListEntry entry : entries) {
keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(), null));
}
importKeys(context, messenger, keys);
}
public static List<ImportKeysListEntry> getEmailKeys(Context context, String mail) {
public static Set<ImportKeysListEntry> getEmailKeys(Context context, String mail) {
Set<ImportKeysListEntry> keys = new HashSet<ImportKeysListEntry>();
// Try _hkp._tcp SRV record first
@ -67,15 +75,14 @@ public class EmailKeyHelper {
keys.addAll(getEmailKeys(mail, hkp));
}
}
return new ArrayList<ImportKeysListEntry>(keys);
return keys;
}
private static void importKeys(Context context, Messenger messenger, List<ImportKeysListEntry> keys) {
private static void importKeys(Context context, Messenger messenger, ArrayList<ParcelableKeyRing> keys) {
Intent importIntent = new Intent(context, KeychainIntentService.class);
importIntent.setAction(KeychainIntentService.ACTION_DOWNLOAD_AND_IMPORT_KEYS);
importIntent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
Bundle importData = new Bundle();
importData.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST,
new ArrayList<ImportKeysListEntry>(keys));
importData.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keys);
importIntent.putExtra(KeychainIntentService.EXTRA_DATA, importData);
importIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);

View File

@ -34,6 +34,8 @@ import java.util.List;
public class KeyUpdateHelper {
/*
public void updateAllKeys(Context context, KeychainIntentServiceHandler finishedHandler) {
UpdateTask updateTask = new UpdateTask(context, finishedHandler);
updateTask.execute();
@ -79,4 +81,6 @@ public class KeyUpdateHelper {
return null;
}
}
*/
}