From 7865b92285893ddb87fa8351d724d09d0a1eb781 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Mon, 26 May 2014 20:24:13 +0200 Subject: [PATCH 01/18] ContactHelper can read email addresses from contact list --- OpenKeychain/src/main/AndroidManifest.xml | 1 + .../keychain/helper/ContactHelper.java | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index f4007c098..fd26d6acf 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -53,6 +53,7 @@ + (emailSet); } + + public static List getContactMails(Context context) { + ContentResolver resolver = context.getContentResolver(); + Cursor mailCursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, + new String[]{ContactsContract.CommonDataKinds.Email.DATA}, + null, null, null); + if (mailCursor == null) return null; + + Set mails = new HashSet(); + while (mailCursor.moveToNext()) { + String email = mailCursor.getString(0); + if (email != null) { + mails.add(email); + } + } + mailCursor.close(); + return new ArrayList(mails); + } } From 3110122a85c5659a758a8f234381a7de783bdbca Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Tue, 27 May 2014 19:45:58 +0200 Subject: [PATCH 02/18] Add ability to resolve HkpKeyserver from _hkp._tcp SRV record --- OpenKeychain/build.gradle | 1 + .../keychain/keyimport/HkpKeyserver.java | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 090a7a2bf..738097fa0 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -10,6 +10,7 @@ sourceSets { dependencies { compile 'com.android.support:support-v4:19.1.0' compile 'com.android.support:appcompat-v7:19.1.0' + compile 'dnsjava:dnsjava:2.1.1' compile project(':extern:openpgp-api-lib') compile project(':extern:openkeychain-api-lib') compile project(':extern:html-textview') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index 5969455bd..2041548f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -33,6 +33,10 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.util.Log; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.SRVRecord; +import org.xbill.DNS.Type; import java.io.IOException; import java.io.InputStream; @@ -45,6 +49,8 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; @@ -336,4 +342,36 @@ public class HkpKeyserver extends Keyserver { client.getConnectionManager().shutdown(); } } + + @Override + public String toString() { + return mHost + ":" + mPort; + } + + /** + * Tries to find a server responsible for a given domain + * + * @return A responsible Keyserver or null if not found. + */ + public static HkpKeyserver resolve(String domain) { + try { + Record[] records = new Lookup("_hkp._tcp." + domain, Type.SRV).run(); + if (records.length > 0) { + Arrays.sort(records, new Comparator() { + @Override + public int compare(Record lhs, Record rhs) { + if (!(lhs instanceof SRVRecord)) return 1; + if (!(rhs instanceof SRVRecord)) return -1; + return ((SRVRecord) lhs).getPriority() - ((SRVRecord) rhs).getPriority(); + } + }); + Record record = records[0]; // This is our best choice + if (record instanceof SRVRecord) { + return new HkpKeyserver(((SRVRecord) record).getTarget().toString(), (short) ((SRVRecord) record).getPort()); + } + } + } catch (Exception ignored) { + } + return null; + } } From 8e5767f967646412a03563c966c3bb5b7c26e0fa Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Tue, 27 May 2014 20:17:49 +0200 Subject: [PATCH 03/18] Store origin with ImportKeysListEntry --- .../keychain/keyimport/HkpKeyserver.java | 1 + .../keychain/keyimport/ImportKeysListEntry.java | 11 +++++++++++ .../keychain/keyimport/KeybaseKeyserver.java | 1 + 3 files changed, 13 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index 2041548f3..fbe38c30a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -244,6 +244,7 @@ public class HkpKeyserver extends Keyserver { while (matcher.find()) { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(query); + entry.setOrigin("hkp:"+mHost+":"+mPort); entry.setBitStrength(Integer.parseInt(matcher.group(3))); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index 04b86e295..8af41a8a3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -52,6 +52,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { public String mPrimaryUserId; private String mExtraData; private String mQuery; + private String mOrigin; private boolean mSelected; @@ -77,6 +78,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { dest.writeInt(mBytes.length); dest.writeByteArray(mBytes); dest.writeString(mExtraData); + dest.writeString(mOrigin); } public static final Creator CREATOR = new Creator() { @@ -97,6 +99,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { vr.mBytes = new byte[source.readInt()]; source.readByteArray(vr.mBytes); vr.mExtraData = source.readString(); + vr.mOrigin = source.readString(); return vr; } @@ -218,6 +221,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mQuery = query; } + public String getOrigin() { + return mOrigin; + } + + public void setOrigin(String origin) { + mOrigin = origin; + } + /** * Constructor for later querying from keyserver */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java index f9b6abf18..43c0b12fd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -87,6 +87,7 @@ public class KeybaseKeyserver extends Keyserver { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(mQuery); + entry.setOrigin("keybase.io"); String keybaseId = JWalk.getString(match, "components", "username", "val"); String fullName = JWalk.getString(match, "components", "full_name", "val"); From cb92c9ccc811a72b4e216f819be32b19748113c7 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Tue, 27 May 2014 21:16:52 +0200 Subject: [PATCH 04/18] Add hkps support --- .../keychain/keyimport/HkpKeyserver.java | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index fbe38c30a..71c251ddc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -81,6 +81,7 @@ public class HkpKeyserver extends Keyserver { private String mHost; private short mPort; + private boolean mSecure; /** * pub:%keyid%:%algo%:%keylen%:%creationdate%:%expirationdate%:%flags% @@ -147,6 +148,7 @@ public class HkpKeyserver extends Keyserver { Pattern.CASE_INSENSITIVE); private static final short PORT_DEFAULT = 11371; + private static final short PORT_DEFAULT_HKPS = 443; /** * @param hostAndPort may be just @@ -157,19 +159,45 @@ public class HkpKeyserver extends Keyserver { public HkpKeyserver(String hostAndPort) { String host = hostAndPort; short port = PORT_DEFAULT; - final int colonPosition = hostAndPort.lastIndexOf(':'); - if (colonPosition > 0) { - host = hostAndPort.substring(0, colonPosition); - final String portStr = hostAndPort.substring(colonPosition + 1); - port = Short.decode(portStr); + boolean secure = false; + String[] parts = hostAndPort.split(":"); + if (parts.length > 1) { + if (!parts[0].contains(".")) { // This is not a domain or ip, so it must be a protocol name + if (parts[0].equalsIgnoreCase("hkps") || parts[0].equalsIgnoreCase("https")) { + secure = true; + port = PORT_DEFAULT_HKPS; + } else if (!parts[0].equalsIgnoreCase("hkp") && !parts[0].equalsIgnoreCase("http")) { + throw new IllegalArgumentException("Protocol " + parts[0] + " is unknown"); + } + host = parts[1]; + if (host.startsWith("//")) { // People tend to type https:// and hkps://, so we'll support that as well + host = host.substring(2); + } + if (parts.length > 2) { + port = Short.decode(parts[2]); + } + } else { + host = parts[0]; + port = Short.decode(parts[1]); + } } mHost = host; mPort = port; + mSecure = secure; } public HkpKeyserver(String host, short port) { + this(host, port, false); + } + + public HkpKeyserver(String host, short port, boolean secure) { mHost = host; mPort = port; + mSecure = secure; + } + + private String getUrlPrefix() { + return mSecure ? "https://" : "http://"; } private String query(String request) throws QueryFailedException, HttpError { @@ -181,7 +209,7 @@ public class HkpKeyserver extends Keyserver { } for (int i = 0; i < ips.length; ++i) { try { - String url = "http://" + ips[i].getHostAddress() + ":" + mPort + request; + String url = getUrlPrefix() + ips[i].getHostAddress() + ":" + mPort + request; Log.d(Constants.TAG, "hkp keyserver query: " + url); URL realUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); @@ -244,7 +272,7 @@ public class HkpKeyserver extends Keyserver { while (matcher.find()) { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(query); - entry.setOrigin("hkp:"+mHost+":"+mPort); + entry.setOrigin("hkp:" + mHost + ":" + mPort); entry.setBitStrength(Integer.parseInt(matcher.group(3))); @@ -297,7 +325,7 @@ public class HkpKeyserver extends Keyserver { public String get(String keyIdHex) throws QueryFailedException { HttpClient client = new DefaultHttpClient(); try { - String query = "http://" + mHost + ":" + mPort + + String query = getUrlPrefix() + mHost + ":" + mPort + "/pks/lookup?op=get&options=mr&search=" + keyIdHex; Log.d(Constants.TAG, "hkp keyserver get: " + query); HttpGet get = new HttpGet(query); @@ -326,7 +354,7 @@ public class HkpKeyserver extends Keyserver { public void add(String armoredKey) throws AddKeyException { HttpClient client = new DefaultHttpClient(); try { - String query = "http://" + mHost + ":" + mPort + "/pks/add"; + String query = getUrlPrefix() + mHost + ":" + mPort + "/pks/add"; HttpPost post = new HttpPost(query); Log.d(Constants.TAG, "hkp keyserver add: " + query); List nameValuePairs = new ArrayList(2); From c676e534799545f6aa95071463c10aa0b2f92b9d Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Wed, 28 May 2014 20:44:01 +0200 Subject: [PATCH 05/18] Fix url building to support certificate check on hkps servers Note: the CA used by sks-keyservers.net is not valid for android, thus using hkps fails for them. pgp.mit.edu uses a perfectly valid cert. --- .../keychain/keyimport/HkpKeyserver.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index 71c251ddc..b064fc5b1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -201,15 +201,26 @@ public class HkpKeyserver extends Keyserver { } private String query(String request) throws QueryFailedException, HttpError { - InetAddress ips[]; - try { - ips = InetAddress.getAllByName(mHost); - } catch (UnknownHostException e) { - throw new QueryFailedException(e.toString()); - } - for (int i = 0; i < ips.length; ++i) { + List urls = new ArrayList(); + if (mSecure) { + urls.add(getUrlPrefix() + mHost + ":" + mPort + request); + } else { + InetAddress ips[]; + try { + ips = InetAddress.getAllByName(mHost); + } catch (UnknownHostException e) { + throw new QueryFailedException(e.toString()); + } + for (InetAddress ip : ips) { + // Note: This is actually not HTTP 1.1 compliant, as we hide the real "Host" value, + // but Android's HTTPUrlConnection does not support any other way to set + // Socket's remote IP address... + urls.add(getUrlPrefix() + ip.getHostAddress() + ":" + mPort + request); + } + } + + for (String url : urls) { try { - String url = getUrlPrefix() + ips[i].getHostAddress() + ":" + mPort + request; Log.d(Constants.TAG, "hkp keyserver query: " + url); URL realUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); @@ -272,7 +283,7 @@ public class HkpKeyserver extends Keyserver { while (matcher.find()) { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(query); - entry.setOrigin("hkp:" + mHost + ":" + mPort); + entry.setOrigin(getUrlPrefix() + mHost + ":" + mPort); entry.setBitStrength(Integer.parseInt(matcher.group(3))); From be490307f97b5e73bad5c4882afab6dff1f10b48 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 29 May 2014 10:24:00 +0200 Subject: [PATCH 06/18] Download from origin during ACTION_DOWNLOAD_AND_IMPORT_KEYS --- .../keychain/service/KeychainIntentService.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 6f38418ff..b6ee234f4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -796,10 +796,11 @@ public class KeychainIntentService extends IntentService ArrayList entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST); String keyServer = data.getString(DOWNLOAD_KEY_SERVER); - // this downloads the keys and places them into the ImportKeysListEntry entries - HkpKeyserver server = new HkpKeyserver(keyServer); - for (ImportKeysListEntry entry : entries) { + + // this downloads the keys and places them into the ImportKeysListEntry entries + HkpKeyserver server = new HkpKeyserver(entry.getOrigin() != null ? entry.getOrigin() : keyServer); + // if available use complete fingerprint for get request byte[] downloadedKeyBytes; if (entry.getFingerprintHex() != null) { From 518f3e1763ae1fad54a20f3cba6578e79adcdb63 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 29 May 2014 10:33:15 +0200 Subject: [PATCH 07/18] Make abstract methods in Keyserver public (implementations make them public anyway) --- .../sufficientlysecure/keychain/keyimport/Keyserver.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java index 868f543f0..842e7d922 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java @@ -48,12 +48,12 @@ public abstract class Keyserver { private static final long serialVersionUID = -507574859137295530L; } - abstract List search(String query) throws QueryFailedException, + public abstract List search(String query) throws QueryFailedException, QueryNeedsRepairException; - abstract String get(String keyIdHex) throws QueryFailedException; + public abstract String get(String keyIdHex) throws QueryFailedException; - abstract void add(String armoredKey) throws AddKeyException; + public abstract void add(String armoredKey) throws AddKeyException; public static String readAll(InputStream in, String encoding) throws IOException { ByteArrayOutputStream raw = new ByteArrayOutputStream(); From 3417a7a2de190efe5c5ea19581dd4f099453a99e Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 29 May 2014 10:34:50 +0200 Subject: [PATCH 08/18] Store nice origin with keybase keys (that can't be interpreted as HKP server) --- .../keychain/keyimport/KeybaseKeyserver.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java index 43c0b12fd..8054edd3e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -31,6 +31,7 @@ import java.net.URLEncoder; import java.util.ArrayList; public class KeybaseKeyserver extends Keyserver { + public static final String ORIGIN = "keybase:keybase.io"; private String mQuery; @Override @@ -87,7 +88,7 @@ public class KeybaseKeyserver extends Keyserver { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(mQuery); - entry.setOrigin("keybase.io"); + entry.setOrigin(ORIGIN); String keybaseId = JWalk.getString(match, "components", "username", "val"); String fullName = JWalk.getString(match, "components", "full_name", "val"); From 34b97cb136376d14d4a1a48ccd89afa7d8abbb48 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 29 May 2014 11:43:41 +0200 Subject: [PATCH 09/18] Merge ACTION_DOWNLOAD_AND_IMPORT_KEYS and ACTION_IMPORT_KEYBASE_KEYS --- .../service/KeychainIntentService.java | 69 ++++--------------- 1 file changed, 15 insertions(+), 54 deletions(-) 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 b6ee234f4..2b8745f0a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; +import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; @@ -742,68 +743,28 @@ public class KeychainIntentService extends IntentService } catch (Exception e) { sendErrorToHandler(e); } - } else if (ACTION_IMPORT_KEYBASE_KEYS.equals(action)) { - ArrayList entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST); - - try { - KeybaseKeyserver server = new KeybaseKeyserver(); - for (ImportKeysListEntry entry : entries) { - // the keybase handle is in userId(1) - String keybaseId = entry.getExtraData(); - byte[] downloadedKeyBytes = server.get(keybaseId).getBytes(); - - // create PGPKeyRing object based on downloaded armored key - PGPKeyRing downloadedKey = null; - BufferedInputStream bufferedInput = - new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes)); - if (bufferedInput.available() > 0) { - InputStream in = PGPUtil.getDecoderStream(bufferedInput); - PGPObjectFactory objectFactory = new PGPObjectFactory(in); - - // get first object in block - Object obj; - if ((obj = objectFactory.nextObject()) != null) { - - if (obj instanceof PGPKeyRing) { - downloadedKey = (PGPKeyRing) obj; - } else { - throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); - } - } - } - - // save key bytes in entry object for doing the - // actual import afterwards - entry.setBytes(downloadedKey.getEncoded()); - } - - Intent importIntent = new Intent(this, KeychainIntentService.class); - importIntent.setAction(ACTION_IMPORT_KEYRING); - Bundle importData = new Bundle(); - importData.putParcelableArrayList(IMPORT_KEY_LIST, entries); - importIntent.putExtra(EXTRA_DATA, importData); - importIntent.putExtra(EXTRA_MESSENGER, mMessenger); - - // now import it with this service - onHandleIntent(importIntent); - - // result is handled in ACTION_IMPORT_KEYRING - } catch (Exception e) { - sendErrorToHandler(e); - } - } else if (ACTION_DOWNLOAD_AND_IMPORT_KEYS.equals(action)) { + } else if (ACTION_DOWNLOAD_AND_IMPORT_KEYS.equals(action) || ACTION_IMPORT_KEYBASE_KEYS.equals(action)) { try { ArrayList entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST); String keyServer = data.getString(DOWNLOAD_KEY_SERVER); + // this downloads the keys and places them into the ImportKeysListEntry entries for (ImportKeysListEntry entry : entries) { - // this downloads the keys and places them into the ImportKeysListEntry entries - HkpKeyserver server = new HkpKeyserver(entry.getOrigin() != null ? entry.getOrigin() : keyServer); + Keyserver server; + if (entry.getOrigin() == null) { + server = new HkpKeyserver(keyServer); + } else if (KeybaseKeyserver.ORIGIN.equals(entry.getOrigin())) { + server = new KeybaseKeyserver(); + } else { + server = new HkpKeyserver(entry.getOrigin()); + } // if available use complete fingerprint for get request byte[] downloadedKeyBytes; - if (entry.getFingerprintHex() != null) { + if (KeybaseKeyserver.ORIGIN.equals(entry.getOrigin())) { + downloadedKeyBytes = server.get(entry.getExtraData()).getBytes(); + } else if (entry.getFingerprintHex() != null) { downloadedKeyBytes = server.get("0x" + entry.getFingerprintHex()).getBytes(); } else { downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes(); @@ -833,7 +794,7 @@ public class KeychainIntentService extends IntentService if (entry.getFingerprintHex() != null) { String downloadedKeyFp = PgpKeyHelper.convertFingerprintToHex( downloadedKey.getPublicKey().getFingerprint()); - if (downloadedKeyFp.equals(entry.getFingerprintHex())) { + if (downloadedKeyFp.equalsIgnoreCase(entry.getFingerprintHex())) { Log.d(Constants.TAG, "fingerprint of downloaded key is the same as " + "the requested fingerprint!"); } else { From cc2ef0c17ca1d032477eb21308c5ea677b1cc548 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Wed, 4 Jun 2014 17:05:57 +0200 Subject: [PATCH 10/18] Store expired state within ImportKeysListEntry --- .../keychain/keyimport/HkpKeyserver.java | 1 + .../keychain/keyimport/ImportKeysListEntry.java | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index b064fc5b1..d4f1af84f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -308,6 +308,7 @@ public class HkpKeyserver extends Keyserver { entry.setDate(tmpGreg.getTime()); entry.setRevoked(matcher.group(6).contains("r")); + entry.setExpired(matcher.group(6).contains("e")); ArrayList userIds = new ArrayList(); final String uidLines = matcher.group(7); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index 8af41a8a3..ea4f5948e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -44,6 +44,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { public long keyId; public String keyIdHex; public boolean revoked; + public boolean expired; public Date date; // TODO: not displayed public String fingerprintHex; public int bitStrength; @@ -68,6 +69,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { dest.writeStringList(userIds); dest.writeLong(keyId); dest.writeByte((byte) (revoked ? 1 : 0)); + dest.writeByte((byte) (expired ? 1 : 0)); dest.writeSerializable(date); dest.writeString(fingerprintHex); dest.writeString(keyIdHex); @@ -89,6 +91,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { source.readStringList(vr.userIds); vr.keyId = source.readLong(); vr.revoked = source.readByte() == 1; + vr.expired = source.readByte() == 1; vr.date = (Date) source.readSerializable(); vr.fingerprintHex = source.readString(); vr.keyIdHex = source.readString(); @@ -129,6 +132,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable { this.mSelected = selected; } + public boolean isExpired() { + return expired; + } + + public void setExpired(boolean expired) { + this.expired = expired; + } + public long getKeyId() { return keyId; } From dd959876f4a1a7f26a3f7524e238416c1a30c7e5 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Wed, 4 Jun 2014 17:55:24 +0200 Subject: [PATCH 11/18] First version of automatic contact discovery. TODO: - Configuration (much of it) - Enabled by default? - Which keys to import? Current state: All non-revoked and non-expired with matching userid - Search for keys if already known? Current state: yes, may cause traffic (configuration: only when wifi?) - Update interval: Currently Android handles it, might be good (causes automatic refresh on new contact and stuff like that) or bad (too many of refreshes) --- OpenKeychain/src/main/AndroidManifest.xml | 26 ++++ .../keychain/KeychainApplication.java | 13 ++ .../keychain/helper/EmailKeyHelper.java | 97 +++++++++++++ .../service/ContactSyncAdapterService.java | 70 ++++++++++ .../keychain/service/DummyAccountService.java | 131 ++++++++++++++++++ OpenKeychain/src/main/res/values/strings.xml | 2 + .../src/main/res/xml/account_desc.xml | 6 + .../res/xml/custom_pgp_contacts_structure.xml | 7 + .../src/main/res/xml/sync_adapter_desc.xml | 6 + 9 files changed, 358 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java create mode 100644 OpenKeychain/src/main/res/xml/account_desc.xml create mode 100644 OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml create mode 100644 OpenKeychain/src/main/res/xml/sync_adapter_desc.xml diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index fd26d6acf..9a2011205 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -53,6 +53,10 @@ + + + + @@ -435,6 +439,28 @@ + + + + + + + + + + + + + + + + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index f911318a0..3ac3a9dee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain; +import android.accounts.Account; +import android.accounts.AccountManager; import android.app.Application; import android.content.Context; import android.graphics.PorterDuff; @@ -76,6 +78,17 @@ public class KeychainApplication extends Application { brandGlowEffect(getApplicationContext(), getApplicationContext().getResources().getColor(R.color.emphasis)); + + setupAccountAsNeeded(); + } + + private void setupAccountAsNeeded() { + AccountManager manager = AccountManager.get(this); + Account[] accounts = manager.getAccountsByType(getPackageName()); + if (accounts == null || accounts.length == 0) { + Account dummy = new Account(getString(R.string.app_name), getPackageName()); + manager.addAccountExplicitly(dummy, null, null); + } } static void brandGlowEffect(Context context, int brandColor) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java new file mode 100644 index 000000000..80f52f914 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/EmailKeyHelper.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.helper; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Messenger; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.keyimport.Keyserver; +import org.sufficientlysecure.keychain.service.KeychainIntentService; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class EmailKeyHelper { + + public static void importContacts(Context context, Messenger messenger) { + importAll(context, messenger, ContactHelper.getContactMails(context)); + } + + public static void importAll(Context context, Messenger messenger, List mails) { + Set keys = new HashSet(); + for (String mail : mails) { + keys.addAll(getEmailKeys(context, mail)); + } + importKeys(context, messenger, new ArrayList(keys)); + } + + public static List getEmailKeys(Context context, String mail) { + Set keys = new HashSet(); + + // Try _hkp._tcp SRV record first + String[] mailparts = mail.split("@"); + if (mailparts.length == 2) { + HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1]); + if (hkp != null) { + keys.addAll(getEmailKeys(mail, hkp)); + } + } + + // Most users don't have the SRV record, so ask a default server as well + String[] servers = Preferences.getPreferences(context).getKeyServers(); + if (servers != null && servers.length != 0) { + HkpKeyserver hkp = new HkpKeyserver(servers[0]); + keys.addAll(getEmailKeys(mail, hkp)); + } + return new ArrayList(keys); + } + + private static void importKeys(Context context, Messenger messenger, List keys) { + Intent importIntent = new Intent(context, KeychainIntentService.class); + importIntent.setAction(KeychainIntentService.ACTION_DOWNLOAD_AND_IMPORT_KEYS); + Bundle importData = new Bundle(); + importData.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST, + new ArrayList(keys)); + importIntent.putExtra(KeychainIntentService.EXTRA_DATA, importData); + importIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + context.startService(importIntent); + } + + public static List getEmailKeys(String mail, Keyserver keyServer) { + Set keys = new HashSet(); + try { + for (ImportKeysListEntry key : keyServer.search(mail)) { + if (key.isRevoked() || key.isExpired()) continue; + for (String userId : key.getUserIds()) { + if (userId.toLowerCase().contains(mail.toLowerCase())) { + keys.add(key); + } + } + } + } catch (Keyserver.QueryFailedException ignored) { + } catch (Keyserver.QueryNeedsRepairException ignored) { + } + return new ArrayList(keys); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java new file mode 100644 index 000000000..4d0397196 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.service; + +import android.accounts.Account; +import android.app.Service; +import android.content.AbstractThreadedSyncAdapter; +import android.content.ContentProviderClient; +import android.content.Intent; +import android.content.SyncResult; +import android.os.*; +import org.sufficientlysecure.keychain.helper.EmailKeyHelper; +import org.sufficientlysecure.keychain.util.Log; + +public class ContactSyncAdapterService extends Service { + + private class ContactSyncAdapter extends AbstractThreadedSyncAdapter { + + public ContactSyncAdapter() { + super(ContactSyncAdapterService.this, true); + } + + @Override + public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, + final SyncResult syncResult) { + EmailKeyHelper.importContacts(getContext(), new Messenger(new Handler(Looper.getMainLooper(), + new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + Bundle data = msg.getData(); + switch (msg.arg1) { + case KeychainIntentServiceHandler.MESSAGE_OKAY: + return true; + case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS: + if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) && + data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) { + Log.d("Keychain/ContactSync/DownloadKeys", "Progress: " + + data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" + + data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)); + return false; + } + default: + Log.d("Keychain/ContactSync/DownloadKeys", "Syncing... " + msg.toString()); + return false; + } + } + }))); + } + } + + @Override + public IBinder onBind(Intent intent) { + return new ContactSyncAdapter().getSyncAdapterBinder(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java new file mode 100644 index 000000000..d3b29d5cf --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.service; + +import android.accounts.AbstractAccountAuthenticator; +import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.NetworkErrorException; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.widget.Toast; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; + +/** + * This service actually does nothing, it's sole task is to show a Toast if the use tries to create an account. + */ +public class DummyAccountService extends Service { + + private class Toaster { + private static final String TOAST_MESSAGE = "toast_message"; + private Context context; + private Handler handler = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + Toast.makeText(context, msg.getData().getString(TOAST_MESSAGE), Toast.LENGTH_LONG).show(); + return true; + } + }); + + private Toaster(Context context) { + this.context = context; + } + + public void toast(int resourceId) { + toast(context.getString(resourceId)); + } + + public void toast(String message) { + Message msg = new Message(); + Bundle bundle = new Bundle(); + bundle.putString(TOAST_MESSAGE, message); + msg.setData(bundle); + handler.sendMessage(msg); + } + } + + private class Authenticator extends AbstractAccountAuthenticator { + + public Authenticator() { + super(DummyAccountService.this); + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { + Log.d("DummyAccountService", "editProperties"); + return null; + } + + @Override + public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, + String[] requiredFeatures, Bundle options) throws NetworkErrorException { + response.onResult(new Bundle()); + toaster.toast(R.string.info_no_manual_account_creation); + Log.d("DummyAccountService", "addAccount"); + return null; + } + + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) + throws NetworkErrorException { + Log.d("DummyAccountService", "confirmCredentials"); + return null; + } + + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, + Bundle options) throws NetworkErrorException { + Log.d("DummyAccountService", "getAuthToken"); + return null; + } + + @Override + public String getAuthTokenLabel(String authTokenType) { + Log.d("DummyAccountService", "getAuthTokenLabel"); + return null; + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, + Bundle options) throws NetworkErrorException { + Log.d("DummyAccountService", "updateCredentials"); + return null; + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) + throws NetworkErrorException { + Log.d("DummyAccountService", "hasFeatures"); + return null; + } + } + + private Toaster toaster; + + @Override + public IBinder onBind(Intent intent) { + toaster = new Toaster(this); + return new Authenticator().getIBinder(); + } +} diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 1ba8a6d2d..70b8616d4 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -518,5 +518,7 @@ unknown cannot sign No encryption subkey available! + Do not create OpenKeychain-Accounts manually. + For more information, see Help. diff --git a/OpenKeychain/src/main/res/xml/account_desc.xml b/OpenKeychain/src/main/res/xml/account_desc.xml new file mode 100644 index 000000000..94ffdf40b --- /dev/null +++ b/OpenKeychain/src/main/res/xml/account_desc.xml @@ -0,0 +1,6 @@ + + + diff --git a/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml b/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml new file mode 100644 index 000000000..3318f3b45 --- /dev/null +++ b/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/xml/sync_adapter_desc.xml b/OpenKeychain/src/main/res/xml/sync_adapter_desc.xml new file mode 100644 index 000000000..d8fe60e91 --- /dev/null +++ b/OpenKeychain/src/main/res/xml/sync_adapter_desc.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file From 6a637462782b4ce57ecf154edf0974114181b8ad Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Wed, 4 Jun 2014 18:07:28 +0200 Subject: [PATCH 12/18] Fix regex for hkp parsing to support multiple uids --- .../sufficientlysecure/keychain/keyimport/HkpKeyserver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index d4f1af84f..2ec9e1c07 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -116,7 +116,7 @@ public class HkpKeyserver extends Keyserver { */ public static final Pattern PUB_KEY_LINE = Pattern .compile("pub:([0-9a-fA-F]+):([0-9]+):([0-9]+):([0-9]+):([0-9]*):([rde]*)[ \n\r]*" // pub line - + "(uid:(.*):([0-9]+):([0-9]*):([rde]*))+", // one or more uid lines + + "((uid:([^:]*):([0-9]+):([0-9]*):([rde]*)[ \n\r]*)+)", // one or more uid lines Pattern.CASE_INSENSITIVE); /** @@ -144,7 +144,7 @@ public class HkpKeyserver extends Keyserver { * */ public static final Pattern UID_LINE = Pattern - .compile("uid:(.*):([0-9]+):([0-9]*):([rde]*)", + .compile("uid:([^:]*):([0-9]+):([0-9]*):([rde]*)", Pattern.CASE_INSENSITIVE); private static final short PORT_DEFAULT = 11371; From dc1e26f39c9c7fa88dd28d2920a2919f83e0575c Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 5 Jun 2014 00:59:39 +0200 Subject: [PATCH 13/18] Make keylist case insensitive You want "michael" to be next to "Michael", don't you? --- .../org/sufficientlysecure/keychain/ui/KeyListFragment.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 9c90b5eb7..d5a753133 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -253,7 +253,7 @@ public class KeyListFragment extends LoaderFragment static final int INDEX_HAS_ANY_SECRET = 6; static final String ORDER = - KeyRings.HAS_ANY_SECRET + " DESC, " + KeyRings.USER_ID + " ASC"; + KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC"; @Override @@ -593,7 +593,7 @@ public class KeyListFragment extends LoaderFragment String userId = mCursor.getString(KeyListFragment.INDEX_USER_ID); String headerText = convertView.getResources().getString(R.string.user_id_no_name); if (userId != null && userId.length() > 0) { - headerText = "" + userId.subSequence(0, 1).charAt(0); + headerText = "" + userId.charAt(0); } holder.mText.setText(headerText); holder.mCount.setVisibility(View.GONE); @@ -622,7 +622,7 @@ public class KeyListFragment extends LoaderFragment // otherwise, return the first character of the name as ID String userId = mCursor.getString(KeyListFragment.INDEX_USER_ID); if (userId != null && userId.length() > 0) { - return userId.charAt(0); + return Character.toUpperCase(userId.charAt(0)); } else { return Long.MAX_VALUE; } From 80e99986401b635f4eeef5d13740911d10740aef Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 5 Jun 2014 23:22:21 +0200 Subject: [PATCH 14/18] Show keys with android contacts This means to sync userid + keyid into contact storage. Android will merge them to normal contacts based on primary userid. --- OpenKeychain/src/main/AndroidManifest.xml | 6 ++ .../keychain/Constants.java | 2 + .../keychain/KeychainApplication.java | 11 +-- .../keychain/helper/ContactHelper.java | 89 ++++++++++++++++++- .../service/ContactSyncAdapterService.java | 4 + .../keychain/ui/ViewKeyActivity.java | 13 ++- OpenKeychain/src/main/res/values/strings.xml | 1 + .../res/xml/custom_pgp_contacts_structure.xml | 4 +- 8 files changed, 119 insertions(+), 11 deletions(-) diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 9a2011205..31c809334 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -58,6 +58,7 @@ + + + + + + getMailAccounts(Context context) { final Account[] accounts = AccountManager.get(context).getAccounts(); final Set emailSet = new HashSet(); @@ -60,4 +75,74 @@ public class ContactHelper { mailCursor.close(); return new ArrayList(mails); } + + public static Uri dataUriFromContactUri(Context context, Uri contactUri) { + Cursor contactMasterKey = context.getContentResolver().query(contactUri, new String[]{ContactsContract.Data.DATA2}, null, null, null, null); + if (contactMasterKey != null) { + if (contactMasterKey.moveToNext()) { + return KeychainContract.KeyRings.buildGenericKeyRingUri(contactMasterKey.getLong(0)); + } + contactMasterKey.close(); + } + return null; + } + + public static void writeKeysToContacts(Context context) { + ContentResolver resolver = context.getContentResolver(); + Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), KEYS_TO_CONTACT_PROJECTION, + null, null, null); + if (cursor != null) { + while (cursor.moveToNext()) { + String[] userId = PgpKeyHelper.splitUserId(cursor.getString(0)); + String fingerprint = PgpKeyHelper.convertFingerprintToHex(cursor.getBlob(1)); + String keyIdShort = PgpKeyHelper.convertKeyIdToHexShort(cursor.getLong(2)); + long masterKeyId = cursor.getLong(3); + int rawContactId = -1; + Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, RAW_CONTACT_ID_PROJECTION, + FIND_RAW_CONTACT_SELECTION, new String[]{Constants.PACKAGE_NAME, fingerprint}, null, null); + if (raw != null) { + if (raw.moveToNext()) { + rawContactId = raw.getInt(0); + } + raw.close(); + } + ArrayList ops = new ArrayList(); + if (rawContactId == -1) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, context.getString(R.string.app_name)) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.PACKAGE_NAME) + .withValue(ContactsContract.RawContacts.SOURCE_ID, fingerprint) + .build()); + if (userId[0] != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, userId[0]) + .build()); + } + if (userId[1] != null) { + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Email.DATA, userId[1]) + .build()); + } + ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue(ContactsContract.Data.MIMETYPE, Constants.CUSTOM_CONTACT_DATA_MIME_TYPE) + .withValue(ContactsContract.Data.DATA1, String.format(context.getString(R.string.contact_show_key), keyIdShort)) + .withValue(ContactsContract.Data.DATA2, masterKeyId) + .build()); + } + try { + resolver.applyBatch(ContactsContract.AUTHORITY, ops); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (OperationApplicationException e) { + e.printStackTrace(); + } + } + cursor.close(); + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index 4d0397196..a52dbfa27 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -24,6 +24,8 @@ import android.content.ContentProviderClient; import android.content.Intent; import android.content.SyncResult; import android.os.*; +import org.sufficientlysecure.keychain.KeychainApplication; +import org.sufficientlysecure.keychain.helper.ContactHelper; import org.sufficientlysecure.keychain.helper.EmailKeyHelper; import org.sufficientlysecure.keychain.util.Log; @@ -60,6 +62,8 @@ public class ContactSyncAdapterService extends Service { } } }))); + KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this); + ContactHelper.writeKeysToContacts(ContactSyncAdapterService.this); } } 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 bed116f5f..010144851 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -32,6 +33,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.provider.ContactsContract; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -47,6 +49,7 @@ import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ContactHelper; import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; @@ -125,7 +128,7 @@ public class ViewKeyActivity extends ActionBarActivity implements switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB); } - Uri dataUri = getIntent().getData(); + Uri dataUri = getDataUri(); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); finish(); @@ -163,6 +166,14 @@ public class ViewKeyActivity extends ActionBarActivity implements mViewPager.setCurrentItem(switchToTab); } + private Uri getDataUri() { + Uri dataUri = getIntent().getData(); + if (dataUri != null && dataUri.getHost().equals(ContactsContract.AUTHORITY)) { + dataUri = ContactHelper.dataUriFromContactUri(this, dataUri); + } + return dataUri; + } + private void loadData(Uri dataUri) { mDataUri = dataUri; diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 09f76c675..ec6b389ed 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -521,5 +521,6 @@ No encryption subkey available! Do not create OpenKeychain-Accounts manually. For more information, see Help. + Show key (%s) diff --git a/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml b/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml index 3318f3b45..5f5f2be80 100644 --- a/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml +++ b/OpenKeychain/src/main/res/xml/custom_pgp_contacts_structure.xml @@ -1,7 +1,5 @@ + android:detailColumn="data1"/> \ No newline at end of file From 36312b950aa2e3150ec9381c91850c63ed1c604b Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Thu, 5 Jun 2014 23:46:14 +0200 Subject: [PATCH 15/18] Add dnsjava as submodule --- .gitmodules | 3 +++ OpenKeychain/build.gradle | 2 +- extern/dnsjava | 1 + settings.gradle | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) create mode 160000 extern/dnsjava diff --git a/.gitmodules b/.gitmodules index 572293f94..20a0f60e0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "extern/openkeychain-api-lib"] path = extern/openkeychain-api-lib url = https://github.com/open-keychain/openkeychain-api-lib.git +[submodule "extern/dnsjava"] + path = extern/dnsjava + url = https://github.com/open-keychain/dnsjava.git diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 374a63b28..20e5ca594 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -14,7 +14,6 @@ dependencies { compile 'com.android.support:support-v4:19.1.0' compile 'com.android.support:appcompat-v7:19.1.0' - compile 'dnsjava:dnsjava:2.1.1' compile project(':extern:openpgp-api-lib') compile project(':extern:openkeychain-api-lib') compile project(':extern:html-textview') @@ -27,6 +26,7 @@ dependencies { compile project(':extern:spongycastle:pkix') compile project(':extern:spongycastle:prov') compile project(':extern:AppMsg:library') + compile project(':extern:dnsjava') // Dependencies for the `instrumentTest` task, make sure to list all your global dependencies here as well androidTestCompile 'junit:junit:4.10' diff --git a/extern/dnsjava b/extern/dnsjava new file mode 160000 index 000000000..71c8a9e56 --- /dev/null +++ b/extern/dnsjava @@ -0,0 +1 @@ +Subproject commit 71c8a9e56b19b34907e7e2e810ca15b57e3edc2b diff --git a/settings.gradle b/settings.gradle index b0162875a..d7ca15f82 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,3 +11,4 @@ include ':extern:spongycastle:pg' include ':extern:spongycastle:pkix' include ':extern:spongycastle:prov' include ':extern:AppMsg:library' +include ':extern:dnsjava' From 9d02bc85e2c3fffbb18c4bbd8889db827ac2e8f0 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Fri, 6 Jun 2014 00:51:24 +0200 Subject: [PATCH 16/18] Fix compile error introduced during merge --- .../org/sufficientlysecure/keychain/helper/ContactHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java index 4b85d7e80..f50ccf6f8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java @@ -27,6 +27,7 @@ import android.provider.ContactsContract; import android.util.Patterns; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.util.Log; @@ -93,7 +94,7 @@ public class ContactHelper { null, null, null); if (cursor != null) { while (cursor.moveToNext()) { - String[] userId = PgpKeyHelper.splitUserId(cursor.getString(0)); + String[] userId = KeyRing.splitUserId(cursor.getString(0)); String fingerprint = PgpKeyHelper.convertFingerprintToHex(cursor.getBlob(1)); String keyIdShort = PgpKeyHelper.convertKeyIdToHexShort(cursor.getLong(2)); long masterKeyId = cursor.getLong(3); From 55ca0841f6ac9be91fa185442a5c11cebc016441 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Fri, 6 Jun 2014 17:39:11 +0200 Subject: [PATCH 17/18] Fixing TAG and string resource --- .../keychain/service/ContactSyncAdapterService.java | 5 +++-- OpenKeychain/src/main/res/values/strings.xml | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index a52dbfa27..8db9294df 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -24,6 +24,7 @@ import android.content.ContentProviderClient; import android.content.Intent; import android.content.SyncResult; import android.os.*; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.helper.ContactHelper; import org.sufficientlysecure.keychain.helper.EmailKeyHelper; @@ -51,13 +52,13 @@ public class ContactSyncAdapterService extends Service { case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS: if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) && data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) { - Log.d("Keychain/ContactSync/DownloadKeys", "Progress: " + + Log.d(Constants.TAG, "Progress: " + data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" + data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)); return false; } default: - Log.d("Keychain/ContactSync/DownloadKeys", "Syncing... " + msg.toString()); + Log.d(Constants.TAG, "Syncing... " + msg.toString()); return false; } } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index ec6b389ed..9545c1ed4 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -519,8 +519,7 @@ cannot sign Encoding error No encryption subkey available! - Do not create OpenKeychain-Accounts manually. - For more information, see Help. + Do not create OpenKeychain-Accounts manually.\nFor more information, see Help. Show key (%s) From 5601f1b76f42276950487d635c9ea87006f15621 Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Fri, 6 Jun 2014 17:42:28 +0200 Subject: [PATCH 18/18] Fix TAG in account service as well --- .../keychain/service/DummyAccountService.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java index d3b29d5cf..008502ce7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java @@ -29,6 +29,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.widget.Toast; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Log; @@ -73,7 +74,7 @@ public class DummyAccountService extends Service { @Override public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { - Log.d("DummyAccountService", "editProperties"); + Log.d(Constants.TAG, "DummyAccountService.editProperties"); return null; } @@ -82,41 +83,41 @@ public class DummyAccountService extends Service { String[] requiredFeatures, Bundle options) throws NetworkErrorException { response.onResult(new Bundle()); toaster.toast(R.string.info_no_manual_account_creation); - Log.d("DummyAccountService", "addAccount"); + Log.d(Constants.TAG, "DummyAccountService.addAccount"); return null; } @Override public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { - Log.d("DummyAccountService", "confirmCredentials"); + Log.d(Constants.TAG, "DummyAccountService.confirmCredentials"); return null; } @Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { - Log.d("DummyAccountService", "getAuthToken"); + Log.d(Constants.TAG, "DummyAccountService.getAuthToken"); return null; } @Override public String getAuthTokenLabel(String authTokenType) { - Log.d("DummyAccountService", "getAuthTokenLabel"); + Log.d(Constants.TAG, "DummyAccountService.getAuthTokenLabel"); return null; } @Override public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { - Log.d("DummyAccountService", "updateCredentials"); + Log.d(Constants.TAG, "DummyAccountService.updateCredentials"); return null; } @Override public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { - Log.d("DummyAccountService", "hasFeatures"); + Log.d(Constants.TAG, "DummyAccountService.hasFeatures"); return null; } }