From f4a8e71c338c03642514aba2026156d541d3eab8 Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Wed, 14 May 2014 14:07:44 +0200 Subject: Make keybase lookup works for key ids starting with 0x --- .../sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java index 7ffe123c0..442cecef2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java @@ -41,6 +41,11 @@ public class KeybaseKeyServer extends KeyServer { InsufficientQuery { ArrayList results = new ArrayList(); + 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); try { @@ -158,4 +163,4 @@ public class KeybaseKeyServer extends KeyServer { public void add(String armoredKey) throws AddKeyException { throw new AddKeyException(); } -} \ No newline at end of file +} -- cgit v1.2.3 From d50e798c13a6429f3a90341ee239f60cac832f1e Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Wed, 14 May 2014 14:08:33 +0200 Subject: List full keybase id or strong key id matches on top --- .../keychain/keyimport/KeybaseKeyServer.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java index 442cecef2..6fd4c3975 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java @@ -55,10 +55,19 @@ public class KeybaseKeyServer extends KeyServer { // only list them if they have a key 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) { + Log.e(Constants.TAG, "keybase result parsing error", e); throw new QueryException("Unexpected structure in keybase search result: " + e.getMessage()); } -- cgit v1.2.3 From 61622471557dc1f67a1465ca8fcace7604a8c1a7 Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Wed, 14 May 2014 14:13:00 +0200 Subject: Add extraData property in ImportKeyListEntry This allows any KeyServer to store some extra data it might need. In the case of KeybaseKeyServer it is the username, which then can be grabbed directly, without the hack of storing it as userId. --- .../keychain/keyimport/ImportKeysListEntry.java | 11 +++++++++++ .../keychain/keyimport/KeybaseKeyServer.java | 13 +++++++------ .../keychain/service/KeychainIntentService.java | 4 ++-- 3 files changed, 20 insertions(+), 8 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index 1199290e0..60416c3d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -50,6 +50,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { public String algorithm; public boolean secretKey; public String mPrimaryUserId; + private String mExtraData; private boolean mSelected; @@ -74,6 +75,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { dest.writeByte((byte) (mSelected ? 1 : 0)); dest.writeInt(mBytes.length); dest.writeByteArray(mBytes); + dest.writeString(mExtraData); } public static final Creator CREATOR = new Creator() { @@ -93,6 +95,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { vr.mSelected = source.readByte() == 1; vr.mBytes = new byte[source.readInt()]; source.readByteArray(vr.mBytes); + vr.mExtraData = source.readString(); return vr; } @@ -198,6 +201,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mPrimaryUserId = uid; } + public String getExtraData() { + return mExtraData; + } + + public void setExtraData(String extraData) { + mExtraData = extraData; + } + /** * Constructor for later querying from keyserver */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java index 6fd4c3975..d1755fa12 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java @@ -74,13 +74,13 @@ public class KeybaseKeyServer extends KeyServer { return results; } - private JSONObject getUser(String keybaseID) throws QueryException { + private JSONObject getUser(String keybaseId) throws QueryException { try { - return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseID); + return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseId); } catch (Exception e) { String detail = ""; - if (keybaseID != null) { - detail = ". Query was for user '" + keybaseID + "'"; + if (keybaseId != null) { + detail = ". Query was for user '" + keybaseId + "'"; } throw new QueryException(e.getMessage() + detail); } @@ -88,12 +88,14 @@ public class KeybaseKeyServer extends KeyServer { 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(); + String keybaseId = JWalk.getString(match, "components", "username", "val"); + // 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 entry.setBitStrength(4096); @@ -116,7 +118,6 @@ public class KeybaseKeyServer extends KeyServer { ArrayList userIds = new ArrayList(); String name = "keybase.io/" + keybaseID + " <" + keybaseID + "@keybase.io>"; userIds.add(name); - userIds.add(keybaseID); entry.setUserIds(userIds); entry.setPrimaryUserId(name); return entry; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index f1e30c560..ca4f5869b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -749,8 +749,8 @@ public class KeychainIntentService extends IntentService KeybaseKeyServer server = new KeybaseKeyServer(); for (ImportKeysListEntry entry : entries) { // the keybase handle is in userId(1) - String keybaseID = entry.getUserIds().get(1); - byte[] downloadedKeyBytes = server.get(keybaseID).getBytes(); + String keybaseId = entry.getExtraData(); + byte[] downloadedKeyBytes = server.get(keybaseId).getBytes(); // create PGPKeyRing object based on downloaded armored key PGPKeyRing downloadedKey = null; -- cgit v1.2.3 From 056a6dd3479fe1e5cc5981ac51a458b015d3e9cb Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Wed, 14 May 2014 14:14:02 +0200 Subject: Don't guess keybase key algorithm and size Once keybase reports them, we can display them. Until then we simply don't know and shouldn't lie about it. --- .../sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java | 6 ++---- .../sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java | 7 ++++++- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java index d1755fa12..da47c32c6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java @@ -98,10 +98,8 @@ public class KeybaseKeyServer extends KeyServer { entry.setExtraData(keybaseId); // TODO: Fix; have suggested keybase provide this value to avoid search-time crypto calls - entry.setBitStrength(4096); - entry.setAlgorithm("RSA"); - entry.setKeyIdHex("0x" + key_fingerprint); - entry.setRevoked(false); + //entry.setBitStrength(4096); + //entry.setAlgorithm("RSA"); // ctime final long creationDate = JWalk.getLong(match, "them", "public_keys", "primary", "ctime"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 9d323c822..70b9f3f7e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -153,7 +153,12 @@ public class ImportKeysAdapter extends ArrayAdapter { 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) { holder.status.setVisibility(View.VISIBLE); -- cgit v1.2.3 From 2480844884ecffcd0d407a9d967b062ac8950462 Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Wed, 14 May 2014 14:17:20 +0200 Subject: Cleanup keybase query a bit Remove ctime and the cached key, making the query for the entire user object unnecessary. This should only be done when the user decides to import the key. Hopefully keybase.io can provide all info necessary in the search results. --- .../keychain/keyimport/KeybaseKeyServer.java | 29 +++++++++++----------- 1 file changed, 14 insertions(+), 15 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java index da47c32c6..57355d808 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java @@ -88,12 +88,14 @@ public class KeybaseKeyServer extends KeyServer { private ImportKeysListEntry makeEntry(JSONObject match) throws QueryException, JSONException { - String key_fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val"); - key_fingerprint = key_fingerprint.replace(" ", "").toUpperCase(); - match = getUser(keybaseID); - 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); @@ -101,20 +103,17 @@ public class KeybaseKeyServer extends KeyServer { //entry.setBitStrength(4096); //entry.setAlgorithm("RSA"); - // ctime - 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()); + entry.setFingerprintHex(fingerprint); - // key bits - // we have to fetch the user object to construct the search-result list, so we might as - // well (weakly) remember the key, in case they try to import it - mKeyCache.put(keybaseID, JWalk.getString(match,"them", "public_keys", "primary", "bundle")); + // key data + // currently there's no need to query the user right away, and it should be avoided, so the + // user doesn't experience lag and doesn't download many keys unnecessarily, but should we + // 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 userIds = new ArrayList(); - String name = "keybase.io/" + keybaseID + " <" + keybaseID + "@keybase.io>"; + String name = fullName + " "; userIds.add(name); entry.setUserIds(userIds); entry.setPrimaryUserId(name); -- cgit v1.2.3 From 73acb69208ea3cae0a98786a2352e0dd58c2a445 Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Wed, 14 May 2014 14:19:07 +0200 Subject: Add github, twitter, website info for keybase keys The very point of keybase.io is the identification and proven link to those identities, so it should be nice to have them. The display is a bit basic still, as it just adds them as userIds, I'm sure this can be prettified, perhaps with logos? --- .../keychain/keyimport/KeybaseKeyServer.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java index 57355d808..953cf3cbb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java @@ -115,6 +115,23 @@ public class KeybaseKeyServer extends KeyServer { ArrayList userIds = new ArrayList(); String name = fullName + " "; userIds.add(name); + 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.setPrimaryUserId(name); return entry; -- cgit v1.2.3 From b8462de6e53a0e6a54e124ddc5da68abcae4d8bf Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Wed, 14 May 2014 17:18:46 +0200 Subject: Rename fingerPrint to fingerprint --- .../keychain/keyimport/HkpKeyServer.java | 2 +- .../keychain/keyimport/ImportKeysListEntry.java | 16 ++++++++-------- .../keychain/service/KeychainIntentService.java | 8 ++++---- .../keychain/ui/adapter/ImportKeysAdapter.java | 4 ++-- .../keychain/ui/adapter/ImportKeysListServerLoader.java | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java index 85ce6bfcc..8d1e1f460 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java @@ -247,7 +247,7 @@ public class HkpKeyServer extends KeyServer { // see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr String fingerprintOrKeyId = matcher.group(1); if (fingerprintOrKeyId.length() > 16) { - entry.setFingerPrintHex(fingerprintOrKeyId.toLowerCase(Locale.US)); + entry.setFingerprintHex(fingerprintOrKeyId.toLowerCase(Locale.US)); entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length() - 16, fingerprintOrKeyId.length())); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index 60416c3d0..4a8d58e56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -45,7 +45,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { public String keyIdHex; public boolean revoked; public Date date; // TODO: not displayed - public String fingerPrintHex; + public String fingerprintHex; public int bitStrength; public String algorithm; public boolean secretKey; @@ -67,7 +67,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { dest.writeLong(keyId); dest.writeByte((byte) (revoked ? 1 : 0)); dest.writeSerializable(date); - dest.writeString(fingerPrintHex); + dest.writeString(fingerprintHex); dest.writeString(keyIdHex); dest.writeInt(bitStrength); dest.writeString(algorithm); @@ -87,7 +87,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { vr.keyId = source.readLong(); vr.revoked = source.readByte() == 1; vr.date = (Date) source.readSerializable(); - vr.fingerPrintHex = source.readString(); + vr.fingerprintHex = source.readString(); vr.keyIdHex = source.readString(); vr.bitStrength = source.readInt(); vr.algorithm = source.readString(); @@ -153,12 +153,12 @@ public class ImportKeysListEntry implements Serializable, Parcelable { this.date = date; } - public String getFingerPrintHex() { - return fingerPrintHex; + public String getFingerprintHex() { + return fingerprintHex; } - public void setFingerPrintHex(String fingerPrintHex) { - this.fingerPrintHex = fingerPrintHex; + public void setFingerprintHex(String fingerprintHex) { + this.fingerprintHex = fingerprintHex; } public int getBitStrength() { @@ -271,7 +271,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId); this.revoked = key.isRevoked(); - this.fingerPrintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()); + this.fingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()); this.bitStrength = key.getBitStrength(); final int algorithm = key.getAlgorithm(); this.algorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index ca4f5869b..805bb9dad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -802,8 +802,8 @@ public class KeychainIntentService extends IntentService for (ImportKeysListEntry entry : entries) { // if available use complete fingerprint for get request byte[] downloadedKeyBytes; - if (entry.getFingerPrintHex() != null) { - downloadedKeyBytes = server.get("0x" + entry.getFingerPrintHex()).getBytes(); + if (entry.getFingerprintHex() != null) { + downloadedKeyBytes = server.get("0x" + entry.getFingerprintHex()).getBytes(); } else { downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes(); } @@ -829,10 +829,10 @@ public class KeychainIntentService extends IntentService } // verify downloaded key by comparing fingerprints - if (entry.getFingerPrintHex() != null) { + if (entry.getFingerprintHex() != null) { String downloadedKeyFp = PgpKeyHelper.convertFingerprintToHex( 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 " + "the requested fingerprint!"); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 70b9f3f7e..c1fb9ae94 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -146,9 +146,9 @@ public class ImportKeysAdapter extends ArrayAdapter { holder.keyId.setText(entry.keyIdHex); - if (entry.fingerPrintHex != null) { + if (entry.fingerprintHex != null) { holder.fingerprint.setVisibility(View.VISIBLE); - holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerPrintHex)); + holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerprintHex)); } else { holder.fingerprint.setVisibility(View.GONE); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java index 4175592d6..d5329e6f6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java @@ -108,7 +108,7 @@ public class ImportKeysListServerLoader * set fingerprint explicitly after query * to enforce a check when the key is imported by KeychainIntentService */ - uniqueEntry.setFingerPrintHex(fingerprint); + uniqueEntry.setFingerprintHex(fingerprint); uniqueEntry.setSelected(true); mEntryList.add(uniqueEntry); } -- cgit v1.2.3 From d9df04819480e8172515b58ff962ced9f0f4f0d6 Mon Sep 17 00:00:00 2001 From: Tim Bray Date: Sun, 11 May 2014 20:06:07 -0700 Subject: JWalk javadocs --- .../sufficientlysecure/keychain/util/JWalk.java | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java index 7ae1d8fab..76797811d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java @@ -23,29 +23,77 @@ import org.json.JSONObject; /** * Minimal hierarchy selector + * + * This is for picking out an item in a large multilevel JSON object, for example look at + * the Keybase.io User object, documentation at https://keybase.io/__/api-docs/1.0#user-objects + * an example available via + * curl https://keybase.io/_/api/1.0/user/lookup.json?username=timbray + * + * If you want to retrieve the ascii-armored key, you'd say + * String key = JWalk.getString(match,"them", "public_keys", "primary", "bundle"); */ public class JWalk { + /** + * Returns an int member value from the JSON sub-object addressed by the path + * + * @param json The object + * @param path list of string object member selectors + * @return the int addressed by the path, assuming such a thing exists + * @throws JSONException if any step in the path doesn’t work + */ public static int getInt(JSONObject json, String... path) throws JSONException { json = walk(json, path); return json.getInt(path[path.length - 1]); } + /** + * Returns a long member value from the JSON sub-object addressed by the path + * + * @param json The object + * @param path list of string object member selectors + * @return the int addressed by the path, assuming such a thing exists + * @throws JSONException if any step in the path doesn’t work + */ public static long getLong(JSONObject json, String... path) throws JSONException { json = walk(json, path); return json.getLong(path[path.length - 1]); } + /** + * Returns a String member value from the JSON sub-object addressed by the path + * + * @param json The object + * @param path list of string object member selectors + * @return the int addressed by the path, assuming such a thing exists + * @throws JSONException if any step in the path doesn’t work + */ public static String getString(JSONObject json, String... path) throws JSONException { json = walk(json, path); return json.getString(path[path.length - 1]); } + /** + * Returns a JSONArray member value from the JSON sub-object addressed by the path + * + * @param json The object + * @param path list of string object member selectors + * @return the int addressed by the path, assuming such a thing exists + * @throws JSONException if any step in the path doesn’t work + */ public static JSONArray getArray(JSONObject json, String... path) throws JSONException { json = walk(json, path); return json.getJSONArray(path[path.length - 1]); } + /** + * Returns a JSONObject member value from the JSON sub-object addressed by the path, or null + * + * @param json The object + * @param path list of string object member selectors + * @return the int addressed by the path, assuming such a thing exists + * @throws JSONException if any step in the path, except for the last, doesn’t work + */ public static JSONObject optObject(JSONObject json, String... path) throws JSONException { json = walk(json, path); return json.optJSONObject(path[path.length - 1]); -- cgit v1.2.3 From 9b7179167914d19eb00f6cef5b7664e54efcd5e8 Mon Sep 17 00:00:00 2001 From: Tim Bray Date: Thu, 15 May 2014 16:19:10 -0700 Subject: keybase now has key info in search output --- .../org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java index 953cf3cbb..ba05742fb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java @@ -21,6 +21,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.util.JWalk; import org.sufficientlysecure.keychain.util.Log; @@ -100,6 +101,10 @@ public class KeybaseKeyServer extends KeyServer { entry.setExtraData(keybaseId); // TODO: Fix; have suggested keybase provide this value to avoid search-time crypto calls + final int algorithmId = JWalk.getInt(match, "components", "key_fingerprint", "algo"); + entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId)); + final int bitStrength = JWalk.getInt(match, "components", "key_fingerprint", "nbits"); + entry.setBitStrength(bitStrength); //entry.setBitStrength(4096); //entry.setAlgorithm("RSA"); -- cgit v1.2.3 From 148a52269450e13a2c5db7670706beb86db8a631 Mon Sep 17 00:00:00 2001 From: Tim Bray Date: Thu, 15 May 2014 17:14:45 -0700 Subject: keybase cleanups for revised search output format --- .../keychain/keyimport/KeybaseKeyServer.java | 31 +++++----------------- 1 file changed, 7 insertions(+), 24 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java index ba05742fb..5f729a05c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java @@ -35,8 +35,6 @@ import java.util.WeakHashMap; public class KeybaseKeyServer extends KeyServer { - private WeakHashMap mKeyCache = new WeakHashMap(); - @Override public ArrayList search(String query) throws QueryException, TooManyResponses, InsufficientQuery { @@ -93,29 +91,18 @@ public class KeybaseKeyServer extends KeyServer { 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(); + fingerprint = fingerprint.replace(" ", "").toUpperCase(); // not strictly necessary but doesn't hurt + entry.setFingerprintHex(fingerprint); // 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 final int algorithmId = JWalk.getInt(match, "components", "key_fingerprint", "algo"); entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId)); final int bitStrength = JWalk.getInt(match, "components", "key_fingerprint", "nbits"); entry.setBitStrength(bitStrength); - //entry.setBitStrength(4096); - //entry.setAlgorithm("RSA"); - - entry.setFingerprintHex(fingerprint); - - // key data - // currently there's no need to query the user right away, and it should be avoided, so the - // user doesn't experience lag and doesn't download many keys unnecessarily, but should we - // 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")); ArrayList userIds = new ArrayList(); String name = fullName + " "; @@ -176,16 +163,12 @@ public class KeybaseKeyServer extends KeyServer { @Override public String get(String id) throws QueryException { - String key = mKeyCache.get(id); - if (key == null) { - try { - JSONObject user = getUser(id); - key = JWalk.getString(user, "them", "public_keys", "primary", "bundle"); - } catch (Exception e) { - throw new QueryException(e.getMessage()); - } + try { + JSONObject user = getUser(id); + return JWalk.getString(user, "them", "public_keys", "primary", "bundle"); + } catch (Exception e) { + throw new QueryException(e.getMessage()); } - return key; } @Override -- cgit v1.2.3 From ab81d8903a92e1f80105f223a727d75b94754769 Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Wed, 14 May 2014 19:54:20 +0200 Subject: Support mutliple search words and highlight them For the regex matching it would be smart to sort the words by length, so the longest matches come first. This only matters for queries with words containing parts of each other, which is an unlikely event and even then it doesn't break anything. --- .../org/sufficientlysecure/keychain/ui/KeyListFragment.java | 13 +++++++++++-- .../keychain/ui/SelectPublicKeyFragment.java | 13 +++++++++++-- .../keychain/ui/adapter/HighlightQueryCursorAdapter.java | 4 ++-- 3 files changed, 24 insertions(+), 6 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 3fd958bcc..941c95cb0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -264,8 +264,17 @@ public class KeyListFragment extends LoaderFragment String where = null; String whereArgs[] = null; if (mCurQuery != null) { - where = KeyRings.USER_ID + " LIKE ?"; - whereArgs = new String[]{"%" + mCurQuery + "%"}; + String[] words = mCurQuery.trim().split("\\s+"); + whereArgs = new String[words.length]; + for (int i = 0; i < words.length; ++i) { + if (where == null) { + where = ""; + } else { + where += " AND "; + } + where += KeyRings.USER_ID + " LIKE ?"; + whereArgs[i] = "%" + words[i] + "%"; + } } // Now create and return a CursorLoader that will take care of diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java index 2ad769b00..25fa698ef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java @@ -282,8 +282,17 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T String where = null; String whereArgs[] = null; if (mCurQuery != null) { - where = KeyRings.USER_ID + " LIKE ?"; - whereArgs = new String[]{"%" + mCurQuery + "%"}; + String[] words = mCurQuery.trim().split("\\s+"); + whereArgs = new String[words.length]; + for (int i = 0; i < words.length; ++i) { + if (where == null) { + where = ""; + } else { + where += " AND "; + } + where += KeyRings.USER_ID + " LIKE ?"; + whereArgs[i] = "%" + words[i] + "%"; + } } // Now create and return a CursorLoader that will take care of diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java index fd7a2dc30..4a37a9a3a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java @@ -49,9 +49,9 @@ public abstract class HighlightQueryCursorAdapter extends CursorAdapter { Spannable highlight = Spannable.Factory.getInstance().newSpannable(text); if (mCurQuery != null) { - Pattern pattern = Pattern.compile("(?i)" + mCurQuery); + Pattern pattern = Pattern.compile("(?i)(" + mCurQuery.trim().replaceAll("\\s+", "|") + ")"); Matcher matcher = pattern.matcher(text); - if (matcher.find()) { + while (matcher.find()) { highlight.setSpan( new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)), matcher.start(), -- cgit v1.2.3 From 69ce66be9429b3490511655c836f400cc30c4d4f Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Wed, 14 May 2014 20:07:30 +0200 Subject: Add highlighting to keyserver/keybase search --- .../keychain/keyimport/HkpKeyServer.java | 1 + .../keychain/keyimport/ImportKeysListEntry.java | 9 ++++ .../keychain/keyimport/KeybaseKeyServer.java | 5 +- .../keychain/ui/adapter/ImportKeysAdapter.java | 8 +-- .../keychain/util/Highlighter.java | 57 ++++++++++++++++++++++ 5 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Highlighter.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java index 8d1e1f460..28a8c8251 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java @@ -237,6 +237,7 @@ public class HkpKeyServer extends KeyServer { final Matcher matcher = PUB_KEY_LINE.matcher(data); while (matcher.find()) { final ImportKeysListEntry entry = new ImportKeysListEntry(); + entry.setQuery(query); entry.setBitStrength(Integer.parseInt(matcher.group(3))); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index 4a8d58e56..04b86e295 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -51,6 +51,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { public boolean secretKey; public String mPrimaryUserId; private String mExtraData; + private String mQuery; private boolean mSelected; @@ -209,6 +210,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mExtraData = extraData; } + public String getQuery() { + return mQuery; + } + + public void setQuery(String query) { + mQuery = query; + } + /** * Constructor for later querying from keyserver */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java index 5f729a05c..88f68b8f7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java @@ -34,6 +34,7 @@ import java.util.TimeZone; import java.util.WeakHashMap; public class KeybaseKeyServer extends KeyServer { + private String mQuery; @Override public ArrayList search(String query) throws QueryException, TooManyResponses, @@ -86,15 +87,15 @@ public class KeybaseKeyServer extends KeyServer { } private ImportKeysListEntry makeEntry(JSONObject match) throws QueryException, JSONException { - final ImportKeysListEntry entry = new ImportKeysListEntry(); + entry.setQuery(mQuery); + 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(); // not strictly necessary but doesn't hurt entry.setFingerprintHex(fingerprint); - // 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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index c1fb9ae94..9573efdfe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -33,6 +33,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.util.Highlighter; import java.util.ArrayList; import java.util.Iterator; @@ -99,6 +100,7 @@ public class ImportKeysAdapter extends ArrayAdapter { public View getView(int position, View convertView, ViewGroup parent) { ImportKeysListEntry entry = mData.get(position); + Highlighter highlighter = new Highlighter(mActivity, entry.getQuery()); ViewHolder holder; if (convertView == null) { holder = new ViewHolder(); @@ -128,7 +130,7 @@ public class ImportKeysAdapter extends ArrayAdapter { + " " + userIdSplit[0]); holder.mainUserId.setTextColor(Color.RED); } else { - holder.mainUserId.setText(userIdSplit[0]); + holder.mainUserId.setText(highlighter.highlight(userIdSplit[0])); holder.mainUserId.setTextColor(Color.BLACK); } } else { @@ -139,7 +141,7 @@ public class ImportKeysAdapter extends ArrayAdapter { // email if (userIdSplit[1] != null) { holder.mainUserIdRest.setVisibility(View.VISIBLE); - holder.mainUserIdRest.setText(userIdSplit[1]); + holder.mainUserIdRest.setText(highlighter.highlight(userIdSplit[1])); } else { holder.mainUserIdRest.setVisibility(View.GONE); } @@ -182,7 +184,7 @@ public class ImportKeysAdapter extends ArrayAdapter { String uid = it.next(); TextView uidView = (TextView) mInflater.inflate( R.layout.import_keys_list_entry_user_id, null); - uidView.setText(uid); + uidView.setText(highlighter.highlight(uid)); holder.userIdsList.addView(uidView); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Highlighter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Highlighter.java new file mode 100644 index 000000000..eeeacf465 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Highlighter.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 Thialfihar + * + * 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 . + */ + +package org.sufficientlysecure.keychain.util; + +import android.content.Context; +import android.text.Spannable; +import android.text.style.ForegroundColorSpan; + +import org.sufficientlysecure.keychain.R; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Highlighter { + private Context mContext; + private String mQuery; + + public Highlighter(Context context, String query) { + mContext = context; + mQuery = query; + } + + public Spannable highlight(String text) { + Spannable highlight = Spannable.Factory.getInstance().newSpannable(text); + + if (mQuery == null) { + return highlight; + } + + Pattern pattern = Pattern.compile("(?i)(" + mQuery.trim().replaceAll("\\s+", "|") + ")"); + Matcher matcher = pattern.matcher(text); + while (matcher.find()) { + highlight.setSpan( + new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)), + matcher.start(), + matcher.end(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + return highlight; + } +} -- cgit v1.2.3 From 84b754341d1628b542e1382124f3a02989ac7eea Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Wed, 14 May 2014 20:52:17 +0200 Subject: Use Highlighter instead of HighlightQueryCursorAdapter --- .../keychain/ui/KeyListFragment.java | 27 +++++---- .../ui/adapter/HighlightQueryCursorAdapter.java | 66 ---------------------- .../ui/adapter/SelectKeyCursorAdapter.java | 14 ++++- 3 files changed, 28 insertions(+), 79 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 941c95cb0..efaaa579b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -34,6 +34,7 @@ import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.view.MenuItemCompat; import android.support.v7.app.ActionBarActivity; +import android.support.v4.widget.CursorAdapter; import android.support.v7.widget.SearchView; import android.text.TextUtils; import android.view.ActionMode; @@ -61,8 +62,8 @@ import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.ui.adapter.HighlightQueryCursorAdapter; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; +import org.sufficientlysecure.keychain.util.Highlighter; import org.sufficientlysecure.keychain.util.Log; import java.util.Date; @@ -82,7 +83,7 @@ public class KeyListFragment extends LoaderFragment private KeyListAdapter mAdapter; private StickyListHeadersListView mStickyList; - private String mCurQuery; + private String mQuery; private SearchView mSearchView; // empty list layout private BootstrapButton mButtonEmptyCreate; @@ -263,8 +264,8 @@ public class KeyListFragment extends LoaderFragment Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); String where = null; String whereArgs[] = null; - if (mCurQuery != null) { - String[] words = mCurQuery.trim().split("\\s+"); + if (mQuery != null) { + String[] words = mQuery.trim().split("\\s+"); whereArgs = new String[words.length]; for (int i = 0; i < words.length; ++i) { if (where == null) { @@ -286,7 +287,7 @@ public class KeyListFragment extends LoaderFragment public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) - mAdapter.setSearchQuery(mCurQuery); + mAdapter.setSearchQuery(mQuery); mAdapter.swapCursor(data); mStickyList.setAdapter(mAdapter); @@ -388,7 +389,7 @@ public class KeyListFragment extends LoaderFragment @Override public boolean onMenuItemActionCollapse(MenuItem item) { - mCurQuery = null; + mQuery = null; mSearchView.setQuery("", true); getLoaderManager().restartLoader(0, null, KeyListFragment.this); return true; @@ -408,7 +409,7 @@ public class KeyListFragment extends LoaderFragment // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. - mCurQuery = !TextUtils.isEmpty(s) ? s : null; + mQuery = !TextUtils.isEmpty(s) ? s : null; getLoaderManager().restartLoader(0, null, this); return true; } @@ -416,7 +417,8 @@ public class KeyListFragment extends LoaderFragment /** * Implements StickyListHeadersAdapter from library */ - private class KeyListAdapter extends HighlightQueryCursorAdapter implements StickyListHeadersAdapter { + private class KeyListAdapter extends CursorAdapter implements StickyListHeadersAdapter { + private String mQuery; private LayoutInflater mInflater; private HashMap mSelection = new HashMap(); @@ -427,6 +429,10 @@ public class KeyListFragment extends LoaderFragment mInflater = LayoutInflater.from(context); } + public void setSearchQuery(String query) { + mQuery = query; + } + @Override public Cursor swapCursor(Cursor newCursor) { return super.swapCursor(newCursor); @@ -465,18 +471,19 @@ public class KeyListFragment extends LoaderFragment */ @Override public void bindView(View view, Context context, Cursor cursor) { + Highlighter highlighter = new Highlighter(context, mQuery); ItemViewHolder h = (ItemViewHolder) view.getTag(); { // set name and stuff, common to both key types String userId = cursor.getString(INDEX_USER_ID); String[] userIdSplit = PgpKeyHelper.splitUserId(userId); if (userIdSplit[0] != null) { - h.mMainUserId.setText(highlightSearchQuery(userIdSplit[0])); + h.mMainUserId.setText(highlighter.highlight(userIdSplit[0])); } else { h.mMainUserId.setText(R.string.user_id_no_name); } if (userIdSplit[1] != null) { - h.mMainUserIdRest.setText(highlightSearchQuery(userIdSplit[1])); + h.mMainUserIdRest.setText(highlighter.highlight(userIdSplit[1])); h.mMainUserIdRest.setVisibility(View.VISIBLE); } else { h.mMainUserIdRest.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java deleted file mode 100644 index 4a37a9a3a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/HighlightQueryCursorAdapter.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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 . - */ - -package org.sufficientlysecure.keychain.ui.adapter; - -import android.content.Context; -import android.database.Cursor; -import android.support.v4.widget.CursorAdapter; -import android.text.Spannable; -import android.text.style.ForegroundColorSpan; - -import org.sufficientlysecure.keychain.R; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public abstract class HighlightQueryCursorAdapter extends CursorAdapter { - - private String mCurQuery; - - public HighlightQueryCursorAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - mCurQuery = null; - } - - public void setSearchQuery(String searchQuery) { - mCurQuery = searchQuery; - } - - public String getSearchQuery() { - return mCurQuery; - } - - protected Spannable highlightSearchQuery(String text) { - Spannable highlight = Spannable.Factory.getInstance().newSpannable(text); - - if (mCurQuery != null) { - Pattern pattern = Pattern.compile("(?i)(" + mCurQuery.trim().replaceAll("\\s+", "|") + ")"); - Matcher matcher = pattern.matcher(text); - while (matcher.find()) { - highlight.setSpan( - new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)), - matcher.start(), - matcher.end(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - return highlight; - } else { - return highlight; - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index cde008175..00cd554b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; import android.database.Cursor; +import android.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -29,6 +30,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.util.Highlighter; import java.util.Date; @@ -36,8 +38,9 @@ import java.util.Date; /** * Yes this class is abstract! */ -abstract public class SelectKeyCursorAdapter extends HighlightQueryCursorAdapter { +abstract public class SelectKeyCursorAdapter extends CursorAdapter { + private String mQuery; private LayoutInflater mInflater; protected int mIndexUserId, mIndexMasterKeyId, mIndexRevoked, mIndexExpiry; @@ -48,6 +51,10 @@ abstract public class SelectKeyCursorAdapter extends HighlightQueryCursorAdapter initIndex(c); } + public void setSearchQuery(String query) { + mQuery = query; + } + @Override public Cursor swapCursor(Cursor newCursor) { initIndex(newCursor); @@ -101,19 +108,20 @@ abstract public class SelectKeyCursorAdapter extends HighlightQueryCursorAdapter @Override public void bindView(View view, Context context, Cursor cursor) { + Highlighter highlighter = new Highlighter(context, mQuery); ViewHolderItem h = (ViewHolderItem) view.getTag(); String userId = cursor.getString(mIndexUserId); String[] userIdSplit = PgpKeyHelper.splitUserId(userId); if (userIdSplit[0] != null) { - h.mainUserId.setText(highlightSearchQuery(userIdSplit[0])); + h.mainUserId.setText(highlighter.highlight(userIdSplit[0])); } else { h.mainUserId.setText(R.string.user_id_no_name); } if (userIdSplit[1] != null) { h.mainUserIdRest.setVisibility(View.VISIBLE); - h.mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1])); + h.mainUserIdRest.setText(highlighter.highlight(userIdSplit[1])); } else { h.mainUserIdRest.setVisibility(View.GONE); } -- cgit v1.2.3 From 1386282840128529f54572b6dee216cf5c5cf044 Mon Sep 17 00:00:00 2001 From: Thialfihar Date: Wed, 14 May 2014 20:55:53 +0200 Subject: Rename mCurQuery to mQuery for consistency --- .../keychain/ui/SelectPublicKeyFragment.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java index 25fa698ef..9343b166a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java @@ -55,7 +55,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T private SelectKeyCursorAdapter mAdapter; private EditText mSearchView; private long mSelectedMasterKeyIds[]; - private String mCurQuery; + private String mQuery; // copied from ListFragment static final int INTERNAL_EMPTY_ID = 0x00ff0001; @@ -281,8 +281,8 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T } String where = null; String whereArgs[] = null; - if (mCurQuery != null) { - String[] words = mCurQuery.trim().split("\\s+"); + if (mQuery != null) { + String[] words = mQuery.trim().split("\\s+"); whereArgs = new String[words.length]; for (int i = 0; i < words.length; ++i) { if (where == null) { @@ -304,7 +304,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) - mAdapter.setSearchQuery(mCurQuery); + mAdapter.setSearchQuery(mQuery); mAdapter.swapCursor(data); // The list should now be shown. @@ -338,7 +338,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T @Override public void afterTextChanged(Editable editable) { - mCurQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null; + mQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null; getLoaderManager().restartLoader(0, null, this); } -- cgit v1.2.3 From 19072824bada5ae2fcac6e4e991bf300f270b081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 18 May 2014 23:06:50 +0200 Subject: Rename KeyServer to Keyserver --- .../keychain/keyimport/HkpKeyServer.java | 338 --------------------- .../keychain/keyimport/HkpKeyserver.java | 338 +++++++++++++++++++++ .../keychain/keyimport/KeyServer.java | 68 ----- .../keychain/keyimport/KeybaseKeyServer.java | 178 ----------- .../keychain/keyimport/KeybaseKeyserver.java | 175 +++++++++++ .../keychain/keyimport/Keyserver.java | 68 +++++ .../keychain/pgp/PgpImportExport.java | 6 +- .../keychain/service/KeychainIntentService.java | 10 +- .../keychain/ui/ImportKeysListFragment.java | 10 +- .../keychain/ui/KeyListFragment.java | 9 +- .../ui/adapter/ImportKeysListKeybaseLoader.java | 12 +- .../ui/adapter/ImportKeysListServerLoader.java | 12 +- 12 files changed, 610 insertions(+), 614 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeyServer.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java deleted file mode 100644 index 8d1e1f460..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyServer.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann - * Copyright (C) 2011-2014 Thialfihar - * Copyright (C) 2011 Senecaso - * - * 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 org.sufficientlysecure.keychain.keyimport; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.util.Log; - -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.Locale; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class HkpKeyServer extends KeyServer { - private static class HttpError extends Exception { - private static final long serialVersionUID = 1718783705229428893L; - private int mCode; - private String mData; - - public HttpError(int code, String data) { - super("" + code + ": " + data); - mCode = code; - mData = data; - } - - public int getCode() { - return mCode; - } - - public String getData() { - return mData; - } - } - - private String mHost; - private short mPort; - - /** - * pub:%keyid%:%algo%:%keylen%:%creationdate%:%expirationdate%:%flags% - *
    - *
  • %keyid% = this is either the fingerprint or the key ID of the key. - * Either the 16-digit or 8-digit key IDs are acceptable, but obviously the fingerprint is best. - *
  • - *
  • %algo% = the algorithm number, (i.e. 1==RSA, 17==DSA, etc). - * See RFC-2440
  • - *
  • %keylen% = the key length (i.e. 1024, 2048, 4096, etc.)
  • - *
  • %creationdate% = creation date of the key in standard - * RFC-2440 form (i.e. number of - * seconds since 1/1/1970 UTC time)
  • - *
  • %expirationdate% = expiration date of the key in standard - * RFC-2440 form (i.e. number of - * seconds since 1/1/1970 UTC time)
  • - *
  • %flags% = letter codes to indicate details of the key, if any. Flags may be in any - * order. The meaning of "disabled" is implementation-specific. Note that individual flags may - * be unimplemented, so the absence of a given flag does not necessarily mean the absence of the - * detail. - *
      - *
    • r == revoked
    • - *
    • d == disabled
    • - *
    • e == expired
    • - *
    - *
  • - *
- * - * @see - * 5.2. Machine Readable Indexes - * in Internet-Draft OpenPGP HTTP Keyserver Protocol Document - */ - public static final Pattern PUB_KEY_LINE = Pattern - .compile("pub:([0-9a-fA-F]+):([0-9]+):([0-9]+):([0-9]+):([0-9]*):([rde]*)[ \n\r]*" // pub line - + "(uid:(.*):([0-9]+):([0-9]*):([rde]*))+", // one or more uid lines - Pattern.CASE_INSENSITIVE); - - /** - * uid:%escaped uid string%:%creationdate%:%expirationdate%:%flags% - *
    - *
  • %escaped uid string% = the user ID string, with HTTP %-escaping for anything that - * isn't 7-bit safe as well as for the ":" character. Any other characters may be escaped, as - * desired.
  • - *
  • %creationdate% = creation date of the key in standard - * RFC-2440 form (i.e. number of - * seconds since 1/1/1970 UTC time)
  • - *
  • %expirationdate% = expiration date of the key in standard - * RFC-2440 form (i.e. number of - * seconds since 1/1/1970 UTC time)
  • - *
  • %flags% = letter codes to indicate details of the key, if any. Flags may be in any - * order. The meaning of "disabled" is implementation-specific. Note that individual flags may - * be unimplemented, so the absence of a given flag does not necessarily mean the absence of - * the detail. - *
      - *
    • r == revoked
    • - *
    • d == disabled
    • - *
    • e == expired
    • - *
    - *
  • - *
- */ - public static final Pattern UID_LINE = Pattern - .compile("uid:(.*):([0-9]+):([0-9]*):([rde]*)", - Pattern.CASE_INSENSITIVE); - - private static final short PORT_DEFAULT = 11371; - - /** - * @param hostAndPort may be just - * "hostname" (eg. "pool.sks-keyservers.net"), then it will - * connect using {@link #PORT_DEFAULT}. However, port may be specified after colon - * ("hostname:port", eg. "p80.pool.sks-keyservers.net:80"). - */ - public HkpKeyServer(String hostAndPort) { - String host = hostAndPort; - short port = PORT_DEFAULT; - final int colonPosition = hostAndPort.lastIndexOf(':'); - if (colonPosition > 0) { - host = hostAndPort.substring(0, colonPosition); - final String portStr = hostAndPort.substring(colonPosition + 1); - port = Short.decode(portStr); - } - mHost = host; - mPort = port; - } - - public HkpKeyServer(String host, short port) { - mHost = host; - mPort = port; - } - - private String query(String request) throws QueryException, HttpError { - InetAddress ips[]; - try { - ips = InetAddress.getAllByName(mHost); - } catch (UnknownHostException e) { - throw new QueryException(e.toString()); - } - for (int i = 0; i < ips.length; ++i) { - try { - String url = "http://" + ips[i].getHostAddress() + ":" + mPort + request; - Log.d(Constants.TAG, "hkp keyserver query: " + url); - URL realUrl = new URL(url); - HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); - conn.setConnectTimeout(5000); - conn.setReadTimeout(25000); - conn.connect(); - int response = conn.getResponseCode(); - if (response >= 200 && response < 300) { - return readAll(conn.getInputStream(), conn.getContentEncoding()); - } else { - String data = readAll(conn.getErrorStream(), conn.getContentEncoding()); - throw new HttpError(response, data); - } - } catch (MalformedURLException e) { - // nothing to do, try next IP - } catch (IOException e) { - // nothing to do, try next IP - } - } - - throw new QueryException("querying server(s) for '" + mHost + "' failed"); - } - - @Override - public ArrayList search(String query) throws QueryException, TooManyResponses, - InsufficientQuery { - ArrayList results = new ArrayList(); - - if (query.length() < 3) { - throw new InsufficientQuery(); - } - - String encodedQuery; - try { - encodedQuery = URLEncoder.encode(query, "utf8"); - } catch (UnsupportedEncodingException e) { - return null; - } - String request = "/pks/lookup?op=index&options=mr&search=" + encodedQuery; - - String data; - try { - data = query(request); - } catch (HttpError e) { - if (e.getCode() == 404) { - return results; - } else { - if (e.getData().toLowerCase(Locale.US).contains("no keys found")) { - return results; - } else if (e.getData().toLowerCase(Locale.US).contains("too many")) { - throw new TooManyResponses(); - } else if (e.getData().toLowerCase(Locale.US).contains("insufficient")) { - throw new InsufficientQuery(); - } - } - throw new QueryException("querying server(s) for '" + mHost + "' failed"); - } - - final Matcher matcher = PUB_KEY_LINE.matcher(data); - while (matcher.find()) { - final ImportKeysListEntry entry = new ImportKeysListEntry(); - - entry.setBitStrength(Integer.parseInt(matcher.group(3))); - - final int algorithmId = Integer.decode(matcher.group(2)); - entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId)); - - // group 1 contains the full fingerprint (v4) or the long key id if available - // see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr - String fingerprintOrKeyId = matcher.group(1); - if (fingerprintOrKeyId.length() > 16) { - entry.setFingerprintHex(fingerprintOrKeyId.toLowerCase(Locale.US)); - entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length() - - 16, fingerprintOrKeyId.length())); - } else { - // set key id only - entry.setKeyIdHex("0x" + fingerprintOrKeyId); - } - - final long creationDate = Long.parseLong(matcher.group(4)); - final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - tmpGreg.setTimeInMillis(creationDate * 1000); - entry.setDate(tmpGreg.getTime()); - - entry.setRevoked(matcher.group(6).contains("r")); - - ArrayList userIds = new ArrayList(); - final String uidLines = matcher.group(7); - final Matcher uidMatcher = UID_LINE.matcher(uidLines); - while (uidMatcher.find()) { - String tmp = uidMatcher.group(1).trim(); - if (tmp.contains("%")) { - try { - // converts Strings like "Universit%C3%A4t" to a proper encoding form "Universität". - tmp = (URLDecoder.decode(tmp, "UTF8")); - } catch (UnsupportedEncodingException ignored) { - // will never happen, because "UTF8" is supported - } - } - userIds.add(tmp); - } - entry.setUserIds(userIds); - entry.setPrimaryUserId(userIds.get(0)); - - results.add(entry); - } - return results; - } - - @Override - public String get(String keyIdHex) throws QueryException { - HttpClient client = new DefaultHttpClient(); - try { - String query = "http://" + mHost + ":" + mPort + - "/pks/lookup?op=get&options=mr&search=" + keyIdHex; - Log.d(Constants.TAG, "hkp keyserver get: " + query); - HttpGet get = new HttpGet(query); - HttpResponse response = client.execute(get); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { - throw new QueryException("not found"); - } - - HttpEntity entity = response.getEntity(); - InputStream is = entity.getContent(); - String data = readAll(is, EntityUtils.getContentCharSet(entity)); - Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data); - if (matcher.find()) { - return matcher.group(1); - } - } catch (IOException e) { - // nothing to do, better luck on the next keyserver - } finally { - client.getConnectionManager().shutdown(); - } - - return null; - } - - @Override - public void add(String armoredKey) throws AddKeyException { - HttpClient client = new DefaultHttpClient(); - try { - String query = "http://" + mHost + ":" + mPort + "/pks/add"; - HttpPost post = new HttpPost(query); - Log.d(Constants.TAG, "hkp keyserver add: " + query); - List nameValuePairs = new ArrayList(2); - nameValuePairs.add(new BasicNameValuePair("keytext", armoredKey)); - post.setEntity(new UrlEncodedFormEntity(nameValuePairs)); - - HttpResponse response = client.execute(post); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { - throw new AddKeyException(); - } - } catch (IOException e) { - // nothing to do, better luck on the next keyserver - } finally { - client.getConnectionManager().shutdown(); - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java new file mode 100644 index 000000000..1f101f6ef --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * Copyright (C) 2011-2014 Thialfihar + * Copyright (C) 2011 Senecaso + * + * 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 org.sufficientlysecure.keychain.keyimport; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class HkpKeyserver extends Keyserver { + private static class HttpError extends Exception { + private static final long serialVersionUID = 1718783705229428893L; + private int mCode; + private String mData; + + public HttpError(int code, String data) { + super("" + code + ": " + data); + mCode = code; + mData = data; + } + + public int getCode() { + return mCode; + } + + public String getData() { + return mData; + } + } + + private String mHost; + private short mPort; + + /** + * pub:%keyid%:%algo%:%keylen%:%creationdate%:%expirationdate%:%flags% + *
    + *
  • %keyid% = this is either the fingerprint or the key ID of the key. + * Either the 16-digit or 8-digit key IDs are acceptable, but obviously the fingerprint is best. + *
  • + *
  • %algo% = the algorithm number, (i.e. 1==RSA, 17==DSA, etc). + * See RFC-2440
  • + *
  • %keylen% = the key length (i.e. 1024, 2048, 4096, etc.)
  • + *
  • %creationdate% = creation date of the key in standard + * RFC-2440 form (i.e. number of + * seconds since 1/1/1970 UTC time)
  • + *
  • %expirationdate% = expiration date of the key in standard + * RFC-2440 form (i.e. number of + * seconds since 1/1/1970 UTC time)
  • + *
  • %flags% = letter codes to indicate details of the key, if any. Flags may be in any + * order. The meaning of "disabled" is implementation-specific. Note that individual flags may + * be unimplemented, so the absence of a given flag does not necessarily mean the absence of the + * detail. + *
      + *
    • r == revoked
    • + *
    • d == disabled
    • + *
    • e == expired
    • + *
    + *
  • + *
+ * + * @see + * 5.2. Machine Readable Indexes + * in Internet-Draft OpenPGP HTTP Keyserver Protocol Document + */ + public static final Pattern PUB_KEY_LINE = Pattern + .compile("pub:([0-9a-fA-F]+):([0-9]+):([0-9]+):([0-9]+):([0-9]*):([rde]*)[ \n\r]*" // pub line + + "(uid:(.*):([0-9]+):([0-9]*):([rde]*))+", // one or more uid lines + Pattern.CASE_INSENSITIVE); + + /** + * uid:%escaped uid string%:%creationdate%:%expirationdate%:%flags% + *
    + *
  • %escaped uid string% = the user ID string, with HTTP %-escaping for anything that + * isn't 7-bit safe as well as for the ":" character. Any other characters may be escaped, as + * desired.
  • + *
  • %creationdate% = creation date of the key in standard + * RFC-2440 form (i.e. number of + * seconds since 1/1/1970 UTC time)
  • + *
  • %expirationdate% = expiration date of the key in standard + * RFC-2440 form (i.e. number of + * seconds since 1/1/1970 UTC time)
  • + *
  • %flags% = letter codes to indicate details of the key, if any. Flags may be in any + * order. The meaning of "disabled" is implementation-specific. Note that individual flags may + * be unimplemented, so the absence of a given flag does not necessarily mean the absence of + * the detail. + *
      + *
    • r == revoked
    • + *
    • d == disabled
    • + *
    • e == expired
    • + *
    + *
  • + *
+ */ + public static final Pattern UID_LINE = Pattern + .compile("uid:(.*):([0-9]+):([0-9]*):([rde]*)", + Pattern.CASE_INSENSITIVE); + + private static final short PORT_DEFAULT = 11371; + + /** + * @param hostAndPort may be just + * "hostname" (eg. "pool.sks-keyservers.net"), then it will + * connect using {@link #PORT_DEFAULT}. However, port may be specified after colon + * ("hostname:port", eg. "p80.pool.sks-keyservers.net:80"). + */ + public HkpKeyserver(String hostAndPort) { + String host = hostAndPort; + short port = PORT_DEFAULT; + final int colonPosition = hostAndPort.lastIndexOf(':'); + if (colonPosition > 0) { + host = hostAndPort.substring(0, colonPosition); + final String portStr = hostAndPort.substring(colonPosition + 1); + port = Short.decode(portStr); + } + mHost = host; + mPort = port; + } + + public HkpKeyserver(String host, short port) { + mHost = host; + mPort = port; + } + + private String query(String request) throws QueryException, HttpError { + InetAddress ips[]; + try { + ips = InetAddress.getAllByName(mHost); + } catch (UnknownHostException e) { + throw new QueryException(e.toString()); + } + for (int i = 0; i < ips.length; ++i) { + try { + String url = "http://" + ips[i].getHostAddress() + ":" + mPort + request; + Log.d(Constants.TAG, "hkp keyserver query: " + url); + URL realUrl = new URL(url); + HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); + conn.setConnectTimeout(5000); + conn.setReadTimeout(25000); + conn.connect(); + int response = conn.getResponseCode(); + if (response >= 200 && response < 300) { + return readAll(conn.getInputStream(), conn.getContentEncoding()); + } else { + String data = readAll(conn.getErrorStream(), conn.getContentEncoding()); + throw new HttpError(response, data); + } + } catch (MalformedURLException e) { + // nothing to do, try next IP + } catch (IOException e) { + // nothing to do, try next IP + } + } + + throw new QueryException("querying server(s) for '" + mHost + "' failed"); + } + + @Override + public ArrayList search(String query) throws QueryException, TooManyResponses, + InsufficientQuery { + ArrayList results = new ArrayList(); + + if (query.length() < 3) { + throw new InsufficientQuery(); + } + + String encodedQuery; + try { + encodedQuery = URLEncoder.encode(query, "utf8"); + } catch (UnsupportedEncodingException e) { + return null; + } + String request = "/pks/lookup?op=index&options=mr&search=" + encodedQuery; + + String data; + try { + data = query(request); + } catch (HttpError e) { + if (e.getCode() == 404) { + return results; + } else { + if (e.getData().toLowerCase(Locale.US).contains("no keys found")) { + return results; + } else if (e.getData().toLowerCase(Locale.US).contains("too many")) { + throw new TooManyResponses(); + } else if (e.getData().toLowerCase(Locale.US).contains("insufficient")) { + throw new InsufficientQuery(); + } + } + throw new QueryException("querying server(s) for '" + mHost + "' failed"); + } + + final Matcher matcher = PUB_KEY_LINE.matcher(data); + while (matcher.find()) { + final ImportKeysListEntry entry = new ImportKeysListEntry(); + + entry.setBitStrength(Integer.parseInt(matcher.group(3))); + + final int algorithmId = Integer.decode(matcher.group(2)); + entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId)); + + // group 1 contains the full fingerprint (v4) or the long key id if available + // see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr + String fingerprintOrKeyId = matcher.group(1); + if (fingerprintOrKeyId.length() > 16) { + entry.setFingerprintHex(fingerprintOrKeyId.toLowerCase(Locale.US)); + entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length() + - 16, fingerprintOrKeyId.length())); + } else { + // set key id only + entry.setKeyIdHex("0x" + fingerprintOrKeyId); + } + + final long creationDate = Long.parseLong(matcher.group(4)); + final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + tmpGreg.setTimeInMillis(creationDate * 1000); + entry.setDate(tmpGreg.getTime()); + + entry.setRevoked(matcher.group(6).contains("r")); + + ArrayList userIds = new ArrayList(); + final String uidLines = matcher.group(7); + final Matcher uidMatcher = UID_LINE.matcher(uidLines); + while (uidMatcher.find()) { + String tmp = uidMatcher.group(1).trim(); + if (tmp.contains("%")) { + try { + // converts Strings like "Universit%C3%A4t" to a proper encoding form "Universität". + tmp = (URLDecoder.decode(tmp, "UTF8")); + } catch (UnsupportedEncodingException ignored) { + // will never happen, because "UTF8" is supported + } + } + userIds.add(tmp); + } + entry.setUserIds(userIds); + entry.setPrimaryUserId(userIds.get(0)); + + results.add(entry); + } + return results; + } + + @Override + public String get(String keyIdHex) throws QueryException { + HttpClient client = new DefaultHttpClient(); + try { + String query = "http://" + mHost + ":" + mPort + + "/pks/lookup?op=get&options=mr&search=" + keyIdHex; + Log.d(Constants.TAG, "hkp keyserver get: " + query); + HttpGet get = new HttpGet(query); + HttpResponse response = client.execute(get); + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + throw new QueryException("not found"); + } + + HttpEntity entity = response.getEntity(); + InputStream is = entity.getContent(); + String data = readAll(is, EntityUtils.getContentCharSet(entity)); + Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data); + if (matcher.find()) { + return matcher.group(1); + } + } catch (IOException e) { + // nothing to do, better luck on the next keyserver + } finally { + client.getConnectionManager().shutdown(); + } + + return null; + } + + @Override + public void add(String armoredKey) throws AddKeyException { + HttpClient client = new DefaultHttpClient(); + try { + String query = "http://" + mHost + ":" + mPort + "/pks/add"; + HttpPost post = new HttpPost(query); + Log.d(Constants.TAG, "hkp keyserver add: " + query); + List nameValuePairs = new ArrayList(2); + nameValuePairs.add(new BasicNameValuePair("keytext", armoredKey)); + post.setEntity(new UrlEncodedFormEntity(nameValuePairs)); + + HttpResponse response = client.execute(post); + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + throw new AddKeyException(); + } + } catch (IOException e) { + // nothing to do, better luck on the next keyserver + } finally { + client.getConnectionManager().shutdown(); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeyServer.java deleted file mode 100644 index d6ebca5a6..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeyServer.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann - * Copyright (C) 2011-2014 Thialfihar - * Copyright (C) 2011 Senecaso - * - * 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 org.sufficientlysecure.keychain.keyimport; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -public abstract class KeyServer { - public static class QueryException extends Exception { - private static final long serialVersionUID = 2703768928624654512L; - - public QueryException(String message) { - super(message); - } - } - - public static class TooManyResponses extends Exception { - private static final long serialVersionUID = 2703768928624654513L; - } - - public static class InsufficientQuery extends Exception { - private static final long serialVersionUID = 2703768928624654514L; - } - - public static class AddKeyException extends Exception { - private static final long serialVersionUID = -507574859137295530L; - } - - abstract List search(String query) throws QueryException, TooManyResponses, - InsufficientQuery; - - abstract String get(String keyIdHex) throws QueryException; - - abstract void add(String armoredKey) throws AddKeyException; - - public static String readAll(InputStream in, String encoding) throws IOException { - ByteArrayOutputStream raw = new ByteArrayOutputStream(); - - byte buffer[] = new byte[1 << 16]; - int n = 0; - while ((n = in.read(buffer)) != -1) { - raw.write(buffer, 0, n); - } - - if (encoding == null) { - encoding = "utf8"; - } - return raw.toString(encoding); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java deleted file mode 100644 index 5f729a05c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyServer.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2014 Tim Bray - * - * 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 . - */ - -package org.sufficientlysecure.keychain.keyimport; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.util.JWalk; -import org.sufficientlysecure.keychain.util.Log; - -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.GregorianCalendar; -import java.util.TimeZone; -import java.util.WeakHashMap; - -public class KeybaseKeyServer extends KeyServer { - - @Override - public ArrayList search(String query) throws QueryException, TooManyResponses, - InsufficientQuery { - ArrayList results = new ArrayList(); - - 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); - try { - - JSONArray matches = JWalk.getArray(fromQuery, "completions"); - for (int i = 0; i < matches.length(); i++) { - JSONObject match = matches.getJSONObject(i); - - // only list them if they have a key - if (JWalk.optObject(match, "components", "key_fingerprint") != null) { - 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) { - Log.e(Constants.TAG, "keybase result parsing error", e); - throw new QueryException("Unexpected structure in keybase search result: " + e.getMessage()); - } - - return results; - } - - private JSONObject getUser(String keybaseId) throws QueryException { - try { - return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseId); - } catch (Exception e) { - String detail = ""; - if (keybaseId != null) { - detail = ". Query was for user '" + keybaseId + "'"; - } - throw new QueryException(e.getMessage() + detail); - } - } - - private ImportKeysListEntry makeEntry(JSONObject match) throws QueryException, JSONException { - - 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(); // not strictly necessary but doesn't hurt - entry.setFingerprintHex(fingerprint); - - // 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); - - final int algorithmId = JWalk.getInt(match, "components", "key_fingerprint", "algo"); - entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId)); - final int bitStrength = JWalk.getInt(match, "components", "key_fingerprint", "nbits"); - entry.setBitStrength(bitStrength); - - ArrayList userIds = new ArrayList(); - String name = fullName + " "; - userIds.add(name); - 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.setPrimaryUserId(name); - return entry; - } - - private JSONObject getFromKeybase(String path, String query) throws QueryException { - try { - String url = "https://keybase.io/" + path + URLEncoder.encode(query, "utf8"); - Log.d(Constants.TAG, "keybase query: " + url); - - URL realUrl = new URL(url); - HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); - conn.setConnectTimeout(5000); // TODO: Reasonable values for keybase - conn.setReadTimeout(25000); - conn.connect(); - int response = conn.getResponseCode(); - if (response >= 200 && response < 300) { - String text = readAll(conn.getInputStream(), conn.getContentEncoding()); - try { - JSONObject json = new JSONObject(text); - if (JWalk.getInt(json, "status", "code") != 0) { - throw new QueryException("Keybase autocomplete search failed"); - } - return json; - } catch (JSONException e) { - throw new QueryException("Keybase.io query returned broken JSON"); - } - } else { - String message = readAll(conn.getErrorStream(), conn.getContentEncoding()); - throw new QueryException("Keybase.io query error (status=" + response + - "): " + message); - } - } catch (Exception e) { - throw new QueryException("Keybase.io query error"); - } - } - - @Override - public String get(String id) throws QueryException { - try { - JSONObject user = getUser(id); - return JWalk.getString(user, "them", "public_keys", "primary", "bundle"); - } catch (Exception e) { - throw new QueryException(e.getMessage()); - } - } - - @Override - public void add(String armoredKey) throws AddKeyException { - throw new AddKeyException(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java new file mode 100644 index 000000000..3094b065e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2014 Tim Bray + * + * 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 . + */ + +package org.sufficientlysecure.keychain.keyimport; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.util.JWalk; +import org.sufficientlysecure.keychain.util.Log; + +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; + +public class KeybaseKeyserver extends Keyserver { + + @Override + public ArrayList search(String query) throws QueryException, TooManyResponses, + InsufficientQuery { + ArrayList results = new ArrayList(); + + 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); + try { + + JSONArray matches = JWalk.getArray(fromQuery, "completions"); + for (int i = 0; i < matches.length(); i++) { + JSONObject match = matches.getJSONObject(i); + + // only list them if they have a key + if (JWalk.optObject(match, "components", "key_fingerprint") != null) { + 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) { + Log.e(Constants.TAG, "keybase result parsing error", e); + throw new QueryException("Unexpected structure in keybase search result: " + e.getMessage()); + } + + return results; + } + + private JSONObject getUser(String keybaseId) throws QueryException { + try { + return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseId); + } catch (Exception e) { + String detail = ""; + if (keybaseId != null) { + detail = ". Query was for user '" + keybaseId + "'"; + } + throw new QueryException(e.getMessage() + detail); + } + } + + private ImportKeysListEntry makeEntry(JSONObject match) throws QueryException, JSONException { + + 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(); // not strictly necessary but doesn't hurt + entry.setFingerprintHex(fingerprint); + + // 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); + + final int algorithmId = JWalk.getInt(match, "components", "key_fingerprint", "algo"); + entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId)); + final int bitStrength = JWalk.getInt(match, "components", "key_fingerprint", "nbits"); + entry.setBitStrength(bitStrength); + + ArrayList userIds = new ArrayList(); + String name = fullName + " "; + userIds.add(name); + 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.setPrimaryUserId(name); + return entry; + } + + private JSONObject getFromKeybase(String path, String query) throws QueryException { + try { + String url = "https://keybase.io/" + path + URLEncoder.encode(query, "utf8"); + Log.d(Constants.TAG, "keybase query: " + url); + + URL realUrl = new URL(url); + HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); + conn.setConnectTimeout(5000); // TODO: Reasonable values for keybase + conn.setReadTimeout(25000); + conn.connect(); + int response = conn.getResponseCode(); + if (response >= 200 && response < 300) { + String text = readAll(conn.getInputStream(), conn.getContentEncoding()); + try { + JSONObject json = new JSONObject(text); + if (JWalk.getInt(json, "status", "code") != 0) { + throw new QueryException("Keybase autocomplete search failed"); + } + return json; + } catch (JSONException e) { + throw new QueryException("Keybase.io query returned broken JSON"); + } + } else { + String message = readAll(conn.getErrorStream(), conn.getContentEncoding()); + throw new QueryException("Keybase.io query error (status=" + response + + "): " + message); + } + } catch (Exception e) { + throw new QueryException("Keybase.io query error"); + } + } + + @Override + public String get(String id) throws QueryException { + try { + JSONObject user = getUser(id); + return JWalk.getString(user, "them", "public_keys", "primary", "bundle"); + } catch (Exception e) { + throw new QueryException(e.getMessage()); + } + } + + @Override + public void add(String armoredKey) throws AddKeyException { + throw new AddKeyException(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java new file mode 100644 index 000000000..19591eda8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * Copyright (C) 2011-2014 Thialfihar + * Copyright (C) 2011 Senecaso + * + * 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 org.sufficientlysecure.keychain.keyimport; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +public abstract class Keyserver { + public static class QueryException extends Exception { + private static final long serialVersionUID = 2703768928624654512L; + + public QueryException(String message) { + super(message); + } + } + + public static class TooManyResponses extends Exception { + private static final long serialVersionUID = 2703768928624654513L; + } + + public static class InsufficientQuery extends Exception { + private static final long serialVersionUID = 2703768928624654514L; + } + + public static class AddKeyException extends Exception { + private static final long serialVersionUID = -507574859137295530L; + } + + abstract List search(String query) throws QueryException, TooManyResponses, + InsufficientQuery; + + abstract String get(String keyIdHex) throws QueryException; + + abstract void add(String armoredKey) throws AddKeyException; + + public static String readAll(InputStream in, String encoding) throws IOException { + ByteArrayOutputStream raw = new ByteArrayOutputStream(); + + byte buffer[] = new byte[1 << 16]; + int n = 0; + while ((n = in.read(buffer)) != -1) { + raw.write(buffer, 0, n); + } + + if (encoding == null) { + encoding = "utf8"; + } + return raw.toString(encoding); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index ca9edf7ae..f64282f5f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -36,9 +36,9 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; -import org.sufficientlysecure.keychain.keyimport.HkpKeyServer; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.util.IterableIterator; -import org.sufficientlysecure.keychain.keyimport.KeyServer.AddKeyException; +import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayOutputStream; @@ -100,7 +100,7 @@ public class PgpImportExport { } } - public boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) { + public boolean uploadKeyRingToServer(HkpKeyserver server, PGPPublicKeyRing keyring) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ArmoredOutputStream aos = null; try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 805bb9dad..6f38418ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; @@ -54,9 +55,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; -import org.sufficientlysecure.keychain.keyimport.HkpKeyServer; import org.sufficientlysecure.keychain.util.InputData; -import org.sufficientlysecure.keychain.keyimport.KeybaseKeyServer; +import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressScaler; @@ -724,7 +724,7 @@ public class KeychainIntentService extends IntentService // and dataUri! /* Operation */ - HkpKeyServer server = new HkpKeyServer(keyServer); + HkpKeyserver server = new HkpKeyserver(keyServer); ProviderHelper providerHelper = new ProviderHelper(this); PGPPublicKeyRing keyring = (PGPPublicKeyRing) providerHelper.getPGPKeyRing(dataUri); @@ -746,7 +746,7 @@ public class KeychainIntentService extends IntentService ArrayList entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST); try { - KeybaseKeyServer server = new KeybaseKeyServer(); + KeybaseKeyserver server = new KeybaseKeyserver(); for (ImportKeysListEntry entry : entries) { // the keybase handle is in userId(1) String keybaseId = entry.getExtraData(); @@ -797,7 +797,7 @@ public class KeychainIntentService extends IntentService String keyServer = data.getString(DOWNLOAD_KEY_SERVER); // this downloads the keys and places them into the ImportKeysListEntry entries - HkpKeyServer server = new HkpKeyServer(keyServer); + HkpKeyserver server = new HkpKeyserver(keyServer); for (ImportKeysListEntry entry : entries) { // if available use complete fingerprint for get request diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index bf6abcd6f..e93d717e1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListKeybaseLoader; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader; import org.sufficientlysecure.keychain.util.InputData; -import org.sufficientlysecure.keychain.keyimport.KeyServer; +import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayInputStream; @@ -280,13 +280,13 @@ public class ImportKeysListFragment extends ListFragment implements mAdapter.getCount(), mAdapter.getCount()), AppMsg.STYLE_INFO ).show(); - } else if (error instanceof KeyServer.InsufficientQuery) { + } else if (error instanceof Keyserver.InsufficientQuery) { AppMsg.makeText(getActivity(), R.string.error_keyserver_insufficient_query, AppMsg.STYLE_ALERT).show(); - } else if (error instanceof KeyServer.QueryException) { + } else if (error instanceof Keyserver.QueryException) { AppMsg.makeText(getActivity(), R.string.error_keyserver_query, AppMsg.STYLE_ALERT).show(); - } else if (error instanceof KeyServer.TooManyResponses) { + } else if (error instanceof Keyserver.TooManyResponses) { AppMsg.makeText(getActivity(), R.string.error_keyserver_too_many_responses, AppMsg.STYLE_ALERT).show(); } @@ -300,7 +300,7 @@ public class ImportKeysListFragment extends ListFragment implements mAdapter.getCount(), mAdapter.getCount()), AppMsg.STYLE_INFO ).show(); - } else if (error instanceof KeyServer.QueryException) { + } else if (error instanceof Keyserver.QueryException) { AppMsg.makeText(getActivity(), R.string.error_keyserver_query, AppMsg.STYLE_ALERT).show(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 3fd958bcc..5b936c188 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -130,7 +130,7 @@ public class KeyListFragment extends LoaderFragment /** * Define Adapter and Loader on create of Activity */ - @SuppressLint("NewApi") + @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -141,8 +141,7 @@ public class KeyListFragment extends LoaderFragment mStickyList.setFastScrollEnabled(true); /* - * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only - * available for Android >= 3.0 + * Multi-selection is only available for Android >= 3.0 */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { mStickyList.setFastScrollAlwaysVisible(true); @@ -312,7 +311,7 @@ public class KeyListFragment extends LoaderFragment startActivity(viewIntent); } - @TargetApi(11) + @TargetApi(Build.VERSION_CODES.HONEYCOMB) protected void encrypt(ActionMode mode, long[] masterKeyIds) { Intent intent = new Intent(getActivity(), EncryptActivity.class); intent.setAction(EncryptActivity.ACTION_ENCRYPT); @@ -329,7 +328,7 @@ public class KeyListFragment extends LoaderFragment * @param masterKeyIds * @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not */ - @TargetApi(11) + @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) { // Can only work on singular secret keys if(hasSecret && masterKeyIds.length > 1) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java index 420880522..0fdc019d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java @@ -22,8 +22,8 @@ import android.support.v4.content.AsyncTaskLoader; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; -import org.sufficientlysecure.keychain.keyimport.KeyServer; -import org.sufficientlysecure.keychain.keyimport.KeybaseKeyServer; +import org.sufficientlysecure.keychain.keyimport.Keyserver; +import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; @@ -86,7 +86,7 @@ public class ImportKeysListKeybaseLoader */ private void queryServer(String query) { - KeybaseKeyServer server = new KeybaseKeyServer(); + KeybaseKeyserver server = new KeybaseKeyserver(); try { ArrayList searchResult = server.search(query); @@ -94,11 +94,11 @@ public class ImportKeysListKeybaseLoader mEntryList.addAll(searchResult); mEntryListWrapper = new AsyncTaskResultWrapper>(mEntryList, null); - } catch (KeyServer.InsufficientQuery e) { + } catch (Keyserver.InsufficientQuery e) { mEntryListWrapper = new AsyncTaskResultWrapper>(mEntryList, e); - } catch (KeyServer.QueryException e) { + } catch (Keyserver.QueryException e) { mEntryListWrapper = new AsyncTaskResultWrapper>(mEntryList, e); - } catch (KeyServer.TooManyResponses e) { + } catch (Keyserver.TooManyResponses e) { mEntryListWrapper = new AsyncTaskResultWrapper>(mEntryList, e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java index d5329e6f6..1b8d7d30e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java @@ -21,9 +21,9 @@ import android.content.Context; import android.support.v4.content.AsyncTaskLoader; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.keyimport.HkpKeyServer; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; -import org.sufficientlysecure.keychain.keyimport.KeyServer; +import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; @@ -92,7 +92,7 @@ public class ImportKeysListServerLoader * Query keyserver */ private void queryServer(String query, String keyServer, boolean enforceFingerprint) { - HkpKeyServer server = new HkpKeyServer(keyServer); + HkpKeyserver server = new HkpKeyserver(keyServer); try { ArrayList searchResult = server.search(query); @@ -116,11 +116,11 @@ public class ImportKeysListServerLoader mEntryList.addAll(searchResult); } mEntryListWrapper = new AsyncTaskResultWrapper>(mEntryList, null); - } catch (KeyServer.InsufficientQuery e) { + } catch (Keyserver.InsufficientQuery e) { mEntryListWrapper = new AsyncTaskResultWrapper>(mEntryList, e); - } catch (KeyServer.QueryException e) { + } catch (Keyserver.QueryException e) { mEntryListWrapper = new AsyncTaskResultWrapper>(mEntryList, e); - } catch (KeyServer.TooManyResponses e) { + } catch (Keyserver.TooManyResponses e) { mEntryListWrapper = new AsyncTaskResultWrapper>(mEntryList, e); } } -- cgit v1.2.3