work more on separation of linked identities and resources, initial ui work

This commit is contained in:
Vincent Breitmoser
2015-03-04 12:30:56 +01:00
parent d4df509a1d
commit 8222315dbd
21 changed files with 438 additions and 227 deletions

View File

@@ -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);
}
LinkedResource res;
protected static LinkedCookieResource fromUri (int nonce, URI uri) {
res = GenericHttpsResource.create(flags, params, 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) {
LinkedCookieResource res;
res = GenericHttpsResource.create(flags, params, subUri);
if (res != null) {
return res;
}
return new UnknownResource(flags, params, uri);
return new UnknownResource(flags, params, subUri);
}

View File

@@ -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";
}
}

View File

@@ -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";
}
}

View File

@@ -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]+)");

View File

@@ -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));

View File

@@ -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) {

View File

@@ -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);