From 17f43ad21bde79caf1b815c89fd5a95f1f959ca5 Mon Sep 17 00:00:00 2001 From: Tim Bray Date: Fri, 20 Jun 2014 09:26:04 -0700 Subject: Moved Keybase stuff into KeybaseLib submodule --- OpenKeychain/build.gradle | 1 + .../keychain/keyimport/KeybaseKeyserver.java | 130 +++++---------------- .../sufficientlysecure/keychain/util/JWalk.java | 121 ------------------- OpenKeychain/src/main/res/values/strings.xml | 2 +- 4 files changed, 31 insertions(+), 223 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index f8bf79d59..fb79d2163 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -20,6 +20,7 @@ dependencies { compile project(':extern:AppMsg:library') compile project(':extern:SuperToasts:supertoasts') compile project(':extern:dnsjava') + compile project(':extern:KeybaseLib:Lib') // Unit tests are run with Robolectric 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 bf886cc6c..98e5111f7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -17,19 +17,17 @@ package org.sufficientlysecure.keychain.keyimport; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; +import com.textuality.keybase.lib.KeybaseException; +import com.textuality.keybase.lib.Match; +import com.textuality.keybase.lib.Search; +import com.textuality.keybase.lib.User; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.util.JWalk; import org.sufficientlysecure.keychain.util.Log; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; import java.util.ArrayList; -import java.util.Locale; +import java.util.List; public class KeybaseKeyserver extends Keyserver { public static final String ORIGIN = "keybase:keybase.io"; @@ -44,29 +42,14 @@ public class KeybaseKeyserver extends Keyserver { // cut off "0x" if a user is searching for a key id query = query.substring(2); } + mQuery = query; - JSONObject fromQuery = getFromKeybase("_/api/1.0/user/autocomplete.json?q=", query); try { - - JSONArray matches = JWalk.getArray(fromQuery, "completions"); - for (int i = 0; i < matches.length(); i++) { - JSONObject match = matches.getJSONObject(i); - - // only list them if they have a key - if (JWalk.optObject(match, "components", "key_fingerprint") != null) { - // TODO: needed anymore? -// String keybaseId = JWalk.getString(match, "components", "username", "val"); -// String fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val"); -// fingerprint = fingerprint.replace(" ", "").toUpperCase(); -// if (keybaseId.equals(query) || fingerprint.startsWith(query.toUpperCase())) { -// results.add(makeEntry(match)); -// } else { -// results.add(makeEntry(match)); -// } - results.add(makeEntry(match)); - } + Iterable matches = Search.search(query); + for (Match match : matches) { + results.add(makeEntry(match)); } - } catch (Exception e) { + } catch (KeybaseException e) { Log.e(Constants.TAG, "keybase result parsing error", e); throw new QueryFailedException("Unexpected structure in keybase search result: " + e.getMessage()); } @@ -74,103 +57,48 @@ public class KeybaseKeyserver extends Keyserver { return results; } - private JSONObject getUser(String keybaseId) throws QueryFailedException { - try { - return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseId); - } catch (Exception e) { - String detail = ""; - if (keybaseId != null) { - detail = ". Query was for user '" + keybaseId + "'"; - } - throw new QueryFailedException(e.getMessage() + detail); - } - } - - private ImportKeysListEntry makeEntry(JSONObject match) throws QueryFailedException, JSONException { - + private ImportKeysListEntry makeEntry(Match match) throws KeybaseException { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(mQuery); entry.setOrigin(ORIGIN); - String keybaseId = JWalk.getString(match, "components", "username", "val"); - String fullName = JWalk.getString(match, "components", "full_name", "val"); - String fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val"); - fingerprint = fingerprint.replace(" ", "").toLowerCase(Locale.US); // not strictly necessary but doesn't hurt + String username = null; + username = match.getUsername(); + String fullName = match.getFullName(); + String fingerprint = match.getFingerprint(); entry.setFingerprintHex(fingerprint); - entry.setKeyIdHex("0x" + fingerprint.substring(Math.max(0, fingerprint.length() - 16))); + entry.setKeyIdHex("0x" + match.getKeyID()); // store extra info, so we can query for the keybase id directly - entry.setExtraData(keybaseId); + entry.setExtraData(username); - final int algorithmId = JWalk.getInt(match, "components", "key_fingerprint", "algo"); + final int algorithmId = match.getAlgorithmId(); entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId)); - final int bitStrength = JWalk.getInt(match, "components", "key_fingerprint", "nbits"); + final int bitStrength = match.getBitStrength(); entry.setBitStrength(bitStrength); ArrayList userIds = new ArrayList(); - String name = fullName + " "; + String name = fullName + " "; userIds.add(name); - try { - userIds.add("github.com/" + JWalk.getString(match, "components", "github", "val")); - } catch (JSONException e) { - // ignore - } - try { - userIds.add("twitter.com/" + JWalk.getString(match, "components", "twitter", "val")); - } catch (JSONException e) { - // ignore - } - try { - JSONArray array = JWalk.getArray(match, "components", "websites"); - JSONObject website = array.getJSONObject(0); - userIds.add(JWalk.getString(website, "val")); - } catch (JSONException e) { - // ignore + + List proofLabels = match.getProofLabels(); + for (String proofLabel : proofLabels) { + userIds.add(proofLabel); } entry.setUserIds(userIds); entry.setPrimaryUserId(name); return entry; } - private JSONObject getFromKeybase(String path, String query) throws QueryFailedException { - try { - String url = "https://keybase.io/" + path + URLEncoder.encode(query, "utf8"); - Log.d(Constants.TAG, "keybase query: " + url); - - URL realUrl = new URL(url); - HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); - conn.setConnectTimeout(5000); // TODO: Reasonable values for keybase - conn.setReadTimeout(25000); - conn.connect(); - int response = conn.getResponseCode(); - if (response >= 200 && response < 300) { - String text = readAll(conn.getInputStream(), conn.getContentEncoding()); - try { - JSONObject json = new JSONObject(text); - if (JWalk.getInt(json, "status", "code") != 0) { - throw new QueryFailedException("Keybase.io query failed: " + path + "?" + - query); - } - return json; - } catch (JSONException e) { - throw new QueryFailedException("Keybase.io query returned broken JSON"); - } - } else { - String message = readAll(conn.getErrorStream(), conn.getContentEncoding()); - throw new QueryFailedException("Keybase.io query error (status=" + response + - "): " + message); - } - } catch (Exception e) { - throw new QueryFailedException("Keybase.io query error"); - } - } - @Override public String get(String id) throws QueryFailedException { try { + /* JSONObject user = getUser(id); return JWalk.getString(user, "them", "public_keys", "primary", "bundle"); - } catch (Exception e) { + */ + return User.keyForUsername(id); + } catch (KeybaseException e) { throw new QueryFailedException(e.getMessage()); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java deleted file mode 100644 index 76797811d..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2014 Tim Bray - * - * 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.util; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Minimal hierarchy selector - * - * This is for picking out an item in a large multilevel JSON object, for example look at - * the Keybase.io User object, documentation at https://keybase.io/__/api-docs/1.0#user-objects - * an example available via - * curl https://keybase.io/_/api/1.0/user/lookup.json?username=timbray - * - * If you want to retrieve the ascii-armored key, you'd say - * String key = JWalk.getString(match,"them", "public_keys", "primary", "bundle"); - */ -public class JWalk { - - /** - * Returns an int member value from the JSON sub-object addressed by the path - * - * @param json The object - * @param path list of string object member selectors - * @return the int addressed by the path, assuming such a thing exists - * @throws JSONException if any step in the path doesn’t work - */ - public static int getInt(JSONObject json, String... path) throws JSONException { - json = walk(json, path); - return json.getInt(path[path.length - 1]); - } - - /** - * Returns a long member value from the JSON sub-object addressed by the path - * - * @param json The object - * @param path list of string object member selectors - * @return the int addressed by the path, assuming such a thing exists - * @throws JSONException if any step in the path doesn’t work - */ - public static long getLong(JSONObject json, String... path) throws JSONException { - json = walk(json, path); - return json.getLong(path[path.length - 1]); - } - - /** - * Returns a String member value from the JSON sub-object addressed by the path - * - * @param json The object - * @param path list of string object member selectors - * @return the int addressed by the path, assuming such a thing exists - * @throws JSONException if any step in the path doesn’t work - */ - public static String getString(JSONObject json, String... path) throws JSONException { - json = walk(json, path); - return json.getString(path[path.length - 1]); - } - - /** - * Returns a JSONArray member value from the JSON sub-object addressed by the path - * - * @param json The object - * @param path list of string object member selectors - * @return the int addressed by the path, assuming such a thing exists - * @throws JSONException if any step in the path doesn’t work - */ - public static JSONArray getArray(JSONObject json, String... path) throws JSONException { - json = walk(json, path); - return json.getJSONArray(path[path.length - 1]); - } - - /** - * Returns a JSONObject member value from the JSON sub-object addressed by the path, or null - * - * @param json The object - * @param path list of string object member selectors - * @return the int addressed by the path, assuming such a thing exists - * @throws JSONException if any step in the path, except for the last, doesn’t work - */ - public static JSONObject optObject(JSONObject json, String... path) throws JSONException { - json = walk(json, path); - return json.optJSONObject(path[path.length - 1]); - } - - private static JSONObject walk(JSONObject json, String... path) throws JSONException { - int len = path.length - 1; - int pathIndex = 0; - try { - while (pathIndex < len) { - json = json.getJSONObject(path[pathIndex]); - pathIndex++; - } - } catch (JSONException e) { - // try to give ’em a nice-looking error - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < len; i++) { - sb.append(path[i]).append('.'); - } - sb.append(path[len]); - throw new JSONException("JWalk error at step " + pathIndex + " of " + sb); - } - return json; - } -} diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index e24ac6925..91676f09c 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -332,7 +332,7 @@ Name/Email/Key ID… Search Secret Keys Share Key with… - Name/Keybase.io username… + Search Keybase.io for… 512 -- cgit v1.2.3 From 0af2b27cb3d286f3f4e2f830c78b307ca222ff31 Mon Sep 17 00:00:00 2001 From: Art O Cathain Date: Sat, 21 Jun 2014 10:47:07 +0100 Subject: VERY basic test for save keyring --- OpenKeychain/build.gradle | 16 +------ .../keychain/pgp/NullProgressable.java | 19 ++++++++ .../keychain/provider/ProviderHelper.java | 15 +----- .../keychain/testsupport/KeyringTestingHelper.java | 56 ++++++++++++++++++++++ .../testsupport/PgpVerifyTestingHelper.java | 40 +--------------- .../keychain/testsupport/ProviderHelperStub.java | 22 +++++++++ .../keychain/testsupport/TestDataUtil.java | 25 ++++++++++ .../src/test/java/tests/PgpDecryptVerifyTest.java | 37 ++++++++++++++ .../test/java/tests/ProviderHelperKeyringTest.java | 20 ++++++++ OpenKeychain/src/test/java/tests/RoboTest.java | 37 -------------- 10 files changed, 184 insertions(+), 103 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/NullProgressable.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/KeyringTestingHelper.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/ProviderHelperStub.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/TestDataUtil.java create mode 100644 OpenKeychain/src/test/java/tests/PgpDecryptVerifyTest.java create mode 100644 OpenKeychain/src/test/java/tests/ProviderHelperKeyringTest.java delete mode 100644 OpenKeychain/src/test/java/tests/RoboTest.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index f8bf79d59..70304ef25 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -27,21 +27,7 @@ dependencies { testCompile 'org.robolectric:robolectric:2.3' testCompile 'com.squareup:fest-android:1.0.8' testCompile 'com.google.android:android:4.1.1.4' - testCompile 'com.android.support:support-v4:19.1.0' - testCompile 'com.android.support:appcompat-v7:19.1.0' - testCompile project(':extern:openpgp-api-lib') - testCompile project(':extern:openkeychain-api-lib') - testCompile project(':extern:html-textview') - testCompile project(':extern:StickyListHeaders:library') - testCompile project(':extern:AndroidBootstrap:AndroidBootstrap') - testCompile project(':extern:zxing-qr-code') - testCompile project(':extern:zxing-android-integration') - testCompile project(':extern:spongycastle:core') - testCompile project(':extern:spongycastle:pg') - testCompile project(':extern:spongycastle:pkix') - testCompile project(':extern:spongycastle:prov') - testCompile project(':extern:AppMsg:library') - testCompile project(':extern:SuperToasts:supertoasts') + // compile dependencies are automatically also included in testCompile } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/NullProgressable.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/NullProgressable.java new file mode 100644 index 000000000..68312dca3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/NullProgressable.java @@ -0,0 +1,19 @@ +package org.sufficientlysecure.keychain.pgp; + +/** + * No-op implementation of Progressable + */ +public class NullProgressable implements Progressable { + + @Override + public void setProgress(String message, int current, int total) { + } + + @Override + public void setProgress(int resourceId, int current, int total) { + } + + @Override + public void setProgress(int current, int total) { + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 955fb90ba..0db570be9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -29,6 +29,7 @@ import android.support.v4.util.LongSparseArray; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.NullProgressable; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.WrappedPublicKey; import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; @@ -655,19 +656,7 @@ public class ProviderHelper { @Deprecated public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) { - return savePublicKeyRing(keyRing, new Progressable() { - @Override - public void setProgress(String message, int current, int total) { - } - - @Override - public void setProgress(int resourceId, int current, int total) { - } - - @Override - public void setProgress(int current, int total) { - } - }); + return savePublicKeyRing(keyRing, new NullProgressable()); } /** Save a public keyring into the database. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/KeyringTestingHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/KeyringTestingHelper.java new file mode 100644 index 000000000..d4bc6c541 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/KeyringTestingHelper.java @@ -0,0 +1,56 @@ +package org.sufficientlysecure.keychain.testsupport; + +import android.content.Context; + +import org.sufficientlysecure.keychain.pgp.NullProgressable; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.service.OperationResults; + +/** + * Helper for tests of the Keyring import in ProviderHelper. + */ +public class KeyringTestingHelper { + + private final Context context; + + public KeyringTestingHelper(Context robolectricContext) { + this.context = robolectricContext; + } + + public boolean addKeyring() throws Exception { + + ProviderHelper providerHelper = new ProviderHelper(context); + +// providerHelper.insertApiApp(new AppSettings("robo-test-package", new byte[]{5, 4, 3, 2, 1})); + + byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob")); + UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data); + long masterKeyId = ring.getMasterKeyId(); + + // Should throw an exception; key is not yet saved + retrieveKeyAndExpectNotFound(providerHelper, masterKeyId); + + OperationResults.SaveKeyringResult saveKeyringResult = providerHelper.savePublicKeyRing(ring, new NullProgressable()); + + boolean saveSuccess = saveKeyringResult.success(); + + // Now re-retrieve the saved key. Should not throw an exception. + providerHelper.getWrappedPublicKeyRing(masterKeyId); + + // A different ID should still fail + retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1); + + return saveSuccess; + } + + private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) { + try { + providerHelper.getWrappedPublicKeyRing(masterKeyId); + throw new AssertionError("Was expecting the previous call to fail!"); + } catch (ProviderHelper.NotFoundException expectedException) { + // good + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/PgpVerifyTestingHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/PgpVerifyTestingHelper.java index e27ca578d..1ab5878cc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/PgpVerifyTestingHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/PgpVerifyTestingHelper.java @@ -1,19 +1,14 @@ package org.sufficientlysecure.keychain.testsupport; import android.content.Context; -import android.net.Uri; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; -import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.InputData; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -25,7 +20,7 @@ public class PgpVerifyTestingHelper { private final Context context; public PgpVerifyTestingHelper(Context robolectricContext) { - this.context=robolectricContext; + this.context = robolectricContext; } public int doTestFile(String testFileName) throws Exception { @@ -37,7 +32,7 @@ public class PgpVerifyTestingHelper { } }; - byte[] sampleInputBytes = readFully(getClass().getResourceAsStream(testFileName)); + byte[] sampleInputBytes = TestDataUtil.readFully(getClass().getResourceAsStream(testFileName)); InputStream sampleInput = new ByteArrayInputStream(sampleInputBytes); @@ -51,35 +46,4 @@ public class PgpVerifyTestingHelper { } - static class ProviderHelperStub extends ProviderHelper { - public ProviderHelperStub(Context context) { - super(context); - } - - @Override - public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri id) throws NotFoundException { - try { - byte[] data = readFully(getClass().getResourceAsStream("/public-key-for-sample.blob")); - return new WrappedPublicKeyRing(data, false, 0); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - public static byte[] readFully(InputStream input) throws IOException - { - byte[] buffer = new byte[8192]; - int bytesRead; - ByteArrayOutputStream output = new ByteArrayOutputStream(); - while ((bytesRead = input.read(buffer)) != -1) - { - output.write(buffer, 0, bytesRead); - } - return output.toByteArray(); - } - - - - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/ProviderHelperStub.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/ProviderHelperStub.java new file mode 100644 index 000000000..c6d834bf9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/ProviderHelperStub.java @@ -0,0 +1,22 @@ +package org.sufficientlysecure.keychain.testsupport; + +import android.content.Context; +import android.net.Uri; + +import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.ProviderHelper; + +/** + * Created by art on 21/06/14. + */ +class ProviderHelperStub extends ProviderHelper { + public ProviderHelperStub(Context context) { + super(context); + } + + @Override + public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri id) throws NotFoundException { + byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob")); + return new WrappedPublicKeyRing(data, false, 0); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/TestDataUtil.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/TestDataUtil.java new file mode 100644 index 000000000..06dc08eab --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/TestDataUtil.java @@ -0,0 +1,25 @@ +package org.sufficientlysecure.keychain.testsupport; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Misc support functions. Would just use Guava / Apache Commons but + * avoiding extra dependencies. + */ +public class TestDataUtil { + public static byte[] readFully(InputStream input) { + byte[] buffer = new byte[8192]; + int bytesRead; + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try { + while ((bytesRead = input.read(buffer)) != -1) { + output.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return output.toByteArray(); + } +} diff --git a/OpenKeychain/src/test/java/tests/PgpDecryptVerifyTest.java b/OpenKeychain/src/test/java/tests/PgpDecryptVerifyTest.java new file mode 100644 index 000000000..346a1f9df --- /dev/null +++ b/OpenKeychain/src/test/java/tests/PgpDecryptVerifyTest.java @@ -0,0 +1,37 @@ +package tests; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.*; +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.sufficientlysecure.keychain.testsupport.PgpVerifyTestingHelper; + +@RunWith(RobolectricTestRunner.class) +@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19 +public class PgpDecryptVerifyTest { + + @Test + public void testVerifySuccess() throws Exception { + + String testFileName = "/sample.txt"; + int expectedSignatureResult = OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED; + + int status = new PgpVerifyTestingHelper(Robolectric.application).doTestFile(testFileName); + + Assert.assertEquals(expectedSignatureResult, status); + } + + + @Test + public void testVerifyFailure() throws Exception { + + String testFileName = "/sample-altered.txt"; + int expectedSignatureResult = OpenPgpSignatureResult.SIGNATURE_ERROR; + + int status = new PgpVerifyTestingHelper(Robolectric.application).doTestFile(testFileName); + + Assert.assertEquals(expectedSignatureResult, status); + } + +} diff --git a/OpenKeychain/src/test/java/tests/ProviderHelperKeyringTest.java b/OpenKeychain/src/test/java/tests/ProviderHelperKeyringTest.java new file mode 100644 index 000000000..265e01170 --- /dev/null +++ b/OpenKeychain/src/test/java/tests/ProviderHelperKeyringTest.java @@ -0,0 +1,20 @@ +package tests; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.*; +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.sufficientlysecure.keychain.testsupport.KeyringTestingHelper; +import org.sufficientlysecure.keychain.testsupport.PgpVerifyTestingHelper; + +@RunWith(RobolectricTestRunner.class) +@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19 +public class ProviderHelperKeyringTest { + + @Test + public void testSavePublicKeyring() throws Exception { + Assert.assertTrue(new KeyringTestingHelper(Robolectric.application).addKeyring()); + } + +} diff --git a/OpenKeychain/src/test/java/tests/RoboTest.java b/OpenKeychain/src/test/java/tests/RoboTest.java deleted file mode 100644 index 1a4f6b4aa..000000000 --- a/OpenKeychain/src/test/java/tests/RoboTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package tests; - -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.*; -import org.openintents.openpgp.OpenPgpSignatureResult; -import org.sufficientlysecure.keychain.testsupport.PgpVerifyTestingHelper; - -@RunWith(RobolectricTestRunner.class) -@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19 -public class RoboTest { - - @Test - public void testVerifySuccess() throws Exception { - - String testFileName = "/sample.txt"; - int expectedSignatureResult = OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED; - - int status = new PgpVerifyTestingHelper(Robolectric.application).doTestFile(testFileName); - - Assert.assertEquals(expectedSignatureResult, status); - } - - - @Test - public void testVerifyFailure() throws Exception { - - String testFileName = "/sample-altered.txt"; - int expectedSignatureResult = OpenPgpSignatureResult.SIGNATURE_ERROR; - - int status = new PgpVerifyTestingHelper(Robolectric.application).doTestFile(testFileName); - - Assert.assertEquals(expectedSignatureResult, status); - } - -} -- cgit v1.2.3 From 18430bef17234be2446c66405526a158c42c5141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 21 Jun 2014 20:25:51 +0200 Subject: Start new designed activity --- OpenKeychain/src/main/AndroidManifest.xml | 5 + .../keychain/ui/EditKeyActivityNew.java | 93 ++++++++++ .../keychain/ui/EditKeyFragment.java | 200 +++++++++++++++++++++ .../keychain/ui/ViewKeyKeysFragment.java | 10 +- .../keychain/ui/ViewKeyMainFragment.java | 15 ++ .../keychain/ui/adapter/ViewKeyKeysAdapter.java | 16 ++ .../src/main/res/layout/edit_key_activity_new.xml | 13 ++ .../src/main/res/layout/edit_key_fragment.xml | 44 +++++ .../src/main/res/layout/view_key_main_fragment.xml | 16 ++ 9 files changed, 404 insertions(+), 8 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityNew.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java create mode 100644 OpenKeychain/src/main/res/layout/edit_key_activity_new.xml create mode 100644 OpenKeychain/src/main/res/layout/edit_key_fragment.xml (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 48677431c..c6e528f4d 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -84,6 +84,11 @@ android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_edit_key" android:windowSoftInputMode="stateHidden" /> + + * + * 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.ui; + +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.ActionBarActivity; +import android.view.View; +import android.view.View.OnClickListener; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ActionBarHelper; +import org.sufficientlysecure.keychain.remote.ui.AccountsListFragment; +import org.sufficientlysecure.keychain.util.Log; + +public class EditKeyActivityNew extends ActionBarActivity { + + private Uri mDataUri; + + private EditKeyFragment mEditKeyFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.edit_key_activity_new); + +// // Inflate a "Done"/"Cancel" custom action bar view +// ActionBarHelper.setTwoButtonView(getSupportActionBar(), +// R.string.btn_save, R.drawable.ic_action_save, +// new OnClickListener() { +// @Override +// public void onClick(View v) { +// // Save +// +// } +// }, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel, +// new OnClickListener() { +// @Override +// public void onClick(View v) { +// // Cancel +// +// } +// } +// ); + + Uri dataUri = getIntent().getData(); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + finish(); + return; + } + + loadFragment(savedInstanceState, dataUri); + } + + private void loadFragment(Bundle savedInstanceState, Uri dataUri) { + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; + } + + // Create an instance of the fragment + mEditKeyFragment = EditKeyFragment.newInstance(dataUri); + + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.edit_key_fragment_container, mEditKeyFragment) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java new file mode 100644 index 000000000..9231ce142 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -0,0 +1,200 @@ +/* + * 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.ui; + +import android.database.Cursor; +import android.net.Uri; +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.app.ActionBarActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ListView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ActionBarHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter; +import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter; +import org.sufficientlysecure.keychain.util.Log; + +public class EditKeyFragment extends LoaderFragment implements + LoaderManager.LoaderCallbacks { + + public static final String ARG_DATA_URI = "uri"; + + private ListView mUserIds; + private ListView mKeys; + + private static final int LOADER_ID_USER_IDS = 0; + private static final int LOADER_ID_KEYS = 1; + + private ViewKeyUserIdsAdapter mUserIdsAdapter; + private ViewKeyKeysAdapter mKeysAdapter; + + private Uri mDataUri; + + /** + * Creates new instance of this fragment + */ + public static EditKeyFragment newInstance(Uri dataUri) { + EditKeyFragment frag = new EditKeyFragment(); + + Bundle args = new Bundle(); + args.putParcelable(ARG_DATA_URI, dataUri); + + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.edit_key_fragment, getContainer()); + + mUserIds = (ListView) view.findViewById(R.id.edit_key_user_ids); + mKeys = (ListView) view.findViewById(R.id.edit_key_keys); +// mActionEdit = view.findViewById(R.id.view_key_action_edit); + + return root; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + + // Inflate a "Done"/"Cancel" custom action bar view + ActionBarHelper.setTwoButtonView(((ActionBarActivity) getActivity()).getSupportActionBar(), + R.string.btn_save, R.drawable.ic_action_save, + new OnClickListener() { + @Override + public void onClick(View v) { + // Save + save(); + } + }, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel, + new OnClickListener() { + @Override + public void onClick(View v) { + // Cancel + getActivity().finish(); + } + } + ); + + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + getActivity().finish(); + return; + } + + loadData(dataUri); + } + + private void loadData(Uri dataUri) { + mDataUri = dataUri; + + Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + +// mActionEncrypt.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// encrypt(mDataUri); +// } +// }); + + + mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0); + mUserIds.setAdapter(mUserIdsAdapter); + mKeysAdapter = new ViewKeyKeysAdapter(getActivity(), null, 0); + mKeys.setAdapter(mKeysAdapter); + + // Prepare the loaders. Either re-connect with an existing ones, + // or start new ones. + getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); + getLoaderManager().initLoader(LOADER_ID_KEYS, null, this); + } + + public Loader onCreateLoader(int id, Bundle args) { + setContentShown(false); + + switch (id) { + case LOADER_ID_USER_IDS: { + Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, + ViewKeyUserIdsAdapter.USER_IDS_PROJECTION, null, null, null); + } + + case LOADER_ID_KEYS: { + Uri baseUri = KeychainContract.Keys.buildKeysUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, + ViewKeyKeysAdapter.KEYS_PROJECTION, null, null, null); + } + + default: + return null; + } + } + + public void onLoadFinished(Loader loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + switch (loader.getId()) { + case LOADER_ID_USER_IDS: + mUserIdsAdapter.swapCursor(data); + break; + + case LOADER_ID_KEYS: + mKeysAdapter.swapCursor(data); + break; + + } + setContentShown(true); + } + + /** + * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. + * We need to make sure we are no longer using it. + */ + public void onLoaderReset(Loader loader) { + switch (loader.getId()) { + case LOADER_ID_USER_IDS: + mUserIdsAdapter.swapCursor(null); + break; + case LOADER_ID_KEYS: + mKeysAdapter.swapCursor(null); + break; + } + } + + private void save() { + getActivity().finish(); + // TODO + } + + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeysFragment.java index f4a44f526..e01a0140c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeysFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeysFragment.java @@ -83,17 +83,11 @@ public class ViewKeyKeysFragment extends LoaderFragment implements getLoaderManager().initLoader(0, null, this); } - static final String[] KEYS_PROJECTION = new String[] { - Keys._ID, - Keys.KEY_ID, Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE, Keys.HAS_SECRET, - Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, Keys.CAN_SIGN, Keys.IS_REVOKED, - Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT - }; - public Loader onCreateLoader(int id, Bundle args) { setContentShown(false); Uri baseUri = Keys.buildKeysUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, null); + return new CursorLoader(getActivity(), baseUri, + ViewKeyKeysAdapter.KEYS_PROJECTION, null, null, null); } public void onLoadFinished(Loader loader, Cursor data) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java index ae7cef70c..bd29f3820 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java @@ -49,6 +49,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements public static final String ARG_DATA_URI = "uri"; private View mActionEdit; + private View mActionEditNew; private View mActionEditDivider; private View mActionEncrypt; private View mActionCertify; @@ -73,6 +74,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); mActionEdit = view.findViewById(R.id.view_key_action_edit); + mActionEditNew = view.findViewById(R.id.view_key_action_edit_new); mActionEditDivider = view.findViewById(R.id.view_key_action_edit_divider); mActionEncrypt = view.findViewById(R.id.view_key_action_encrypt); mActionCertify = view.findViewById(R.id.view_key_action_certify); @@ -116,6 +118,11 @@ public class ViewKeyMainFragment extends LoaderFragment implements editKey(mDataUri); } }); + mActionEditNew.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + editKeyNew(mDataUri); + } + }); mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0); mUserIds.setAdapter(mUserIdsAdapter); @@ -256,4 +263,12 @@ public class ViewKeyMainFragment extends LoaderFragment implements startActivityForResult(editIntent, 0); } + private void editKeyNew(Uri dataUri) { + Intent editIntent = new Intent(getActivity(), EditKeyActivityNew.class); +// editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri)); + editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri)); + editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); + startActivityForResult(editIntent, 0); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java index f4942a2a0..dae287bbc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java @@ -53,6 +53,22 @@ public class ViewKeyKeysAdapter extends CursorAdapter { private ColorStateList mDefaultTextColor; + public static final String[] KEYS_PROJECTION = new String[] { + Keys._ID, + Keys.KEY_ID, + Keys.RANK, + Keys.ALGORITHM, + Keys.KEY_SIZE, + Keys.HAS_SECRET, + Keys.CAN_CERTIFY, + Keys.CAN_ENCRYPT, + Keys.CAN_SIGN, + Keys.IS_REVOKED, + Keys.CREATION, + Keys.EXPIRY, + Keys.FINGERPRINT + }; + public ViewKeyKeysAdapter(Context context, Cursor c, int flags) { super(context, c, flags); diff --git a/OpenKeychain/src/main/res/layout/edit_key_activity_new.xml b/OpenKeychain/src/main/res/layout/edit_key_activity_new.xml new file mode 100644 index 000000000..f96b993c5 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/edit_key_activity_new.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/edit_key_fragment.xml b/OpenKeychain/src/main/res/layout/edit_key_fragment.xml new file mode 100644 index 000000000..dbc0c3941 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/edit_key_fragment.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + diff --git a/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml index d93420a99..96871aacf 100644 --- a/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml @@ -74,6 +74,22 @@ android:drawablePadding="8dp" android:gravity="center_vertical" /> + + Date: Sat, 21 Jun 2014 21:31:00 +0200 Subject: Cleanup pull keybase code --- .../sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'OpenKeychain') 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 98e5111f7..29ab06264 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -62,8 +62,7 @@ public class KeybaseKeyserver extends Keyserver { entry.setQuery(mQuery); entry.setOrigin(ORIGIN); - String username = null; - username = match.getUsername(); + String username = match.getUsername(); String fullName = match.getFullName(); String fingerprint = match.getFingerprint(); entry.setFingerprintHex(fingerprint); @@ -93,10 +92,6 @@ public class KeybaseKeyserver extends Keyserver { @Override public String get(String id) throws QueryFailedException { try { - /* - JSONObject user = getUser(id); - return JWalk.getString(user, "them", "public_keys", "primary", "bundle"); - */ return User.keyForUsername(id); } catch (KeybaseException e) { throw new QueryFailedException(e.getMessage()); -- cgit v1.2.3 From 9d20e68f115b8af4b76bf634eb919de9f9397486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 22 Jun 2014 10:59:50 +0200 Subject: Revert hint_keybase_search --- OpenKeychain/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 91676f09c..e24ac6925 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -332,7 +332,7 @@ Name/Email/Key ID… Search Secret Keys Share Key with… - Search Keybase.io for… + Name/Keybase.io username… 512 -- cgit v1.2.3