diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org')
47 files changed, 1870 insertions, 225 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index d1b37aed2..f45c4baa3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -26,6 +26,8 @@ import org.spongycastle.jce.provider.BouncyCastleProvider; import org.sufficientlysecure.keychain.BuildConfig; import java.io.File; +import java.net.InetSocketAddress; +import java.net.Proxy; public final class Constants { @@ -93,6 +95,21 @@ public final class Constants { public static final String FILE_USE_COMPRESSION = "useFileCompression"; public static final String TEXT_USE_COMPRESSION = "useTextCompression"; public static final String USE_ARMOR = "useArmor"; + // proxy settings + public static final String USE_NORMAL_PROXY = "useNormalProxy"; + public static final String USE_TOR_PROXY = "useTorProxy"; + public static final String PROXY_HOST = "proxyHost"; + public static final String PROXY_PORT = "proxyPort"; + public static final String PROXY_TYPE = "proxyType"; + } + + /** + * information to connect to Orbot's localhost HTTP proxy + */ + public static final class Orbot { + public static final String PROXY_HOST = "127.0.0.1"; + public static final int PROXY_PORT = 8118; + public static final Proxy.Type PROXY_TYPE = Proxy.Type.HTTP; } public static final class Defaults { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java index 649cede10..d91dd28bc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java @@ -20,6 +20,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; +import java.net.Proxy; import java.util.ArrayList; import java.util.Vector; @@ -30,7 +31,8 @@ public class CloudSearch { private final static long SECONDS = 1000; - public static ArrayList<ImportKeysListEntry> search(final String query, Preferences.CloudSearchPrefs cloudPrefs) + public static ArrayList<ImportKeysListEntry> search(final String query, Preferences.CloudSearchPrefs cloudPrefs, + final Proxy proxy) throws Keyserver.CloudSearchFailureException { final ArrayList<Keyserver> servers = new ArrayList<>(); @@ -51,7 +53,7 @@ public class CloudSearch { @Override public void run() { try { - results.addAll(keyserver.search(query)); + results.addAll(keyserver.search(query, proxy)); } catch (Keyserver.CloudSearchFailureException e) { problems.add(e); } @@ -63,20 +65,24 @@ public class CloudSearch { searchThread.start(); } - // wait for either all the searches to come back, or 10 seconds + // wait for either all the searches to come back, or 10 seconds. If using proxy, wait 30 seconds. synchronized (results) { try { - results.wait(10 * SECONDS); + if (proxy != null) { + results.wait(30 * SECONDS); + } else { + results.wait(10 * SECONDS); + } for (Thread thread : searchThreads) { // kill threads that haven't returned yet thread.interrupt(); - } + } } catch (InterruptedException e) { } } if (results.outstandingSuppliers() > 0) { - String message = "Launched " + servers.size() + " cloud searchers, but" + + String message = "Launched " + servers.size() + " cloud searchers, but " + results.outstandingSuppliers() + "failed to complete."; problems.add(new Keyserver.QueryFailedException(message)); } 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 cb8a53e25..bd85b7a0a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -18,18 +18,20 @@ package org.sufficientlysecure.keychain.keyimport; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import com.squareup.okhttp.Response; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.TlsHelper; -import java.io.BufferedWriter; import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; @@ -39,6 +41,7 @@ import java.util.Comparator; import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -190,36 +193,47 @@ public class HkpKeyserver extends Keyserver { return mSecure ? "https://" : "http://"; } - private HttpURLConnection openConnection(URL url) throws IOException { - HttpURLConnection conn = null; + /** + * returns a client with pinned certificate if necessary + * + * @param url + * @param proxy + * @return + */ + public static OkHttpClient getClient(URL url, Proxy proxy) throws IOException { + OkHttpClient client = new OkHttpClient(); + try { - conn = (HttpURLConnection) TlsHelper.openConnection(url); + TlsHelper.pinCertificateIfNecessary(client, url); } catch (TlsHelper.TlsHelperException e) { Log.w(Constants.TAG, e); } - if (conn == null) { - conn = (HttpURLConnection) url.openConnection(); - } - conn.setConnectTimeout(5000); - conn.setReadTimeout(25000); - return conn; + + client.setProxy(proxy); + client.setConnectTimeout(proxy != null ? 30000 : 5000, TimeUnit.MILLISECONDS); + client.setReadTimeout(45000, TimeUnit.MILLISECONDS); + + return client; } - private String query(String request) throws QueryFailedException, HttpError { + private String query(String request, Proxy proxy) throws QueryFailedException, HttpError { try { URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request); - Log.d(Constants.TAG, "hkp keyserver query: " + url); - HttpURLConnection conn = openConnection(url); - conn.connect(); - int response = conn.getResponseCode(); - if (response >= 200 && response < 300) { - return readAll(conn.getInputStream(), conn.getContentEncoding()); + Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy); + OkHttpClient client = getClient(url, proxy); + Response response = client.newCall(new Request.Builder().url(url).build()).execute(); + + String responseBody = response.body().string();// contains body both in case of success or failure + + if (response.isSuccessful()) { + return responseBody; } else { - String data = readAll(conn.getErrorStream(), conn.getContentEncoding()); - throw new HttpError(response, data); + throw new HttpError(response.code(), responseBody); } } catch (IOException e) { - throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!"); + Log.e(Constants.TAG, "IOException at HkpKeyserver", e); + throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!" + + proxy == null?"":" Using proxy " + proxy); } } @@ -232,7 +246,7 @@ public class HkpKeyserver extends Keyserver { * @throws QueryNeedsRepairException */ @Override - public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException, + public ArrayList<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException, QueryNeedsRepairException { ArrayList<ImportKeysListEntry> results = new ArrayList<>(); @@ -250,7 +264,7 @@ public class HkpKeyserver extends Keyserver { String data; try { - data = query(request); + data = query(request, proxy); } catch (HttpError e) { if (e.getData() != null) { Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.ENGLISH)); @@ -334,13 +348,14 @@ public class HkpKeyserver extends Keyserver { } @Override - public String get(String keyIdHex) throws QueryFailedException { + public String get(String keyIdHex, Proxy proxy) throws QueryFailedException { String request = "/pks/lookup?op=get&options=mr&search=" + keyIdHex; - Log.d(Constants.TAG, "hkp keyserver get: " + request); + Log.d(Constants.TAG, "hkp keyserver get: " + request + " using Proxy: " + proxy); String data; try { - data = query(request); + data = query(request, proxy); } catch (HttpError httpError) { + Log.e(Constants.TAG, "Failed to get key at HkpKeyserver", httpError); throw new QueryFailedException("not found"); } Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data); @@ -351,38 +366,34 @@ public class HkpKeyserver extends Keyserver { } @Override - public void add(String armoredKey) throws AddKeyException { + public void add(String armoredKey, Proxy proxy) throws AddKeyException { try { - String request = "/pks/add"; + String path = "/pks/add"; String params; try { params = "keytext=" + URLEncoder.encode(armoredKey, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new AddKeyException(); } - URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request); + URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + path); Log.d(Constants.TAG, "hkp keyserver add: " + url.toString()); Log.d(Constants.TAG, "params: " + params); - HttpURLConnection conn = openConnection(url); - conn.setRequestMethod("POST"); - conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - conn.setRequestProperty("Content-Length", Integer.toString(params.getBytes().length)); - conn.setDoInput(true); - conn.setDoOutput(true); + RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), params); + + Request request = new Request.Builder() + .url(url) + .addHeader("Content-Type", "application/x-www-form-urlencoded") + .addHeader("Content-Length", Integer.toString(params.getBytes().length)) + .post(body) + .build(); - OutputStream os = conn.getOutputStream(); - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); - writer.write(params); - writer.flush(); - writer.close(); - os.close(); + Response response = getClient(url, proxy).newCall(request).execute(); - conn.connect(); + Log.d(Constants.TAG, "response code: " + response.code()); + Log.d(Constants.TAG, "answer: " + response.body().string()); - Log.d(Constants.TAG, "response code: " + conn.getResponseCode()); - Log.d(Constants.TAG, "answer: " + readAll(conn.getInputStream(), conn.getContentEncoding())); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); throw new AddKeyException(); @@ -398,6 +409,7 @@ public class HkpKeyserver extends Keyserver { * Tries to find a server responsible for a given domain * * @return A responsible Keyserver or null if not found. + * TODO: PHILIP Add proxy functionality */ public static HkpKeyserver resolve(String domain) { try { 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 e310e9a3f..c2865410e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -26,6 +26,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import java.net.Proxy; import java.util.ArrayList; import java.util.List; @@ -34,7 +35,7 @@ public class KeybaseKeyserver extends Keyserver { private String mQuery; @Override - public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException, + public ArrayList<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException, QueryNeedsRepairException { ArrayList<ImportKeysListEntry> results = new ArrayList<>(); @@ -48,7 +49,7 @@ public class KeybaseKeyserver extends Keyserver { mQuery = query; try { - Iterable<Match> matches = Search.search(query); + Iterable<Match> matches = Search.search(query, proxy); for (Match match : matches) { results.add(makeEntry(match)); } @@ -98,16 +99,16 @@ public class KeybaseKeyserver extends Keyserver { } @Override - public String get(String id) throws QueryFailedException { + public String get(String id, Proxy proxy) throws QueryFailedException { try { - return User.keyForUsername(id); + return User.keyForUsername(id, proxy); } catch (KeybaseException e) { throw new QueryFailedException(e.getMessage()); } } @Override - public void add(String armoredKey) throws AddKeyException { + public void add(String armoredKey, Proxy proxy) throws AddKeyException { throw new AddKeyException(); } } 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 5e4bd0b70..640b39f44 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.keyimport; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.Proxy; import java.util.List; public abstract class Keyserver { @@ -31,6 +32,7 @@ public abstract class Keyserver { public CloudSearchFailureException(String message) { super(message); } + public CloudSearchFailureException() { super(); } @@ -67,12 +69,12 @@ public abstract class Keyserver { private static final long serialVersionUID = -507574859137295530L; } - public abstract List<ImportKeysListEntry> search(String query) throws QueryFailedException, + public abstract List<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException, QueryNeedsRepairException; - public abstract String get(String keyIdHex) throws QueryFailedException; + public abstract String get(String keyIdHex, Proxy proxy) throws QueryFailedException; - public abstract void add(String armoredKey) throws AddKeyException; + public abstract void add(String armoredKey, Proxy proxy) throws AddKeyException; public static String readAll(InputStream in, String encoding) throws IOException { ByteArrayOutputStream raw = new ByteArrayOutputStream(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 0806e6a16..87a2325d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -21,8 +21,9 @@ import android.content.Context; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; -import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; +import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.operations.results.CertifyResult; +import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; @@ -43,24 +44,28 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Passphrase; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import java.net.Proxy; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; -/** An operation which implements a high level user id certification operation. - * +/** + * An operation which implements a high level user id certification operation. + * <p/> * This operation takes a specific CertifyActionsParcel as its input. These * contain a masterKeyId to be used for certification, and a list of * masterKeyIds and related user ids to certify. * * @see CertifyActionsParcel - * */ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { - public CertifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean cancelled) { + public CertifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean + cancelled) { super(context, providerHelper, progressable, cancelled); } @@ -174,7 +179,7 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { } - if ( ! allRequiredInput.isEmpty()) { + if (!allRequiredInput.isEmpty()) { log.add(LogType.MSG_CRT_NFC_RETURN, 1); return new CertifyResult(log, allRequiredInput.build()); } @@ -187,11 +192,24 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { return new CertifyResult(CertifyResult.RESULT_CANCELLED, log); } + // these variables are used inside the following loop, but they need to be created only once HkpKeyserver keyServer = null; ExportOperation exportOperation = null; + Proxy proxy = null; if (parcel.keyServerUri != null) { keyServer = new HkpKeyserver(parcel.keyServerUri); exportOperation = new ExportOperation(mContext, mProviderHelper, mProgressable); + if (cryptoInput.getParcelableProxy() == null) { + // explicit proxy not set + if (!OrbotHelper.isOrbotInRequiredState(mContext)) { + return new CertifyResult(null, + RequiredInputParcel.createOrbotRequiredOperation()); + } + proxy = Preferences.getPreferences(mContext).getProxyPrefs() + .parcelableProxy.getProxy(); + } else { + proxy = cryptoInput.getParcelableProxy().getProxy(); + } } // Write all certified keys into the database @@ -200,7 +218,8 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { // Check if we were cancelled if (checkCancelled()) { log.add(LogType.MSG_OPERATION_CANCELLED, 0); - return new CertifyResult(CertifyResult.RESULT_CANCELLED, log, certifyOk, certifyError, uploadOk, uploadError); + return new CertifyResult(CertifyResult.RESULT_CANCELLED, log, certifyOk, certifyError, uploadOk, + uploadError); } log.add(LogType.MSG_CRT_SAVE, 2, @@ -210,12 +229,15 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { SaveKeyringResult result = mProviderHelper.savePublicKeyRing(certifiedKey); if (exportOperation != null) { - // TODO use subresult, get rid of try/catch! - try { - exportOperation.uploadKeyRingToServer(keyServer, certifiedKey); + ExportResult uploadResult = exportOperation.uploadKeyRingToServer( + keyServer, + certifiedKey, + proxy); + log.add(uploadResult, 2); + + if (uploadResult.success()) { uploadOk += 1; - } catch (AddKeyException e) { - Log.e(Constants.TAG, "error uploading key", e); + } else { uploadError += 1; } } @@ -227,19 +249,24 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { } log.add(result, 2); - } if (certifyOk == 0) { log.add(LogType.MSG_CRT_ERROR_NOTHING, 0); - return new CertifyResult(CertifyResult.RESULT_ERROR, log, certifyOk, certifyError, uploadOk, uploadError); + return new CertifyResult(CertifyResult.RESULT_ERROR, log, certifyOk, certifyError, + uploadOk, uploadError); } - log.add(LogType.MSG_CRT_SUCCESS, 0); - //since only verified keys are synced to contacts, we need to initiate a sync now + // since only verified keys are synced to contacts, we need to initiate a sync now ContactSyncAdapterService.requestSync(); - - return new CertifyResult(CertifyResult.RESULT_OK, log, certifyOk, certifyError, uploadOk, uploadError); + + log.add(LogType.MSG_CRT_SUCCESS, 0); + if (uploadError != 0) { + return new CertifyResult(CertifyResult.RESULT_WARNINGS, log, certifyOk, certifyError, uploadOk, + uploadError); + } else { + return new CertifyResult(CertifyResult.RESULT_OK, log, certifyOk, certifyError, uploadOk, uploadError); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java index 01a45bc79..3457665b0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java @@ -27,6 +27,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; +import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -40,9 +41,12 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ExportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; @@ -51,6 +55,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.net.Proxy; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -62,7 +67,6 @@ import java.util.concurrent.atomic.AtomicBoolean; * @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries() * For the export operation, the input consists of a set of key ids and * either the name of a file or an output uri to write to. - * TODO rework uploadKeyRingToServer */ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { @@ -76,25 +80,39 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { super(context, providerHelper, progressable, cancelled); } - public void uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) - throws AddKeyException { - uploadKeyRingToServer(server, keyring.getUncachedKeyRing()); + public ExportResult uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring, + Proxy proxy) { + return uploadKeyRingToServer(server, keyring.getUncachedKeyRing(), proxy); } - public void uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring) throws - AddKeyException { + public ExportResult uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring, Proxy proxy) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ArmoredOutputStream aos = null; + OperationLog log = new OperationLog(); + log.add(LogType.MSG_EXPORT_UPLOAD_PUBLIC, 0, KeyFormattingUtils.convertKeyIdToHex( + keyring.getPublicKey().getKeyId() + )); + try { aos = new ArmoredOutputStream(bos); keyring.encode(aos); aos.close(); String armoredKey = bos.toString("UTF-8"); - server.add(armoredKey); + server.add(armoredKey, proxy); + + log.add(LogType.MSG_EXPORT_UPLOAD_SUCCESS, 1); + return new ExportResult(ExportResult.RESULT_OK, log); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); - throw new AddKeyException(); + + log.add(LogType.MSG_EXPORT_ERROR_KEY, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log); + } catch (AddKeyException e) { + Log.e(Constants.TAG, "AddKeyException", e); + + log.add(LogType.MSG_EXPORT_ERROR_UPLOAD, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log); } finally { try { if (aos != null) { @@ -314,17 +332,28 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { public ExportResult execute(ExportKeyringParcel exportInput, CryptoInputParcel cryptoInput) { switch (exportInput.mExportType) { case UPLOAD_KEYSERVER: { + Proxy proxy; + if (cryptoInput.getParcelableProxy() == null) { + // explicit proxy not set + if (!OrbotHelper.isOrbotInRequiredState(mContext)) { + return new ExportResult(null, + RequiredInputParcel.createOrbotRequiredOperation()); + } + proxy = Preferences.getPreferences(mContext).getProxyPrefs() + .parcelableProxy.getProxy(); + } else { + proxy = cryptoInput.getParcelableProxy().getProxy(); + } + HkpKeyserver hkpKeyserver = new HkpKeyserver(exportInput.mKeyserver); try { CanonicalizedPublicKeyRing keyring = mProviderHelper.getCanonicalizedPublicKeyRing( exportInput.mCanonicalizedPublicKeyringUri); - uploadKeyRingToServer(hkpKeyserver, keyring); - // TODO: replace with proper log - return new ExportResult(ExportResult.RESULT_OK, new OperationLog()); - } catch (Exception e) { + return uploadKeyRingToServer(hkpKeyserver, keyring, proxy); + } catch (ProviderHelper.NotFoundException e) { + Log.e(Constants.TAG, "error uploading key", e); return new ExportResult(ExportResult.RESULT_ERROR, new OperationLog()); - // TODO: Implement better exception handling, replace with log } } case EXPORT_FILE: { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java index ace059dac..39a898487 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -28,6 +28,7 @@ import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.operations.results.InputPendingResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -39,12 +40,16 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ProgressScaler; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.io.IOException; +import java.net.Proxy; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -89,39 +94,39 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { // Overloaded functions for using progressable supplied in constructor during import public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num, - String keyServerUri) { - return serialKeyRingImport(entries, num, keyServerUri, mProgressable); + String keyServerUri, Proxy proxy) { + return serialKeyRingImport(entries, num, keyServerUri, mProgressable, proxy); } public ImportKeyResult serialKeyRingImport(List<ParcelableKeyRing> entries, - String keyServerUri) { + String keyServerUri, Proxy proxy) { Iterator<ParcelableKeyRing> it = entries.iterator(); int numEntries = entries.size(); - return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable); + return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable, proxy); } public ImportKeyResult serialKeyRingImport(List<ParcelableKeyRing> entries, String keyServerUri, - Progressable progressable) { + Progressable progressable, Proxy proxy) { Iterator<ParcelableKeyRing> it = entries.iterator(); int numEntries = entries.size(); - return serialKeyRingImport(it, numEntries, keyServerUri, progressable); + return serialKeyRingImport(it, numEntries, keyServerUri, progressable, proxy); } public ImportKeyResult serialKeyRingImport(ParcelableFileCache<ParcelableKeyRing> cache, - String keyServerUri) { + String keyServerUri, Proxy proxy) { // get entries from cached file try { IteratorWithSize<ParcelableKeyRing> it = cache.readCache(); int numEntries = it.getSize(); - return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable); + return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable, proxy); } catch (IOException e) { // Special treatment here, we need a lot @@ -146,7 +151,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { * @return */ public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num, - String keyServerUri, Progressable progressable) { + String keyServerUri, Progressable progressable, + Proxy proxy) { updateProgress(R.string.progress_importing, 0, 100); OperationLog log = new OperationLog(); @@ -208,10 +214,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { if (entry.mExpectedFingerprint != null) { log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, "0x" + entry.mExpectedFingerprint.substring(24)); - data = keyServer.get("0x" + entry.mExpectedFingerprint).getBytes(); + data = keyServer.get("0x" + entry.mExpectedFingerprint, proxy) + .getBytes(); } else { log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, entry.mKeyIdHex); - data = keyServer.get(entry.mKeyIdHex).getBytes(); + data = keyServer.get(entry.mKeyIdHex, proxy).getBytes(); } key = UncachedKeyRing.decodeFromData(data); if (key != null) { @@ -234,7 +241,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { try { log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.mKeybaseName); - byte[] data = keybaseServer.get(entry.mKeybaseName).getBytes(); + byte[] data = keybaseServer.get(entry.mKeybaseName, proxy).getBytes(); UncachedKeyRing keybaseKey = UncachedKeyRing.decodeFromData(data); // If there already is a key, merge the two @@ -374,11 +381,9 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { } @Override - public ImportKeyResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) { - return importKeys(importInput.mKeyList, importInput.mKeyserver); - } - - public ImportKeyResult importKeys(ArrayList<ParcelableKeyRing> keyList, String keyServer) { + public OperationResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) { + ArrayList<ParcelableKeyRing> keyList = importInput.mKeyList; + String keyServer = importInput.mKeyserver; ImportKeyResult result; @@ -386,8 +391,21 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { ParcelableFileCache<ParcelableKeyRing> cache = new ParcelableFileCache<>(mContext, "key_import.pcl"); - result = serialKeyRingImport(cache, keyServer); + result = serialKeyRingImport(cache, null, null); } else { + Proxy proxy; + if (cryptoInput.getParcelableProxy() == null) { + // explicit proxy not set + if(!OrbotHelper.isOrbotInRequiredState(mContext)) { + // show dialog to enable/install dialog + return new ImportKeyResult(null, + RequiredInputParcel.createOrbotRequiredOperation()); + } + proxy = Preferences.getPreferences(mContext).getProxyPrefs().parcelableProxy + .getProxy(); + } else { + proxy = cryptoInput.getParcelableProxy().getProxy(); + } // if there is more than one key with the same fingerprint, we do a serial import to // prevent // https://github.com/open-keychain/open-keychain/issues/1221 @@ -397,9 +415,10 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { } if (keyFingerprintSet.size() == keyList.size()) { // all keys have unique fingerprints - result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer); + result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer, + proxy); } else { - result = serialKeyRingImport(keyList, keyServer); + result = serialKeyRingImport(keyList, keyServer, proxy); } } @@ -408,7 +427,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { } private ImportKeyResult multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator, - int totKeys, final String keyServer) { + int totKeys, final String keyServer, + final Proxy proxy) { Log.d(Constants.TAG, "Multi-threaded key import starting"); if (keyListIterator != null) { KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable); @@ -436,7 +456,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { ArrayList<ParcelableKeyRing> list = new ArrayList<>(); list.add(pkRing); - return serialKeyRingImport(list, keyServer, ignoreProgressable); + return serialKeyRingImport(list, keyServer, ignoreProgressable, proxy); } }; @@ -562,4 +582,4 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> { } } -}
\ No newline at end of file +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java index 57b99951d..4e9c6a943 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java @@ -32,6 +32,7 @@ import de.measite.minidns.record.TXT; import org.json.JSONObject; import org.spongycastle.openpgp.PGPUtil; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; @@ -41,9 +42,13 @@ import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.net.Proxy; import java.util.ArrayList; import java.util.List; @@ -57,6 +62,18 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat @Override public KeybaseVerificationResult execute(KeybaseVerificationParcel keybaseInput, CryptoInputParcel cryptoInput) { + Proxy proxy; + if (cryptoInput.getParcelableProxy() == null) { + // explicit proxy not set + if (!OrbotHelper.isOrbotInRequiredState(mContext)) { + return new KeybaseVerificationResult(null, + RequiredInputParcel.createOrbotRequiredOperation()); + } + proxy = Preferences.getPreferences(mContext).getProxyPrefs() + .parcelableProxy.getProxy(); + } else { + proxy = cryptoInput.getParcelableProxy().getProxy(); + } String requiredFingerprint = keybaseInput.mRequiredFingerprint; @@ -76,7 +93,7 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); } - if (!prover.fetchProofData()) { + if (!prover.fetchProofData(proxy)) { log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_FETCH_PROOF, 1); return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java index 8fe5b86c5..976a054af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSign import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.ByteArrayInputStream; @@ -84,7 +85,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> { input.getSignatureMasterKeyId()).getSecretSignId(); input.setSignatureSubKeyId(signKeyId); } catch (PgpKeyNotFoundException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "Key not found", e); return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java index c8edce259..199a3f565 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java @@ -19,7 +19,9 @@ package org.sufficientlysecure.keychain.operations.results; import android.os.Parcel; -public class ExportResult extends OperationResult { +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; + +public class ExportResult extends InputPendingResult { final int mOkPublic, mOkSecret; @@ -33,6 +35,14 @@ public class ExportResult extends OperationResult { mOkSecret = okSecret; } + + public ExportResult(OperationLog log, RequiredInputParcel requiredInputParcel) { + super(log, requiredInputParcel); + // we won't use these values + mOkPublic = -1; + mOkSecret = -1; + } + /** Construct from a parcel - trivial because we have no extra data. */ public ExportResult(Parcel source) { super(source); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java index 2a032cef2..ca7079078 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java @@ -23,6 +23,8 @@ import android.content.Intent; import android.os.Parcel; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -30,7 +32,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Showable; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -public class ImportKeyResult extends OperationResult { +public class ImportKeyResult extends InputPendingResult { public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret; public final long[] mImportedMasterKeyIds; @@ -80,7 +82,7 @@ public class ImportKeyResult extends OperationResult { } public ImportKeyResult(int result, OperationLog log) { - this(result, log, 0, 0, 0, 0, new long[] { }); + this(result, log, 0, 0, 0, 0, new long[]{}); } public ImportKeyResult(int result, OperationLog log, @@ -94,6 +96,16 @@ public class ImportKeyResult extends OperationResult { mImportedMasterKeyIds = importedMasterKeyIds; } + public ImportKeyResult(OperationLog log, RequiredInputParcel requiredInputParcel) { + super(log, requiredInputParcel); + // just assign default values, we won't use them anyway + mNewKeys = 0; + mUpdatedKeys = 0; + mBadKeys = 0; + mSecret = 0; + mImportedMasterKeyIds = new long[]{}; + } + @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java index 420cbbf01..173f7f575 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java @@ -24,7 +24,9 @@ import android.os.Parcelable; import com.textuality.keybase.lib.KeybaseException; import com.textuality.keybase.lib.prover.Prover; -public class KeybaseVerificationResult extends OperationResult implements Parcelable { +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; + +public class KeybaseVerificationResult extends InputPendingResult { public final String mProofUrl; public final String mPresenceUrl; public final String mPresenceLabel; @@ -44,6 +46,13 @@ public class KeybaseVerificationResult extends OperationResult implements Parcel mPresenceLabel = prover.getPresenceLabel(); } + public KeybaseVerificationResult(OperationLog log, RequiredInputParcel requiredInputParcel) { + super(log, requiredInputParcel); + mProofUrl = null; + mPresenceUrl = null; + mPresenceLabel = null; + } + protected KeybaseVerificationResult(Parcel in) { super(in); mProofUrl = in.readString(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 245623762..1ae5aaefb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -692,6 +692,7 @@ public abstract class OperationResult implements Parcelable { MSG_CRT_WARN_NOT_FOUND (LogLevel.WARN, R.string.msg_crt_warn_not_found), MSG_CRT_WARN_CERT_FAILED (LogLevel.WARN, R.string.msg_crt_warn_cert_failed), MSG_CRT_WARN_SAVE_FAILED (LogLevel.WARN, R.string.msg_crt_warn_save_failed), + MSG_CRT_WARN_UPLOAD_FAILED (LogLevel.WARN, R.string.msg_crt_warn_upload_failed), MSG_IMPORT (LogLevel.START, R.plurals.msg_import), @@ -712,6 +713,7 @@ public abstract class OperationResult implements Parcelable { MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success), MSG_EXPORT (LogLevel.START, R.plurals.msg_export), + MSG_EXPORT_UPLOAD_PUBLIC (LogLevel.START, R.string.msg_export_upload_public), MSG_EXPORT_PUBLIC (LogLevel.DEBUG, R.string.msg_export_public), MSG_EXPORT_SECRET (LogLevel.DEBUG, R.string.msg_export_secret), MSG_EXPORT_ALL (LogLevel.START, R.string.msg_export_all), @@ -723,7 +725,9 @@ public abstract class OperationResult implements Parcelable { MSG_EXPORT_ERROR_DB (LogLevel.ERROR, R.string.msg_export_error_db), MSG_EXPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_export_error_io), MSG_EXPORT_ERROR_KEY (LogLevel.ERROR, R.string.msg_export_error_key), + MSG_EXPORT_ERROR_UPLOAD (LogLevel.ERROR, R.string.msg_export_error_upload), MSG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_success), + MSG_EXPORT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_export_upload_success), MSG_CRT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_crt_upload_success), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index a6d260d22..acac9ecff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -96,7 +96,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> long inputSize = FileHelper.getFileSize(mContext, input.getInputUri(), 0); inputData = new InputData(inputStream, inputSize); } catch (FileNotFoundException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "File not found at " + input.getInputUri(), e); return null; } } 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 590c58f97..4b7d679d1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -906,7 +906,8 @@ public class ProviderHelper { // If there is a secret key, merge new data (if any) and save the key for later CanonicalizedSecretKeyRing canSecretRing; try { - UncachedKeyRing secretRing = getCanonicalizedSecretKeyRing(publicRing.getMasterKeyId()).getUncachedKeyRing(); + UncachedKeyRing secretRing = getCanonicalizedSecretKeyRing(publicRing.getMasterKeyId()) + .getUncachedKeyRing(); // Merge data from new public ring into secret one log(LogType.MSG_IP_MERGE_SECRET); @@ -1031,7 +1032,8 @@ public class ProviderHelper { publicRing = secretRing.extractPublicKeyRing(); } - CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent); + CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, + mIndent); if (canPublicRing == null) { return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } @@ -1082,7 +1084,7 @@ public class ProviderHelper { indent += 1; final Cursor cursor = mContentResolver.query(KeyRingData.buildSecretKeyRingUri(), - new String[]{ KeyRingData.KEY_RING_DATA }, null, null, null); + new String[]{KeyRingData.KEY_RING_DATA}, null, null, null); if (cursor == null) { log.add(LogType.MSG_CON_ERROR_DB, indent); @@ -1143,7 +1145,7 @@ public class ProviderHelper { final Cursor cursor = mContentResolver.query( KeyRingData.buildPublicKeyRingUri(), - new String[]{ KeyRingData.KEY_RING_DATA }, null, null, null); + new String[]{KeyRingData.KEY_RING_DATA}, null, null, null); if (cursor == null) { log.add(LogType.MSG_CON_ERROR_DB, indent); @@ -1250,7 +1252,7 @@ public class ProviderHelper { ImportKeyResult result = new ImportOperation(mContext, this, new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport)) - .serialKeyRingImport(itSecrets, numSecrets, null); + .serialKeyRingImport(itSecrets, numSecrets, null, null); log.add(result, indent); } else { log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent); @@ -1278,7 +1280,7 @@ public class ProviderHelper { ImportKeyResult result = new ImportOperation(mContext, this, new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport)) - .serialKeyRingImport(itPublics, numPublics, null); + .serialKeyRingImport(itPublics, numPublics, null, null); log.add(result, indent); } else { log.add(LogType.MSG_CON_REIMPORT_PUBLIC_SKIP, indent); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java index a11f81658..e76dcfb49 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -22,13 +22,10 @@ import android.os.Parcel; import android.os.Parcelable; import java.io.Serializable; -import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Date; -import java.util.Map; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.util.ParcelableProxy; /** @@ -54,6 +51,7 @@ public class CertifyActionsParcel implements Parcelable { mMasterKeyId = source.readLong(); // just like parcelables, this is meant for ad-hoc IPC only and is NOT portable! mLevel = CertifyLevel.values()[source.readInt()]; + keyServerUri = source.readString(); mCertifyActions = (ArrayList<CertifyAction>) source.readSerializable(); } @@ -66,6 +64,7 @@ public class CertifyActionsParcel implements Parcelable { public void writeToParcel(Parcel destination, int flags) { destination.writeLong(mMasterKeyId); destination.writeInt(mLevel.ordinal()); + destination.writeString(keyServerUri); destination.writeSerializable(mCertifyActions); } @@ -94,7 +93,7 @@ public class CertifyActionsParcel implements Parcelable { } public CertifyAction(long masterKeyId, ArrayList<String> userIds, - ArrayList<WrappedUserAttribute> attributes) { + ArrayList<WrappedUserAttribute> attributes) { mMasterKeyId = masterKeyId; mUserIds = userIds; mUserAttributes = attributes; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java index ee7caf2d8..96f54dd2f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -17,17 +17,17 @@ package org.sufficientlysecure.keychain.service.input; -import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - import android.os.Parcel; import android.os.Parcelable; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Passphrase; +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + /** * This is a base class for the input of crypto operations. */ @@ -35,6 +35,8 @@ public class CryptoInputParcel implements Parcelable { final Date mSignatureTime; final Passphrase mPassphrase; + // used to supply an explicit proxy to operations that require it + private ParcelableProxy mParcelableProxy; // this map contains both decrypted session keys and signed hashes to be // used in the crypto operation described by this parcel. @@ -43,26 +45,37 @@ public class CryptoInputParcel implements Parcelable { public CryptoInputParcel() { mSignatureTime = new Date(); mPassphrase = null; + mParcelableProxy = null; } public CryptoInputParcel(Date signatureTime, Passphrase passphrase) { mSignatureTime = signatureTime == null ? new Date() : signatureTime; mPassphrase = passphrase; + mParcelableProxy = null; } public CryptoInputParcel(Passphrase passphrase) { mSignatureTime = new Date(); mPassphrase = passphrase; + mParcelableProxy = null; } public CryptoInputParcel(Date signatureTime) { mSignatureTime = signatureTime == null ? new Date() : signatureTime; mPassphrase = null; + mParcelableProxy = null; + } + + public CryptoInputParcel(ParcelableProxy parcelableProxy) { + mSignatureTime = new Date(); // just for compatibility with parcel-ing + mPassphrase = null; + mParcelableProxy = parcelableProxy; } protected CryptoInputParcel(Parcel source) { mSignatureTime = new Date(source.readLong()); mPassphrase = source.readParcelable(getClass().getClassLoader()); + mParcelableProxy = source.readParcelable(getClass().getClassLoader()); { int count = source.readInt(); @@ -85,6 +98,7 @@ public class CryptoInputParcel implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mSignatureTime.getTime()); dest.writeParcelable(mPassphrase, 0); + dest.writeParcelable(mParcelableProxy, 0); dest.writeInt(mCryptoData.size()); for (HashMap.Entry<ByteBuffer, byte[]> entry : mCryptoData.entrySet()) { @@ -93,6 +107,10 @@ public class CryptoInputParcel implements Parcelable { } } + public void addParcelableProxy(ParcelableProxy parcelableProxy) { + mParcelableProxy = parcelableProxy; + } + public void addCryptoData(byte[] hash, byte[] signedHash) { mCryptoData.put(ByteBuffer.wrap(hash), signedHash); } @@ -101,6 +119,10 @@ public class CryptoInputParcel implements Parcelable { mCryptoData.putAll(cachedSessionKeys); } + public ParcelableProxy getParcelableProxy() { + return mParcelableProxy; + } + public Map<ByteBuffer, byte[]> getCryptoData() { return mCryptoData; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index 4eca8d8f9..e378296a5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -15,7 +15,7 @@ import java.util.Date; public class RequiredInputParcel implements Parcelable { public enum RequiredInputType { - PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD + PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD, ENABLE_ORBOT } public Date mSignatureTime; @@ -77,6 +77,10 @@ public class RequiredInputParcel implements Parcelable { return mSubKeyId; } + public static RequiredInputParcel createOrbotRequiredOperation() { + return new RequiredInputParcel(RequiredInputType.ENABLE_ORBOT, null, null, null, 0L, 0L); + } + public static RequiredInputParcel createNfcSignOperation( long masterKeyId, long subKeyId, byte[] inputHash, int signAlgo, Date signatureTime) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 891c2268c..ab631c6fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -54,6 +54,7 @@ import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; @@ -311,6 +312,11 @@ public class CertifyKeyFragment CertifyActionsParcel actionsParcel = new CertifyActionsParcel(selectedKeyId); actionsParcel.mCertifyActions.addAll(certifyActions); + if (mUploadKeyCheckbox.isChecked()) { + actionsParcel.keyServerUri = Preferences.getPreferences(getActivity()) + .getPreferredKeyserver(); + } + // cached for next cryptoOperation loop cacheActionsParcel(actionsParcel); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java index c64f05687..9e3dce28f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java @@ -41,7 +41,9 @@ import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; public class CreateYubiKeyImportFragment @@ -131,7 +133,18 @@ public class CreateYubiKeyImportFragment view.findViewById(R.id.button_search).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - refreshSearch(); + final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity()).getProxyPrefs(); + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + refreshSearch(new ParcelableProxy(null, -1, null)); + } + }; + + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs, + getActivity())) { + refreshSearch(proxyPrefs.parcelableProxy); + } } }); @@ -170,9 +183,10 @@ public class CreateYubiKeyImportFragment } } - public void refreshSearch() { + public void refreshSearch(ParcelableProxy parcelableProxy) { + // TODO: PHILIP verify proxy implementation in YubiKey parts mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mNfcFingerprint, - Preferences.getPreferences(getActivity()).getCloudSearchPrefs())); + Preferences.getPreferences(getActivity()).getCloudSearchPrefs()), parcelableProxy); } public void importKey() { @@ -210,7 +224,20 @@ public class CreateYubiKeyImportFragment public void onNfcPostExecute() throws IOException { setData(); - refreshSearch(); + + Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity()).getProxyPrefs(); + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + refreshSearch(new ParcelableProxy(null, -1, null)); + } + }; + + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs, + getActivity())) { + refreshSearch(proxyPrefs.parcelableProxy); + } + } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 4eb8cd5e8..afd6b337e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -19,15 +19,11 @@ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; -import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.Fragment; -import android.os.Parcelable; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -52,13 +48,14 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; -import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; public abstract class DecryptFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index bc83b05b0..2e8e6f6bd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -42,6 +42,8 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.io.IOException; import java.util.ArrayList; @@ -87,11 +89,14 @@ public class ImportKeysActivity extends BaseNfcActivity private ArrayList<ParcelableKeyRing> mKeyList; private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper; + private Preferences.ProxyPrefs mProxyPrefs; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mProxyPrefs = Preferences.getPreferences(this).getProxyPrefs(); + mImportButton = findViewById(R.id.import_import); mImportButton.setOnClickListener(new OnClickListener() { @Override @@ -224,7 +229,7 @@ public class ImportKeysActivity extends BaseNfcActivity Notify.Style.WARN).show(mTopFragment); // we just set the keyserver startCloudFragment(savedInstanceState, null, false, keyserver); - // it's not necessary to set the keyserver for ImportKeysListFragment since + // we don't set the keyserver for ImportKeysListFragment since // it'll be taken care of by ImportKeysCloudFragment when the user clicks // the search button startListFragment(savedInstanceState, null, null, null, null); @@ -316,7 +321,8 @@ public class ImportKeysActivity extends BaseNfcActivity * specified in user preferences */ - private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String keyserver) { + private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String + keyserver) { // 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. @@ -346,8 +352,24 @@ public class ImportKeysActivity extends BaseNfcActivity } } - public void loadCallback(ImportKeysListFragment.LoaderState loaderState) { - mListFragment.loadNew(loaderState); + public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) { + if (loaderState instanceof ImportKeysListFragment.CloudLoaderState) { + // do the tor check + // this handle will set tor to be ignored whenever a message is received + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + // disables Tor until Activity is recreated + mProxyPrefs = new Preferences.ProxyPrefs(false, false, null, -1, null); + mListFragment.loadNew(loaderState, mProxyPrefs.parcelableProxy); + } + }; + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, mProxyPrefs, this)) { + mListFragment.loadNew(loaderState, mProxyPrefs.parcelableProxy); + } + } else if (loaderState instanceof ImportKeysListFragment.BytesLoaderState) { // must always be true + mListFragment.loadNew(loaderState, mProxyPrefs.parcelableProxy); + } } private void handleMessage(Message message) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index bf7e41045..55ccc3975 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -41,6 +41,7 @@ import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; import java.io.ByteArrayInputStream; @@ -64,6 +65,7 @@ public class ImportKeysListFragment extends ListFragment implements private ImportKeysAdapter mAdapter; private LoaderState mLoaderState; + private ParcelableProxy mProxy; private static final int LOADER_ID_BYTES = 0; private static final int LOADER_ID_CLOUD = 1; @@ -126,6 +128,7 @@ public class ImportKeysListFragment extends ListFragment implements /** * Creates an interactive ImportKeyListFragment which reads keyrings from bytes, or file specified * by dataUri, or searches a keyserver for serverQuery, if parameter is not null, in that order + * Will immediately load data if non-null bytes/dataUri/serverQuery * * @param bytes byte data containing list of keyrings to be imported * @param dataUri file from which keyrings are to be imported @@ -141,7 +144,7 @@ public class ImportKeysListFragment extends ListFragment implements /** * Visually consists of a list of keyrings with checkboxes to specify which are to be imported - * Can immediately load keyrings specified by any of its parameters + * Will immediately load data if non-null bytes/dataUri/serverQuery is supplied * * @param bytes byte data containing list of keyrings to be imported * @param dataUri file from which keyrings are to be imported @@ -258,7 +261,9 @@ public class ImportKeysListFragment extends ListFragment implements mAdapter.notifyDataSetChanged(); } - public void loadNew(LoaderState loaderState) { + public void loadNew(LoaderState loaderState, ParcelableProxy proxy) { + mProxy = proxy; + mLoaderState = loaderState; restartLoaders(); @@ -301,7 +306,7 @@ public class ImportKeysListFragment extends ListFragment implements } case LOADER_ID_CLOUD: { CloudLoaderState ls = (CloudLoaderState) mLoaderState; - return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs); + return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs, mProxy); } default: diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java index da713d0d8..f45f1b350 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -44,7 +44,9 @@ import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.ArrayList; import java.util.Locale; @@ -157,8 +159,7 @@ public class ImportKeysProxyActivity extends FragmentActivity returnResult(intent); return; } - - String fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH); + final String fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH); if (!fingerprint.matches("[a-fA-F0-9]{40}")) { SingletonResult result = new SingletonResult( SingletonResult.RESULT_ERROR, LogType.MSG_WRONG_QR_CODE_FP); @@ -273,7 +274,8 @@ public class ImportKeysProxyActivity extends FragmentActivity // only one message sent during the beam NdefMessage msg = (NdefMessage) rawMsgs[0]; // record 0 contains the MIME type, record 1 is the AAR, if present - byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload(); + final byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload(); + importKeys(receivedKeyringBytes); } 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 e038cf948..c5bfdbedf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -52,6 +52,9 @@ import android.widget.TextView; import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionsMenu; +import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; +import se.emilsjolander.stickylistheaders.StickyListHeadersListView; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; @@ -71,19 +74,16 @@ import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; -import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; -import se.emilsjolander.stickylistheaders.StickyListHeadersListView; - /** * Public key list with sticky list headers. It does _not_ extend ListFragment because it uses * StickyListHeaders library which does not extend upon ListView. @@ -478,10 +478,6 @@ public class KeyListFragment extends LoaderFragment updateAllKeys(); return true; - case R.id.menu_key_list_debug_cons: - consolidate(); - return true; - case R.id.menu_key_list_debug_read: try { KeychainDatabase.debugBackup(getActivity(), true); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java new file mode 100644 index 000000000..587044659 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; + +/** + * Simply encapsulates a dialog. If orbot is not installed, it shows an install dialog, else a + * dialog to enable orbot. + */ +public class OrbotRequiredDialogActivity extends FragmentActivity { + + // to provide any previous crypto input into which proxy preference is merged + public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input"; + + public static final String RESULT_CRYPTO_INPUT = "result_crypto_input"; + + private CryptoInputParcel mCryptoInputParcel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mCryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); + if (mCryptoInputParcel == null) { + mCryptoInputParcel = new CryptoInputParcel(); + } + showDialog(); + } + + /** + * Displays an install or start orbot dialog depending on orbot's presence and state + */ + public void showDialog() { + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + Intent intent = new Intent(); + mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy()); + intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); + setResult(RESULT_OK, intent); + finish(); + } + }; + + Runnable dialogDismissed = new Runnable() { + @Override + public void run() { + finish(); + } + }; + + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, dialogDismissed, + Preferences.getPreferences(OrbotRequiredDialogActivity.this) + .getProxyPrefs(), + OrbotRequiredDialogActivity.this)) { + // no action required after all + Intent intent = new Intent(); + intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); + setResult(RESULT_OK, intent); + } + } + }); + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java index 2e838535d..ed96156fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java @@ -43,7 +43,9 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -207,7 +209,7 @@ public class PassphraseWizardActivity extends FragmentActivity { // transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit(); } } catch (IOException | FormatException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "Failed to write on NFC tag", e); } } else if (readNFC && AUTHENTICATION.equals(selectedAction)) { @@ -232,7 +234,7 @@ public class PassphraseWizardActivity extends FragmentActivity { } } } catch (IOException | FormatException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "Failed to read NFC tag", e); } } } @@ -263,7 +265,7 @@ public class PassphraseWizardActivity extends FragmentActivity { try { password = readText(ndefRecord); } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "Failed to read password from tag", e); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index 6fc0aaac6..f72a552d5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -18,14 +18,21 @@ package org.sufficientlysecure.keychain.ui; +import android.annotation.TargetApi; +import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; import android.support.v7.widget.Toolbar; +import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; @@ -33,9 +40,11 @@ import android.widget.LinearLayout; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity; +import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.List; @@ -43,6 +52,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { public static final String ACTION_PREFS_CLOUD = "org.sufficientlysecure.keychain.ui.PREFS_CLOUD"; public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV"; + public static final String ACTION_PREFS_PROXY = "org.sufficientlysecure.keychain.ui.PREFS_PROXY"; public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005; @@ -209,9 +219,205 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } } + public static class ProxyPrefsFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + new Initializer(this).initialize(); + + } + + public static class Initializer { + private CheckBoxPreference mUseTor; + private CheckBoxPreference mUseNormalProxy; + private EditTextPreference mProxyHost; + private EditTextPreference mProxyPort; + private ListPreference mProxyType; + private PreferenceActivity mActivity; + private PreferenceFragment mFragment; + + public Initializer(PreferenceFragment fragment) { + mFragment = fragment; + } + + public Initializer(PreferenceActivity activity) { + mActivity = activity; + } + + public Preference automaticallyFindPreference(String key) { + if (mFragment != null) { + return mFragment.findPreference(key); + } else { + return mActivity.findPreference(key); + } + } + + public void initialize() { + // makes android's preference framework write to our file instead of default + // This allows us to use the "persistent" attribute to simplify code + if (mFragment != null) { + Preferences.setPreferenceManagerFileAndMode(mFragment.getPreferenceManager()); + // Load the preferences from an XML resource + mFragment.addPreferencesFromResource(R.xml.proxy_prefs); + } else { + Preferences.setPreferenceManagerFileAndMode(mActivity.getPreferenceManager()); + // Load the preferences from an XML resource + mActivity.addPreferencesFromResource(R.xml.proxy_prefs); + } + + mUseTor = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY); + mUseNormalProxy = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY); + mProxyHost = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_HOST); + mProxyPort = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_PORT); + mProxyType = (ListPreference) automaticallyFindPreference(Constants.Pref.PROXY_TYPE); + initializeUseTorPref(); + initializeUseNormalProxyPref(); + initializeEditTextPreferences(); + initializeProxyTypePreference(); + + if (mUseTor.isChecked()) disableNormalProxyPrefs(); + else if (mUseNormalProxy.isChecked()) disableUseTorPrefs(); + } + + private void initializeUseTorPref() { + mUseTor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + if ((Boolean) newValue) { + boolean installed = OrbotHelper.isOrbotInstalled(activity); + if (!installed) { + Log.d(Constants.TAG, "Prompting to install Tor"); + OrbotHelper.getPreferenceInstallDialogFragment().show(activity.getFragmentManager(), + "installDialog"); + // don't let the user check the box until he's installed orbot + return false; + } else { + disableNormalProxyPrefs(); + // let the enable tor box be checked + return true; + } + } else { + // we're unchecking Tor, so enable other proxy + enableNormalProxyPrefs(); + return true; + } + } + }); + } + + private void initializeUseNormalProxyPref() { + mUseNormalProxy.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if ((Boolean) newValue) { + disableUseTorPrefs(); + } else { + enableUseTorPrefs(); + } + return true; + } + }); + } + + private void initializeEditTextPreferences() { + mProxyHost.setSummary(mProxyHost.getText()); + mProxyPort.setSummary(mProxyPort.getText()); + + mProxyHost.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + if (TextUtils.isEmpty((String) newValue)) { + Notify.create( + activity, + R.string.pref_proxy_host_err_invalid, + Notify.Style.ERROR + ).show(); + return false; + } else { + mProxyHost.setSummary((CharSequence) newValue); + return true; + } + } + }); + + mProxyPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + try { + int port = Integer.parseInt((String) newValue); + if (port < 0 || port > 65535) { + Notify.create( + activity, + R.string.pref_proxy_port_err_invalid, + Notify.Style.ERROR + ).show(); + return false; + } + // no issues, save port + mProxyPort.setSummary("" + port); + return true; + } catch (NumberFormatException e) { + Notify.create( + activity, + R.string.pref_proxy_port_err_invalid, + Notify.Style.ERROR + ).show(); + return false; + } + } + }); + } + + private void initializeProxyTypePreference() { + mProxyType.setSummary(mProxyType.getEntry()); + + mProxyType.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + CharSequence entry = mProxyType.getEntries()[mProxyType.findIndexOfValue((String) newValue)]; + mProxyType.setSummary(entry); + return true; + } + }); + } + + private void disableNormalProxyPrefs() { + mUseNormalProxy.setChecked(false); + mUseNormalProxy.setEnabled(false); + mProxyHost.setEnabled(false); + mProxyPort.setEnabled(false); + mProxyType.setEnabled(false); + } + + private void enableNormalProxyPrefs() { + mUseNormalProxy.setEnabled(true); + mProxyHost.setEnabled(true); + mProxyPort.setEnabled(true); + mProxyType.setEnabled(true); + } + + private void disableUseTorPrefs() { + mUseTor.setChecked(false); + mUseTor.setEnabled(false); + } + + private void enableUseTorPrefs() { + mUseTor.setEnabled(true); + } + } + + } + + @TargetApi(Build.VERSION_CODES.KITKAT) protected boolean isValidFragment(String fragmentName) { return AdvancedPrefsFragment.class.getName().equals(fragmentName) || CloudSearchPrefsFragment.class.getName().equals(fragmentName) + || ProxyPrefsFragment.class.getName().equals(fragmentName) || super.isValidFragment(fragmentName); } @@ -270,7 +476,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity { String[] servers = sPreferences.getKeyServers(); String serverSummary = context.getResources().getQuantityString( R.plurals.n_keyservers, servers.length, servers.length); - return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences.getPreferredKeyserver(); + return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences + .getPreferredKeyserver(); } private static void initializeUseDefaultYubiKeyPin(final CheckBoxPreference mUseDefaultYubiKeyPin) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index 8b49f3b96..703a85b0f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -26,7 +26,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Spinner; -import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -38,6 +37,7 @@ import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; /** * Sends the selected public key to a keyserver @@ -132,8 +132,7 @@ public class UploadKeyActivity extends BaseActivity @Override public void onCryptoOperationSuccess(ExportResult result) { - Toast.makeText(UploadKeyActivity.this, R.string.msg_crt_upload_success, - Toast.LENGTH_SHORT).show(); + result.createNotify(this).show(); } @Override @@ -143,7 +142,7 @@ public class UploadKeyActivity extends BaseActivity @Override public void onCryptoOperationError(ExportResult result) { - // TODO: Implement proper log for key upload then show error + result.createNotify(this).show(); } @Override 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 27832b665..ce8935bce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -84,7 +84,9 @@ import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.NfcHelper; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.io.IOException; import java.util.ArrayList; @@ -464,11 +466,11 @@ public class ViewKeyActivity extends BaseNfcActivity implements HashMap<String, Object> data = providerHelper.getGenericData( baseUri, - new String[] {KeychainContract.Keys.MASTER_KEY_ID, KeychainContract.KeyRings.HAS_SECRET}, - new int[] {ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_INTEGER}); + new String[]{KeychainContract.Keys.MASTER_KEY_ID, KeychainContract.KeyRings.HAS_SECRET}, + new int[]{ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_INTEGER}); exportHelper.showExportKeysDialog( - new long[] {(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)}, + new long[]{(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)}, Constants.Path.APP_DIR_FILE, ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) != 0) ); } catch (ProviderHelper.NotFoundException e) { @@ -492,7 +494,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements // Create a new Messenger for the communication back Messenger messenger = new Messenger(returnHandler); DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, - new long[] {mMasterKeyId}); + new long[]{mMasterKeyId}); deleteKeyDialog.show(getSupportFragmentManager(), "deleteKeyDialog"); } @@ -636,7 +638,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements long keyId = new ProviderHelper(this) .getCachedPublicKeyRing(dataUri) .extractOrGetMasterKeyId(); - long[] encryptionKeyIds = new long[] {keyId}; + long[] encryptionKeyIds = new long[]{keyId}; Intent intent; if (text) { intent = new Intent(this, EncryptTextActivity.class); @@ -742,7 +744,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[] { + static final String[] PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, @@ -830,7 +832,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements AsyncTask<Long, Void, Bitmap> photoTask = new AsyncTask<Long, Void, Bitmap>() { protected Bitmap doInBackground(Long... mMasterKeyId) { - return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), mMasterKeyId[0], true); + return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), + mMasterKeyId[0], true); } protected void onPostExecute(Bitmap photo) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java index 6052eec16..40cbba5b3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java @@ -51,6 +51,9 @@ import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.ArrayList; import java.util.Hashtable; @@ -197,8 +200,22 @@ public class ViewKeyTrustFragment extends LoaderFragment implements mStartSearch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - mStartSearch.setEnabled(false); - new DescribeKey().execute(fingerprint); + final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity()) + .getProxyPrefs(); + + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + mStartSearch.setEnabled(false); + new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); + } + }; + + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs, + getActivity())) { + mStartSearch.setEnabled(false); + new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); + } } }); } @@ -229,6 +246,11 @@ public class ViewKeyTrustFragment extends LoaderFragment implements // look for evidence from keybase in the background, make tabular version of result // private class DescribeKey extends AsyncTask<String, Void, ResultPage> { + ParcelableProxy mParcelableProxy; + + public DescribeKey(ParcelableProxy parcelableProxy) { + mParcelableProxy = parcelableProxy; + } @Override protected ResultPage doInBackground(String... args) { @@ -237,7 +259,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements final ArrayList<CharSequence> proofList = new ArrayList<CharSequence>(); final Hashtable<Integer, ArrayList<Proof>> proofs = new Hashtable<Integer, ArrayList<Proof>>(); try { - User keybaseUser = User.findByFingerprint(fingerprint); + User keybaseUser = User.findByFingerprint(fingerprint, mParcelableProxy.getProxy()); for (Proof proof : keybaseUser.getProofs()) { Integer proofType = proof.getType(); appendIfOK(proofs, proofType, proof); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java index af919f3b6..c7f69207c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java @@ -27,6 +27,7 @@ import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; @@ -38,15 +39,18 @@ public class ImportKeysListCloudLoader Preferences.CloudSearchPrefs mCloudPrefs; String mServerQuery; + private ParcelableProxy mParcelableProxy; private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<>(); private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper; - public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) { + public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs, + ParcelableProxy proxy) { super(context); mContext = context; mServerQuery = serverQuery; mCloudPrefs = cloudPrefs; + mParcelableProxy = proxy; } @Override @@ -96,8 +100,11 @@ public class ImportKeysListCloudLoader */ private void queryServer(boolean enforceFingerprint) { try { - ArrayList<ImportKeysListEntry> searchResult - = CloudSearch.search(mServerQuery, mCloudPrefs); + ArrayList<ImportKeysListEntry> searchResult = CloudSearch.search( + mServerQuery, + mCloudPrefs, + mParcelableProxy.getProxy() + ); mEntryList.clear(); // add result to data diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java index 115f52d56..af664fc75 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java @@ -83,11 +83,11 @@ public abstract class CryptoOperationFragment<T extends Parcelable, S extends Op } /** - * * To be overriden by subclasses, if desired. Provides a way to access the method by the * same name in CryptoOperationHelper, if super.onCryptoOperationSuccess and * super.onCryptoOperationError are called at the start of the respective functions in the * subclass overriding them + * * @param result */ protected void onCryptoOperationResult(S result) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java index 240dd0972..af8a84aca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java @@ -28,8 +28,8 @@ import android.os.Messenger; import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; - import android.support.v4.app.FragmentManager; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.InputPendingResult; @@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.NfcOperationActivity; +import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.util.Log; @@ -52,16 +53,21 @@ import org.sufficientlysecure.keychain.util.Log; */ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResult> { - public interface Callback <T extends Parcelable, S extends OperationResult> { + public interface Callback<T extends Parcelable, S extends OperationResult> { T createOperationInput(); + void onCryptoOperationSuccess(S result); + void onCryptoOperationCancelled(); + void onCryptoOperationError(S result); + boolean onCryptoSetProgress(String msg, int progress, int max); } public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; public static final int REQUEST_CODE_NFC = 0x00008002; + public static final int REQUEST_CODE_ENABLE_ORBOT = 0x00008004; // keeps track of request code used to start an activity from this CryptoOperationHelper. // this is necessary when multiple CryptoOperationHelpers are used in the same fragment/activity @@ -116,11 +122,14 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu mProgressMessageResource = id; } - private void initiateInputActivity(RequiredInputParcel requiredInput) { + private void initiateInputActivity(RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { Activity activity = mUseFragment ? mFragment.getActivity() : mActivity; switch (requiredInput.mType) { + // TODO: Verify that all started activities add to cryptoInputParcel if necessary (like OrbotRequiredDialogActivity) + // don't forget to set mRequestedCode! case NFC_MOVE_KEY_TO_CARD: case NFC_DECRYPT: case NFC_SIGN: { @@ -130,7 +139,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu if (mUseFragment) { mFragment.startActivityForResult(intent, mRequestedCode); } else { - mActivity.startActivityForResult(intent, mRequestedCode); + activity.startActivityForResult(intent, mRequestedCode); } return; } @@ -143,18 +152,32 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu if (mUseFragment) { mFragment.startActivityForResult(intent, mRequestedCode); } else { - mActivity.startActivityForResult(intent, mRequestedCode); + activity.startActivityForResult(intent, mRequestedCode); + } + return; + } + + case ENABLE_ORBOT: { + Intent intent = new Intent(activity, OrbotRequiredDialogActivity.class); + intent.putExtra(OrbotRequiredDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel); + mRequestedCode = REQUEST_CODE_ENABLE_ORBOT; + if (mUseFragment) { + mFragment.startActivityForResult(intent, mRequestedCode); + } else { + activity.startActivityForResult(intent, mRequestedCode); } return; } - } - throw new RuntimeException("Unhandled pending result!"); + default: { + throw new RuntimeException("Unhandled pending result!"); + } + } } /** - * Attempts the result of an activity started by this helper. Returns true if requestCode is recognized, - * false otherwise. + * Attempts the result of an activity started by this helper. Returns true if requestCode is + * recognized, false otherwise. * * @param requestCode * @param resultCode @@ -196,6 +219,16 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu break; } + case REQUEST_CODE_ENABLE_ORBOT: { + if (resultCode == Activity.RESULT_OK && data != null) { + CryptoInputParcel cryptoInput = + data.getParcelableExtra( + OrbotRequiredDialogActivity.RESULT_CRYPTO_INPUT); + cryptoOperation(cryptoInput); + return true; + } + } + default: { return false; } @@ -225,7 +258,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu } - public void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) { + public void cryptoOperation(final CryptoInputParcel cryptoInput, boolean showProgress) { FragmentActivity activity = mUseFragment ? mFragment.getActivity() : mActivity; @@ -257,14 +290,14 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu final OperationResult result = returnData.getParcelable(OperationResult.EXTRA_RESULT); - onHandleResult(result); + onHandleResult(result, cryptoInput); } } @Override protected void onSetProgress(String msg, int progress, int max) { // allow handling of progress in fragment, or delegate upwards - if ( ! mCallback.onCryptoSetProgress(msg, progress, max)) { + if (!mCallback.onCryptoSetProgress(msg, progress, max)) { super.onSetProgress(msg, progress, max); } } @@ -299,7 +332,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu } } - public void onHandleResult(OperationResult result) { + public void onHandleResult(OperationResult result, CryptoInputParcel oldCryptoInput) { Log.d(Constants.TAG, "Handling result in OperationHelper success: " + result.success()); if (result instanceof InputPendingResult) { @@ -307,7 +340,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu if (pendingResult.isPending()) { RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel(); - initiateInputActivity(requiredInput); + initiateInputActivity(requiredInput, oldCryptoInput); return; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java index 321242b2f..b8d93dc00 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java @@ -48,10 +48,17 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.TlsHelper; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; + +import java.net.Proxy; public class AddEditKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener { private static final String ARG_MESSENGER = "arg_messenger"; @@ -205,9 +212,21 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On @Override public void onClick(View v) { // behaviour same for edit and add - String keyserverUrl = mKeyserverEditText.getText().toString(); + final String keyserverUrl = mKeyserverEditText.getText().toString(); if (mVerifyKeyserverCheckBox.isChecked()) { - verifyConnection(keyserverUrl); + final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity()) + .getProxyPrefs(); + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + verifyConnection(keyserverUrl, null); + } + }; + + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs, + getActivity())) { + verifyConnection(keyserverUrl, proxyPrefs.parcelableProxy.getProxy()); + } } else { dismiss(); // return unverified keyserver back to activity @@ -249,7 +268,7 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On sendMessageToHandler(MESSAGE_VERIFICATION_FAILED, data); } - public void verifyConnection(String keyserver) { + public void verifyConnection(String keyserver, final Proxy proxy) { new AsyncTask<String, Void, FailureReason>() { ProgressDialog mProgressDialog; @@ -283,10 +302,11 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On } URI newKeyserver = new URI(scheme, schemeSpecificPart, fragment); - Log.d(Constants.TAG, "Converted URL" + newKeyserver); + Log.d("Converted URL", newKeyserver.toString()); - // just see if we can get a connection, then immediately close - TlsHelper.openConnection(newKeyserver.toURL()).getInputStream().close(); + OkHttpClient client = HkpKeyserver.getClient(newKeyserver.toURL(), proxy); + TlsHelper.pinCertificateIfNecessary(client, newKeyserver.toURL()); + client.newCall(new Request.Builder().url(newKeyserver.toURL()).build()).execute(); } catch (TlsHelper.TlsHelperException e) { reason = FailureReason.CONNECTION_FAILED; } catch (MalformedURLException | URISyntaxException e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java new file mode 100644 index 000000000..dd7d2256a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.dialog; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.support.v4.app.DialogFragment; +import android.view.ContextThemeWrapper; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; + +/** + * displays a dialog asking the user to enable Tor + */ +public class OrbotStartDialogFragment extends DialogFragment { + private static final String ARG_MESSENGER = "messenger"; + private static final String ARG_TITLE = "title"; + private static final String ARG_MESSAGE = "message"; + private static final String ARG_MIDDLE_BUTTON = "middleButton"; + + public static final int MESSAGE_MIDDLE_BUTTON = 1; + public static final int MESSAGE_DIALOG_DISMISSED = 2; // for either cancel or enable pressed + + public static OrbotStartDialogFragment newInstance(Messenger messenger, int title, int message, int middleButton) { + Bundle args = new Bundle(); + args.putParcelable(ARG_MESSENGER, messenger); + args.putInt(ARG_TITLE, title); + args.putInt(ARG_MESSAGE, message); + args.putInt(ARG_MIDDLE_BUTTON, middleButton); + + OrbotStartDialogFragment fragment = new OrbotStartDialogFragment(); + fragment.setArguments(args); + + return fragment; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + final Messenger messenger = getArguments().getParcelable(ARG_MESSENGER); + int title = getArguments().getInt(ARG_TITLE); + final int message = getArguments().getInt(ARG_MESSAGE); + int middleButton = getArguments().getInt(ARG_MIDDLE_BUTTON); + final Activity activity = getActivity(); + + // if the dialog is displayed from the application class, design is missing. + // hack to get holo design (which is not automatically applied due to activity's + // Theme.NoDisplay) + ContextThemeWrapper theme = new ContextThemeWrapper(activity, + R.style.Theme_AppCompat_Light_Dialog); + + CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); + builder.setTitle(title).setMessage(message); + + builder.setNegativeButton(R.string.orbot_start_dialog_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = Message.obtain(); + msg.what = MESSAGE_DIALOG_DISMISSED; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + + } + }); + + builder.setPositiveButton(R.string.orbot_start_dialog_start, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + getActivity().startActivityForResult(OrbotHelper.getOrbotStartIntent(), 1); + + Message msg = Message.obtain(); + msg.what = MESSAGE_DIALOG_DISMISSED; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + }); + + builder.setNeutralButton(middleButton, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = new Message(); + msg.what = MESSAGE_MIDDLE_BUTTON; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + }); + + return builder.show(); + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java new file mode 100644 index 000000000..3f8bce28b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.dialog; + +import android.app.Dialog; +import android.os.Bundle; +import android.os.Messenger; +import android.app.DialogFragment; + +import org.sufficientlysecure.keychain.ui.util.InstallDialogFragmentHelper; + +public class PreferenceInstallDialogFragment extends DialogFragment { + + public static final int MESSAGE_MIDDLE_CLICKED = 1; + public static final int MESSAGE_DIALOG_DISMISSED = 2; + + /** + * Creates a dialog which prompts the user to install an application. Consists of two default buttons ("Install" + * and "Cancel") and an optional third button. Callbacks are provided only for the middle button, if set. + * + * @param messenger required only for callback from middle button if it has been set + * @param title + * @param message content of dialog + * @param packageToInstall package name of application to install + * @param middleButton if not null, adds a third button to the app with a call back + * @return The dialog to display + */ + public static PreferenceInstallDialogFragment newInstance(Messenger messenger, int title, int message, + String packageToInstall, int middleButton, boolean + useMiddleButton) { + PreferenceInstallDialogFragment frag = new PreferenceInstallDialogFragment(); + Bundle args = new Bundle(); + + InstallDialogFragmentHelper.wrapIntoArgs(messenger, title, message, packageToInstall, middleButton, + useMiddleButton, args); + + frag.setArguments(args); + + return frag; + } + + /** + * To create a DialogFragment with only two buttons + * + * @param title + * @param message + * @param packageToInstall + * @return + */ + public static PreferenceInstallDialogFragment newInstance(int title, int message, + String packageToInstall) { + return newInstance(null, title, message, packageToInstall, -1, false); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return InstallDialogFragmentHelper.getInstallDialogFromArgs(getArguments(), getActivity(), + MESSAGE_MIDDLE_CLICKED, MESSAGE_DIALOG_DISMISSED); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java new file mode 100644 index 000000000..b2b71b364 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.dialog; + +import android.app.Dialog; +import android.os.Bundle; +import android.os.Messenger; +import android.support.v4.app.DialogFragment; + +import org.sufficientlysecure.keychain.ui.util.InstallDialogFragmentHelper; + +public class SupportInstallDialogFragment extends DialogFragment { + + public static final int MESSAGE_MIDDLE_CLICKED = 1; + public static final int MESSAGE_DIALOG_DISMISSED = 2; + + /** + * Creates a dialog which prompts the user to install an application. Consists of two default buttons ("Install" + * and "Cancel") and an optional third button. Callbacks are provided only for the middle button, if set. + * + * @param messenger required only for callback from middle button if it has been set + * @param title + * @param message content of dialog + * @param packageToInstall package name of application to install + * @param middleButton if not null, adds a third button to the app with a call back + * @return The dialog to display + */ + public static SupportInstallDialogFragment newInstance(Messenger messenger, int title, int message, + String packageToInstall, int middleButton, boolean + useMiddleButton) { + SupportInstallDialogFragment frag = new SupportInstallDialogFragment(); + Bundle args = new Bundle(); + + InstallDialogFragmentHelper.wrapIntoArgs(messenger, title, message, packageToInstall, middleButton, + useMiddleButton, args); + + frag.setArguments(args); + + return frag; + } + + /** + * To create a DialogFragment with only two buttons + * + * @param title + * @param message + * @param packageToInstall + * @return + */ + public static SupportInstallDialogFragment newInstance(int title, int message, + String packageToInstall) { + return newInstance(null, title, message, packageToInstall, -1, false); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + return InstallDialogFragmentHelper.getInstallDialogFromArgs(getArguments(), getActivity(), + MESSAGE_MIDDLE_CLICKED, MESSAGE_DIALOG_DISMISSED); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java new file mode 100644 index 000000000..f21d69c48 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.util; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.view.ContextThemeWrapper; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; +import org.sufficientlysecure.keychain.util.Log; + +public class InstallDialogFragmentHelper { + private static final String ARG_MESSENGER = "messenger"; + private static final String ARG_TITLE = "title"; + private static final String ARG_MESSAGE = "message"; + private static final String ARG_MIDDLE_BUTTON = "middleButton"; + private static final String ARG_INSTALL_PATH = "installPath"; + private static final String ARG_USE_MIDDLE_BUTTON = "useMiddleButton"; + + private static final String PLAY_STORE_PATH = "market://search?q=pname:"; + + public static void wrapIntoArgs(Messenger messenger, int title, int message, String packageToInstall, + int middleButton, boolean useMiddleButton, Bundle args) { + args.putParcelable(ARG_MESSENGER, messenger); + + args.putInt(ARG_TITLE, title); + args.putInt(ARG_MESSAGE, message); + args.putInt(ARG_MIDDLE_BUTTON, middleButton); + args.putString(ARG_INSTALL_PATH, PLAY_STORE_PATH + packageToInstall); + args.putBoolean(ARG_USE_MIDDLE_BUTTON, useMiddleButton); + } + + public static AlertDialog getInstallDialogFromArgs(Bundle args, final Activity activity, + final int messengerMiddleButtonClicked, + final int messengerDialogDimissed) { + final Messenger messenger = args.getParcelable(ARG_MESSENGER); + + final int title = args.getInt(ARG_TITLE); + final int message = args.getInt(ARG_MESSAGE); + final int middleButton = args.getInt(ARG_MIDDLE_BUTTON); + final String installPath = args.getString(ARG_INSTALL_PATH); + final boolean useMiddleButton = args.getBoolean(ARG_USE_MIDDLE_BUTTON); + + // if the dialog is displayed from the application class, design is missing. + // hack to get holo design (which is not automatically applied due to activity's + // Theme.NoDisplay) + ContextThemeWrapper theme = new ContextThemeWrapper(activity, + R.style.Theme_AppCompat_Light_Dialog); + CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); + + builder.setTitle(title).setMessage(message); + + builder.setNegativeButton(R.string.orbot_install_dialog_cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = Message.obtain(); + msg.what = messengerDialogDimissed; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + }); + + builder.setPositiveButton(R.string.orbot_install_dialog_install, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Uri uri = Uri.parse(installPath); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + activity.startActivity(intent); + + Message msg = Message.obtain(); + msg.what = messengerDialogDimissed; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + } + ); + + if (useMiddleButton) { + builder.setNeutralButton(middleButton, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = Message.obtain(); + msg.what = messengerMiddleButtonClicked; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + } + ); + } + + return builder.show(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java index 0ec145657..30bdfb92a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java @@ -31,7 +31,9 @@ import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; /** * Created by Matt Allen diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java index b814f72b2..d7491ab26 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java @@ -27,6 +27,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import java.net.Proxy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -34,6 +35,7 @@ import java.util.Locale; import java.util.Set; public class EmailKeyHelper { + // TODO: Make this not require a proxy in it's constructor, redesign when it is to be used // to import keys, simply use CryptoOperationHelper with this callback public abstract class ImportContactKeysCallback implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> { @@ -41,14 +43,15 @@ public class EmailKeyHelper { private ArrayList<ParcelableKeyRing> mKeyList; private String mKeyserver; - public ImportContactKeysCallback(Context context, String keyserver) { - this(context, ContactHelper.getContactMails(context), keyserver); + public ImportContactKeysCallback(Context context, String keyserver, Proxy proxy) { + this(context, ContactHelper.getContactMails(context), keyserver, proxy); } - public ImportContactKeysCallback(Context context, List<String> mails, String keyserver) { + public ImportContactKeysCallback(Context context, List<String> mails, String keyserver, + Proxy proxy) { Set<ImportKeysListEntry> entries = new HashSet<>(); for (String mail : mails) { - entries.addAll(getEmailKeys(context, mail)); + entries.addAll(getEmailKeys(context, mail, proxy)); } // Put them in a list and import @@ -65,7 +68,7 @@ public class EmailKeyHelper { } } - public static Set<ImportKeysListEntry> getEmailKeys(Context context, String mail) { + public static Set<ImportKeysListEntry> getEmailKeys(Context context, String mail, Proxy proxy) { Set<ImportKeysListEntry> keys = new HashSet<>(); // Try _hkp._tcp SRV record first @@ -73,7 +76,7 @@ public class EmailKeyHelper { if (mailparts.length == 2) { HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1]); if (hkp != null) { - keys.addAll(getEmailKeys(mail, hkp)); + keys.addAll(getEmailKeys(mail, hkp, proxy)); } } @@ -82,16 +85,17 @@ public class EmailKeyHelper { String server = Preferences.getPreferences(context).getPreferredKeyserver(); if (server != null) { HkpKeyserver hkp = new HkpKeyserver(server); - keys.addAll(getEmailKeys(mail, hkp)); + keys.addAll(getEmailKeys(mail, hkp, proxy)); } } return keys; } - public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer) { + public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer, + Proxy proxy) { Set<ImportKeysListEntry> keys = new HashSet<>(); try { - for (ImportKeysListEntry key : keyServer.search(mail)) { + for (ImportKeysListEntry key : keyServer.search(mail, proxy)) { if (key.isRevoked() || key.isExpired()) continue; for (String userId : key.getUserIds()) { if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java new file mode 100644 index 000000000..b00b03ec7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.util; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.net.InetSocketAddress; +import java.net.Proxy; + +/** + * used to simply transport java.net.Proxy objects created using InetSockets between services/activities + */ +public class ParcelableProxy implements Parcelable { + private String mProxyHost; + private int mProxyPort; + private Proxy.Type mProxyType; + + public ParcelableProxy(String hostName, int port, Proxy.Type type) { + mProxyHost = hostName; + + if (hostName == null) { + return; // represents a null proxy + } + + mProxyPort = port; + + mProxyType = type; + } + + public static ParcelableProxy getForNoProxy() { + return new ParcelableProxy(null, -1, null); + } + + public Proxy getProxy() { + if (mProxyHost == null) { + return null; + } + + return new Proxy(mProxyType, new InetSocketAddress(mProxyHost, mProxyPort)); + } + + protected ParcelableProxy(Parcel in) { + mProxyHost = in.readString(); + mProxyPort = in.readInt(); + mProxyType = (Proxy.Type) in.readSerializable(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mProxyHost); + dest.writeInt(mProxyPort); + dest.writeSerializable(mProxyType); + } + + @SuppressWarnings("unused") + public static final Parcelable.Creator<ParcelableProxy> CREATOR = new Parcelable.Creator<ParcelableProxy>() { + @Override + public ParcelableProxy createFromParcel(Parcel in) { + return new ParcelableProxy(in); + } + + @Override + public ParcelableProxy[] newArray(int size) { + return new ParcelableProxy[size]; + } + }; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index 713d5f5ea..c13c07503 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -21,9 +21,13 @@ package org.sufficientlysecure.keychain.util; import android.content.Context; import android.content.SharedPreferences; +import android.content.res.Resources; +import android.preference.PreferenceManager; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.Pref; +import org.sufficientlysecure.keychain.R; +import java.net.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.ListIterator; @@ -35,6 +39,10 @@ import java.util.Vector; public class Preferences { private static Preferences sPreferences; private SharedPreferences mSharedPreferences; + private Resources mResources; + + private static String PREF_FILE_NAME = "APG.main"; + private static int PREF_FILE_MODE = Context.MODE_MULTI_PROCESS; public static synchronized Preferences getPreferences(Context context) { return getPreferences(context, false); @@ -51,12 +59,18 @@ public class Preferences { } private Preferences(Context context) { + mResources = context.getResources(); updateSharedPreferences(context); } + public static void setPreferenceManagerFileAndMode(PreferenceManager manager) { + manager.setSharedPreferencesName(PREF_FILE_NAME); + manager.setSharedPreferencesMode(PREF_FILE_MODE); + } + public void updateSharedPreferences(Context context) { // multi-process safe preferences - mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_MULTI_PROCESS); + mSharedPreferences = context.getSharedPreferences(PREF_FILE_NAME, PREF_FILE_MODE); } public String getLanguage() { @@ -207,7 +221,6 @@ public class Preferences { } - public void setUseArmor(boolean useArmor) { SharedPreferences.Editor editor = mSharedPreferences.edit(); editor.putBoolean(Pref.USE_ARMOR, useArmor); @@ -228,6 +241,89 @@ public class Preferences { return mSharedPreferences.getBoolean(Pref.ENCRYPT_FILENAMES, true); } + // proxy preference functions start here + + public boolean getUseNormalProxy() { + return mSharedPreferences.getBoolean(Constants.Pref.USE_NORMAL_PROXY, false); + } + + public boolean getUseTorProxy() { + return mSharedPreferences.getBoolean(Constants.Pref.USE_TOR_PROXY, false); + } + + public String getProxyHost() { + return mSharedPreferences.getString(Constants.Pref.PROXY_HOST, null); + } + + /** + * we store port as String for easy interfacing with EditTextPreference, but return it as an integer + * + * @return port number of proxy + */ + public int getProxyPort() { + return Integer.parseInt(mSharedPreferences.getString(Pref.PROXY_PORT, "-1")); + } + + /** + * we store port as String for easy interfacing with EditTextPreference, but return it as an integer + * + * @param port proxy port + */ + public void setProxyPort(String port) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putString(Pref.PROXY_PORT, port); + editor.commit(); + } + + public Proxy.Type getProxyType() { + final String typeHttp = mResources.getString(R.string.pref_proxy_type_value_http); + final String typeSocks = mResources.getString(R.string.pref_proxy_type_value_socks); + + String type = mSharedPreferences.getString(Pref.PROXY_TYPE, typeHttp); + + if (type.equals(typeHttp)) return Proxy.Type.HTTP; + else if (type.equals(typeSocks)) return Proxy.Type.SOCKS; + else { // shouldn't happen + Log.e(Constants.TAG, "Invalid Proxy Type in preferences"); + return null; + } + } + + public ProxyPrefs getProxyPrefs() { + boolean useTor = getUseTorProxy(); + boolean useNormalProxy = getUseNormalProxy(); + + if (useTor) { + return new ProxyPrefs(true, false, Constants.Orbot.PROXY_HOST, Constants.Orbot.PROXY_PORT, + Constants.Orbot.PROXY_TYPE); + } else if (useNormalProxy) { + return new ProxyPrefs(useTor, useNormalProxy, getProxyHost(), getProxyPort(), getProxyType()); + } else { + return new ProxyPrefs(false, false, null, -1, null); + } + } + + public static class ProxyPrefs { + public final ParcelableProxy parcelableProxy; + public final boolean torEnabled; + public final boolean normalPorxyEnabled; + + /** + * torEnabled and normalProxyEnabled are not expected to both be true + * + * @param torEnabled if Tor is to be used + * @param normalPorxyEnabled if user-specified proxy is to be used + */ + public ProxyPrefs(boolean torEnabled, boolean normalPorxyEnabled, String hostName, int port, Proxy.Type type) { + this.torEnabled = torEnabled; + this.normalPorxyEnabled = normalPorxyEnabled; + if (!torEnabled && !normalPorxyEnabled) this.parcelableProxy = new ParcelableProxy(null, -1, null); + else this.parcelableProxy = new ParcelableProxy(hostName, port, type); + } + } + + // proxy preference functions ends here + public CloudSearchPrefs getCloudSearchPrefs() { return new CloudSearchPrefs(mSharedPreferences.getBoolean(Pref.SEARCH_KEYSERVER, true), mSharedPreferences.getBoolean(Pref.SEARCH_KEYBASE, true), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java index 4ff14e3bb..d1d1ada2a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.util; import android.content.res.AssetManager; +import com.squareup.okhttp.OkHttpClient; import org.sufficientlysecure.keychain.Constants; import java.io.ByteArrayInputStream; @@ -26,7 +27,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.net.URLConnection; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -61,7 +61,7 @@ public class TlsHelper { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int reads = is.read(); - while(reads != -1){ + while (reads != -1) { baos.write(reads); reads = is.read(); } @@ -74,15 +74,56 @@ public class TlsHelper { } } - public static URLConnection openConnection(URL url) throws IOException, TlsHelperException { + public static void pinCertificateIfNecessary(OkHttpClient client, URL url) throws TlsHelperException, IOException { if (url.getProtocol().equals("https")) { for (String domain : sStaticCA.keySet()) { if (url.getHost().endsWith(domain)) { - return openCAConnection(sStaticCA.get(domain), url); + pinCertificate(sStaticCA.get(domain), client); } } } - return url.openConnection(); + } + + /** + * Modifies the client to accept only requests with a given certificate. Applies to all URLs requested by the + * client. + * Therefore a client that is pinned this way should be used to only make requests to URLs with passed certificate. + * TODO: Refactor - More like SSH StrictHostKeyChecking than pinning? + * + * @param certificate certificate to pin + * @param client OkHttpClient to enforce pinning on + * @throws TlsHelperException + * @throws IOException + */ + private static void pinCertificate(byte[] certificate, OkHttpClient client) + throws TlsHelperException, IOException { + // We don't use OkHttp's CertificatePinner since it depends on a TrustManager to verify it too. Refer to + // note at end of description: http://square.github.io/okhttp/javadoc/com/squareup/okhttp/CertificatePinner.html + // Creating our own TrustManager that trusts only our certificate eliminates the need for certificate pinning + try { + // Load CA + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate ca = cf.generateCertificate(new ByteArrayInputStream(certificate)); + + // Create a KeyStore containing our trusted CAs + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("ca", ca); + + // Create a TrustManager that trusts the CAs in our KeyStore + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + + // Create an SSLContext that uses our TrustManager + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), null); + + client.setSslSocketFactory(context.getSocketFactory()); + } catch (CertificateException | KeyStoreException | KeyManagementException | NoSuchAlgorithmException e) { + throw new TlsHelperException(e); + } } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java new file mode 100644 index 000000000..9d97ba305 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java @@ -0,0 +1,222 @@ +/* This is the license for Orlib, a free software project to + provide anonymity on the Internet from a Google Android smartphone. + + For more information about Orlib, see https://guardianproject.info/ + + If you got this file as a part of a larger bundle, there may be other + license terms that you should be aware of. + =============================================================================== + Orlib is distributed under this license (aka the 3-clause BSD license) + + Copyright (c) 2009-2010, Nathan Freitas, The Guardian Project + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + * Neither the names of the copyright owners nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ***** + Orlib contains a binary distribution of the JSocks library: + http://code.google.com/p/jsocks-mirror/ + which is licensed under the GNU Lesser General Public License: + http://www.gnu.org/licenses/lgpl.html + + ***** +*/ + +package org.sufficientlysecure.keychain.util.orbot; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.dialog.SupportInstallDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.OrbotStartDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.PreferenceInstallDialogFragment; +import org.sufficientlysecure.keychain.util.Preferences; + +/** + * This class is taken from the NetCipher library: https://github.com/guardianproject/NetCipher/ + */ +public class OrbotHelper { + + public final static String ORBOT_PACKAGE_NAME = "org.torproject.android"; + public final static String TOR_BIN_PATH = "/data/data/org.torproject.android/app_bin/tor"; + + public final static String ACTION_START_TOR = "org.torproject.android.START_TOR"; + + public static boolean isOrbotRunning() { + int procId = TorServiceUtils.findProcessId(TOR_BIN_PATH); + + return (procId != -1); + } + + public static boolean isOrbotInstalled(Context context) { + return isAppInstalled(ORBOT_PACKAGE_NAME, context); + } + + private static boolean isAppInstalled(String uri, Context context) { + PackageManager pm = context.getPackageManager(); + + boolean installed; + try { + pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES); + installed = true; + } catch (PackageManager.NameNotFoundException e) { + installed = false; + } + return installed; + } + + /** + * hack to get around the fact that PreferenceActivity still supports only android.app.DialogFragment + * + * @return + */ + public static android.app.DialogFragment getPreferenceInstallDialogFragment() { + return PreferenceInstallDialogFragment.newInstance(R.string.orbot_install_dialog_title, + R.string.orbot_install_dialog_content, ORBOT_PACKAGE_NAME); + } + + public static DialogFragment getInstallDialogFragment() { + return SupportInstallDialogFragment.newInstance(R.string.orbot_install_dialog_title, + R.string.orbot_install_dialog_content, ORBOT_PACKAGE_NAME); + } + + public static DialogFragment getInstallDialogFragmentWithThirdButton(Messenger messenger, int middleButton) { + return SupportInstallDialogFragment.newInstance(messenger, R.string.orbot_install_dialog_title, + R.string.orbot_install_dialog_content, ORBOT_PACKAGE_NAME, middleButton, true); + } + + public static DialogFragment getOrbotStartDialogFragment(Messenger messenger, int middleButton) { + return OrbotStartDialogFragment.newInstance(messenger, R.string.orbot_start_dialog_title, R.string + .orbot_start_dialog_content, + middleButton); + } + + public static Intent getOrbotStartIntent() { + Intent intent = new Intent(ACTION_START_TOR); + intent.setPackage(ORBOT_PACKAGE_NAME); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return intent; + } + + public static boolean isOrbotInRequiredState(Context context) { + Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(context).getProxyPrefs(); + if (!proxyPrefs.torEnabled) { + return true; + } else if (!OrbotHelper.isOrbotInstalled(context) || !OrbotHelper.isOrbotRunning()) { + return false; + } + return true; + } + + /** + * checks if Tor is enabled and if it is, that Orbot is installed and runnign. Generates appropriate dialogs. + * + * @param middleButton resourceId of string to display as the middle button of install and enable dialogs + * @param middleButtonRunnable runnable to be executed if the user clicks on the middle button + * @param proxyPrefs + * @param fragmentActivity + * @return true if Tor is not enabled or Tor is enabled and Orbot is installed and running, else false + */ + public static boolean putOrbotInRequiredState(final int middleButton, + final Runnable middleButtonRunnable, + final Runnable dialogDismissRunnable, + Preferences.ProxyPrefs proxyPrefs, + FragmentActivity fragmentActivity) { + + if (!proxyPrefs.torEnabled) { + return true; + } + + if (!OrbotHelper.isOrbotInstalled(fragmentActivity)) { + Handler ignoreTorHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case SupportInstallDialogFragment.MESSAGE_MIDDLE_CLICKED: + middleButtonRunnable.run(); + break; + case SupportInstallDialogFragment.MESSAGE_DIALOG_DISMISSED: + dialogDismissRunnable.run(); + break; + } + } + }; + + OrbotHelper.getInstallDialogFragmentWithThirdButton( + new Messenger(ignoreTorHandler), + middleButton + ).show(fragmentActivity.getSupportFragmentManager(), "OrbotHelperOrbotInstallDialog"); + + return false; + } else if (!OrbotHelper.isOrbotRunning()) { + + Handler ignoreTorHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case OrbotStartDialogFragment.MESSAGE_MIDDLE_BUTTON: + middleButtonRunnable.run(); + break; + case OrbotStartDialogFragment.MESSAGE_DIALOG_DISMISSED: + dialogDismissRunnable.run(); + break; + } + } + }; + + OrbotHelper.getOrbotStartDialogFragment(new Messenger(ignoreTorHandler), + middleButton) + .show(fragmentActivity.getSupportFragmentManager(), "OrbotHelperOrbotStartDialog"); + + return false; + } else { + return true; + } + } + + public static boolean putOrbotInRequiredState(final int middleButton, + final Runnable middleButtonRunnable, + Preferences.ProxyPrefs proxyPrefs, + FragmentActivity fragmentActivity) { + Runnable emptyRunnable = new Runnable() { + @Override + public void run() { + + } + }; + return putOrbotInRequiredState(middleButton, middleButtonRunnable, emptyRunnable, + proxyPrefs, fragmentActivity); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java new file mode 100644 index 000000000..d11e80f7a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/TorServiceUtils.java @@ -0,0 +1,144 @@ +/* This is the license for Orlib, a free software project to + provide anonymity on the Internet from a Google Android smartphone. + + For more information about Orlib, see https://guardianproject.info/ + + If you got this file as a part of a larger bundle, there may be other + license terms that you should be aware of. + =============================================================================== + Orlib is distributed under this license (aka the 3-clause BSD license) + + Copyright (c) 2009-2010, Nathan Freitas, The Guardian Project + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + * Neither the names of the copyright owners nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ***** + Orlib contains a binary distribution of the JSocks library: + http://code.google.com/p/jsocks-mirror/ + which is licensed under the GNU Lesser General Public License: + http://www.gnu.org/licenses/lgpl.html + + ***** +*/ + +package org.sufficientlysecure.keychain.util.orbot; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.net.URLEncoder; +import java.util.StringTokenizer; + +/** + * This class is taken from the NetCipher library: https://github.com/guardianproject/NetCipher/ + */ +public class TorServiceUtils { + // various console cmds + public final static String SHELL_CMD_PS = "ps"; + public final static String SHELL_CMD_PIDOF = "pidof"; + + public static int findProcessId(String command) { + int procId = -1; + + try { + procId = findProcessIdWithPidOf(command); + + if (procId == -1) { + procId = findProcessIdWithPS(command); + } + } catch (Exception e) { + try { + procId = findProcessIdWithPS(command); + } catch (Exception e2) { + Log.e(Constants.TAG, "Unable to get proc id for command: " + URLEncoder.encode(command), e2); + } + } + + return procId; + } + + // use 'pidof' command + public static int findProcessIdWithPidOf(String command) throws Exception { + + int procId = -1; + Runtime r = Runtime.getRuntime(); + Process procPs; + + String baseName = new File(command).getName(); + // fix contributed my mikos on 2010.12.10 + procPs = r.exec(new String[]{ + SHELL_CMD_PIDOF, baseName + }); + // procPs = r.exec(SHELL_CMD_PIDOF); + + BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream())); + String line; + + while ((line = reader.readLine()) != null) { + + try { + // this line should just be the process id + procId = Integer.parseInt(line.trim()); + break; + } catch (NumberFormatException e) { + Log.e("TorServiceUtils", "unable to parse process pid: " + line, e); + } + } + + return procId; + } + + // use 'ps' command + public static int findProcessIdWithPS(String command) throws Exception { + int procId = -1; + Runtime r = Runtime.getRuntime(); + Process procPs; + + procPs = r.exec(SHELL_CMD_PS); + + BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + if (line.contains(' ' + command)) { + + StringTokenizer st = new StringTokenizer(line, " "); + st.nextToken(); // proc owner + + procId = Integer.parseInt(st.nextToken().trim()); + + break; + } + } + + return procId; + } +} |