diff options
Diffstat (limited to 'OpenKeychain/src/main/java')
24 files changed, 2476 insertions, 1 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/LinkedVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/LinkedVerifyResult.java new file mode 100644 index 000000000..42e9ec3f0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/LinkedVerifyResult.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.operations.results; + +import android.os.Parcel; + +public class LinkedVerifyResult extends OperationResult { + + public LinkedVerifyResult(int result, OperationLog log) { + super(result, log); + } + + /** Construct from a parcel - trivial because we have no extra data. */ + public LinkedVerifyResult(Parcel source) { + super(source); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } + + public static Creator<LinkedVerifyResult> CREATOR = new Creator<LinkedVerifyResult>() { + public LinkedVerifyResult createFromParcel(final Parcel source) { + return new LinkedVerifyResult(source); + } + + public LinkedVerifyResult[] newArray(final int size) { + return new LinkedVerifyResult[size]; + } + }; + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index f79900aab..ea4fedc56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -707,6 +707,22 @@ public abstract class OperationResult implements Parcelable { MSG_DEL_CONSOLIDATE (LogLevel.DEBUG, R.string.msg_del_consolidate), MSG_DEL_OK (LogLevel.OK, R.plurals.msg_del_ok), MSG_DEL_FAIL (LogLevel.WARN, R.plurals.msg_del_fail), + + MSG_LV (LogLevel.START, R.string.msg_lv), + MSG_LV_MATCH (LogLevel.DEBUG, R.string.msg_lv_match), + MSG_LV_MATCH_ERROR (LogLevel.ERROR, R.string.msg_lv_match_error), + MSG_LV_FP_OK (LogLevel.DEBUG, R.string.msg_lv_fp_ok), + MSG_LV_FP_ERROR (LogLevel.ERROR, R.string.msg_lv_fp_error), + MSG_LV_NONCE_OK (LogLevel.OK, R.string.msg_lv_nonce_ok), + MSG_LV_NONCE_ERROR (LogLevel.ERROR, R.string.msg_lv_nonce_error), + + MSG_LV_FETCH (LogLevel.DEBUG, R.string.msg_lv_fetch), + MSG_LV_FETCH_REDIR (LogLevel.DEBUG, R.string.msg_lv_fetch_redir), + MSG_LV_FETCH_OK (LogLevel.DEBUG, R.string.msg_lv_fetch_ok), + MSG_LV_FETCH_ERROR (LogLevel.ERROR, R.string.msg_lv_fetch_error), + MSG_LV_FETCH_ERROR_URL (LogLevel.ERROR, R.string.msg_lv_fetch_error_url), + MSG_LV_FETCH_ERROR_IO (LogLevel.ERROR, R.string.msg_lv_fetch_error_io), + ; public final int mMsgId; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 9276cba10..0173a1d83 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -361,4 +361,5 @@ public class UncachedPublicKey { return calendar.getTime(); } + } 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 8e23d36d9..8f967499e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java @@ -1,4 +1,8 @@ /* +<<<<<<< HEAD + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> +======= +>>>>>>> development * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify @@ -37,6 +41,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/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<String> mFlags; + final HashMap<String,String> mParams; + + protected LinkedIdentity(byte[] data, String nonce, Set<String> flags, + HashMap<String, String> 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<String> flags, + HashMap<String, String> 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<Entry<String, String>> it = mParams.entrySet().iterator(); + while (it.hasNext()) { + if (!first) { + b.append(";"); + } + first = false; + Entry<String, String> 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<String> flags = new HashSet<String>(); + HashMap<String,String> params = new HashMap<String,String>(); + { + 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..b7d111dc9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedResource.java @@ -0,0 +1,129 @@ +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<String> mFlags; + protected final HashMap<String,String> mParams; + + static Pattern magicPattern = + Pattern.compile("\\[Verifying my PGP key: pgpid\\+cookie:([a-zA-Z0-9]+)#([a-zA-Z0-9]+)\\]"); + + protected LinkedResource(Set<String> flags, HashMap<String, String> params, URI uri) { + mFlags = flags; + mParams = params; + mSubUri = uri; + } + + public Set<String> getFlags () { + return new HashSet<String>(mFlags); + } + + public HashMap<String,String> getParams () { + return new HashMap<String,String>(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 static String generatePreview () { + return "[Verifying my PGP key: pgpid+cookie:0x…]"; + } + + 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<String> flags, HashMap<String,String> 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..a2836e666 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/DnsResource.java @@ -0,0 +1,99 @@ +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.CLASS; +import de.measite.minidns.Record.TYPE; +import de.measite.minidns.record.TXT; + +public class DnsResource extends LinkedResource { + + final static Pattern magicPattern = + Pattern.compile("pgpid\\+cookie=([a-zA-Z0-9]+)(?:#|;)([a-zA-Z0-9]+)"); + + String mFqdn; + CLASS mClass; + TYPE mType; + + DnsResource(Set<String> flags, HashMap<String, String> params, URI uri, + String fqdn, CLASS clazz, TYPE type) { + super(flags, params, uri); + + mFqdn = fqdn; + mClass = clazz; + mType = type; + } + + public static String generateText (Context context, byte[] fingerprint, String nonce) { + + return "pgpid+cookie=" + + KeyFormattingUtils.convertFingerprintToHex(fingerprint) + ";" + nonce + ""; + + } + + public static DnsResource createNew (String domain) { + HashSet<String> flags = new HashSet<String>(); + HashMap<String,String> params = new HashMap<String,String>(); + URI uri = URI.create("dns:" + domain); + return create(flags, params, uri); + } + + public static DnsResource create(Set<String> flags, HashMap<String,String> params, URI uri) { + if ( ! ("dns".equals(uri.getScheme()) + && (flags == null || flags.isEmpty()) + && (params == null || params.isEmpty()))) { + return null; + } + + // + String spec = uri.getSchemeSpecificPart(); + // If there are // at the beginning, this includes an authority - we don't support those! + if (spec.startsWith("//")) { + return null; + } + + String[] pieces = spec.split("\\?", 2); + // In either case, part before a ? is the fqdn + String fqdn = pieces[0]; + // There may be a query part + if (pieces.length > 1) { + // TODO parse CLASS and TYPE query paramters + } + + CLASS clazz = CLASS.IN; + TYPE type = TYPE.TXT; + + return new DnsResource(flags, params, uri, fqdn, clazz, type); + } + + @Override + protected String fetchResource (OperationLog log, int indent) { + + Client c = new Client(); + DNSMessage msg = c.query(new Question(mFqdn, mType, mClass)); + 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<String> flags, HashMap<String,String> 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<String> flags = new HashSet<String>(); + flags.add("generic"); + HashMap<String,String> params = new HashMap<String,String>(); + return create(flags, params, uri); + } + + public static GenericHttpsResource create(Set<String> flags, HashMap<String,String> 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<String> flags, HashMap<String,String> 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<String> flags, HashMap<String,String> 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/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 2601c1f69..3aa156a8e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -699,7 +699,7 @@ public class KeychainProvider extends ContentProvider { )) { throw new AssertionError("Incorrect type for user packet! This is a bug!"); } - if (((Number)values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) { + if (values.get(UserPacketsColumns.RANK) == 0 && values.get(UserPacketsColumns.USER_ID) == null) { throw new AssertionError("Rank 0 user packet must be a user id!"); } db.insertOrThrow(Tables.USER_PACKETS, null, values); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index d947ae053..8a9f36c03 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -30,6 +30,12 @@ import android.support.v4.util.LongSparseArray; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.ImportExportOperation; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; 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 d95701458..0ddf0c76d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -48,6 +48,14 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.DeleteResult; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.ExportResult; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.operations.results.CertifyResult; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; +import org.sufficientlysecure.keychain.keyimport.Keyserver; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index d5ca08936..53fd9cdfd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -212,6 +212,9 @@ public class PassphraseDialogActivity extends FragmentActivity { case DIVERT_TO_CARD: message = getString(R.string.yubikey_pin_for, userId); break; + // special case: empty passphrase just returns the empty passphrase + case PASSPHRASE_EMPTY: + finishCaching(""); default: message = "This should not happen!"; break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java new file mode 100644 index 000000000..973067246 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Patterns; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.pgp.linked.resources.DnsResource; + +public class LinkedIdCreateDnsStep1Fragment extends Fragment { + + LinkedIdWizard mLinkedIdWizard; + + EditText mEditDns; + + /** + * Creates new instance of this fragment + */ + public static LinkedIdCreateDnsStep1Fragment newInstance() { + LinkedIdCreateDnsStep1Fragment frag = new LinkedIdCreateDnsStep1Fragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.linked_create_dns_fragment_step1, container, false); + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + + String uri = mEditDns.getText().toString(); + + if (!checkUri(uri)) { + mEditDns.setError("Please enter a valid domain name!"); + return; + } + + String proofNonce = LinkedIdentity.generateNonce(); + String proofText = DnsResource.generateText(getActivity(), + mLinkedIdWizard.mFingerprint, proofNonce); + + LinkedIdCreateDnsStep2Fragment frag = + LinkedIdCreateDnsStep2Fragment.newInstance(uri, proofNonce, proofText); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + + } + }); + + view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + mEditDns = (EditText) view.findViewById(R.id.linked_create_dns_domain); + + mEditDns.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void afterTextChanged(Editable editable) { + String uri = editable.toString(); + if (uri.length() > 0) { + if (checkUri(uri)) { + mEditDns.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_ok, 0); + } else { + mEditDns.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_bad, 0); + } + } else { + // remove drawable if email is empty + mEditDns.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + } + }); + + mEditDns.setText("test.mugenguild.com"); + + return view; + } + + private static boolean checkUri(String uri) { + return Patterns.DOMAIN_NAME.matcher(uri).matches(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java new file mode 100644 index 000000000..3c413556d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.PorterDuff; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; +import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.pgp.linked.resources.DnsResource; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.FileHelper; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; + +public class LinkedIdCreateDnsStep2Fragment extends Fragment { + + private static final int REQUEST_CODE_OUTPUT = 0x00007007; + private static final int REQUEST_CODE_PASSPHRASE = 0x00007008; + + public static final String DOMAIN = "domain", NONCE = "nonce", TEXT = "text"; + + LinkedIdWizard mLinkedIdWizard; + + TextView mTextView; + ImageView mVerifyImage; + View mVerifyProgress; + TextView mVerifyStatus; + + String mResourceDomain; + String mResourceNonce, mResourceString; + + // This is a resource, set AFTER it has been verified + DnsResource mVerifiedResource = null; + + /** + * Creates new instance of this fragment + */ + public static LinkedIdCreateDnsStep2Fragment newInstance + (String uri, String proofNonce, String proofText) { + + LinkedIdCreateDnsStep2Fragment frag = new LinkedIdCreateDnsStep2Fragment(); + + Bundle args = new Bundle(); + args.putString(DOMAIN, uri); + args.putString(NONCE, proofNonce); + args.putString(TEXT, proofText); + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.linked_create_dns_fragment_step2, container, false); + + mResourceDomain = getArguments().getString(DOMAIN); + mResourceNonce = getArguments().getString(NONCE); + mResourceString = getArguments().getString(TEXT); + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + startCertify(); + } + }); + + view.findViewById(R.id.back_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + mVerifyImage = (ImageView) view.findViewById(R.id.verify_image); + mVerifyProgress = view.findViewById(R.id.verify_progress); + mVerifyStatus = (TextView) view.findViewById(R.id.verify_status); + + view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofSend(); + } + }); + + view.findViewById(R.id.button_save).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofSave(); + } + }); + + view.findViewById(R.id.button_verify).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofVerify(); + } + }); + + mTextView = (TextView) view.findViewById(R.id.linked_create_dns_text); + mTextView.setText(mResourceString); + + setVerifyProgress(false, null); + mVerifyStatus.setText(R.string.linked_verify_pending); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + } + + public void setVerifyProgress(boolean on, Boolean success) { + mVerifyProgress.setVisibility(on ? View.VISIBLE : View.GONE); + mVerifyImage.setVisibility(on ? View.GONE : View.VISIBLE); + if (success == null) { + mVerifyStatus.setText(R.string.linked_verifying); + mVerifyImage.setImageResource(R.drawable.status_signature_unverified_cutout_24px); + mVerifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light), + PorterDuff.Mode.SRC_IN); + } else if (success) { + mVerifyStatus.setText(R.string.linked_verify_success); + mVerifyImage.setImageResource(R.drawable.status_signature_verified_cutout_24px); + mVerifyImage.setColorFilter(getResources().getColor(R.color.android_green_dark), + PorterDuff.Mode.SRC_IN); + } else { + mVerifyStatus.setText(R.string.linked_verify_error); + mVerifyImage.setImageResource(R.drawable.status_signature_unknown_cutout_24px); + mVerifyImage.setColorFilter(getResources().getColor(R.color.android_red_dark), + PorterDuff.Mode.SRC_IN); + } + } + + private void proofSend () { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, mResourceString); + sendIntent.setType("text/plain"); + startActivity(sendIntent); + } + + private void proofSave () { + String state = Environment.getExternalStorageState(); + if (!Environment.MEDIA_MOUNTED.equals(state)) { + Notify.showNotify(getActivity(), "External storage not available!", Style.ERROR); + return; + } + + String targetName = "pgpkey.txt"; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + File targetFile = new File(Constants.Path.APP_DIR, targetName); + FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), + getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); + } else { + FileHelper.saveDocument(this, "text/plain", targetName, REQUEST_CODE_OUTPUT); + } + } + + private void saveFile(Uri uri) { + try { + PrintWriter out = + new PrintWriter(getActivity().getContentResolver().openOutputStream(uri)); + out.print(mResourceString); + if (out.checkError()) { + Notify.showNotify(getActivity(), "Error writing file!", Style.ERROR); + } + } catch (FileNotFoundException e) { + Notify.showNotify(getActivity(), "File could not be opened for writing!", Style.ERROR); + e.printStackTrace(); + } + } + + public void proofVerify() { + setVerifyProgress(true, null); + + final DnsResource resource = DnsResource.createNew(mResourceDomain); + + new AsyncTask<Void,Void,LinkedVerifyResult>() { + + @Override + protected LinkedVerifyResult doInBackground(Void... params) { + return resource.verify(mLinkedIdWizard.mFingerprint, mResourceNonce); + } + + @Override + protected void onPostExecute(LinkedVerifyResult result) { + super.onPostExecute(result); + if (result.success()) { + setVerifyProgress(false, true); + mVerifiedResource = resource; + } else { + setVerifyProgress(false, false); + // on error, show error message + result.createNotify(getActivity()).show(); + } + } + }.execute(); + + } + + public void startCertify() { + + if (mVerifiedResource == null) { + Notify.showNotify(getActivity(), R.string.linked_need_verify, Style.ERROR); + return; + } + + Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); + intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mLinkedIdWizard.mMasterKeyId); + startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); + + } + + public void certifyLinkedIdentity (String passphrase) { + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( + getActivity(), + getString(R.string.progress_saving), + ProgressDialog.STYLE_HORIZONTAL, + true) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + + // get returned data bundle + Bundle returnData = message.getData(); + if (returnData == null) { + return; + } + final OperationResult result = + returnData.getParcelable(OperationResult.EXTRA_RESULT); + if (result == null) { + return; + } + + // if bad -> display here! + if (!result.success()) { + result.createNotify(getActivity()).show(); + return; + } + + result.createNotify(getActivity()).show(); + + // if good -> finish, return result to showkey and display there! + // Intent intent = new Intent(); + // intent.putExtra(OperationResult.EXTRA_RESULT, result); + // getActivity().setResult(EditKeyActivity.RESULT_OK, intent); + + // AffirmationCreateHttpsStep3Fragment frag = + // AffirmationCreateHttpsStep3Fragment.newInstance( + // mResourceDomain, mResourceNonce, mResourceString); + + // mAffirmationWizard.loadFragment(null, frag, AffirmationWizard.FRAG_ACTION_TO_RIGHT); + + } + } + }; + + SaveKeyringParcel skp = + new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint); + + WrappedUserAttribute ua = + LinkedIdentity.fromResource(mVerifiedResource, mResourceNonce).toUserAttribute(); + + skp.mAddUserAttribute.add(ua); + + // Send all information needed to service to import key in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + intent.setAction(KeychainIntentService.ACTION_EDIT_KEYRING); + + // fill values for this action + Bundle data = new Bundle(); + data.putString(KeychainIntentService.EDIT_KEYRING_PASSPHRASE, passphrase); + data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, skp); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + // For saving a file + case REQUEST_CODE_OUTPUT: + if (data == null) { + return; + } + Uri uri = data.getData(); + saveFile(uri); + break; + case REQUEST_CODE_PASSPHRASE: + if (resultCode == Activity.RESULT_OK && data != null) { + String passphrase = + data.getStringExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE); + certifyLinkedIdentity(passphrase); + } + break; + } + super.onActivityResult(requestCode, resultCode, data); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java new file mode 100644 index 000000000..7c5f1f032 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Patterns; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.pgp.linked.resources.GenericHttpsResource; + +public class LinkedIdCreateHttpsStep1Fragment extends Fragment { + + LinkedIdWizard mLinkedIdWizard; + + EditText mEditUri; + + /** + * Creates new instance of this fragment + */ + public static LinkedIdCreateHttpsStep1Fragment newInstance() { + LinkedIdCreateHttpsStep1Fragment frag = new LinkedIdCreateHttpsStep1Fragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.linked_create_https_fragment_step1, container, false); + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + + String uri = "https://" + mEditUri.getText(); + + if (!checkUri(uri)) { + return; + } + + String proofNonce = LinkedIdentity.generateNonce(); + String proofText = GenericHttpsResource.generateText(getActivity(), + mLinkedIdWizard.mFingerprint, proofNonce); + + LinkedIdCreateHttpsStep2Fragment frag = + LinkedIdCreateHttpsStep2Fragment.newInstance(uri, proofNonce, proofText); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + + } + }); + + view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + mEditUri = (EditText) view.findViewById(R.id.linked_create_https_uri); + + mEditUri.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void afterTextChanged(Editable editable) { + String uri = "https://" + editable; + if (uri.length() > 0) { + if (checkUri(uri)) { + mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_ok, 0); + } else { + mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_bad, 0); + } + } else { + // remove drawable if email is empty + mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + } + }); + + mEditUri.setText("mugenguild.com/pgpkey.txt"); + + return view; + } + + private static boolean checkUri(String uri) { + return Patterns.WEB_URL.matcher(uri).matches(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java new file mode 100644 index 000000000..6f25ef069 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.PorterDuff; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; +import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.pgp.linked.resources.GenericHttpsResource; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.FileHelper; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.net.URI; +import java.net.URISyntaxException; + +public class LinkedIdCreateHttpsStep2Fragment extends Fragment { + + private static final int REQUEST_CODE_OUTPUT = 0x00007007; + private static final int REQUEST_CODE_PASSPHRASE = 0x00007008; + + public static final String URI = "uri", NONCE = "nonce", TEXT = "text"; + + LinkedIdWizard mLinkedIdWizard; + + EditText mEditUri; + ImageView mVerifyImage; + View mVerifyProgress; + TextView mVerifyStatus; + + String mResourceUri; + String mResourceNonce, mResourceString; + + // This is a resource, set AFTER it has been verified + GenericHttpsResource mVerifiedResource = null; + + /** + * Creates new instance of this fragment + */ + public static LinkedIdCreateHttpsStep2Fragment newInstance + (String uri, String proofNonce, String proofText) { + + LinkedIdCreateHttpsStep2Fragment frag = new LinkedIdCreateHttpsStep2Fragment(); + + Bundle args = new Bundle(); + args.putString(URI, uri); + args.putString(NONCE, proofNonce); + args.putString(TEXT, proofText); + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.linked_create_https_fragment_step2, container, false); + + mResourceUri = getArguments().getString(URI); + mResourceNonce = getArguments().getString(NONCE); + mResourceString = getArguments().getString(TEXT); + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + startCertify(); + } + }); + + view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + mVerifyImage = (ImageView) view.findViewById(R.id.verify_image); + mVerifyProgress = view.findViewById(R.id.verify_progress); + mVerifyStatus = (TextView) view.findViewById(R.id.verify_status); + + view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofSend(); + } + }); + + view.findViewById(R.id.button_save).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofSave(); + } + }); + + view.findViewById(R.id.button_verify).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofVerify(); + } + }); + + mEditUri = (EditText) view.findViewById(R.id.linked_create_https_uri); + mEditUri.setText(mResourceUri); + + setVerifyProgress(false, null); + mVerifyStatus.setText(R.string.linked_verify_pending); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + } + + public void setVerifyProgress(boolean on, Boolean success) { + mVerifyProgress.setVisibility(on ? View.VISIBLE : View.GONE); + mVerifyImage.setVisibility(on ? View.GONE : View.VISIBLE); + if (success == null) { + mVerifyStatus.setText(R.string.linked_verifying); + mVerifyImage.setImageResource(R.drawable.status_signature_unverified_cutout_24px); + mVerifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light), + PorterDuff.Mode.SRC_IN); + } else if (success) { + mVerifyStatus.setText(R.string.linked_verify_success); + mVerifyImage.setImageResource(R.drawable.status_signature_verified_cutout_24px); + mVerifyImage.setColorFilter(getResources().getColor(R.color.android_green_dark), + PorterDuff.Mode.SRC_IN); + } else { + mVerifyStatus.setText(R.string.linked_verify_error); + mVerifyImage.setImageResource(R.drawable.status_signature_unknown_cutout_24px); + mVerifyImage.setColorFilter(getResources().getColor(R.color.android_red_dark), + PorterDuff.Mode.SRC_IN); + } + } + + private void proofSend () { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, mResourceString); + sendIntent.setType("text/plain"); + startActivity(sendIntent); + } + + private void proofSave () { + String state = Environment.getExternalStorageState(); + if (!Environment.MEDIA_MOUNTED.equals(state)) { + Notify.showNotify(getActivity(), "External storage not available!", Style.ERROR); + return; + } + + String targetName = "pgpkey.txt"; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + File targetFile = new File(Constants.Path.APP_DIR, targetName); + FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), + getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); + } else { + FileHelper.saveDocument(this, "text/plain", targetName, REQUEST_CODE_OUTPUT); + } + } + + private void saveFile(Uri uri) { + try { + PrintWriter out = + new PrintWriter(getActivity().getContentResolver().openOutputStream(uri)); + out.print(mResourceString); + if (out.checkError()) { + Notify.showNotify(getActivity(), "Error writing file!", Style.ERROR); + } + } catch (FileNotFoundException e) { + Notify.showNotify(getActivity(), "File could not be opened for writing!", Style.ERROR); + e.printStackTrace(); + } + } + + public void proofVerify() { + setVerifyProgress(true, null); + + try { + final GenericHttpsResource resource = GenericHttpsResource.createNew(new URI(mResourceUri)); + + new AsyncTask<Void,Void,LinkedVerifyResult>() { + + @Override + protected LinkedVerifyResult doInBackground(Void... params) { + return resource.verify(mLinkedIdWizard.mFingerprint, mResourceNonce); + } + + @Override + protected void onPostExecute(LinkedVerifyResult result) { + super.onPostExecute(result); + if (result.success()) { + setVerifyProgress(false, true); + mVerifiedResource = resource; + } else { + setVerifyProgress(false, false); + // on error, show error message + result.createNotify(getActivity()).show(); + } + } + }.execute(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + + } + + public void startCertify() { + + if (mVerifiedResource == null) { + Notify.showNotify(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR); + return; + } + + Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); + intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mLinkedIdWizard.mMasterKeyId); + startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); + + } + + public void certifyLinkedIdentity (String passphrase) { + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( + getActivity(), + getString(R.string.progress_saving), + ProgressDialog.STYLE_HORIZONTAL, + true) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + + // get returned data bundle + Bundle returnData = message.getData(); + if (returnData == null) { + return; + } + final OperationResult result = + returnData.getParcelable(OperationResult.EXTRA_RESULT); + if (result == null) { + return; + } + + // if bad -> display here! + if (!result.success()) { + result.createNotify(getActivity()).show(); + return; + } + + result.createNotify(getActivity()).show(); + + // if good -> finish, return result to showkey and display there! + // Intent intent = new Intent(); + // intent.putExtra(OperationResult.EXTRA_RESULT, result); + // getActivity().setResult(EditKeyActivity.RESULT_OK, intent); + + // AffirmationCreateHttpsStep3Fragment frag = + // AffirmationCreateHttpsStep3Fragment.newInstance( + // mResourceUri, mResourceNonce, mResourceString); + + // mAffirmationWizard.loadFragment(null, frag, AffirmationWizard.FRAG_ACTION_TO_RIGHT); + + } + } + }; + + SaveKeyringParcel skp = + new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint); + + WrappedUserAttribute ua = + LinkedIdentity.fromResource(mVerifiedResource, mResourceNonce).toUserAttribute(); + + skp.mAddUserAttribute.add(ua); + + // Send all information needed to service to import key in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + intent.setAction(KeychainIntentService.ACTION_EDIT_KEYRING); + + // fill values for this action + Bundle data = new Bundle(); + data.putString(KeychainIntentService.EDIT_KEYRING_PASSPHRASE, passphrase); + data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, skp); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + // For saving a file + case REQUEST_CODE_OUTPUT: + if (data == null) { + return; + } + Uri uri = data.getData(); + saveFile(uri); + break; + case REQUEST_CODE_PASSPHRASE: + if (resultCode == Activity.RESULT_OK && data != null) { + String passphrase = + data.getStringExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE); + certifyLinkedIdentity(passphrase); + } + break; + } + super.onActivityResult(requestCode, resultCode, data); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java new file mode 100644 index 000000000..25a4f6463 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.pgp.linked.resources.TwitterResource; +import org.sufficientlysecure.keychain.ui.util.Notify; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +public class LinkedIdCreateTwitterStep1Fragment extends Fragment { + + LinkedIdWizard mLinkedIdWizard; + + EditText mEditHandle; + + /** + * Creates new instance of this fragment + */ + public static LinkedIdCreateTwitterStep1Fragment newInstance() { + LinkedIdCreateTwitterStep1Fragment frag = new LinkedIdCreateTwitterStep1Fragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.linked_create_twitter_fragment_step1, container, false); + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + + final String handle = mEditHandle.getText().toString(); + + new AsyncTask<Void,Void,Boolean>() { + + @Override + protected Boolean doInBackground(Void... params) { + return true; // checkHandle(handle); + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + + if (result == null) { + Notify.showNotify(getActivity(), "Connection error while checking username!", Notify.Style.ERROR); + return; + } + + if (!result) { + Notify.showNotify(getActivity(), "This handle does not exist on Twitter!", Notify.Style.ERROR); + return; + } + + String proofNonce = LinkedIdentity.generateNonce(); + String proofText = TwitterResource.generateText(getActivity(), + mLinkedIdWizard.mFingerprint, proofNonce); + + LinkedIdCreateTwitterStep2Fragment frag = + LinkedIdCreateTwitterStep2Fragment.newInstance(handle, proofNonce, proofText); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + } + }.execute(); + + } + }); + + view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + mEditHandle = (EditText) view.findViewById(R.id.linked_create_twitter_handle); + mEditHandle.setText("Valodim"); + + return view; + } + + private static Boolean checkHandle(String handle) { + try { + HttpURLConnection nection = + (HttpURLConnection) new URL("https://twitter.com/" + handle).openConnection(); + nection.setRequestMethod("HEAD"); + return nection.getResponseCode() == 200; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java new file mode 100644 index 000000000..ab56e7a5d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.InputFilter; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.linked.resources.TwitterResource; + +public class LinkedIdCreateTwitterStep2Fragment extends Fragment { + + public static final String HANDLE = "uri", NONCE = "nonce", TEXT = "text"; + + LinkedIdWizard mLinkedIdWizard; + + EditText mEditTweetCustom, mEditTweetPreview; + ImageView mVerifyImage; + View mVerifyProgress; + TextView mVerifyStatus, mEditTweetTextLen; + + String mResourceHandle; + String mResourceNonce, mResourceString; + String mCookiePreview; + + /** + * Creates new instance of this fragment + */ + public static LinkedIdCreateTwitterStep2Fragment newInstance + (String handle, String proofNonce, String proofText) { + + LinkedIdCreateTwitterStep2Fragment frag = new LinkedIdCreateTwitterStep2Fragment(); + + Bundle args = new Bundle(); + args.putString(HANDLE, handle); + args.putString(NONCE, proofNonce); + args.putString(TEXT, proofText); + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.linked_create_twitter_fragment_step2, container, false); + + mCookiePreview = TwitterResource.generatePreview(); + + mResourceHandle = getArguments().getString(HANDLE); + mResourceNonce = getArguments().getString(NONCE); + mResourceString = getArguments().getString(TEXT); + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + + LinkedIdCreateTwitterStep3Fragment frag = + LinkedIdCreateTwitterStep3Fragment.newInstance(mResourceHandle, + mResourceNonce, mResourceString, + mEditTweetCustom.getText().toString()); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + } + }); + + view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + mVerifyImage = (ImageView) view.findViewById(R.id.verify_image); + mVerifyProgress = view.findViewById(R.id.verify_progress); + mVerifyStatus = (TextView) view.findViewById(R.id.verify_status); + + mEditTweetPreview = (EditText) view.findViewById(R.id.linked_create_twitter_preview); + mEditTweetPreview.setText(mCookiePreview); + + mEditTweetCustom = (EditText) view.findViewById(R.id.linked_create_twitter_custom); + mEditTweetCustom.setFilters(new InputFilter[] { + new InputFilter.LengthFilter(139 - mResourceString.length()) + }); + + mEditTweetTextLen = (TextView) view.findViewById(R.id.linked_create_twitter_textlen); + mEditTweetTextLen.setText(mResourceString.length() + "/140"); + + mEditTweetCustom.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void afterTextChanged(Editable editable) { + if (editable != null && editable.length() > 0) { + String str = editable + " " + mCookiePreview; + mEditTweetPreview.setText(str); + + mEditTweetTextLen.setText( + (editable.length() + mResourceString.length() + 1) + "/140"); + mEditTweetTextLen.setTextColor(getResources().getColor(str.length() == 140 + ? R.color.android_red_dark + : R.color.primary_dark_material_light)); + + + } else { + mEditTweetPreview.setText(mCookiePreview); + mEditTweetTextLen.setText(mResourceString.length() + "/140"); + } + } + }); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep3Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep3Fragment.java new file mode 100644 index 000000000..f395807cf --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep3Fragment.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.Notify; + +import java.util.List; + +public class LinkedIdCreateTwitterStep3Fragment extends Fragment { + + public static final String HANDLE = "uri", NONCE = "nonce", TEXT = "text", CUSTOM = "custom"; + + LinkedIdWizard mLinkedIdWizard; + + EditText mEditTweetPreview; + ImageView mVerifyImage; + View mVerifyProgress; + TextView mVerifyStatus; + + String mResourceHandle, mCustom, mFullString; + String mResourceNonce, mResourceString; + + /** + * Creates new instance of this fragment + */ + public static LinkedIdCreateTwitterStep3Fragment newInstance + (String handle, String proofNonce, String proofText, String customText) { + + LinkedIdCreateTwitterStep3Fragment frag = new LinkedIdCreateTwitterStep3Fragment(); + + Bundle args = new Bundle(); + args.putString(HANDLE, handle); + args.putString(NONCE, proofNonce); + args.putString(TEXT, proofText); + args.putString(CUSTOM, customText); + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.linked_create_twitter_fragment_step3, container, false); + + mResourceHandle = getArguments().getString(HANDLE); + mResourceNonce = getArguments().getString(NONCE); + mResourceString = getArguments().getString(TEXT); + mCustom = getArguments().getString(CUSTOM); + + mFullString = mCustom.isEmpty() ? mResourceString : (mCustom + " " + mResourceString); + + mVerifyImage = (ImageView) view.findViewById(R.id.verify_image); + mVerifyProgress = view.findViewById(R.id.verify_progress); + mVerifyStatus = (TextView) view.findViewById(R.id.verify_status); + + mEditTweetPreview = (EditText) view.findViewById(R.id.linked_create_twitter_preview); + mEditTweetPreview.setText(mFullString); + + view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); + } + }); + + view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofSend(); + } + }); + + view.findViewById(R.id.button_share).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofShare(); + } + }); + + view.findViewById(R.id.button_verify).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + proofVerify(); + } + }); + + setVerifyProgress(false, null); + mVerifyStatus.setText(R.string.linked_verify_pending); + + + view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + + // AffirmationCreateHttpsStep2Fragment frag = + // AffirmationCreateHttpsStep2Fragment.newInstance(); + + // mAffirmationWizard.loadFragment(null, frag, AffirmationWizard.FRAG_ACTION_TO_RIGHT); + } + }); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + } + + public void setVerifyProgress(boolean on, Boolean success) { + mVerifyProgress.setVisibility(on ? View.VISIBLE : View.GONE); + mVerifyImage.setVisibility(on ? View.GONE : View.VISIBLE); + if (success == null) { + mVerifyStatus.setText(R.string.linked_verifying); + mVerifyImage.setImageResource(R.drawable.status_signature_unverified_cutout_24px); + mVerifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light), + PorterDuff.Mode.SRC_IN); + } else if (success) { + mVerifyStatus.setText(R.string.linked_verify_success); + mVerifyImage.setImageResource(R.drawable.status_signature_verified_cutout_24px); + mVerifyImage.setColorFilter(getResources().getColor(R.color.android_green_dark), + PorterDuff.Mode.SRC_IN); + } else { + mVerifyStatus.setText(R.string.linked_verify_error); + mVerifyImage.setImageResource(R.drawable.status_signature_unknown_cutout_24px); + mVerifyImage.setColorFilter(getResources().getColor(R.color.android_red_dark), + PorterDuff.Mode.SRC_IN); + } + } + + public void proofVerify() { + setVerifyProgress(true, null); + + /* + try { + final TwitterResource resource = TwitterResource.createNew(new URI(mResourceHandle)); + + new AsyncTask<Void,Void,LinkedVerifyResult>() { + + @Override + protected LinkedVerifyResult doInBackground(Void... params) { + return resource.verify(mAffirmationWizard.mFingerprint, mResourceNonce); + } + + @Override + protected void onPostExecute(LinkedVerifyResult result) { + super.onPostExecute(result); + if (result.success()) { + setVerifyProgress(false, true); + } else { + setVerifyProgress(false, false); + // on error, show error message + result.createNotify(getActivity()).show(); + } + } + }.execute(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + */ + + } + + private void proofShare() { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, mFullString); + sendIntent.setType("text/plain"); + startActivity(sendIntent); + } + + private void proofSend() { + + Intent tweetIntent = new Intent(Intent.ACTION_SEND); + tweetIntent.putExtra(Intent.EXTRA_TEXT, mFullString); + tweetIntent.setType("text/plain"); + + PackageManager packManager = getActivity().getPackageManager(); + List<ResolveInfo> resolvedInfoList = packManager.queryIntentActivities(tweetIntent, + PackageManager.MATCH_DEFAULT_ONLY); + + boolean resolved = false; + for(ResolveInfo resolveInfo : resolvedInfoList){ + if(resolveInfo.activityInfo.packageName.startsWith("com.twitter.android")) { + tweetIntent.setClassName( + resolveInfo.activityInfo.packageName, + resolveInfo.activityInfo.name ); + resolved = true; + break; + } + } + + if (resolved) { + startActivity(tweetIntent); + } else { + Notify.showNotify(getActivity(), + "Twitter app is not installed, please use the send intent!", + Notify.Style.ERROR); + } + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java new file mode 100644 index 000000000..abe7dbaf1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.R; + +public class LinkedIdSelectFragment extends Fragment { + + LinkedIdWizard mLinkedIdWizard; + + /** + * Creates new instance of this fragment + */ + public static LinkedIdSelectFragment newInstance() { + LinkedIdSelectFragment frag = new LinkedIdSelectFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.linked_select_fragment, container, false); + + view.findViewById(R.id.linked_create_https_button) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + LinkedIdCreateHttpsStep1Fragment frag = + LinkedIdCreateHttpsStep1Fragment.newInstance(); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + } + }); + + view.findViewById(R.id.linked_create_dns_button) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + LinkedIdCreateDnsStep1Fragment frag = + LinkedIdCreateDnsStep1Fragment.newInstance(); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + } + }); + + view.findViewById(R.id.linked_create_twitter_button) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + LinkedIdCreateTwitterStep1Fragment frag = + LinkedIdCreateTwitterStep1Fragment.newInstance(); + + mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + } + }); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java new file mode 100644 index 000000000..b8f3329c1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.linked; + +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.ActionBarActivity; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.util.Log; + +public class LinkedIdWizard extends ActionBarActivity { + + public static final int FRAG_ACTION_START = 0; + public static final int FRAG_ACTION_TO_RIGHT = 1; + public static final int FRAG_ACTION_TO_LEFT = 2; + + long mMasterKeyId; + byte[] mFingerprint; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.create_key_activity); + + try { + Uri uri = getIntent().getData(); + CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(uri); + mMasterKeyId = ring.extractOrGetMasterKeyId(); + mFingerprint = ring.getFingerprint(); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "Invalid uri given, key does not exist!"); + finish(); + return; + } + + // pass extras into fragment + LinkedIdSelectFragment frag = LinkedIdSelectFragment.newInstance(); + loadFragment(null, frag, FRAG_ACTION_START); + } + + public void loadFragment(Bundle savedInstanceState, Fragment fragment, int action) { + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; + } + + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + + switch (action) { + case FRAG_ACTION_START: + transaction.setCustomAnimations(0, 0); + transaction.replace(R.id.create_key_fragment_container, fragment) + .commitAllowingStateLoss(); + break; + case FRAG_ACTION_TO_LEFT: + getSupportFragmentManager().popBackStackImmediate(); + break; + case FRAG_ACTION_TO_RIGHT: + transaction.setCustomAnimations(R.anim.frag_slide_in_from_right, R.anim.frag_slide_out_to_left, + R.anim.frag_slide_in_from_left, R.anim.frag_slide_out_to_right); + transaction.addToBackStack(null); + transaction.replace(R.id.create_key_fragment_container, fragment) + .commitAllowingStateLoss(); + break; + + } + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/HttpsPrefixedText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/HttpsPrefixedText.java new file mode 100644 index 000000000..292343eb7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/HttpsPrefixedText.java @@ -0,0 +1,38 @@ +package org.sufficientlysecure.keychain.ui.widget; + +import android.content.Context; +import android.graphics.*; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.widget.EditText; + +/** */ +public class HttpsPrefixedText extends EditText { + + private String mPrefix; // can be hardcoded for demo purposes + private Rect mPrefixRect = new Rect(); + + public HttpsPrefixedText(Context context, AttributeSet attrs) { + super(context, attrs); + mPrefix = "https://"; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + getPaint().getTextBounds(mPrefix, 0, mPrefix.length(), mPrefixRect); + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onDraw(@NonNull Canvas canvas) { + super.onDraw(canvas); + canvas.drawText(mPrefix, super.getCompoundPaddingLeft(), getBaseline(), getPaint()); + } + + @Override + public int getCompoundPaddingLeft() { + return super.getCompoundPaddingLeft() + mPrefixRect.width(); + } + +}
\ No newline at end of file |