Merge pull request #627 from thi/improve-keybase.io-support

Improve keybase.io support
This commit is contained in:
Dominik Schürmann 2014-05-14 20:19:44 +02:00
commit bbf19248ef
6 changed files with 91 additions and 46 deletions

View File

@ -247,7 +247,7 @@ public class HkpKeyServer extends KeyServer {
// see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr // see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr
String fingerprintOrKeyId = matcher.group(1); String fingerprintOrKeyId = matcher.group(1);
if (fingerprintOrKeyId.length() > 16) { if (fingerprintOrKeyId.length() > 16) {
entry.setFingerPrintHex(fingerprintOrKeyId.toLowerCase(Locale.US)); entry.setFingerprintHex(fingerprintOrKeyId.toLowerCase(Locale.US));
entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length() entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length()
- 16, fingerprintOrKeyId.length())); - 16, fingerprintOrKeyId.length()));
} else { } else {

View File

@ -45,11 +45,12 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
public String keyIdHex; public String keyIdHex;
public boolean revoked; public boolean revoked;
public Date date; // TODO: not displayed public Date date; // TODO: not displayed
public String fingerPrintHex; public String fingerprintHex;
public int bitStrength; public int bitStrength;
public String algorithm; public String algorithm;
public boolean secretKey; public boolean secretKey;
public String mPrimaryUserId; public String mPrimaryUserId;
private String mExtraData;
private boolean mSelected; private boolean mSelected;
@ -66,7 +67,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
dest.writeLong(keyId); dest.writeLong(keyId);
dest.writeByte((byte) (revoked ? 1 : 0)); dest.writeByte((byte) (revoked ? 1 : 0));
dest.writeSerializable(date); dest.writeSerializable(date);
dest.writeString(fingerPrintHex); dest.writeString(fingerprintHex);
dest.writeString(keyIdHex); dest.writeString(keyIdHex);
dest.writeInt(bitStrength); dest.writeInt(bitStrength);
dest.writeString(algorithm); dest.writeString(algorithm);
@ -74,6 +75,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
dest.writeByte((byte) (mSelected ? 1 : 0)); dest.writeByte((byte) (mSelected ? 1 : 0));
dest.writeInt(mBytes.length); dest.writeInt(mBytes.length);
dest.writeByteArray(mBytes); dest.writeByteArray(mBytes);
dest.writeString(mExtraData);
} }
public static final Creator<ImportKeysListEntry> CREATOR = new Creator<ImportKeysListEntry>() { public static final Creator<ImportKeysListEntry> CREATOR = new Creator<ImportKeysListEntry>() {
@ -85,7 +87,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
vr.keyId = source.readLong(); vr.keyId = source.readLong();
vr.revoked = source.readByte() == 1; vr.revoked = source.readByte() == 1;
vr.date = (Date) source.readSerializable(); vr.date = (Date) source.readSerializable();
vr.fingerPrintHex = source.readString(); vr.fingerprintHex = source.readString();
vr.keyIdHex = source.readString(); vr.keyIdHex = source.readString();
vr.bitStrength = source.readInt(); vr.bitStrength = source.readInt();
vr.algorithm = source.readString(); vr.algorithm = source.readString();
@ -93,6 +95,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
vr.mSelected = source.readByte() == 1; vr.mSelected = source.readByte() == 1;
vr.mBytes = new byte[source.readInt()]; vr.mBytes = new byte[source.readInt()];
source.readByteArray(vr.mBytes); source.readByteArray(vr.mBytes);
vr.mExtraData = source.readString();
return vr; return vr;
} }
@ -150,12 +153,12 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
this.date = date; this.date = date;
} }
public String getFingerPrintHex() { public String getFingerprintHex() {
return fingerPrintHex; return fingerprintHex;
} }
public void setFingerPrintHex(String fingerPrintHex) { public void setFingerprintHex(String fingerprintHex) {
this.fingerPrintHex = fingerPrintHex; this.fingerprintHex = fingerprintHex;
} }
public int getBitStrength() { public int getBitStrength() {
@ -198,6 +201,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mPrimaryUserId = uid; mPrimaryUserId = uid;
} }
public String getExtraData() {
return mExtraData;
}
public void setExtraData(String extraData) {
mExtraData = extraData;
}
/** /**
* Constructor for later querying from keyserver * Constructor for later querying from keyserver
*/ */
@ -260,7 +271,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId); this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId);
this.revoked = key.isRevoked(); this.revoked = key.isRevoked();
this.fingerPrintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()); this.fingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint());
this.bitStrength = key.getBitStrength(); this.bitStrength = key.getBitStrength();
final int algorithm = key.getAlgorithm(); final int algorithm = key.getAlgorithm();
this.algorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm); this.algorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm);

View File

@ -41,6 +41,11 @@ public class KeybaseKeyServer extends KeyServer {
InsufficientQuery { InsufficientQuery {
ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>(); ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>();
if (query.startsWith("0x")) {
// cut off "0x" if a user is searching for a key id
query = query.substring(2);
}
JSONObject fromQuery = getFromKeybase("_/api/1.0/user/autocomplete.json?q=", query); JSONObject fromQuery = getFromKeybase("_/api/1.0/user/autocomplete.json?q=", query);
try { try {
@ -50,23 +55,32 @@ public class KeybaseKeyServer extends KeyServer {
// only list them if they have a key // only list them if they have a key
if (JWalk.optObject(match, "components", "key_fingerprint") != null) { if (JWalk.optObject(match, "components", "key_fingerprint") != null) {
results.add(makeEntry(match)); String keybaseId = JWalk.getString(match, "components", "username", "val");
String fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val");
fingerprint = fingerprint.replace(" ", "").toUpperCase();
if (keybaseId.equals(query) || fingerprint.startsWith(query.toUpperCase())) {
results.add(makeEntry(match));
} else {
results.add(makeEntry(match));
}
} }
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(Constants.TAG, "keybase result parsing error", e);
throw new QueryException("Unexpected structure in keybase search result: " + e.getMessage()); throw new QueryException("Unexpected structure in keybase search result: " + e.getMessage());
} }
return results; return results;
} }
private JSONObject getUser(String keybaseID) throws QueryException { private JSONObject getUser(String keybaseId) throws QueryException {
try { try {
return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseID); return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseId);
} catch (Exception e) { } catch (Exception e) {
String detail = ""; String detail = "";
if (keybaseID != null) { if (keybaseId != null) {
detail = ". Query was for user '" + keybaseID + "'"; detail = ". Query was for user '" + keybaseId + "'";
} }
throw new QueryException(e.getMessage() + detail); throw new QueryException(e.getMessage() + detail);
} }
@ -74,35 +88,50 @@ public class KeybaseKeyServer extends KeyServer {
private ImportKeysListEntry makeEntry(JSONObject match) throws QueryException, JSONException { private ImportKeysListEntry makeEntry(JSONObject match) throws QueryException, JSONException {
String keybaseID = JWalk.getString(match, "components", "username", "val");
String key_fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val");
key_fingerprint = key_fingerprint.replace(" ", "").toUpperCase();
match = getUser(keybaseID);
final ImportKeysListEntry entry = new ImportKeysListEntry(); final ImportKeysListEntry entry = new ImportKeysListEntry();
String keybaseId = JWalk.getString(match, "components", "username", "val");
String fullName = JWalk.getString(match, "components", "full_name", "val");
String fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val");
fingerprint = fingerprint.replace(" ", "").toUpperCase();
// in anticipation of a full fingerprint, only use the last 16 chars as 64-bit key id
entry.setKeyIdHex("0x" + fingerprint.substring(Math.max(0, fingerprint.length() - 16)));
// store extra info, so we can query for the keybase id directly
entry.setExtraData(keybaseId);
// TODO: Fix; have suggested keybase provide this value to avoid search-time crypto calls // TODO: Fix; have suggested keybase provide this value to avoid search-time crypto calls
entry.setBitStrength(4096); //entry.setBitStrength(4096);
entry.setAlgorithm("RSA"); //entry.setAlgorithm("RSA");
entry.setKeyIdHex("0x" + key_fingerprint);
entry.setRevoked(false);
// ctime entry.setFingerprintHex(fingerprint);
final long creationDate = JWalk.getLong(match, "them", "public_keys", "primary", "ctime");
final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
tmpGreg.setTimeInMillis(creationDate * 1000);
entry.setDate(tmpGreg.getTime());
// key bits // key data
// we have to fetch the user object to construct the search-result list, so we might as // currently there's no need to query the user right away, and it should be avoided, so the
// well (weakly) remember the key, in case they try to import it // user doesn't experience lag and doesn't download many keys unnecessarily, but should we
mKeyCache.put(keybaseID, JWalk.getString(match,"them", "public_keys", "primary", "bundle")); // require to do it at soe point:
// (weakly) remember the key, in case the user tries to import it
//mKeyCache.put(keybaseId, JWalk.getString(match, "them", "public_keys", "primary", "bundle"));
// String displayName = JWalk.getString(match, "them", "profile", "full_name");
ArrayList<String> userIds = new ArrayList<String>(); ArrayList<String> userIds = new ArrayList<String>();
String name = "keybase.io/" + keybaseID + " <" + keybaseID + "@keybase.io>"; String name = fullName + " <keybase.io/" + keybaseId + ">";
userIds.add(name); userIds.add(name);
userIds.add(keybaseID); try {
userIds.add("github.com/" + JWalk.getString(match, "components", "github", "val"));
} catch (JSONException e) {
// ignore
}
try {
userIds.add("twitter.com/" + JWalk.getString(match, "components", "twitter", "val"));
} catch (JSONException e) {
// ignore
}
try {
JSONArray array = JWalk.getArray(match, "components", "websites");
JSONObject website = array.getJSONObject(0);
userIds.add(JWalk.getString(website, "val"));
} catch (JSONException e) {
// ignore
}
entry.setUserIds(userIds); entry.setUserIds(userIds);
entry.setPrimaryUserId(name); entry.setPrimaryUserId(name);
return entry; return entry;

View File

@ -749,8 +749,8 @@ public class KeychainIntentService extends IntentService
KeybaseKeyServer server = new KeybaseKeyServer(); KeybaseKeyServer server = new KeybaseKeyServer();
for (ImportKeysListEntry entry : entries) { for (ImportKeysListEntry entry : entries) {
// the keybase handle is in userId(1) // the keybase handle is in userId(1)
String keybaseID = entry.getUserIds().get(1); String keybaseId = entry.getExtraData();
byte[] downloadedKeyBytes = server.get(keybaseID).getBytes(); byte[] downloadedKeyBytes = server.get(keybaseId).getBytes();
// create PGPKeyRing object based on downloaded armored key // create PGPKeyRing object based on downloaded armored key
PGPKeyRing downloadedKey = null; PGPKeyRing downloadedKey = null;
@ -802,8 +802,8 @@ public class KeychainIntentService extends IntentService
for (ImportKeysListEntry entry : entries) { for (ImportKeysListEntry entry : entries) {
// if available use complete fingerprint for get request // if available use complete fingerprint for get request
byte[] downloadedKeyBytes; byte[] downloadedKeyBytes;
if (entry.getFingerPrintHex() != null) { if (entry.getFingerprintHex() != null) {
downloadedKeyBytes = server.get("0x" + entry.getFingerPrintHex()).getBytes(); downloadedKeyBytes = server.get("0x" + entry.getFingerprintHex()).getBytes();
} else { } else {
downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes(); downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes();
} }
@ -829,10 +829,10 @@ public class KeychainIntentService extends IntentService
} }
// verify downloaded key by comparing fingerprints // verify downloaded key by comparing fingerprints
if (entry.getFingerPrintHex() != null) { if (entry.getFingerprintHex() != null) {
String downloadedKeyFp = PgpKeyHelper.convertFingerprintToHex( String downloadedKeyFp = PgpKeyHelper.convertFingerprintToHex(
downloadedKey.getPublicKey().getFingerprint()); downloadedKey.getPublicKey().getFingerprint());
if (downloadedKeyFp.equals(entry.getFingerPrintHex())) { if (downloadedKeyFp.equals(entry.getFingerprintHex())) {
Log.d(Constants.TAG, "fingerprint of downloaded key is the same as " + Log.d(Constants.TAG, "fingerprint of downloaded key is the same as " +
"the requested fingerprint!"); "the requested fingerprint!");
} else { } else {

View File

@ -146,14 +146,19 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
holder.keyId.setText(entry.keyIdHex); holder.keyId.setText(entry.keyIdHex);
if (entry.fingerPrintHex != null) { if (entry.fingerprintHex != null) {
holder.fingerprint.setVisibility(View.VISIBLE); holder.fingerprint.setVisibility(View.VISIBLE);
holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerPrintHex)); holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerprintHex));
} else { } else {
holder.fingerprint.setVisibility(View.GONE); holder.fingerprint.setVisibility(View.GONE);
} }
holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm); if (entry.bitStrength != 0 && entry.algorithm != null) {
holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm);
holder.algorithm.setVisibility(View.VISIBLE);
} else {
holder.algorithm.setVisibility(View.INVISIBLE);
}
if (entry.revoked) { if (entry.revoked) {
holder.status.setVisibility(View.VISIBLE); holder.status.setVisibility(View.VISIBLE);

View File

@ -108,7 +108,7 @@ public class ImportKeysListServerLoader
* set fingerprint explicitly after query * set fingerprint explicitly after query
* to enforce a check when the key is imported by KeychainIntentService * to enforce a check when the key is imported by KeychainIntentService
*/ */
uniqueEntry.setFingerPrintHex(fingerprint); uniqueEntry.setFingerprintHex(fingerprint);
uniqueEntry.setSelected(true); uniqueEntry.setSelected(true);
mEntryList.add(uniqueEntry); mEntryList.add(uniqueEntry);
} }