diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org')
36 files changed, 2920 insertions, 55 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 54cd9b1b4..a856a4fc3 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 @@ -763,6 +763,21 @@ public abstract class OperationResult implements Parcelable {          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), +          //export log          MSG_EXPORT_LOG(LogLevel.START,R.string.msg_export_log_start),          MSG_EXPORT_LOG_EXPORT_ERROR_NO_FILE(LogLevel.ERROR,R.string.msg_export_log_error_no_file), 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..49e4d9793 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; @@ -109,6 +114,15 @@ public class WrappedUserAttribute implements Serializable {      } +    public byte[][] getSubpackets() { +        UserAttributeSubpacket[] subpackets = mVector.toSubpacketArray(); +        byte[][] ret = new byte[subpackets.length][]; +        for (int i = 0; i < subpackets.length; i++) { +            ret[i] = subpackets[i].getData(); +        } +        return ret; +    } +      private void readObjectNoData() throws ObjectStreamException {      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedCookieResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedCookieResource.java new file mode 100644 index 000000000..b21f482b7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedCookieResource.java @@ -0,0 +1,138 @@ +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.Map.Entry; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class LinkedCookieResource extends LinkedResource { + +    protected LinkedCookieResource(Set<String> flags, HashMap<String, String> params, URI uri) { +        super(flags, params, uri); +    } + +    public URI toUri () { + +        StringBuilder b = new StringBuilder(); +        b.append("pgpid+cookie:"); + +        // 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; +            for (Entry<String, String> stringStringEntry : mParams.entrySet()) { +                if (!first) { +                    b.append(";"); +                } +                first = false; +                b.append(stringStringEntry.getKey()).append("=").append(stringStringEntry.getValue()); +            } +        } + +        b.append("@"); +        b.append(mSubUri); + +        return URI.create(b.toString()); + +    } + +    public URI getSubUri () { +        return mSubUri; +    } + +    public static String generate (Context context, byte[] fingerprint, int nonce) { +        return String.format("\"[Verifying my PGP key: openpgp4fpr:%s#%08x]\"", +                KeyFormattingUtils.convertFingerprintToHex(fingerprint), nonce); +    } + +    public static String generatePreview () { +        return "[Verifying my PGP key: openpgp4fpr:0x…]"; +    } + +    public LinkedVerifyResult verify(byte[] fingerprint, int 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, +                                               int 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(); +        try { +            int nonceCandidate = Integer.parseInt(match.group(2).toLowerCase(), 16); + +            if (nonce != nonceCandidate) { +                log.add(LogType.MSG_LV_NONCE_ERROR, indent); +                return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); +            } +        } catch (NumberFormatException e) { +            log.add(LogType.MSG_LV_NONCE_ERROR, indent); +            return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); +        } + +        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); + +        log.add(LogType.MSG_LV_NONCE_OK, indent); +        return new LinkedVerifyResult(LinkedVerifyResult.RESULT_OK, log); + +    } + +} 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..f06a23681 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedIdentity.java @@ -0,0 +1,75 @@ +package org.sufficientlysecure.keychain.pgp.linked; + +import org.spongycastle.bcpg.UserAttributeSubpacket; +import org.spongycastle.util.Strings; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.Arrays; + +public class LinkedIdentity extends RawLinkedIdentity { + +    public final LinkedResource mResource; + +    protected LinkedIdentity(int nonce, URI uri, LinkedResource resource) { +        super(nonce, uri); +        if (resource == null) { +            throw new AssertionError("resource must not be null in a LinkedIdentity!"); +        } +        mResource = resource; +    } + +    public static RawLinkedIdentity fromAttributeData(byte[] data) throws IOException { +        WrappedUserAttribute att = WrappedUserAttribute.fromData(data); + +        byte[][] subpackets = att.getSubpackets(); +        if (subpackets.length >= 1) { +            return fromSubpacketData(subpackets[0]); +        } + +        throw new IOException("no subpacket data"); +    } + +    /** 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 RawLinkedIdentity fromAttributeSubpacket(UserAttributeSubpacket subpacket) { +        if (subpacket.getType() != 100) { +            return null; +        } + +        byte[] data = subpacket.getData(); + +        return fromSubpacketData(data); + +    } + +    static RawLinkedIdentity fromSubpacketData(byte[] data) { + +        try { +            int nonce = ByteBuffer.wrap(data).getInt(); +            String uriStr = Strings.fromUTF8ByteArray(Arrays.copyOfRange(data, 4, data.length)); +            URI uri = URI.create(uriStr); + +            LinkedResource res = LinkedResource.fromUri(uri); +            if (res == null) { +                return new RawLinkedIdentity(nonce, uri); +            } + +            return new LinkedIdentity(nonce, uri, res); + +        } catch (IllegalArgumentException e) { +            Log.e(Constants.TAG, "error parsing uri in (suspected) linked id packet"); +            return null; +        } +    } + +    public static RawLinkedIdentity fromResource (LinkedCookieResource res, int nonce) { +        return new RawLinkedIdentity(nonce, res.toUri()); +    } + +} 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..59ffbfc45 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedResource.java @@ -0,0 +1,98 @@ +package org.sufficientlysecure.keychain.pgp.linked; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.linked.resources.DnsResource; +import org.sufficientlysecure.keychain.pgp.linked.resources.GenericHttpsResource; +import org.sufficientlysecure.keychain.pgp.linked.resources.UnknownResource; +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.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: openpgp4fpr:([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 abstract URI toUri(); + +    public Set<String> getFlags () { +        return new HashSet<>(mFlags); +    } + +    public HashMap<String,String> getParams () { +        return new HashMap<>(mParams); +    } + +    protected static LinkedCookieResource fromUri (URI uri) { + +        if (!"pgpid+cookie".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>(); +        if (!pieces[0].isEmpty()) { +            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 findResourceType(flags, params, subUri); + +    } + +    protected static LinkedCookieResource findResourceType (Set<String> flags, +                                                            HashMap<String,String> params, +                                                            URI  subUri) { + +        LinkedCookieResource res; + +        res = GenericHttpsResource.create(flags, params, subUri); +        if (res != null) { +            return res; +        } +        res = DnsResource.create(flags, params, subUri); +        if (res != null) { +            return res; +        } + +        return null; + +    } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/RawLinkedIdentity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/RawLinkedIdentity.java new file mode 100644 index 000000000..bfde3c3b9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/RawLinkedIdentity.java @@ -0,0 +1,45 @@ +package org.sufficientlysecure.keychain.pgp.linked; + +import org.spongycastle.util.Strings; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; + +import java.net.URI; +import java.nio.ByteBuffer; + +/** The RawLinkedIdentity contains raw parsed data from a Linked Identity subpacket. */ +public class RawLinkedIdentity { + +    public final int mNonce; +    public final URI mUri; + +    protected RawLinkedIdentity(int nonce, URI uri) { +        mNonce = nonce; +        mUri = uri; +    } + +    public byte[] getEncoded() { +        byte[] uriData = Strings.toUTF8ByteArray(mUri.toASCIIString()); + +        ByteBuffer buf = ByteBuffer.allocate(4 + uriData.length); + +        buf.putInt(mNonce); +        buf.put(uriData); + +        return buf.array(); +    } + +    public WrappedUserAttribute toUserAttribute () { +        return WrappedUserAttribute.fromSubpacket(WrappedUserAttribute.UAT_LINKED_ID, getEncoded()); +    } + +    public static int generateNonce() { +        // TODO make this actually random +        // byte[] data = new byte[4]; +        // new SecureRandom().nextBytes(data); +        // return Hex.toHexString(data); + +        // debug for now +        return 1234567; +    } + +} 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..d5b8a0345 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/DnsResource.java @@ -0,0 +1,103 @@ +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.LinkedCookieResource; +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 LinkedCookieResource { + +    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, int nonce) { + +        return String.format("pgpid+cookie=%s;%08x", +                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); +    } + +    public String getFqdn() { +        return mFqdn; +    } + +    @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..ba94bae75 --- /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.LinkedCookieResource; +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 LinkedCookieResource { + +    GenericHttpsResource(Set<String> flags, HashMap<String,String> params, URI uri) { +        super(flags, params, uri); +    } + +    public static String generateText (Context context, byte[] fingerprint, int nonce) { +        String cookie = LinkedCookieResource.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..f6ec4f97a --- /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.LinkedCookieResource; + +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 LinkedCookieResource { + +    TwitterResource(Set<String> flags, HashMap<String,String> params, URI uri) { +        super(flags, params, uri); +    } + +    public static String generateText (Context context, byte[] fingerprint, int nonce) { +        // nothing special here for now, might change this later +        return LinkedCookieResource.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..f29ab5b39 --- /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.LinkedCookieResource; + +import java.net.URI; +import java.util.HashMap; +import java.util.Set; + +public class UnknownResource extends LinkedCookieResource { + +    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/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 5856589c4..a7ca613d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -106,6 +106,7 @@ public class KeychainContract {      public static final String PATH_PUBLIC = "public";      public static final String PATH_SECRET = "secret";      public static final String PATH_USER_IDS = "user_ids"; +    public static final String PATH_LINKED_IDS = "linked_ids";      public static final String PATH_KEYS = "keys";      public static final String PATH_CERTS = "certs"; @@ -261,6 +262,10 @@ public class KeychainContract {          public static Uri buildUserIdsUri(Uri uri) {              return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build();          } + +        public static Uri buildLinkedIdsUri(Uri uri) { +            return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_LINKED_IDS).build(); +        }      }      public static class ApiApps implements ApiAppsColumns, BaseColumns { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index b88cd6006..4a162989f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -53,7 +53,7 @@ import java.io.IOException;   */  public class KeychainDatabase extends SQLiteOpenHelper {      private static final String DATABASE_NAME = "openkeychain.db"; -    private static final int DATABASE_VERSION = 8; +    private static final int DATABASE_VERSION = 9;      static Boolean apgHack = false;      private Context mContext; @@ -61,7 +61,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {          String KEY_RINGS_PUBLIC = "keyrings_public";          String KEY_RINGS_SECRET = "keyrings_secret";          String KEYS = "keys"; -        String USER_PACKETS = "user_ids"; +        String USER_PACKETS = "user_packets";          String CERTS = "certs";          String API_APPS = "api_apps";          String API_ACCOUNTS = "api_accounts"; @@ -119,8 +119,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {                  + UserPacketsColumns.IS_REVOKED + " INTEGER, "                  + UserPacketsColumns.RANK+ " INTEGER, " -                + "PRIMARY KEY(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.USER_ID + "), " -                + "UNIQUE (" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + "), " +                + "PRIMARY KEY(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + "), "                  + "FOREIGN KEY(" + UserPacketsColumns.MASTER_KEY_ID + ") REFERENCES "                      + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"              + ")"; @@ -267,6 +266,13 @@ public class KeychainDatabase extends SQLiteOpenHelper {                  } catch (Exception e) {                      // never mind, the column probably already existed                  } +            case 9: +                // tbale name for user_ids changed to user_packets +                db.execSQL("DROP TABLE IF EXISTS certs"); +                db.execSQL("DROP TABLE IF EXISTS user_ids"); +                db.execSQL(CREATE_USER_PACKETS); +                db.execSQL(CREATE_CERTS); +          }          // always do consolidate after upgrade 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..31ed89d67 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -62,6 +62,7 @@ public class KeychainProvider extends ContentProvider {      private static final int KEY_RING_SECRET = 204;      private static final int KEY_RING_CERTS = 205;      private static final int KEY_RING_CERTS_SPECIFIC = 206; +    private static final int KEY_RING_LINKED_IDS = 207;      private static final int API_APPS = 301;      private static final int API_APPS_BY_PACKAGE_NAME = 302; @@ -127,6 +128,7 @@ public class KeychainProvider extends ContentProvider {           * key_rings/_/unified           * key_rings/_/keys           * key_rings/_/user_ids +         * key_rings/_/linked_ids           * key_rings/_/public           * key_rings/_/secret           * key_rings/_/certs @@ -143,6 +145,9 @@ public class KeychainProvider extends ContentProvider {                  + KeychainContract.PATH_USER_IDS,                  KEY_RING_USER_IDS);          matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" +                        + KeychainContract.PATH_LINKED_IDS, +                KEY_RING_LINKED_IDS); +        matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"                  + KeychainContract.PATH_PUBLIC,                  KEY_RING_PUBLIC);          matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" @@ -469,7 +474,8 @@ public class KeychainProvider extends ContentProvider {              }              case KEY_RINGS_USER_IDS: -            case KEY_RING_USER_IDS: { +            case KEY_RING_USER_IDS: +            case KEY_RING_LINKED_IDS: {                  HashMap<String, String> projectionMap = new HashMap<>();                  projectionMap.put(UserPackets._ID, Tables.USER_PACKETS + ".oid AS _id");                  projectionMap.put(UserPackets.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID); @@ -494,13 +500,14 @@ public class KeychainProvider extends ContentProvider {                  groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID                          + ", " + Tables.USER_PACKETS + "." + UserPackets.RANK; -                // for now, we only respect user ids here, so TYPE must be NULL -                // TODO expand with KEY_RING_USER_PACKETS query type which lifts this restriction -                qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"); +                if (match == KEY_RING_LINKED_IDS) { +                    qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " = 100"); +                } else { +                    qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"); +                }                  // If we are searching for a particular keyring's ids, add where -                if (match == KEY_RING_USER_IDS) { -                    // TODO remove with the thing above +                if (match == KEY_RING_USER_IDS || match == KEY_RING_LINKED_IDS) {                      qb.appendWhere(" AND ");                      qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = ");                      qb.appendWhereEscapeString(uri.getPathSegments().get(1)); @@ -699,7 +706,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..2ff4803fb 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; @@ -1076,9 +1082,8 @@ public class ProviderHelper {              log.add(LogType.MSG_CON_SAVE_SECRET, indent);              indent += 1; -            final Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[]{ -                    KeyRings.PRIVKEY_DATA, KeyRings.FINGERPRINT, KeyRings.HAS_ANY_SECRET -            }, KeyRings.HAS_ANY_SECRET + " = 1", null, null); +            final Cursor cursor = mContentResolver.query(KeyRingData.buildSecretKeyRingUri(), +                    new String[]{ KeyRingData.KEY_RING_DATA }, null, null, null);              if (cursor == null) {                  log.add(LogType.MSG_CON_ERROR_DB, indent); @@ -1100,8 +1105,7 @@ public class ProviderHelper {                      if (cursor.isAfterLast()) {                          return false;                      } -                    ring = new ParcelableKeyRing(KeyFormattingUtils.convertFingerprintToHex(cursor.getBlob(1)), cursor.getBlob(0) -                    ); +                    ring = new ParcelableKeyRing(cursor.getBlob(0));                      cursor.moveToNext();                      return true;                  } @@ -1138,9 +1142,9 @@ public class ProviderHelper {              log.add(LogType.MSG_CON_SAVE_PUBLIC, indent);              indent += 1; -            final Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[]{ -                    KeyRings.PUBKEY_DATA, KeyRings.FINGERPRINT -            }, null, null, null); +            final Cursor cursor = mContentResolver.query( +                    KeyRingData.buildPublicKeyRingUri(), +                    new String[]{ KeyRingData.KEY_RING_DATA }, null, null, null);              if (cursor == null) {                  log.add(LogType.MSG_CON_ERROR_DB, indent); @@ -1162,8 +1166,7 @@ public class ProviderHelper {                      if (cursor.isAfterLast()) {                          return false;                      } -                    ring = new ParcelableKeyRing(KeyFormattingUtils.convertFingerprintToHex(cursor.getBlob(1)), cursor.getBlob(0) -                    ); +                    ring = new ParcelableKeyRing(cursor.getBlob(0));                      cursor.moveToNext();                      return true;                  } 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/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 25ca6e8fd..005b60ce0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -328,7 +328,7 @@ public class EditKeyFragment extends LoaderFragment implements              case LOADER_ID_USER_IDS: {                  Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);                  return new CursorLoader(getActivity(), baseUri, -                        UserIdsAdapter.USER_IDS_PROJECTION, null, null, null); +                        UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null);              }              case LOADER_ID_SUBKEYS: { 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 bb669f6b8..29621662f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -237,6 +237,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/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 1b4fc503c..678ecd289 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -72,6 +72,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;  import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.service.KeychainIntentService;  import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;  import org.sufficientlysecure.keychain.ui.util.FormattingUtils;  import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;  import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -282,7 +283,7 @@ public class ViewKeyActivity extends BaseActivity implements          // Add the fragment to the 'fragment_container' FrameLayout          // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!          getSupportFragmentManager().beginTransaction() -                .replace(R.id.view_key_fragment, frag) +                .replace(R.id.view_key_fragment, frag, "main")                  .commitAllowingStateLoss();          // do it immediately!          getSupportFragmentManager().executePendingTransactions(); @@ -336,6 +337,12 @@ public class ViewKeyActivity extends BaseActivity implements                      certifyFingeprint(mDataUri);                      return true;                  } +                case R.id.menu_key_view_add_linked_identity: { +                    Intent intent = new Intent(this, LinkedIdWizard.class); +                    intent.setData(mDataUri); +                    startActivity(intent); +                    return true; +                }              }          } catch (ProviderHelper.NotFoundException e) {              Notify.showNotify(this, R.string.error_key_not_found, Notify.Style.ERROR); @@ -348,6 +355,10 @@ public class ViewKeyActivity extends BaseActivity implements      public boolean onPrepareOptionsMenu(Menu menu) {          MenuItem editKey = menu.findItem(R.id.menu_key_view_edit);          editKey.setVisible(mIsSecret); + +        MenuItem addLinked = menu.findItem(R.id.menu_key_view_add_linked_identity); +        addLinked.setVisible(mIsSecret); +          MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint);          certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java index 7bfebaf62..ad437f924 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java @@ -133,7 +133,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements              case LOADER_ID_USER_IDS: {                  Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);                  return new CursorLoader(getActivity(), baseUri, -                        UserIdsAdapter.USER_IDS_PROJECTION, null, null, null); +                        UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null);              }              default: diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java index 628970b27..20386b372 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -20,20 +20,29 @@ package org.sufficientlysecure.keychain.ui;  import android.database.Cursor;  import android.net.Uri; +import android.os.Build;  import android.os.Bundle; +import android.support.v4.app.Fragment;  import android.support.v4.app.LoaderManager;  import android.support.v4.content.CursorLoader;  import android.support.v4.content.Loader; +import android.support.v7.widget.CardView; +import android.transition.Explode; +import android.transition.Fade; +import android.transition.Transition; +import android.transition.TransitionInflater;  import android.view.LayoutInflater;  import android.view.View;  import android.view.ViewGroup;  import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener;  import android.widget.ListView;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;  import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;  import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;  import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;  import org.sufficientlysecure.keychain.util.Log; @@ -49,10 +58,14 @@ public class ViewKeyFragment extends LoaderFragment implements      private static final int LOADER_ID_UNIFIED = 0;      private static final int LOADER_ID_USER_IDS = 1; +    private static final int LOADER_ID_LINKED_IDS = 2;      private UserIdsAdapter mUserIdsAdapter; +    private LinkedIdsAdapter mLinkedIdsAdapter;      private Uri mDataUri; +    private ListView mLinkedIds; +    private CardView mLinkedIdsCard;      /**       * Creates new instance of this fragment @@ -73,6 +86,11 @@ public class ViewKeyFragment extends LoaderFragment implements          View view = inflater.inflate(R.layout.view_key_fragment, getContainer());          mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); +        mLinkedIdsCard = (CardView) view.findViewById(R.id.card_linked_ids); + +        mLinkedIds = (ListView) view.findViewById(R.id.view_key_linked_ids); +        mLinkedIdsAdapter = new LinkedIdsAdapter(getActivity(), null, 0); +        mLinkedIds.setAdapter(mLinkedIdsAdapter);          mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {              @Override @@ -80,10 +98,34 @@ public class ViewKeyFragment extends LoaderFragment implements                  showUserIdInfo(position);              }          }); +        mLinkedIds.setOnItemClickListener(new OnItemClickListener() { +            @Override +            public void onItemClick(AdapterView<?> parent, View view, int position, long id) { +                showLinkedId(position); +            } +        });          return root;      } +    private void showLinkedId(final int position) { +        Fragment frag = mLinkedIdsAdapter.getLinkedIdFragment(position); + +        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { +            Transition trans = TransitionInflater.from(getActivity()) +                            .inflateTransition(R.transition.linked_id_card_trans); +            // setSharedElementReturnTransition(trans); +            setExitTransition(new Fade()); +            frag.setSharedElementEnterTransition(trans); +        } + +        getFragmentManager().beginTransaction() +                .replace(R.id.view_key_fragment, frag) +                .addSharedElement(mLinkedIdsCard, "card_linked_ids") +                .addToBackStack(null) +                .commit(); +    } +      private void showUserIdInfo(final int position) {          if (!mIsSecret) {              final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); @@ -118,34 +160,18 @@ public class ViewKeyFragment extends LoaderFragment implements      // These are the rows that we will retrieve.      static final String[] UNIFIED_PROJECTION = new String[]{              KeychainContract.KeyRings._ID, -            KeychainContract.KeyRings.MASTER_KEY_ID, -            KeychainContract.KeyRings.USER_ID, -            KeychainContract.KeyRings.IS_REVOKED, -            KeychainContract.KeyRings.IS_EXPIRED, -            KeychainContract.KeyRings.VERIFIED,              KeychainContract.KeyRings.HAS_ANY_SECRET, -            KeychainContract.KeyRings.FINGERPRINT, -            KeychainContract.KeyRings.HAS_ENCRYPT      }; -    static final int INDEX_MASTER_KEY_ID = 1; -    static final int INDEX_USER_ID = 2; -    static final int INDEX_IS_REVOKED = 3; -    static final int INDEX_IS_EXPIRED = 4; -    static final int INDEX_VERIFIED = 5; -    static final int INDEX_HAS_ANY_SECRET = 6; -    static final int INDEX_FINGERPRINT = 7; -    static final int INDEX_HAS_ENCRYPT = 8; +    static final int INDEX_HAS_ANY_SECRET = 1;      private void loadData(Uri dataUri) {          mDataUri = dataUri;          Log.i(Constants.TAG, "mDataUri: " + mDataUri); -        // Prepare the loaders. Either re-connect with an existing ones, -        // or start new ones. -        // TODO Is this loader the same as the one in the activity?          getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); +        getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this);      }      public Loader<Cursor> onCreateLoader(int id, Bundle args) { @@ -159,23 +185,23 @@ public class ViewKeyFragment extends LoaderFragment implements              case LOADER_ID_USER_IDS:                  return UserIdsAdapter.createLoader(getActivity(), mDataUri); +            case LOADER_ID_LINKED_IDS: +                return LinkedIdsAdapter.createLoader(getActivity(), mDataUri); +              default:                  return null;          }      }      public void onLoadFinished(Loader<Cursor> loader, Cursor data) { -        /* TODO better error handling? May cause problems when a key is deleted, -         * because the notification triggers faster than the activity closes. -         */ -        // Avoid NullPointerExceptions... -        if (data.getCount() == 0) { -            return; -        }          // Swap the new cursor in. (The framework will take care of closing the          // old cursor once we return.)          switch (loader.getId()) {              case LOADER_ID_UNIFIED: { +                // Avoid NullPointerExceptions... +                if (data.getCount() == 0) { +                    return; +                }                  if (data.moveToFirst()) {                      mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; @@ -194,6 +220,11 @@ public class ViewKeyFragment extends LoaderFragment implements                  break;              } +            case LOADER_ID_LINKED_IDS: { +                mLinkedIdsCard.setVisibility(data.getCount() > 0 ? View.VISIBLE : View.GONE); +                mLinkedIdsAdapter.swapCursor(data); +                break; +            }          }          setContentShown(true);      } @@ -208,6 +239,11 @@ public class ViewKeyFragment extends LoaderFragment implements                  mUserIdsAdapter.swapCursor(null);                  break;              } +            case LOADER_ID_LINKED_IDS: { +                mLinkedIdsCard.setVisibility(View.GONE); +                mLinkedIdsAdapter.swapCursor(null); +                break; +            }          }      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java new file mode 100644 index 000000000..329b95ebc --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 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.ui.adapter; + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.v4.app.Fragment; +import android.support.v4.content.CursorLoader; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.pgp.linked.LinkedResource; +import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity; +import org.sufficientlysecure.keychain.pgp.linked.resources.DnsResource; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; + +import java.io.IOException; +import java.util.WeakHashMap; + + +public class LinkedIdsAdapter extends UserAttributesAdapter { +    protected LayoutInflater mInflater; +    WeakHashMap<Integer,RawLinkedIdentity> mLinkedIdentityCache = new WeakHashMap<>(); + +    public LinkedIdsAdapter(Context context, Cursor c, int flags) { +        super(context, c, flags); +        mInflater = LayoutInflater.from(context); +    } + +    @Override +    public void bindView(View view, Context context, Cursor cursor) { + +        RawLinkedIdentity id = getItem(cursor.getPosition()); +        ViewHolder holder = (ViewHolder) view.getTag(); + +        int isVerified = cursor.getInt(INDEX_VERIFIED); +        switch (isVerified) { +            case Certs.VERIFIED_SECRET: +                KeyFormattingUtils.setStatusImage(mContext, holder.vVerified, +                        null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); +                break; +            case Certs.VERIFIED_SELF: +                KeyFormattingUtils.setStatusImage(mContext, holder.vVerified, +                        null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); +                break; +            default: +                KeyFormattingUtils.setStatusImage(mContext, holder.vVerified, +                        null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR); +                break; +        } + +        if (holder instanceof ViewHolderNonRaw) { +            ((ViewHolderNonRaw) holder).setData(mContext, (LinkedIdentity) id); +        } + +    } + +    @Override +    public int getItemViewType(int position) { +        RawLinkedIdentity id = getItem(position); + +        if (id instanceof LinkedIdentity) { +            LinkedResource res = ((LinkedIdentity) id).mResource; +            if (res instanceof DnsResource) { +                return 1; +            } +        } + +        return 0; +    } + +    @Override +    public int getViewTypeCount() { +        return 2; +    } + +    @Override +    public RawLinkedIdentity getItem(int position) { +        RawLinkedIdentity ret = mLinkedIdentityCache.get(position); +        if (ret != null) { +            return ret; +        } + +        Cursor c = getCursor(); +        c.moveToPosition(position); + +        byte[] data = c.getBlob(INDEX_ATTRIBUTE_DATA); +        try { +            ret = LinkedIdentity.fromAttributeData(data); +            mLinkedIdentityCache.put(position, ret); +            return ret; +        } catch (IOException e) { +            Log.e(Constants.TAG, "could not read linked identity subpacket data", e); +            return null; +        } +    } + +    @Override +    public View newView(Context context, Cursor cursor, ViewGroup parent) { +        int type = getItemViewType(cursor.getPosition()); +        switch(type) { +            case 0: { +                View v = mInflater.inflate(R.layout.linked_id_item_unknown, null); +                ViewHolder holder = new ViewHolder(v); +                v.setTag(holder); +                return v; +            } +            case 1: { +                View v = mInflater.inflate(R.layout.linked_id_item_dns, null); +                ViewHolder holder = new ViewHolderDns(v); +                v.setTag(holder); +                return v; +            } +            default: +                throw new AssertionError("all cases must be covered in LinkedIdsAdapter.newView!"); +        } +    } + +    // don't show revoked user ids, irrelevant for average users +    public static final String LINKED_IDS_WHERE = UserPackets.IS_REVOKED + " = 0"; + +    public static CursorLoader createLoader(Activity activity, Uri dataUri) { +        Uri baseUri = UserPackets.buildLinkedIdsUri(dataUri); +        return new CursorLoader(activity, baseUri, +                UserIdsAdapter.USER_PACKETS_PROJECTION, LINKED_IDS_WHERE, null, null); +    } + +    public Fragment getLinkedIdFragment(int position) { +        RawLinkedIdentity id = getItem(position); + +        return LinkedIdViewFragment.newInstance(id); +    } + +    static class ViewHolder { +        ImageView vVerified; + +        ViewHolder(View view) { +            vVerified = (ImageView) view.findViewById(R.id.user_id_item_certified); +        } +    } + +    static abstract class ViewHolderNonRaw extends ViewHolder { +        ViewHolderNonRaw(View view) { +            super(view); +        } + +        abstract void setData(Context context, LinkedIdentity id); +    } + +    static class ViewHolderDns extends ViewHolderNonRaw { +        TextView vFqdn; + +        ViewHolderDns(View view) { +            super(view); + +            vFqdn = (TextView) view.findViewById(R.id.linked_id_dns_fqdn); +        } + +        @Override +        void setData(Context context, LinkedIdentity id) { +            DnsResource res = (DnsResource) id.mResource; +            vFqdn.setText(res.getFqdn()); +        } + +    } + +    @Override +    public void notifyDataSetChanged() { +        mLinkedIdentityCache.clear(); +        super.notifyDataSetChanged(); +    } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java index 457083770..345ccccc0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java @@ -8,10 +8,11 @@ import android.view.View;  import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;  public abstract class UserAttributesAdapter extends CursorAdapter { -    public static final String[] USER_IDS_PROJECTION = new String[]{ +    public static final String[] USER_PACKETS_PROJECTION = new String[]{              UserPackets._ID,              UserPackets.TYPE,              UserPackets.USER_ID, +            UserPackets.ATTRIBUTE_DATA,              UserPackets.RANK,              UserPackets.VERIFIED,              UserPackets.IS_PRIMARY, @@ -20,10 +21,11 @@ public abstract class UserAttributesAdapter extends CursorAdapter {      protected static final int INDEX_ID = 0;      protected static final int INDEX_TYPE = 1;      protected static final int INDEX_USER_ID = 2; -    protected static final int INDEX_RANK = 3; -    protected static final int INDEX_VERIFIED = 4; -    protected static final int INDEX_IS_PRIMARY = 5; -    protected static final int INDEX_IS_REVOKED = 6; +    protected static final int INDEX_ATTRIBUTE_DATA = 3; +    protected static final int INDEX_RANK = 4; +    protected static final int INDEX_VERIFIED = 5; +    protected static final int INDEX_IS_PRIMARY = 6; +    protected static final int INDEX_IS_REVOKED = 7;      public UserAttributesAdapter(Context context, Cursor c, int flags) {          super(context, c, flags); @@ -46,4 +48,5 @@ public abstract class UserAttributesAdapter extends CursorAdapter {          mCursor.moveToPosition(position);          return mCursor.getInt(INDEX_VERIFIED);      } +  } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index 3486f1516..1cf3f4d38 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -188,7 +188,7 @@ public class UserIdsAdapter extends UserAttributesAdapter {      public static CursorLoader createLoader(Activity activity, Uri dataUri) {          Uri baseUri = UserPackets.buildUserIdsUri(dataUri);          return new CursorLoader(activity, baseUri, -                UserIdsAdapter.USER_IDS_PROJECTION, USER_IDS_WHERE, null, null); +                UserIdsAdapter.USER_PACKETS_PROJECTION, USER_IDS_WHERE, null, null);      }  } 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..26b0a0539 --- /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.RawLinkedIdentity; +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; +                } + +                int proofNonce = RawLinkedIdentity.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..3b0860fa9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java @@ -0,0 +1,362 @@ +/* + * 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.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.RawLinkedIdentity; +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; +    int mResourceNonce; +    String 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, int proofNonce, String proofText) { + +        LinkedIdCreateDnsStep2Fragment frag = new LinkedIdCreateDnsStep2Fragment(); + +        Bundle args = new Bundle(); +        args.putString(DOMAIN, uri); +        args.putInt(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().getInt(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_24dp); +            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_24dp); +            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_24dp); +            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); +                    mVerifiedResource = resource; +                    // 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..78ca4cfe9 --- /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.RawLinkedIdentity; +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; +                } + +                int proofNonce = RawLinkedIdentity.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..7d2f3dfdb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java @@ -0,0 +1,368 @@ +/* + * 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.RawLinkedIdentity; +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; +    int mResourceNonce; +    String 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, int proofNonce, String proofText) { + +        LinkedIdCreateHttpsStep2Fragment frag = new LinkedIdCreateHttpsStep2Fragment(); + +        Bundle args = new Bundle(); +        args.putString(URI, uri); +        args.putInt(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().getInt(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_24dp); +            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_24dp); +            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_24dp); +            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..e966fd71f --- /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.RawLinkedIdentity; +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; +                        } + +                        int proofNonce = RawLinkedIdentity.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..837b84d40 --- /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, int proofNonce, String proofText) { + +        LinkedIdCreateTwitterStep2Fragment frag = new LinkedIdCreateTwitterStep2Fragment(); + +        Bundle args = new Bundle(); +        args.putString(HANDLE, handle); +        args.putInt(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..d298e8251 --- /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_24dp); +            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_24dp); +            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_24dp); +            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/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java new file mode 100644 index 000000000..0eb0dcd45 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java @@ -0,0 +1,45 @@ +package org.sufficientlysecure.keychain.ui.linked; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v7.widget.CardView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity; + + +public class LinkedIdViewFragment extends Fragment { + +    private CardView mLinkedIdsCard; + +    public static Fragment newInstance(RawLinkedIdentity id) { +        LinkedIdViewFragment frag = new LinkedIdViewFragment(); + +        Bundle args = new Bundle(); +        frag.setArguments(args); + +        return frag; +    } + +    @Override +    public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { +        View root = inflater.inflate(R.layout.linked_id_view_fragment, null); + +        mLinkedIdsCard = (CardView) root.findViewById(R.id.card_linked_ids); + +        root.findViewById(R.id.back_button).setClickable(true); +        root.findViewById(R.id.back_button).setOnClickListener(new OnClickListener() { +            @Override +            public void onClick(View v) { +                getFragmentManager().popBackStack(); +            } +        }); + +        return root; +    } + +} 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..161efc8fb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java @@ -0,0 +1,106 @@ +/* + * 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.KeychainContract; +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(); +            uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(uri); +            CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(uri); +            if (!ring.hasAnySecret()) { +                Log.e(Constants.TAG, "Linked Identities can only be added to secret keys!"); +                finish(); +                return; +            } + +            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  | 
