diff --git a/Lib/build.gradle b/Lib/build.gradle index 13fcd23..4bef8d3 100644 --- a/Lib/build.gradle +++ b/Lib/build.gradle @@ -1,19 +1,19 @@ -apply plugin: 'android-library' +apply plugin: 'com.android.library' + +dependencies { + compile 'com.squareup.okhttp:okhttp:2.4.0' +} android { - compileSdkVersion 19 - buildToolsVersion "19.1.0" + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - applicationId "com.textuality.keybase.lib" minSdkVersion 9 targetSdkVersion 19 } - - buildTypes { - release { - runProguard false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' - } + + lintOptions { + abortOnError false } -} +} \ No newline at end of file diff --git a/Lib/src/main/java/com/textuality/keybase/lib/Match.java b/Lib/src/main/java/com/textuality/keybase/lib/Match.java index e71edf2..5916b5f 100644 --- a/Lib/src/main/java/com/textuality/keybase/lib/Match.java +++ b/Lib/src/main/java/com/textuality/keybase/lib/Match.java @@ -21,7 +21,9 @@ import org.json.JSONObject; import java.util.ArrayList; +import java.util.Hashtable; import java.util.List; +import java.util.Set; public class Match { private final JSONObject mComponents; @@ -86,18 +88,48 @@ public int getBitStrength() throws KeybaseException { public List getProofLabels() { ArrayList labels = new ArrayList(); try { - labels.add("twitter.com/" + JWalk.getString(mComponents, "twitter", "val")); + labels.add("Twitter: @" + JWalk.getString(mComponents, "twitter", "val")); } catch (JSONException e) { // s'OK } try { - labels.add("github.com/" + JWalk.getString(mComponents, "github", "val")); + labels.add("GitHub: " + JWalk.getString(mComponents, "github", "val")); + } catch (JSONException e) { + // s'OK + } + try { + labels.add("Reddit: " + JWalk.getString(mComponents, "reddit", "val")); + } catch (JSONException e) { + // s'OK + } + try { + labels.add("Hacker News: " + JWalk.getString(mComponents, "hackernews", "val")); + } catch (JSONException e) { + // s'OK + } + try { + labels.add("Coinbase: " + JWalk.getString(mComponents, "coinbase", "val")); } catch (JSONException e) { // s'OK } try { JSONArray sites = JWalk.getArray(mComponents, "websites"); - labels.add(JWalk.getString(sites.getJSONObject(0), "val")); + Hashtable uniqueNames = new Hashtable(); + int i; + for (i = 0; i < sites.length(); i++) { + uniqueNames.put(JWalk.getString(sites.getJSONObject(i), "val"), 1); + } + Set names = uniqueNames.keySet(); + StringBuilder label = new StringBuilder("Web: "); + i = 0; + for (String name : names) { + label.append(name); + if (i < names.size() - 1) { + label.append(", "); + } + i++; + } + labels.add(label.toString()); } catch (JSONException e) { // s'OK } diff --git a/Lib/src/main/java/com/textuality/keybase/lib/Search.java b/Lib/src/main/java/com/textuality/keybase/lib/Search.java index 98e1b56..e14b512 100644 --- a/Lib/src/main/java/com/textuality/keybase/lib/Search.java +++ b/Lib/src/main/java/com/textuality/keybase/lib/Search.java @@ -18,6 +18,9 @@ import android.util.Log; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -25,18 +28,19 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; import java.net.URLEncoder; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.concurrent.TimeUnit; public class Search { private static final String TAG = "KEYBASE-LIB"; - public static Iterable search(String query) throws KeybaseException { - JSONObject result = getFromKeybase("_/api/1.0/user/autocomplete.json?q=", query); + public static Iterable search(String query, Proxy proxy) throws KeybaseException { + JSONObject result = getFromKeybase("_/api/1.0/user/autocomplete.json?q=", query, proxy); try { return new MatchIterator(JWalk.getArray(result, "completions")); } catch (JSONException e) { @@ -44,30 +48,42 @@ public static Iterable search(String query) throws KeybaseException { } } - public static JSONObject getFromKeybase(String path, String query) throws KeybaseException { + public static JSONObject getFromKeybase(String path, String query, Proxy proxy) throws KeybaseException { try { String url = "https://keybase.io/" + path + URLEncoder.encode(query, "utf8"); 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(); + + OkHttpClient client = new OkHttpClient(); + client.setProxy(proxy); + + if (proxy != null) { + client.setConnectTimeout(30000, TimeUnit.MILLISECONDS); + client.setReadTimeout(40000, TimeUnit.MILLISECONDS); + } else { + client.setConnectTimeout(5000, TimeUnit.MILLISECONDS); // TODO: Reasonable values for keybase + client.setReadTimeout(25000, TimeUnit.MILLISECONDS); + } + + Response resp = client.newCall(new Request.Builder().url(realUrl).build()).execute(); + + int response = resp.code(); + + String text = resp.body().string(); + if (response >= 200 && response < 300) { - String text = snarf(conn.getInputStream()); try { JSONObject json = new JSONObject(text); if (JWalk.getInt(json, "status", "code") != 0) { - throw KeybaseException.queryScrewup("Keybase.io query failed: " + path + "?" + query); + throw KeybaseException.queryScrewup("Keybase.io query failed: " + path + "?" + query + + " using proxy: " + proxy); } return json; } catch (JSONException e) { throw KeybaseException.keybaseScrewup(e); } } else { - String message = snarf(conn.getErrorStream()); - throw KeybaseException.networkScrewup("Keybase.io query error (status=" + response + "): " + message); + throw KeybaseException.networkScrewup("Keybase.io query error (status=" + response + "): " + text); } } catch (Exception e) { throw KeybaseException.networkScrewup(e); diff --git a/Lib/src/main/java/com/textuality/keybase/lib/User.java b/Lib/src/main/java/com/textuality/keybase/lib/User.java index 6548f77..fadab8d 100644 --- a/Lib/src/main/java/com/textuality/keybase/lib/User.java +++ b/Lib/src/main/java/com/textuality/keybase/lib/User.java @@ -20,14 +20,15 @@ import org.json.JSONException; import org.json.JSONObject; +import java.net.Proxy; import java.util.Iterator; public class User { private final JSONObject mJson; - public static User findByUsername(String username) throws KeybaseException { - JSONObject json = Search.getFromKeybase("_/api/1.0/user/lookup.json?username=", username); + public static User findByUsername(String username, Proxy proxy) throws KeybaseException { + JSONObject json = Search.getFromKeybase("_/api/1.0/user/lookup.json?username=", username, proxy); try { json = JWalk.getObject(json, "them"); } catch (JSONException e) { @@ -35,11 +36,11 @@ public static User findByUsername(String username) throws KeybaseException { } return new User(json); } - public static String keyForUsername(String username) throws KeybaseException { - return findByUsername(username).getKey(); + public static String keyForUsername(String username, Proxy proxy) throws KeybaseException { + return findByUsername(username, proxy).getKey(); } - public static User findByFingerprint(String fingerprint) throws KeybaseException { - JSONObject json = Search.getFromKeybase("_/api/1.0/user/lookup.json?key_fingerprint=", fingerprint); + public static User findByFingerprint(String fingerprint, Proxy proxy) throws KeybaseException { + JSONObject json = Search.getFromKeybase("_/api/1.0/user/lookup.json?key_fingerprint=", fingerprint, proxy); try { JSONArray them = JWalk.getArray(json, "them"); if (them.length() != 1) { diff --git a/Lib/src/main/java/com/textuality/keybase/lib/prover/Coinbase.java b/Lib/src/main/java/com/textuality/keybase/lib/prover/Coinbase.java index 7c67881..efa08ac 100644 --- a/Lib/src/main/java/com/textuality/keybase/lib/prover/Coinbase.java +++ b/Lib/src/main/java/com/textuality/keybase/lib/prover/Coinbase.java @@ -24,15 +24,16 @@ import org.json.JSONObject; import java.net.MalformedURLException; +import java.net.Proxy; import java.net.URL; public class Coinbase extends Prover { @Override - public boolean fetchProofData() { + public boolean fetchProofData(Proxy proxy) { try { - JSONObject sigJSON = readSig(mProof.getSigId()); + JSONObject sigJSON = readSig(mProof.getSigId(), proxy); String proofUrl = mProof.getProofUrl(); diff --git a/Lib/src/main/java/com/textuality/keybase/lib/prover/DNS.java b/Lib/src/main/java/com/textuality/keybase/lib/prover/DNS.java index db00999..50d70e7 100644 --- a/Lib/src/main/java/com/textuality/keybase/lib/prover/DNS.java +++ b/Lib/src/main/java/com/textuality/keybase/lib/prover/DNS.java @@ -23,6 +23,7 @@ import org.json.JSONException; import org.json.JSONObject; +import java.net.Proxy; import java.util.List; public class DNS extends Prover { @@ -30,10 +31,10 @@ public class DNS extends Prover { private String mDomain = null; @Override - public boolean fetchProofData() { + public boolean fetchProofData(Proxy proxy) { try { - JSONObject sigJSON = readSig(mProof.getSigId()); + JSONObject sigJSON = readSig(mProof.getSigId(), proxy); // the magic string is the base64 of the SHA of the raw message mShortenedMessageHash = JWalk.getString(sigJSON, "sig_id_short"); diff --git a/Lib/src/main/java/com/textuality/keybase/lib/prover/GitHub.java b/Lib/src/main/java/com/textuality/keybase/lib/prover/GitHub.java index 8c7ec8b..52c05e6 100644 --- a/Lib/src/main/java/com/textuality/keybase/lib/prover/GitHub.java +++ b/Lib/src/main/java/com/textuality/keybase/lib/prover/GitHub.java @@ -24,6 +24,8 @@ import org.json.JSONException; import org.json.JSONObject; +import java.net.Proxy; + public class GitHub extends Prover { private static final String[] sAllowedApiBases = { @@ -31,10 +33,10 @@ public class GitHub extends Prover { }; @Override - public boolean fetchProofData() { + public boolean fetchProofData(Proxy proxy) { try { - JSONObject sigJSON = readSig(mProof.getSigId()); + JSONObject sigJSON = readSig(mProof.getSigId(), proxy); // find the URL for the markdown form of the gist String markdownURL = JWalk.getString(sigJSON, "api_url"); diff --git a/Lib/src/main/java/com/textuality/keybase/lib/prover/HackerNews.java b/Lib/src/main/java/com/textuality/keybase/lib/prover/HackerNews.java index 161694d..cf764c8 100644 --- a/Lib/src/main/java/com/textuality/keybase/lib/prover/HackerNews.java +++ b/Lib/src/main/java/com/textuality/keybase/lib/prover/HackerNews.java @@ -24,15 +24,16 @@ import org.json.JSONObject; import java.net.MalformedURLException; +import java.net.Proxy; import java.net.URL; public class HackerNews extends Prover { @Override - public boolean fetchProofData() { + public boolean fetchProofData(Proxy proxy) { try { - JSONObject sigJSON = readSig(mProof.getSigId()); + JSONObject sigJSON = readSig(mProof.getSigId(), proxy); // the magic string is the base64 of the SHA of the raw message mShortenedMessageHash = JWalk.getString(sigJSON, "sig_id_short"); diff --git a/Lib/src/main/java/com/textuality/keybase/lib/prover/Prover.java b/Lib/src/main/java/com/textuality/keybase/lib/prover/Prover.java index c5e06cb..9742972 100644 --- a/Lib/src/main/java/com/textuality/keybase/lib/prover/Prover.java +++ b/Lib/src/main/java/com/textuality/keybase/lib/prover/Prover.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; +import java.net.Proxy; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -52,15 +53,17 @@ * How to use: * 1. call fetchProofData(), which will exhibit network latency. If it returns false the proof * verification failed; an explanation can be found in the log. - * 2. fetch the PGP message with getPgpMessage(), check that it’s signed with the right fingerprint + * 2. call checkFingerprint(), passing it the fingerprint of the key you’re checking up on; if + * if it returns false the verification failed. + * 3. fetch the PGP message with getPgpMessage(), check that it’s signed with the right fingerprint * (see above). - * 3. Call dnsTxtCheckRequired() and if it returns non-null, the return value is a domain name; + * 4. Call dnsTxtCheckRequired() and if it returns non-null, the return value is a domain name; * retrieve TXT records from that domain and pass them to checkDnsTxt(); if it returns false * the proof verification failed; an explanation can be found in the log. - * 4. call rawMessageCheckRequired() and if it returns true, feed the raw (de-armored) bytes + * 5. call rawMessageCheckRequired() and if it returns true, feed the raw (de-armored) bytes * of the message to checkRawMessageBytes(). if it returns false the proof verification failed; * an explanation can be found in the log. This may exhibit crypto latency. - * 5. Pass the message to validate(), which should have no real latency. If it returns false the + * 6. Pass the message to validate(), which should have no real latency. If it returns false the * proof verification failed; an explanation can be found in the log. */ public abstract class Prover { @@ -68,6 +71,7 @@ public abstract class Prover { String mPgpMessage; String mPayload; String mShortenedMessageHash; + String mFingerprintUsedInProof = null; final Proof mProof; final List mLog = new ArrayList(); @@ -88,12 +92,16 @@ public Prover(Proof proof) { mProof = proof; } - abstract public boolean fetchProofData(); + abstract public boolean fetchProofData(Proxy proxy); public String getPgpMessage() { return mPgpMessage; } + public boolean checkFingerprint(String fingerprint) { + return fingerprint.equalsIgnoreCase(mFingerprintUsedInProof); + } + public boolean validate(String decryptedMessage) { return mPayload.equals(decryptedMessage); } @@ -102,15 +110,16 @@ public List getLog() { return mLog; } - JSONObject readSig(String sigId) throws JSONException, KeybaseException { + JSONObject readSig(String sigId, Proxy proxy) throws JSONException, KeybaseException { // fetch the sig - JSONObject sigJSON = Search.getFromKeybase("_/api/1.0/sig/get.json?sig_id=", sigId); + JSONObject sigJSON = Search.getFromKeybase("_/api/1.0/sig/get.json?sig_id=", sigId, proxy); mLog.add("Successfully retrieved sig from Keybase"); sigJSON = JWalk.getArray(sigJSON, "sigs").getJSONObject(0); mPayload = JWalk.getString(sigJSON, "payload_json"); mPgpMessage = JWalk.getString(sigJSON, "sig"); + mFingerprintUsedInProof = JWalk.getString(sigJSON, "fingerprint"); mLog.add("Extracted payload & message from sig"); diff --git a/Lib/src/main/java/com/textuality/keybase/lib/prover/Reddit.java b/Lib/src/main/java/com/textuality/keybase/lib/prover/Reddit.java index 817a87f..f2ec427 100644 --- a/Lib/src/main/java/com/textuality/keybase/lib/prover/Reddit.java +++ b/Lib/src/main/java/com/textuality/keybase/lib/prover/Reddit.java @@ -26,6 +26,7 @@ import org.json.JSONObject; import java.net.MalformedURLException; +import java.net.Proxy; import java.net.URL; public class Reddit extends Prover { @@ -33,10 +34,10 @@ public class Reddit extends Prover { private String mApiUrl = null; @Override - public boolean fetchProofData() { + public boolean fetchProofData(Proxy proxy) { try { - JSONObject sigJSON = readSig(mProof.getSigId()); + JSONObject sigJSON = readSig(mProof.getSigId(), proxy); // the magic string is the base64 of the SHA of the raw message mShortenedMessageHash = JWalk.getString(sigJSON, "sig_id_short"); diff --git a/Lib/src/main/java/com/textuality/keybase/lib/prover/Twitter.java b/Lib/src/main/java/com/textuality/keybase/lib/prover/Twitter.java index a0f717a..0c01f81 100644 --- a/Lib/src/main/java/com/textuality/keybase/lib/prover/Twitter.java +++ b/Lib/src/main/java/com/textuality/keybase/lib/prover/Twitter.java @@ -24,6 +24,7 @@ import org.json.JSONObject; import java.net.MalformedURLException; +import java.net.Proxy; import java.net.URL; public class Twitter extends Prover { @@ -33,11 +34,11 @@ public Twitter(Proof proof) { } @Override - public boolean fetchProofData() { + public boolean fetchProofData(Proxy proxy) { String tweetUrl = null; try { - JSONObject sigJSON = readSig(mProof.getSigId()); + JSONObject sigJSON = readSig(mProof.getSigId(), proxy); // the magic string is the base64 of the SHA of the raw message mShortenedMessageHash = JWalk.getString(sigJSON, "sig_id_short"); diff --git a/Lib/src/main/java/com/textuality/keybase/lib/prover/Website.java b/Lib/src/main/java/com/textuality/keybase/lib/prover/Website.java index 4811286..e5e3c35 100644 --- a/Lib/src/main/java/com/textuality/keybase/lib/prover/Website.java +++ b/Lib/src/main/java/com/textuality/keybase/lib/prover/Website.java @@ -25,6 +25,7 @@ import org.json.JSONObject; import java.net.MalformedURLException; +import java.net.Proxy; import java.net.URL; public class Website extends Prover { @@ -34,10 +35,10 @@ public Website(Proof proof) { } @Override - public boolean fetchProofData() { + public boolean fetchProofData(Proxy proxy) { try { - JSONObject sigJSON = readSig(mProof.getSigId()); + JSONObject sigJSON = readSig(mProof.getSigId(), proxy); // find the .well-known URL String wellKnownUrl = JWalk.getString(sigJSON, "api_url"); diff --git a/Lib/src/main/res/values/styles.xml b/Lib/src/main/res/values/styles.xml index 6ce89c7..d5ff31a 100644 --- a/Lib/src/main/res/values/styles.xml +++ b/Lib/src/main/res/values/styles.xml @@ -13,7 +13,7 @@ - diff --git a/build.gradle b/build.gradle index 0d9b5eb..b53db4f 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,6 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:0.12.+' + classpath 'com.android.tools.build:gradle:1.0.0-rc3' } }