diff options
21 files changed, 438 insertions, 227 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedCookieResource.java index b7d111dc9..6228b29ec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedCookieResource.java @@ -14,20 +14,21 @@ 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 LinkedResource { +public abstract class LinkedCookieResource { protected final URI mSubUri; protected final Set<String> mFlags; protected final HashMap<String,String> mParams; static Pattern magicPattern = - Pattern.compile("\\[Verifying my PGP key: pgpid\\+cookie:([a-zA-Z0-9]+)#([a-zA-Z0-9]+)\\]"); + 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) { + protected LinkedCookieResource(Set<String> flags, HashMap<String, String> params, URI uri) { mFlags = flags; mParams = params; mSubUri = uri; @@ -41,22 +42,58 @@ public abstract class LinkedResource { return new HashMap<String,String>(mParams); } + 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, String nonce) { - return "[Verifying my PGP key: pgpid+cookie:" + return "[Verifying my PGP key: openpgp4fpr:" + KeyFormattingUtils.convertFingerprintToHex(fingerprint) + "#" + nonce + "]"; } public static String generatePreview () { - return "[Verifying my PGP key: pgpid+cookie:0x…]"; + return "[Verifying my PGP key: openpgp4fpr:0x…]"; } - public LinkedVerifyResult verify(byte[] fingerprint, String nonce) { + public LinkedVerifyResult verify(byte[] fingerprint, int nonce) { OperationLog log = new OperationLog(); log.add(LogType.MSG_LV, 0); @@ -82,7 +119,7 @@ public abstract class LinkedResource { protected LinkedVerifyResult verifyString (OperationLog log, int indent, String res, - String nonce, byte[] fingerprint) { + int nonce, byte[] fingerprint) { log.add(LogType.MSG_LV_MATCH, indent); Matcher match = matchResource(log, indent+1, res); @@ -92,7 +129,7 @@ public abstract class LinkedResource { } String candidateFp = match.group(1).toLowerCase(); - String nonceCandidate = match.group(2).toLowerCase(); + int nonceCandidate = Integer.parseInt(match.group(2).toLowerCase(), 16); String fp = KeyFormattingUtils.convertFingerprintToHex(fingerprint); @@ -102,7 +139,7 @@ public abstract class LinkedResource { } log.add(LogType.MSG_LV_FP_OK, indent); - if (!nonce.equals(nonceCandidate)) { + if (nonce != nonceCandidate) { log.add(LogType.MSG_LV_NONCE_ERROR, indent); return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); } @@ -112,17 +149,61 @@ public abstract class LinkedResource { } - public static LinkedResource findResourceType - (Set<String> flags, HashMap<String,String> params, URI uri) { + protected static LinkedCookieResource fromRawLinkedId (RawLinkedIdentity id) { + return fromUri(id.mNonce, id.mUri); + } + + protected static LinkedCookieResource fromUri (int nonce, URI uri) { + + if ("pgpid".equals(uri.getScheme())) { + Log.e(Constants.TAG, "unknown uri scheme in (suspected) linked id packet"); + return null; + } + + if (!uri.isOpaque()) { + Log.e(Constants.TAG, "non-opaque uri in (suspected) linked id packet"); + return null; + } + + String specific = uri.getSchemeSpecificPart(); + if (!specific.contains("@")) { + Log.e(Constants.TAG, "unknown uri scheme in linked id packet"); + return null; + } + + String[] pieces = specific.split("@", 2); + URI subUri = URI.create(pieces[1]); + + Set<String> flags = new HashSet<String>(); + HashMap<String,String> params = new HashMap<String,String>(); + { + String[] rawParams = pieces[0].split(";"); + for (String param : rawParams) { + String[] p = param.split("=", 2); + if (p.length == 1) { + flags.add(param); + } else { + params.put(p[0], p[1]); + } + } + } + + return findResourceType(nonce, flags, params, subUri); + + } + + protected static LinkedCookieResource findResourceType (int nonce, Set<String> flags, + HashMap<String,String> params, + URI subUri) { - LinkedResource res; + LinkedCookieResource res; - res = GenericHttpsResource.create(flags, params, uri); + res = GenericHttpsResource.create(flags, params, subUri); if (res != null) { return res; } - return new UnknownResource(flags, params, uri); + return new UnknownResource(flags, params, subUri); } 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 deleted file mode 100644 index c46d0aa0a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedIdentity.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.sufficientlysecure.keychain.pgp.linked; - -import org.spongycastle.bcpg.UserAttributeSubpacket; -import org.spongycastle.util.Strings; -import org.spongycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.util.Log; - -import java.net.URI; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map.Entry; -import java.util.Set; - -public class LinkedIdentity { - - protected byte[] mData; - public final String mNonce; - public final URI mSubUri; - final Set<String> mFlags; - final HashMap<String,String> mParams; - - protected LinkedIdentity(byte[] data, String nonce, Set<String> flags, - HashMap<String, String> params, URI subUri) { - if ( ! nonce.matches("[0-9a-zA-Z]+")) { - throw new AssertionError("bug: nonce must be hexstring!"); - } - - mData = data; - mNonce = nonce; - mFlags = flags; - mParams = params; - mSubUri = subUri; - } - - LinkedIdentity(String nonce, Set<String> flags, - HashMap<String, String> params, URI subUri) { - this(null, nonce, flags, params, subUri); - } - - public byte[] getEncoded() { - if (mData != null) { - return mData; - } - - StringBuilder b = new StringBuilder(); - b.append("pgpid:"); - - // add flags - if (mFlags != null) { - boolean first = true; - for (String flag : mFlags) { - if (!first) { - b.append(";"); - } - first = false; - b.append(flag); - } - } - - // add parameters - if (mParams != null) { - boolean first = true; - Iterator<Entry<String, String>> it = mParams.entrySet().iterator(); - while (it.hasNext()) { - if (!first) { - b.append(";"); - } - first = false; - Entry<String, String> entry = it.next(); - b.append(entry.getKey()).append("=").append(entry.getValue()); - } - } - - b.append("@"); - b.append(mSubUri); - - byte[] nonceBytes = Hex.decode(mNonce); - if (nonceBytes.length != 4) { - throw new AssertionError("nonce must be 4 bytes"); - } - byte[] data = Strings.toUTF8ByteArray(b.toString()); - - byte[] result = new byte[data.length+4]; - System.arraycopy(nonceBytes, 0, result, 0, 4); - System.arraycopy(data, 0, result, 4, data.length); - - return result; - } - - /** This method parses a linked id from a UserAttributeSubpacket, or returns null if the - * subpacket can not be parsed as a valid linked id. - */ - static LinkedIdentity parseAttributeSubpacket(UserAttributeSubpacket subpacket) { - if (subpacket.getType() != 100) { - return null; - } - - byte[] data = subpacket.getData(); - String nonce = Hex.toHexString(data, 0, 4); - - try { - return parseUri(nonce, Strings.fromUTF8ByteArray(Arrays.copyOfRange(data, 4, data.length))); - - } catch (IllegalArgumentException e) { - Log.e(Constants.TAG, "error parsing uri in (suspected) linked id packet"); - return null; - } - } - - protected static LinkedIdentity parseUri (String nonce, String uriString) { - URI uri = URI.create(uriString); - - if ("pgpid".equals(uri.getScheme())) { - Log.e(Constants.TAG, "unknown uri scheme in (suspected) linked id packet"); - return null; - } - - if (!uri.isOpaque()) { - Log.e(Constants.TAG, "non-opaque uri in (suspected) linked id packet"); - return null; - } - - String specific = uri.getSchemeSpecificPart(); - if (!specific.contains("@")) { - Log.e(Constants.TAG, "unknown uri scheme in linked id packet"); - return null; - } - - String[] pieces = specific.split("@", 2); - URI subUri = URI.create(pieces[1]); - - Set<String> flags = new HashSet<String>(); - HashMap<String,String> params = new HashMap<String,String>(); - { - String[] rawParams = pieces[0].split(";"); - for (String param : rawParams) { - String[] p = param.split("=", 2); - if (p.length == 1) { - flags.add(param); - } else { - params.put(p[0], p[1]); - } - } - } - - return new LinkedIdentity(nonce, flags, params, subUri); - - } - - public static LinkedIdentity fromResource (LinkedResource res, String nonce) { - return new LinkedIdentity(nonce, res.getFlags(), res.getParams(), res.getSubUri()); - } - - public WrappedUserAttribute toUserAttribute () { - return WrappedUserAttribute.fromSubpacket(WrappedUserAttribute.UAT_LINKED_ID, getEncoded()); - } - - public static String generateNonce() { - // TODO make this actually random - // byte[] data = new byte[4]; - // new SecureRandom().nextBytes(data); - // return Hex.toHexString(data); - - // debug for now - return "01234567"; - } - -} 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..931f2ec6b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/RawLinkedIdentity.java @@ -0,0 +1,85 @@ +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.net.URI; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Set; + +/** 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(); + } + + /** 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); + + } + + public static RawLinkedIdentity fromSubpacketData(byte[] data) { + + try { + int nonce = ByteBuffer.wrap(data).getInt(); + String uri = Strings.fromUTF8ByteArray(Arrays.copyOfRange(data, 4, data.length)); + + return new RawLinkedIdentity(nonce, URI.create(uri)); + + } 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()); + } + + public WrappedUserAttribute toUserAttribute () { + return WrappedUserAttribute.fromSubpacket(WrappedUserAttribute.UAT_LINKED_ID, getEncoded()); + } + + public static String generateNonce() { + // TODO make this actually random + // byte[] data = new byte[4]; + // new SecureRandom().nextBytes(data); + // return Hex.toHexString(data); + + // debug for now + return "01234567"; + } + +} 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 index a2836e666..796e2f120 100644 --- 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 @@ -3,7 +3,7 @@ package org.sufficientlysecure.keychain.pgp.linked.resources; import android.content.Context; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.linked.LinkedResource; +import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import java.net.URI; @@ -21,7 +21,7 @@ import de.measite.minidns.Record.CLASS; import de.measite.minidns.Record.TYPE; import de.measite.minidns.record.TXT; -public class DnsResource extends LinkedResource { +public class DnsResource extends LinkedCookieResource { final static Pattern magicPattern = Pattern.compile("pgpid\\+cookie=([a-zA-Z0-9]+)(?:#|;)([a-zA-Z0-9]+)"); 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 index abe773f6c..a0f1cf0aa 100644 --- 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 @@ -8,7 +8,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.linked.LinkedResource; +import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -22,14 +22,14 @@ import java.util.Set; import javax.net.ssl.HttpsURLConnection; -public class GenericHttpsResource extends LinkedResource { +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, String nonce) { - String cookie = LinkedResource.generate(context, fingerprint, 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)); 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 index 1b0db1fa1..84277380d 100644 --- 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 @@ -17,7 +17,7 @@ import org.apache.http.params.BasicHttpParams; import org.json.JSONException; import org.json.JSONObject; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.linked.LinkedResource; +import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource; import java.io.BufferedReader; import java.io.IOException; @@ -29,7 +29,7 @@ import java.net.URLEncoder; import java.util.HashMap; import java.util.Set; -public class TwitterResource extends LinkedResource { +public class TwitterResource extends LinkedCookieResource { TwitterResource(Set<String> flags, HashMap<String,String> params, URI uri) { super(flags, params, uri); @@ -37,7 +37,7 @@ public class TwitterResource extends LinkedResource { public static String generateText (Context context, byte[] fingerprint, String nonce) { // nothing special here for now, might change this later - return LinkedResource.generate(context, fingerprint, nonce); + return LinkedCookieResource.generate(context, fingerprint, nonce); } private String getTwitterStream(String screenName) { 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 index ae99cdd86..f29ab5b39 100644 --- 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 @@ -1,13 +1,13 @@ package org.sufficientlysecure.keychain.pgp.linked.resources; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.linked.LinkedResource; +import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource; import java.net.URI; import java.util.HashMap; import java.util.Set; -public class UnknownResource extends LinkedResource { +public class UnknownResource extends LinkedCookieResource { public UnknownResource(Set<String> flags, HashMap<String,String> params, URI uri) { super(flags, params, uri); 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/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/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java index c4e6639a8..492dfd0f7 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 5afec7903..c6df016ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -24,6 +24,7 @@ import android.os.Bundle; 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.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -34,6 +35,7 @@ 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 +51,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 +79,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,6 +91,7 @@ public class ViewKeyFragment extends LoaderFragment implements showUserIdInfo(position); } }); + mLinkedIdsCard.setVisibility(View.GONE); return root; } @@ -145,23 +157,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; @@ -180,6 +192,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); } @@ -194,6 +211,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..1e4968615 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java @@ -0,0 +1,158 @@ +/* + * 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.graphics.Typeface; +import android.net.Uri; +import android.support.v4.content.CursorLoader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + +public class LinkedIdsAdapter extends UserAttributesAdapter { + protected LayoutInflater mInflater; + + public LinkedIdsAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + mInflater = LayoutInflater.from(context); + } + + @Override + public int getItemViewType(int position) { + RawLinkedIdentity id = (RawLinkedIdentity) getItem(position); + + // TODO return different ids by type + + return 0; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public Object getItem(int position) { + Cursor c = getCursor(); + c.moveToPosition(position); + + byte[] data = c.getBlob(INDEX_ATTRIBUTE_DATA); + RawLinkedIdentity identity = RawLinkedIdentity.fromSubpacketData(data); + + return identity; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + TextView vName = (TextView) view.findViewById(R.id.user_id_item_name); + TextView vAddress = (TextView) view.findViewById(R.id.user_id_item_address); + TextView vComment = (TextView) view.findViewById(R.id.user_id_item_comment); + ImageView vVerified = (ImageView) view.findViewById(R.id.user_id_item_certified); + View vVerifiedLayout = view.findViewById(R.id.user_id_item_certified_layout); + ImageView vEditImage = (ImageView) view.findViewById(R.id.user_id_item_edit_image); + ImageView vDeleteButton = (ImageView) view.findViewById(R.id.user_id_item_delete_button); + vDeleteButton.setVisibility(View.GONE); // not used + + String userId = cursor.getString(INDEX_USER_ID); + String[] splitUserId = KeyRing.splitUserId(userId); + if (splitUserId[0] != null) { + vName.setText(splitUserId[0]); + } else { + vName.setText(R.string.user_id_no_name); + } + if (splitUserId[1] != null) { + vAddress.setText(splitUserId[1]); + vAddress.setVisibility(View.VISIBLE); + } else { + vAddress.setVisibility(View.GONE); + } + if (splitUserId[2] != null) { + vComment.setText(splitUserId[2]); + vComment.setVisibility(View.VISIBLE); + } else { + vComment.setVisibility(View.GONE); + } + + boolean isPrimary = cursor.getInt(INDEX_IS_PRIMARY) != 0; + boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; + vVerifiedLayout.setVisibility(View.VISIBLE); + + if (isRevoked) { + // set revocation icon (can this even be primary?) + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); + + // disable revoked user ids + vName.setEnabled(false); + vAddress.setEnabled(false); + vComment.setEnabled(false); + } else { + vName.setEnabled(true); + vAddress.setEnabled(true); + vComment.setEnabled(true); + + if (isPrimary) { + vName.setTypeface(null, Typeface.BOLD); + vAddress.setTypeface(null, Typeface.BOLD); + } else { + vName.setTypeface(null, Typeface.NORMAL); + vAddress.setTypeface(null, Typeface.NORMAL); + } + + int isVerified = cursor.getInt(INDEX_VERIFIED); + switch (isVerified) { + case Certs.VERIFIED_SECRET: + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + break; + case Certs.VERIFIED_SELF: + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + break; + default: + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_INVALID, KeyFormattingUtils.DEFAULT_COLOR); + break; + } + } + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.view_key_adv_user_id_item, null); + } + + // 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); + } + +} 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..3bc1b200b 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); 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 6a4f61f4b..3c4b0fedf 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 @@ -187,7 +187,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 index 973067246..14d5f3b5d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep1Fragment.java @@ -29,7 +29,7 @@ import android.view.ViewGroup; import android.widget.EditText; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity; import org.sufficientlysecure.keychain.pgp.linked.resources.DnsResource; public class LinkedIdCreateDnsStep1Fragment extends Fragment { @@ -73,7 +73,7 @@ public class LinkedIdCreateDnsStep1Fragment extends Fragment { return; } - String proofNonce = LinkedIdentity.generateNonce(); + String proofNonce = RawLinkedIdentity.generateNonce(); String proofText = DnsResource.generateText(getActivity(), mLinkedIdWizard.mFingerprint, proofNonce); 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 index 3c413556d..6c9110ffd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateDnsStep2Fragment.java @@ -33,7 +33,6 @@ 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; @@ -42,7 +41,7 @@ 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; @@ -71,7 +70,8 @@ public class LinkedIdCreateDnsStep2Fragment extends Fragment { TextView mVerifyStatus; String mResourceDomain; - String mResourceNonce, mResourceString; + int mResourceNonce; + String mResourceString; // This is a resource, set AFTER it has been verified DnsResource mVerifiedResource = null; @@ -98,7 +98,7 @@ public class LinkedIdCreateDnsStep2Fragment extends Fragment { final View view = inflater.inflate(R.layout.linked_create_dns_fragment_step2, container, false); mResourceDomain = getArguments().getString(DOMAIN); - mResourceNonce = getArguments().getString(NONCE); + mResourceNonce = getArguments().getInt(NONCE); mResourceString = getArguments().getString(TEXT); view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { @@ -308,7 +308,7 @@ public class LinkedIdCreateDnsStep2Fragment extends Fragment { new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint); WrappedUserAttribute ua = - LinkedIdentity.fromResource(mVerifiedResource, mResourceNonce).toUserAttribute(); + RawLinkedIdentity.fromResource(mVerifiedResource, mResourceNonce).toUserAttribute(); skp.mAddUserAttribute.add(ua); 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 index 7c5f1f032..9fe1d43a3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java @@ -29,7 +29,7 @@ import android.view.ViewGroup; import android.widget.EditText; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity; import org.sufficientlysecure.keychain.pgp.linked.resources.GenericHttpsResource; public class LinkedIdCreateHttpsStep1Fragment extends Fragment { @@ -72,7 +72,7 @@ public class LinkedIdCreateHttpsStep1Fragment extends Fragment { return; } - String proofNonce = LinkedIdentity.generateNonce(); + String proofNonce = RawLinkedIdentity.generateNonce(); String proofText = GenericHttpsResource.generateText(getActivity(), mLinkedIdWizard.mFingerprint, proofNonce); 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 index 6f25ef069..f3fcc4410 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java @@ -42,7 +42,7 @@ 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; @@ -73,7 +73,8 @@ public class LinkedIdCreateHttpsStep2Fragment extends Fragment { TextView mVerifyStatus; String mResourceUri; - String mResourceNonce, mResourceString; + int mResourceNonce; + String mResourceString; // This is a resource, set AFTER it has been verified GenericHttpsResource mVerifiedResource = null; @@ -100,7 +101,7 @@ public class LinkedIdCreateHttpsStep2Fragment extends Fragment { final View view = inflater.inflate(R.layout.linked_create_https_fragment_step2, container, false); mResourceUri = getArguments().getString(URI); - mResourceNonce = getArguments().getString(NONCE); + mResourceNonce = getArguments().getInt(NONCE); mResourceString = getArguments().getString(TEXT); view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { @@ -314,7 +315,7 @@ public class LinkedIdCreateHttpsStep2Fragment extends Fragment { new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint); WrappedUserAttribute ua = - LinkedIdentity.fromResource(mVerifiedResource, mResourceNonce).toUserAttribute(); + RawLinkedIdentity.fromResource(mVerifiedResource, mResourceNonce).toUserAttribute(); skp.mAddUserAttribute.add(ua); 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 index 25a4f6463..8113525be 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java @@ -27,7 +27,7 @@ import android.view.ViewGroup; import android.widget.EditText; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity; +import org.sufficientlysecure.keychain.pgp.linked.RawLinkedIdentity; import org.sufficientlysecure.keychain.pgp.linked.resources.TwitterResource; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -92,7 +92,7 @@ public class LinkedIdCreateTwitterStep1Fragment extends Fragment { return; } - String proofNonce = LinkedIdentity.generateNonce(); + String proofNonce = RawLinkedIdentity.generateNonce(); String proofText = TwitterResource.generateText(getActivity(), mLinkedIdWizard.mFingerprint, proofNonce); diff --git a/OpenKeychain/src/main/res/layout/view_key_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_fragment.xml index bba412f99..20d329a9e 100644 --- a/OpenKeychain/src/main/res/layout/view_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_fragment.xml @@ -13,7 +13,7 @@ android:paddingRight="16dp"> <android.support.v7.widget.CardView - android:id="@+id/card_view" + android:id="@+id/card_identities" android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -41,6 +41,34 @@ </LinearLayout> </android.support.v7.widget.CardView> + <android.support.v7.widget.CardView + android:id="@+id/card_linked_ids" + android:layout_gravity="center" + android:layout_width="match_parent" + android:layout_height="wrap_content" + card_view:cardBackgroundColor="@android:color/white" + card_view:cardElevation="2dp" + card_view:cardUseCompatPadding="true" + card_view:cardCornerRadius="4dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + style="@style/CardViewHeader" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/section_linked_identities" /> + + <org.sufficientlysecure.keychain.ui.widget.FixedListView + android:id="@+id/view_key_linked_ids" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" /> + </LinearLayout> + </android.support.v7.widget.CardView> </LinearLayout> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 7d58ba211..3e1b84e5a 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1296,5 +1296,6 @@ <string name="linked_verify_pending">Not yet verified</string> <string name="linked_need_verify">The resource needs to be verified before you can proceed!</string> <string name="menu_linked_add_identity">"Add Linked Identity"</string> + <string name="section_linked_identities">Linked Identities</string> </resources> |