From 92b8d874ed0d7e8f835dbd8ce4205e74841d7162 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 15 Jan 2015 16:59:11 +0100 Subject: affirmation -> linked identity --- .../keychain/pgp/WrappedUserAttribute.java | 1 + .../pgp/affirmation/AffirmationResource.java | 125 --------------- .../keychain/pgp/affirmation/LinkedIdentity.java | 173 --------------------- .../pgp/affirmation/resources/DnsResource.java | 70 --------- .../resources/GenericHttpsResource.java | 104 ------------- .../pgp/affirmation/resources/TwitterResource.java | 124 --------------- .../pgp/affirmation/resources/UnknownResource.java | 22 --- .../keychain/pgp/linked/LinkedIdentity.java | 172 ++++++++++++++++++++ .../keychain/pgp/linked/LinkedResource.java | 125 +++++++++++++++ .../keychain/pgp/linked/resources/DnsResource.java | 70 +++++++++ .../pgp/linked/resources/GenericHttpsResource.java | 103 ++++++++++++ .../pgp/linked/resources/TwitterResource.java | 124 +++++++++++++++ .../pgp/linked/resources/UnknownResource.java | 21 +++ 13 files changed, 616 insertions(+), 618 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/AffirmationResource.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/LinkedIdentity.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/DnsResource.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/GenericHttpsResource.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/TwitterResource.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/UnknownResource.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedIdentity.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedResource.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/DnsResource.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/GenericHttpsResource.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/TwitterResource.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/UnknownResource.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java index 038718c65..370c3f89d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java @@ -39,6 +39,7 @@ public class WrappedUserAttribute implements Serializable { public static final int UAT_NONE = 0; public static final int UAT_IMAGE = UserAttributeSubpacketTags.IMAGE_ATTRIBUTE; + public static final int UAT_LINKED_ID = 100; private PGPUserAttributeSubpacketVector mVector; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/AffirmationResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/AffirmationResource.java deleted file mode 100644 index 80398396e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/AffirmationResource.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.sufficientlysecure.keychain.pgp.affirmation; - -import android.content.Context; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.affirmation.resources.GenericHttpsResource; -import org.sufficientlysecure.keychain.pgp.affirmation.resources.UnknownResource; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.Log; - -import java.net.URI; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public abstract class AffirmationResource { - - protected final URI mSubUri; - protected final Set mFlags; - protected final HashMap mParams; - - static Pattern magicPattern = - Pattern.compile("\\[Verifying my PGP key: pgpid\\+cookie:([a-zA-Z0-9]+)#([a-zA-Z0-9]+)\\]"); - - protected AffirmationResource(Set flags, HashMap params, URI uri) { - mFlags = flags; - mParams = params; - mSubUri = uri; - } - - public Set getFlags () { - return new HashSet(mFlags); - } - - public HashMap getParams () { - return new HashMap(mParams); - } - - public URI getSubUri () { - return mSubUri; - } - - public static String generate (Context context, byte[] fingerprint, String nonce) { - - return "[Verifying my PGP key: pgpid+cookie:" - + KeyFormattingUtils.convertFingerprintToHex(fingerprint) + "#" + nonce + "]"; - - } - - public LinkedVerifyResult verify(byte[] fingerprint, String nonce) { - - OperationLog log = new OperationLog(); - log.add(LogType.MSG_LV, 0); - - // Try to fetch resource. Logs for itself - String res = fetchResource(log, 1); - if (res == null) { - // if this is null, an error was recorded in fetchResource above - return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); - } - - Log.d(Constants.TAG, "Resource data: '" + res + "'"); - - return verifyString(log, 1, res, nonce, fingerprint); - - } - - protected abstract String fetchResource (OperationLog log, int indent); - - protected Matcher matchResource (OperationLog log, int indent, String res) { - return magicPattern.matcher(res); - } - - protected LinkedVerifyResult verifyString (OperationLog log, int indent, - String res, - String nonce, byte[] fingerprint) { - - log.add(LogType.MSG_LV_MATCH, indent); - Matcher match = matchResource(log, indent+1, res); - if (!match.find()) { - log.add(LogType.MSG_LV_MATCH_ERROR, 2); - return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); - } - - String candidateFp = match.group(1).toLowerCase(); - String nonceCandidate = match.group(2).toLowerCase(); - - String fp = KeyFormattingUtils.convertFingerprintToHex(fingerprint); - - if (!fp.equals(candidateFp)) { - log.add(LogType.MSG_LV_FP_ERROR, indent); - return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); - } - log.add(LogType.MSG_LV_FP_OK, indent); - - if (!nonce.equals(nonceCandidate)) { - log.add(LogType.MSG_LV_NONCE_ERROR, indent); - return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); - } - - log.add(LogType.MSG_LV_NONCE_OK, indent); - return new LinkedVerifyResult(LinkedVerifyResult.RESULT_OK, log); - - } - - public static AffirmationResource findResourceType - (Set flags, HashMap params, URI uri) { - - AffirmationResource res; - - res = GenericHttpsResource.create(flags, params, uri); - if (res != null) { - return res; - } - - return new UnknownResource(flags, params, uri); - - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/LinkedIdentity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/LinkedIdentity.java deleted file mode 100644 index 00d898df9..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/LinkedIdentity.java +++ /dev/null @@ -1,173 +0,0 @@ -package org.sufficientlysecure.keychain.pgp.affirmation; - -import org.spongycastle.bcpg.UserAttributeSubpacket; -import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector; -import org.spongycastle.util.Strings; -import org.spongycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.util.Log; - -import java.net.URI; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map.Entry; -import java.util.Set; - -public class LinkedIdentity { - - protected byte[] mData; - public final String mNonce; - public final URI mSubUri; - final Set mFlags; - final HashMap mParams; - - protected LinkedIdentity(byte[] data, String nonce, Set flags, - HashMap params, URI subUri) { - if ( ! nonce.matches("[0-9a-zA-Z]+")) { - throw new AssertionError("bug: nonce must be hexstring!"); - } - - mData = data; - mNonce = nonce; - mFlags = flags; - mParams = params; - mSubUri = subUri; - } - - LinkedIdentity(String nonce, Set flags, - HashMap params, URI subUri) { - this(null, nonce, flags, params, subUri); - } - - public byte[] getEncoded() { - if (mData != null) { - return mData; - } - - StringBuilder b = new StringBuilder(); - b.append("pgpid:"); - - // add flags - if (mFlags != null) { - boolean first = true; - for (String flag : mFlags) { - if (!first) { - b.append(";"); - } - first = false; - b.append(flag); - } - } - - // add parameters - if (mParams != null) { - boolean first = true; - Iterator> it = mParams.entrySet().iterator(); - while (it.hasNext()) { - if (!first) { - b.append(";"); - } - first = false; - Entry entry = it.next(); - b.append(entry.getKey()).append("=").append(entry.getValue()); - } - } - - b.append("@"); - b.append(mSubUri); - - byte[] nonceBytes = Hex.decode(mNonce); - if (nonceBytes.length != 12) { - throw new AssertionError("nonce must be 12 bytes"); - } - byte[] data = Strings.toUTF8ByteArray(b.toString()); - - byte[] result = new byte[data.length+12]; - System.arraycopy(nonceBytes, 0, result, 0, 12); - System.arraycopy(data, 0, result, 12, data.length); - - return result; - } - - /** This method parses an affirmation from a UserAttributeSubpacket, or returns null if the - * subpacket can not be parsed as a valid affirmation. - */ - static LinkedIdentity parseAffirmation(UserAttributeSubpacket subpacket) { - if (subpacket.getType() != 100) { - return null; - } - - byte[] data = subpacket.getData(); - String nonce = Hex.toHexString(data, 0, 12); - - try { - return parseUri(nonce, Strings.fromUTF8ByteArray(Arrays.copyOfRange(data, 12, data.length))); - - } catch (IllegalArgumentException e) { - Log.e(Constants.TAG, "error parsing uri in (suspected) affirmation packet"); - return null; - } - } - - protected static LinkedIdentity parseUri (String nonce, String uriString) { - URI uri = URI.create(uriString); - - if ("pgpid".equals(uri.getScheme())) { - Log.e(Constants.TAG, "unknown uri scheme in (suspected) affirmation packet"); - return null; - } - - if (!uri.isOpaque()) { - Log.e(Constants.TAG, "non-opaque uri in (suspected) affirmation packet"); - return null; - } - - String specific = uri.getSchemeSpecificPart(); - if (!specific.contains("@")) { - Log.e(Constants.TAG, "unknown uri scheme in affirmation packet"); - return null; - } - - String[] pieces = specific.split("@", 2); - URI subUri = URI.create(pieces[1]); - - Set flags = new HashSet(); - HashMap params = new HashMap(); - { - String[] rawParams = pieces[0].split(";"); - for (String param : rawParams) { - String[] p = param.split("=", 2); - if (p.length == 1) { - flags.add(param); - } else { - params.put(p[0], p[1]); - } - } - } - - return new LinkedIdentity(nonce, flags, params, subUri); - - } - - public static LinkedIdentity fromResource (AffirmationResource res, String nonce) { - return new LinkedIdentity(nonce, res.getFlags(), res.getParams(), res.getSubUri()); - } - - public WrappedUserAttribute toUserAttribute () { - return WrappedUserAttribute.fromSubpacket(WrappedUserAttribute.UAT_LINKED_ID, getEncoded()); - } - - public static String generateNonce() { - // TODO make this actually random - // byte[] data = new byte[96]; - // new SecureRandom().nextBytes(data); - // return Hex.toHexString(data); - - // debug for now - return "0123456789abcdef01234567"; - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/DnsResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/DnsResource.java deleted file mode 100644 index 272aa5dcd..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/DnsResource.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.sufficientlysecure.keychain.pgp.affirmation.resources; - -import android.content.Context; - -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.affirmation.AffirmationResource; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; - -import java.net.URI; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import de.measite.minidns.Client; -import de.measite.minidns.DNSMessage; -import de.measite.minidns.Question; -import de.measite.minidns.Record; -import de.measite.minidns.Record.TYPE; -import de.measite.minidns.record.TXT; - -public class DnsResource extends AffirmationResource { - - static Pattern magicPattern = - Pattern.compile("pgpid\\+cookie=([a-zA-Z0-9]+)(?:#|;)([a-zA-Z0-9]+)"); - - DnsResource(Set flags, HashMap params, URI uri) { - super(flags, params, uri); - } - - public static String generateText (Context context, byte[] fingerprint, String nonce) { - - return "pgpid+cookie=" - + KeyFormattingUtils.convertFingerprintToHex(fingerprint) + ";" + nonce + ""; - - } - - public static DnsResource createNew (String domain) { - HashSet flags = new HashSet(); - HashMap params = new HashMap(); - URI uri = URI.create("dns:" + domain); - return create(flags, params, uri); - } - - public static DnsResource create(Set flags, HashMap params, URI uri) { - if ( ! ("dns".equals(uri.getScheme()) - && (flags == null || flags.isEmpty()) - && (params == null || params.isEmpty()))) { - return null; - } - return new DnsResource(flags, params, uri); - } - - @Override - protected String fetchResource (OperationLog log, int indent) { - - Client c = new Client(); - DNSMessage msg = c.query(new Question("mugenguild.com", TYPE.TXT)); - Record aw = msg.getAnswers()[0]; - TXT txt = (TXT) aw.getPayload(); - return txt.getText().toLowerCase(); - - } - - @Override - protected Matcher matchResource(OperationLog log, int indent, String res) { - return magicPattern.matcher(res); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/GenericHttpsResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/GenericHttpsResource.java deleted file mode 100644 index 74c0689b5..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/GenericHttpsResource.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.sufficientlysecure.keychain.pgp.affirmation.resources; - -import android.content.Context; - -import com.textuality.keybase.lib.Search; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.affirmation.AffirmationResource; -import org.sufficientlysecure.keychain.pgp.affirmation.LinkedIdentity; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.Log; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -import javax.net.ssl.HttpsURLConnection; - -public class GenericHttpsResource extends AffirmationResource { - - GenericHttpsResource(Set flags, HashMap params, URI uri) { - super(flags, params, uri); - } - - public static String generateText (Context context, byte[] fingerprint, String nonce) { - String cookie = AffirmationResource.generate(context, fingerprint, nonce); - - return String.format(context.getResources().getString(R.string.linked_id_generic_text), - cookie, "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprint).substring(24)); - } - - @Override - protected String fetchResource (OperationLog log, int indent) { - - log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString()); - indent += 1; - - try { - - HttpsURLConnection conn = null; - URL url = mSubUri.toURL(); - int status = 0; - int redirects = 0; - - while (redirects < 5) { - conn = (HttpsURLConnection) url.openConnection(); - conn.addRequestProperty("User-Agent", "OpenKeychain"); - conn.setConnectTimeout(5000); - conn.setReadTimeout(25000); - conn.connect(); - status = conn.getResponseCode(); - if (status == 301) { - redirects++; - url = new URL(conn.getHeaderFields().get("Location").get(0)); - log.add(LogType.MSG_LV_FETCH_REDIR, indent, url.toString()); - } else { - break; - } - } - - if (status >= 200 && status < 300) { - log.add(LogType.MSG_LV_FETCH_OK, indent, Integer.toString(status)); - return Search.snarf(conn.getInputStream()); - } else { - // log verbose output to logcat - Log.e(Constants.TAG, Search.snarf(conn.getErrorStream())); - log.add(LogType.MSG_LV_FETCH_ERROR, indent, Integer.toString(status)); - return null; - } - - } catch (MalformedURLException e) { - log.add(LogType.MSG_LV_FETCH_ERROR_URL, indent); - return null; - } catch (IOException e) { - log.add(LogType.MSG_LV_FETCH_ERROR_IO, indent); - return null; - } - - } - - public static GenericHttpsResource createNew (URI uri) { - HashSet flags = new HashSet(); - flags.add("generic"); - HashMap params = new HashMap(); - return create(flags, params, uri); - } - - public static GenericHttpsResource create(Set flags, HashMap params, URI uri) { - if ( ! ("https".equals(uri.getScheme()) - && flags != null && flags.size() == 1 && flags.contains("generic") - && (params == null || params.isEmpty()))) { - return null; - } - return new GenericHttpsResource(flags, params, uri); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/TwitterResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/TwitterResource.java deleted file mode 100644 index f131a8da2..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/TwitterResource.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.sufficientlysecure.keychain.pgp.affirmation.resources; - -import android.content.Context; -import android.util.Base64; - -import com.textuality.keybase.lib.JWalk; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.BasicHttpParams; -import org.json.JSONException; -import org.json.JSONObject; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.affirmation.AffirmationResource; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLEncoder; -import java.util.HashMap; -import java.util.Set; - -public class TwitterResource extends AffirmationResource { - - TwitterResource(Set flags, HashMap params, URI uri) { - super(flags, params, uri); - } - - public static String generateText (Context context, byte[] fingerprint, String nonce) { - // nothing special here for now, might change this later - return AffirmationResource.generate(context, fingerprint, nonce); - } - - private String getTwitterStream(String screenName) { - String results = null; - - // Step 1: Encode consumer key and secret - try { - // URL encode the consumer key and secret - String urlApiKey = URLEncoder.encode("6IhPnWbYxASAoAzH2QaUtHD0J", "UTF-8"); - String urlApiSecret = URLEncoder.encode("L0GnuiOnapWbSBbQtLIqtpeS5BTtvh06dmoMoKQfHQS8UwHuWm", "UTF-8"); - - // Concatenate the encoded consumer key, a colon character, and the - // encoded consumer secret - String combined = urlApiKey + ":" + urlApiSecret; - - // Base64 encode the string - String base64Encoded = Base64.encodeToString(combined.getBytes(), Base64.NO_WRAP); - - // Step 2: Obtain a bearer token - HttpPost httpPost = new HttpPost("https://api.twitter.com/oauth2/token"); - httpPost.setHeader("Authorization", "Basic " + base64Encoded); - httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); - httpPost.setEntity(new StringEntity("grant_type=client_credentials")); - JSONObject rawAuthorization = new JSONObject(getResponseBody(httpPost)); - String auth = JWalk.getString(rawAuthorization, "access_token"); - - // Applications should verify that the value associated with the - // token_type key of the returned object is bearer - if (auth != null && JWalk.getString(rawAuthorization, "token_type").equals("bearer")) { - - // Step 3: Authenticate API requests with bearer token - HttpGet httpGet = - new HttpGet("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=" + screenName); - - // construct a normal HTTPS request and include an Authorization - // header with the value of Bearer <> - httpGet.setHeader("Authorization", "Bearer " + auth); - httpGet.setHeader("Content-Type", "application/json"); - // update the results with the body of the response - results = getResponseBody(httpGet); - } - } catch (UnsupportedEncodingException ex) { - } catch (JSONException ex) { - } catch (IllegalStateException ex1) { - } - return results; - } - - private static String getResponseBody(HttpRequestBase request) { - StringBuilder sb = new StringBuilder(); - try { - - DefaultHttpClient httpClient = new DefaultHttpClient(new BasicHttpParams()); - HttpResponse response = httpClient.execute(request); - int statusCode = response.getStatusLine().getStatusCode(); - String reason = response.getStatusLine().getReasonPhrase(); - - if (statusCode == 200) { - - HttpEntity entity = response.getEntity(); - InputStream inputStream = entity.getContent(); - - BufferedReader bReader = new BufferedReader( - new InputStreamReader(inputStream, "UTF-8"), 8); - String line = null; - while ((line = bReader.readLine()) != null) { - sb.append(line); - } - } else { - sb.append(reason); - } - } catch (UnsupportedEncodingException ex) { - } catch (ClientProtocolException ex1) { - } catch (IOException ex2) { - } - return sb.toString(); - } - - @Override - protected String fetchResource(OperationLog log, int indent) { - return getTwitterStream("Valodim"); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/UnknownResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/UnknownResource.java deleted file mode 100644 index 2f67c948e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/affirmation/resources/UnknownResource.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.sufficientlysecure.keychain.pgp.affirmation.resources; - -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.affirmation.AffirmationResource; - -import java.io.IOException; -import java.net.URI; -import java.util.HashMap; -import java.util.Set; - -public class UnknownResource extends AffirmationResource { - - public UnknownResource(Set flags, HashMap params, URI uri) { - super(flags, params, uri); - } - - @Override - protected String fetchResource(OperationLog log, int indent) { - return null; - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedIdentity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedIdentity.java new file mode 100644 index 000000000..3f7501adc --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedIdentity.java @@ -0,0 +1,172 @@ +package org.sufficientlysecure.keychain.pgp.linked; + +import org.spongycastle.bcpg.UserAttributeSubpacket; +import org.spongycastle.util.Strings; +import org.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; +import org.sufficientlysecure.keychain.util.Log; + +import java.net.URI; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Set; + +public class LinkedIdentity { + + protected byte[] mData; + public final String mNonce; + public final URI mSubUri; + final Set mFlags; + final HashMap mParams; + + protected LinkedIdentity(byte[] data, String nonce, Set flags, + HashMap params, URI subUri) { + if ( ! nonce.matches("[0-9a-zA-Z]+")) { + throw new AssertionError("bug: nonce must be hexstring!"); + } + + mData = data; + mNonce = nonce; + mFlags = flags; + mParams = params; + mSubUri = subUri; + } + + LinkedIdentity(String nonce, Set flags, + HashMap params, URI subUri) { + this(null, nonce, flags, params, subUri); + } + + public byte[] getEncoded() { + if (mData != null) { + return mData; + } + + StringBuilder b = new StringBuilder(); + b.append("pgpid:"); + + // add flags + if (mFlags != null) { + boolean first = true; + for (String flag : mFlags) { + if (!first) { + b.append(";"); + } + first = false; + b.append(flag); + } + } + + // add parameters + if (mParams != null) { + boolean first = true; + Iterator> it = mParams.entrySet().iterator(); + while (it.hasNext()) { + if (!first) { + b.append(";"); + } + first = false; + Entry entry = it.next(); + b.append(entry.getKey()).append("=").append(entry.getValue()); + } + } + + b.append("@"); + b.append(mSubUri); + + byte[] nonceBytes = Hex.decode(mNonce); + if (nonceBytes.length != 12) { + throw new AssertionError("nonce must be 12 bytes"); + } + byte[] data = Strings.toUTF8ByteArray(b.toString()); + + byte[] result = new byte[data.length+12]; + System.arraycopy(nonceBytes, 0, result, 0, 12); + System.arraycopy(data, 0, result, 12, data.length); + + return result; + } + + /** This method parses a linked id from a UserAttributeSubpacket, or returns null if the + * subpacket can not be parsed as a valid linked id. + */ + static LinkedIdentity parseAttributeSubpacket(UserAttributeSubpacket subpacket) { + if (subpacket.getType() != 100) { + return null; + } + + byte[] data = subpacket.getData(); + String nonce = Hex.toHexString(data, 0, 12); + + try { + return parseUri(nonce, Strings.fromUTF8ByteArray(Arrays.copyOfRange(data, 12, data.length))); + + } catch (IllegalArgumentException e) { + Log.e(Constants.TAG, "error parsing uri in (suspected) linked id packet"); + return null; + } + } + + protected static LinkedIdentity parseUri (String nonce, String uriString) { + URI uri = URI.create(uriString); + + if ("pgpid".equals(uri.getScheme())) { + Log.e(Constants.TAG, "unknown uri scheme in (suspected) linked id packet"); + return null; + } + + if (!uri.isOpaque()) { + Log.e(Constants.TAG, "non-opaque uri in (suspected) linked id packet"); + return null; + } + + String specific = uri.getSchemeSpecificPart(); + if (!specific.contains("@")) { + Log.e(Constants.TAG, "unknown uri scheme in linked id packet"); + return null; + } + + String[] pieces = specific.split("@", 2); + URI subUri = URI.create(pieces[1]); + + Set flags = new HashSet(); + HashMap params = new HashMap(); + { + String[] rawParams = pieces[0].split(";"); + for (String param : rawParams) { + String[] p = param.split("=", 2); + if (p.length == 1) { + flags.add(param); + } else { + params.put(p[0], p[1]); + } + } + } + + return new LinkedIdentity(nonce, flags, params, subUri); + + } + + public static LinkedIdentity fromResource (LinkedResource res, String nonce) { + return new LinkedIdentity(nonce, res.getFlags(), res.getParams(), res.getSubUri()); + } + + public WrappedUserAttribute toUserAttribute () { + return WrappedUserAttribute.fromSubpacket(WrappedUserAttribute.UAT_LINKED_ID, getEncoded()); + } + + public static String generateNonce() { + // TODO make this actually random + // byte[] data = new byte[96]; + // new SecureRandom().nextBytes(data); + // return Hex.toHexString(data); + + // debug for now + return "0123456789abcdef01234567"; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedResource.java new file mode 100644 index 000000000..a3f288dbb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedResource.java @@ -0,0 +1,125 @@ +package org.sufficientlysecure.keychain.pgp.linked; + +import android.content.Context; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.linked.resources.GenericHttpsResource; +import org.sufficientlysecure.keychain.pgp.linked.resources.UnknownResource; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; + +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class LinkedResource { + + protected final URI mSubUri; + protected final Set mFlags; + protected final HashMap mParams; + + static Pattern magicPattern = + Pattern.compile("\\[Verifying my PGP key: pgpid\\+cookie:([a-zA-Z0-9]+)#([a-zA-Z0-9]+)\\]"); + + protected LinkedResource(Set flags, HashMap params, URI uri) { + mFlags = flags; + mParams = params; + mSubUri = uri; + } + + public Set getFlags () { + return new HashSet(mFlags); + } + + public HashMap getParams () { + return new HashMap(mParams); + } + + public URI getSubUri () { + return mSubUri; + } + + public static String generate (Context context, byte[] fingerprint, String nonce) { + + return "[Verifying my PGP key: pgpid+cookie:" + + KeyFormattingUtils.convertFingerprintToHex(fingerprint) + "#" + nonce + "]"; + + } + + public LinkedVerifyResult verify(byte[] fingerprint, String nonce) { + + OperationLog log = new OperationLog(); + log.add(LogType.MSG_LV, 0); + + // Try to fetch resource. Logs for itself + String res = fetchResource(log, 1); + if (res == null) { + // if this is null, an error was recorded in fetchResource above + return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); + } + + Log.d(Constants.TAG, "Resource data: '" + res + "'"); + + return verifyString(log, 1, res, nonce, fingerprint); + + } + + protected abstract String fetchResource (OperationLog log, int indent); + + protected Matcher matchResource (OperationLog log, int indent, String res) { + return magicPattern.matcher(res); + } + + protected LinkedVerifyResult verifyString (OperationLog log, int indent, + String res, + String nonce, byte[] fingerprint) { + + log.add(LogType.MSG_LV_MATCH, indent); + Matcher match = matchResource(log, indent+1, res); + if (!match.find()) { + log.add(LogType.MSG_LV_MATCH_ERROR, 2); + return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); + } + + String candidateFp = match.group(1).toLowerCase(); + String nonceCandidate = match.group(2).toLowerCase(); + + String fp = KeyFormattingUtils.convertFingerprintToHex(fingerprint); + + if (!fp.equals(candidateFp)) { + log.add(LogType.MSG_LV_FP_ERROR, indent); + return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); + } + log.add(LogType.MSG_LV_FP_OK, indent); + + if (!nonce.equals(nonceCandidate)) { + log.add(LogType.MSG_LV_NONCE_ERROR, indent); + return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); + } + + log.add(LogType.MSG_LV_NONCE_OK, indent); + return new LinkedVerifyResult(LinkedVerifyResult.RESULT_OK, log); + + } + + public static LinkedResource findResourceType + (Set flags, HashMap params, URI uri) { + + LinkedResource res; + + res = GenericHttpsResource.create(flags, params, uri); + if (res != null) { + return res; + } + + return new UnknownResource(flags, params, uri); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/DnsResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/DnsResource.java new file mode 100644 index 000000000..1c66ffeca --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/DnsResource.java @@ -0,0 +1,70 @@ +package org.sufficientlysecure.keychain.pgp.linked.resources; + +import android.content.Context; + +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.linked.LinkedResource; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.measite.minidns.Client; +import de.measite.minidns.DNSMessage; +import de.measite.minidns.Question; +import de.measite.minidns.Record; +import de.measite.minidns.Record.TYPE; +import de.measite.minidns.record.TXT; + +public class DnsResource extends LinkedResource { + + static Pattern magicPattern = + Pattern.compile("pgpid\\+cookie=([a-zA-Z0-9]+)(?:#|;)([a-zA-Z0-9]+)"); + + DnsResource(Set flags, HashMap params, URI uri) { + super(flags, params, uri); + } + + public static String generateText (Context context, byte[] fingerprint, String nonce) { + + return "pgpid+cookie=" + + KeyFormattingUtils.convertFingerprintToHex(fingerprint) + ";" + nonce + ""; + + } + + public static DnsResource createNew (String domain) { + HashSet flags = new HashSet(); + HashMap params = new HashMap(); + URI uri = URI.create("dns:" + domain); + return create(flags, params, uri); + } + + public static DnsResource create(Set flags, HashMap params, URI uri) { + if ( ! ("dns".equals(uri.getScheme()) + && (flags == null || flags.isEmpty()) + && (params == null || params.isEmpty()))) { + return null; + } + return new DnsResource(flags, params, uri); + } + + @Override + protected String fetchResource (OperationLog log, int indent) { + + Client c = new Client(); + DNSMessage msg = c.query(new Question("mugenguild.com", TYPE.TXT)); + Record aw = msg.getAnswers()[0]; + TXT txt = (TXT) aw.getPayload(); + return txt.getText().toLowerCase(); + + } + + @Override + protected Matcher matchResource(OperationLog log, int indent, String res) { + return magicPattern.matcher(res); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/GenericHttpsResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/GenericHttpsResource.java new file mode 100644 index 000000000..abe773f6c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/GenericHttpsResource.java @@ -0,0 +1,103 @@ +package org.sufficientlysecure.keychain.pgp.linked.resources; + +import android.content.Context; + +import com.textuality.keybase.lib.Search; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.linked.LinkedResource; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +import javax.net.ssl.HttpsURLConnection; + +public class GenericHttpsResource extends LinkedResource { + + GenericHttpsResource(Set flags, HashMap params, URI uri) { + super(flags, params, uri); + } + + public static String generateText (Context context, byte[] fingerprint, String nonce) { + String cookie = LinkedResource.generate(context, fingerprint, nonce); + + return String.format(context.getResources().getString(R.string.linked_id_generic_text), + cookie, "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprint).substring(24)); + } + + @Override + protected String fetchResource (OperationLog log, int indent) { + + log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString()); + indent += 1; + + try { + + HttpsURLConnection conn = null; + URL url = mSubUri.toURL(); + int status = 0; + int redirects = 0; + + while (redirects < 5) { + conn = (HttpsURLConnection) url.openConnection(); + conn.addRequestProperty("User-Agent", "OpenKeychain"); + conn.setConnectTimeout(5000); + conn.setReadTimeout(25000); + conn.connect(); + status = conn.getResponseCode(); + if (status == 301) { + redirects++; + url = new URL(conn.getHeaderFields().get("Location").get(0)); + log.add(LogType.MSG_LV_FETCH_REDIR, indent, url.toString()); + } else { + break; + } + } + + if (status >= 200 && status < 300) { + log.add(LogType.MSG_LV_FETCH_OK, indent, Integer.toString(status)); + return Search.snarf(conn.getInputStream()); + } else { + // log verbose output to logcat + Log.e(Constants.TAG, Search.snarf(conn.getErrorStream())); + log.add(LogType.MSG_LV_FETCH_ERROR, indent, Integer.toString(status)); + return null; + } + + } catch (MalformedURLException e) { + log.add(LogType.MSG_LV_FETCH_ERROR_URL, indent); + return null; + } catch (IOException e) { + log.add(LogType.MSG_LV_FETCH_ERROR_IO, indent); + return null; + } + + } + + public static GenericHttpsResource createNew (URI uri) { + HashSet flags = new HashSet(); + flags.add("generic"); + HashMap params = new HashMap(); + return create(flags, params, uri); + } + + public static GenericHttpsResource create(Set flags, HashMap params, URI uri) { + if ( ! ("https".equals(uri.getScheme()) + && flags != null && flags.size() == 1 && flags.contains("generic") + && (params == null || params.isEmpty()))) { + return null; + } + return new GenericHttpsResource(flags, params, uri); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/TwitterResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/TwitterResource.java new file mode 100644 index 000000000..1b0db1fa1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/TwitterResource.java @@ -0,0 +1,124 @@ +package org.sufficientlysecure.keychain.pgp.linked.resources; + +import android.content.Context; +import android.util.Base64; + +import com.textuality.keybase.lib.JWalk; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.BasicHttpParams; +import org.json.JSONException; +import org.json.JSONObject; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.linked.LinkedResource; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Set; + +public class TwitterResource extends LinkedResource { + + TwitterResource(Set flags, HashMap params, URI uri) { + super(flags, params, uri); + } + + public static String generateText (Context context, byte[] fingerprint, String nonce) { + // nothing special here for now, might change this later + return LinkedResource.generate(context, fingerprint, nonce); + } + + private String getTwitterStream(String screenName) { + String results = null; + + // Step 1: Encode consumer key and secret + try { + // URL encode the consumer key and secret + String urlApiKey = URLEncoder.encode("6IhPnWbYxASAoAzH2QaUtHD0J", "UTF-8"); + String urlApiSecret = URLEncoder.encode("L0GnuiOnapWbSBbQtLIqtpeS5BTtvh06dmoMoKQfHQS8UwHuWm", "UTF-8"); + + // Concatenate the encoded consumer key, a colon character, and the + // encoded consumer secret + String combined = urlApiKey + ":" + urlApiSecret; + + // Base64 encode the string + String base64Encoded = Base64.encodeToString(combined.getBytes(), Base64.NO_WRAP); + + // Step 2: Obtain a bearer token + HttpPost httpPost = new HttpPost("https://api.twitter.com/oauth2/token"); + httpPost.setHeader("Authorization", "Basic " + base64Encoded); + httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); + httpPost.setEntity(new StringEntity("grant_type=client_credentials")); + JSONObject rawAuthorization = new JSONObject(getResponseBody(httpPost)); + String auth = JWalk.getString(rawAuthorization, "access_token"); + + // Applications should verify that the value associated with the + // token_type key of the returned object is bearer + if (auth != null && JWalk.getString(rawAuthorization, "token_type").equals("bearer")) { + + // Step 3: Authenticate API requests with bearer token + HttpGet httpGet = + new HttpGet("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=" + screenName); + + // construct a normal HTTPS request and include an Authorization + // header with the value of Bearer <> + httpGet.setHeader("Authorization", "Bearer " + auth); + httpGet.setHeader("Content-Type", "application/json"); + // update the results with the body of the response + results = getResponseBody(httpGet); + } + } catch (UnsupportedEncodingException ex) { + } catch (JSONException ex) { + } catch (IllegalStateException ex1) { + } + return results; + } + + private static String getResponseBody(HttpRequestBase request) { + StringBuilder sb = new StringBuilder(); + try { + + DefaultHttpClient httpClient = new DefaultHttpClient(new BasicHttpParams()); + HttpResponse response = httpClient.execute(request); + int statusCode = response.getStatusLine().getStatusCode(); + String reason = response.getStatusLine().getReasonPhrase(); + + if (statusCode == 200) { + + HttpEntity entity = response.getEntity(); + InputStream inputStream = entity.getContent(); + + BufferedReader bReader = new BufferedReader( + new InputStreamReader(inputStream, "UTF-8"), 8); + String line = null; + while ((line = bReader.readLine()) != null) { + sb.append(line); + } + } else { + sb.append(reason); + } + } catch (UnsupportedEncodingException ex) { + } catch (ClientProtocolException ex1) { + } catch (IOException ex2) { + } + return sb.toString(); + } + + @Override + protected String fetchResource(OperationLog log, int indent) { + return getTwitterStream("Valodim"); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/UnknownResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/UnknownResource.java new file mode 100644 index 000000000..ae99cdd86 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/UnknownResource.java @@ -0,0 +1,21 @@ +package org.sufficientlysecure.keychain.pgp.linked.resources; + +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.linked.LinkedResource; + +import java.net.URI; +import java.util.HashMap; +import java.util.Set; + +public class UnknownResource extends LinkedResource { + + public UnknownResource(Set flags, HashMap params, URI uri) { + super(flags, params, uri); + } + + @Override + protected String fetchResource(OperationLog log, int indent) { + return null; + } + +} -- cgit v1.2.3