diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 81a18858c..5db751bfa 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -49,6 +49,7 @@ dependencies { compile 'com.mikepenz.iconics:meteocons-typeface:1.1.1@aar' compile 'com.mikepenz.iconics:community-material-typeface:1.0.0@aar' compile 'com.nispok:snackbar:2.10.8' + compile 'com.squareup.okhttp:okhttp:2.4.0' // libs as submodules compile project(':extern:openpgp-api-lib:openpgp-api') @@ -61,7 +62,7 @@ dependencies { compile project(':extern:KeybaseLib:Lib') compile project(':extern:safeslinger-exchange') compile (project( ':extern:NetCipher:libnetcipher')) { - exclude group: 'com.madgag.spongycastle' + exclude group: 'com.madgag.spongycastle' // we're already adding it above, transitive dependency } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 07602e618..722395607 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -108,7 +108,6 @@ public final class Constants { 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 Proxy PROXY = new Proxy(PROXY_TYPE, new InetSocketAddress(PROXY_HOST, PROXY_PORT)); } 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 c0221fad3..0563318ed 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 search(final String query, Preferences.CloudSearchPrefs cloudPrefs) + public static ArrayList search(final String query, Preferences.CloudSearchPrefs cloudPrefs, + final Proxy proxy) throws Keyserver.CloudSearchFailureException { final ArrayList servers = new ArrayList<>(); @@ -50,7 +52,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); } 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..f18878bb5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -18,6 +18,8 @@ package org.sufficientlysecure.keychain.keyimport; +import com.squareup.okhttp.*; +import okio.BufferedSink; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -29,16 +31,14 @@ import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLDecoder; -import java.net.URLEncoder; +import java.net.*; import java.util.ArrayList; import java.util.Arrays; 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,7 +190,7 @@ public class HkpKeyserver extends Keyserver { return mSecure ? "https://" : "http://"; } - private HttpURLConnection openConnection(URL url) throws IOException { + private HttpURLConnection openConnectioan(URL url) throws IOException { HttpURLConnection conn = null; try { conn = (HttpURLConnection) TlsHelper.openConnection(url); @@ -205,18 +205,43 @@ public class HkpKeyserver extends Keyserver { return conn; } - private String query(String request) throws QueryFailedException, HttpError { + /** + * returns a client with pinned certificate if necessary + * + * @param url + * @param proxy + * @return + */ + private OkHttpClient getClient(URL url, Proxy proxy) { + OkHttpClient client = new OkHttpClient(); + + try { + TlsHelper.pinCertificateIfNecessary(client, url); + } catch (TlsHelper.TlsHelperException e) { + Log.w(Constants.TAG, e); + } + + client.setProxy(proxy); + // TODO: if proxy !=null increase timeout? + client.setConnectTimeout(5000, TimeUnit.MILLISECONDS); + client.setReadTimeout(25000, TimeUnit.MILLISECONDS); + + return client; + } + + 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()); + 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!"); @@ -232,7 +257,7 @@ public class HkpKeyserver extends Keyserver { * @throws QueryNeedsRepairException */ @Override - public ArrayList search(String query) throws QueryFailedException, + public ArrayList search(String query, Proxy proxy) throws QueryFailedException, QueryNeedsRepairException { ArrayList results = new ArrayList<>(); @@ -250,7 +275,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 +359,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); String data; try { - data = query(request); + data = query(request, proxy); } catch (HttpError httpError) { + httpError.printStackTrace(); throw new QueryFailedException("not found"); } Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data); @@ -351,38 +377,35 @@ 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); - OutputStream os = conn.getOutputStream(); - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); - writer.write(params); - writer.flush(); - writer.close(); - os.close(); + Log.e("PHILIP", "Media Type charset: "+body.contentType().charset()); - conn.connect(); + 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(); - Log.d(Constants.TAG, "response code: " + conn.getResponseCode()); - Log.d(Constants.TAG, "answer: " + readAll(conn.getInputStream(), conn.getContentEncoding())); + Response response = new OkHttpClient().setProxy(proxy).newCall(request).execute(); + + Log.d(Constants.TAG, "response code: " + response.code()); + Log.d(Constants.TAG, "answer: " + response.body().string()); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); throw new AddKeyException(); @@ -398,6 +421,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: 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..7bbe42993 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,8 +35,9 @@ public class KeybaseKeyserver extends Keyserver { private String mQuery; @Override - public ArrayList search(String query) throws QueryFailedException, + public ArrayList search(String query, Proxy proxy) throws QueryFailedException, QueryNeedsRepairException { + // TODO: implement proxy ArrayList results = new ArrayList<>(); if (query.startsWith("0x")) { @@ -98,7 +100,8 @@ public class KeybaseKeyserver extends Keyserver { } @Override - public String get(String id) throws QueryFailedException { + public String get(String id, Proxy proxy) throws QueryFailedException { + // TODO: implement proxy try { return User.keyForUsername(id); } catch (KeybaseException e) { @@ -107,7 +110,7 @@ public class KeybaseKeyserver extends Keyserver { } @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..260e2af40 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 { @@ -67,12 +68,12 @@ public abstract class Keyserver { private static final long serialVersionUID = -507574859137295530L; } - public abstract List search(String query) throws QueryFailedException, + public abstract List 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 439260b74..7c5eec15b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -46,6 +46,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; +import java.net.Proxy; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; @@ -209,7 +210,7 @@ public class CertifyOperation extends BaseOperation { if (importExportOperation != null) { // TODO use subresult, get rid of try/catch! try { - importExportOperation.uploadKeyRingToServer(keyServer, certifiedKey); + importExportOperation.uploadKeyRingToServer(keyServer, certifiedKey, proxy); uploadOk += 1; } catch (AddKeyException e) { Log.e(Constants.TAG, "error uploading key", e); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java index ffce2f39c..98db57dd3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java @@ -59,6 +59,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.net.Proxy; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -98,11 +99,11 @@ public class ImportExportOperation extends BaseOperation { super(context, providerHelper, progressable, cancelled); } - public void uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) throws AddKeyException { - uploadKeyRingToServer(server, keyring.getUncachedKeyRing()); + public void uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring, Proxy proxy) throws AddKeyException { + uploadKeyRingToServer(server, keyring.getUncachedKeyRing(), proxy); } - public void uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring) throws AddKeyException { + public void uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring, Proxy proxy) throws AddKeyException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ArmoredOutputStream aos = null; try { @@ -111,7 +112,7 @@ public class ImportExportOperation extends BaseOperation { aos.close(); String armoredKey = bos.toString("UTF-8"); - server.add(armoredKey); + server.add(armoredKey, proxy); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); throw new AddKeyException(); @@ -127,23 +128,24 @@ public class ImportExportOperation extends BaseOperation { } } - public ImportKeyResult importKeyRings(List entries, String keyServerUri) { + public ImportKeyResult importKeyRings(List entries, String keyServerUri, Proxy proxy) { Iterator it = entries.iterator(); int numEntries = entries.size(); - return importKeyRings(it, numEntries, keyServerUri); + return importKeyRings(it, numEntries, keyServerUri, proxy); } - public ImportKeyResult importKeyRings(ParcelableFileCache cache, String keyServerUri) { + public ImportKeyResult importKeyRings(ParcelableFileCache cache, String keyServerUri, + Proxy proxy) { // get entries from cached file try { IteratorWithSize it = cache.readCache(); int numEntries = it.getSize(); - return importKeyRings(it, numEntries, keyServerUri); + return importKeyRings(it, numEntries, keyServerUri, proxy); } catch (IOException e) { // Special treatment here, we need a lot @@ -165,7 +167,8 @@ public class ImportExportOperation extends BaseOperation { * @param keyServerUri contains uri of keyserver to import from, if it is an import from cloud * @return */ - public ImportKeyResult importKeyRings(Iterator entries, int num, String keyServerUri) { + public ImportKeyResult importKeyRings(Iterator entries, int num, String keyServerUri, + Proxy proxy) { updateProgress(R.string.progress_importing, 0, 100); OperationLog log = new OperationLog(); @@ -225,10 +228,10 @@ public class ImportExportOperation extends BaseOperation { // Download by fingerprint, or keyId - whichever is available 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) { @@ -251,7 +254,7 @@ public class ImportExportOperation extends BaseOperation { 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 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 bf7014853..bf139e843 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -1250,7 +1250,7 @@ public class ProviderHelper { ImportKeyResult result = new ImportExportOperation(mContext, this, new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport)) - .importKeyRings(itSecrets, numSecrets, null); + .importKeyRings(itSecrets, numSecrets, null, null); log.add(result, indent); } else { log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent); @@ -1278,7 +1278,7 @@ public class ProviderHelper { ImportKeyResult result = new ImportExportOperation(mContext, this, new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport)) - .importKeyRings(itPublics, numPublics, null); + .importKeyRings(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/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java index ba877c2a2..dfc8476da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java @@ -68,6 +68,7 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache; import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.net.Proxy; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -84,6 +85,8 @@ import de.measite.minidns.Question; import de.measite.minidns.Record; import de.measite.minidns.record.Data; import de.measite.minidns.record.TXT; +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import org.sufficientlysecure.keychain.util.Preferences; /** * This Service contains all important long lasting operations for OpenKeychain. It receives Intents with @@ -151,6 +154,9 @@ public class KeychainService extends Service implements Progressable { // consolidate public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery"; + // proxy extras + public static final String EXTRA_PARCELABLE_PROXY = "parcelable_proxy"; + Messenger mMessenger; // this attribute can possibly merged with the one above? not sure... @@ -398,6 +404,7 @@ public class KeychainService extends Service implements Progressable { break; } case ACTION_IMPORT_KEYRING: { + Proxy proxy = getProxyFromBundle(data); // Input String keyServer = data.getString(IMPORT_KEY_SERVER); @@ -405,7 +412,7 @@ public class KeychainService extends Service implements Progressable { // either keyList or cache must be null, no guarantees otherwise if (keyList == null) {// import from file, do serially - serialKeyImport(null, keyServer, providerHelper); + serialKeyImport(null, keyServer, providerHelper, proxy); } else { // 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 @@ -415,9 +422,9 @@ public class KeychainService extends Service implements Progressable { } if (keyFingerprintSet.size() == keyList.size()) { // all keys have unique fingerprints - multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer); + multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer, proxy); } else { - serialKeyImport(keyList, keyServer, providerHelper); + serialKeyImport(keyList, keyServer, providerHelper, proxy); } } @@ -438,7 +445,7 @@ public class KeychainService extends Service implements Progressable { providerHelper, mKeychainService); try { - importExportOperation.uploadKeyRingToServer(server, keyring); + importExportOperation.uploadKeyRingToServer(server, keyring, getProxyFromBundle(data)); } catch (Keyserver.AddKeyException e) { throw new PgpGeneralException("Unable to export key to selected server"); } @@ -463,6 +470,11 @@ public class KeychainService extends Service implements Progressable { return START_NOT_STICKY; } + private final Proxy getProxyFromBundle(Bundle data) { + ParcelableProxy parcelableProxy = data.getParcelable(EXTRA_PARCELABLE_PROXY); + return parcelableProxy==null?null:parcelableProxy.getProxy(); + } + private void sendProofError(List log, String label) { String msg = null; label = (label == null) ? "" : label + ": "; @@ -565,7 +577,7 @@ public class KeychainService extends Service implements Progressable { } public void serialKeyImport(ArrayList keyList, final String keyServer, - ProviderHelper providerHelper) { + ProviderHelper providerHelper, Proxy proxy) { Log.d(Constants.TAG, "serial key import starting"); ParcelableFileCache cache = new ParcelableFileCache<>(mKeychainService, "key_import.pcl"); @@ -576,8 +588,8 @@ public class KeychainService extends Service implements Progressable { mActionCanceled); // Either list or cache must be null, no guarantees otherwise. ImportKeyResult result = keyList != null - ? importExportOperation.importKeyRings(keyList, keyServer) - : importExportOperation.importKeyRings(cache, keyServer); + ? importExportOperation.importKeyRings(keyList, keyServer, proxy) + : importExportOperation.importKeyRings(cache, keyServer, proxy); ContactSyncAdapterService.requestSync(); // Result @@ -587,7 +599,7 @@ public class KeychainService extends Service implements Progressable { } public void multiThreadedKeyImport(Iterator keyListIterator, int totKeys, final String - keyServer) { + keyServer, final Proxy proxy) { Log.d(Constants.TAG, "Multi-threaded key import starting"); if (keyListIterator != null) { mKeyImportAccumulator = new KeyImportAccumulator(totKeys, mKeychainService); @@ -618,7 +630,7 @@ public class KeychainService extends Service implements Progressable { list.add(pkRing); result = importExportOperation.importKeyRings(list, - keyServer); + keyServer, proxy); } finally { // in the off-chance that importKeyRings does something to crash the // thread before it can call singleKeyRingImportCompleted, our imported diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java index 2ab8c5967..09996b8c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java @@ -169,8 +169,9 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe } public void refreshSearch() { + // TODO: PHILIP implement proxy in YubiKey parts mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mNfcFingerprint, - Preferences.getPreferences(getActivity()).getCloudSearchPrefs())); + Preferences.getPreferences(getActivity()).getCloudSearchPrefs()), null); } public void importKey() { 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 07ab88b02..a456b09ad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -28,6 +28,7 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import info.guardianproject.onionkit.ui.OrbotHelper; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; @@ -43,6 +44,7 @@ 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 java.io.IOException; import java.util.ArrayList; @@ -82,10 +84,14 @@ public class ImportKeysActivity extends BaseNfcActivity { private Fragment mTopFragment; private View mImportButton; + 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 @@ -218,7 +224,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); @@ -341,7 +347,29 @@ public class ImportKeysActivity extends BaseNfcActivity { } public void loadCallback(ImportKeysListFragment.LoaderState loaderState) { - mListFragment.loadNew(loaderState); + if (loaderState instanceof ImportKeysListFragment.CloudLoaderState) { + // do the tor check + OrbotHelper helper = new OrbotHelper(this); + // TODO: Add callbacks by modifying OrbotHelper so we know if the user wants to not use Tor + + if(mProxyPrefs.torEnabled && !helper.isOrbotInstalled()) { + helper.promptToInstall(this); + return; + } + if(mProxyPrefs.torEnabled && !helper.isOrbotRunning()) { + helper.requestOrbotStart(this); + return; + } + } + + mListFragment.loadNew(loaderState, mProxyPrefs.proxy); + } + + /** + * disables use of Tor as proxy for this session + */ + private void disableTorForSession() { + mProxyPrefs = new Preferences.ProxyPrefs(false, false, null); } 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..a5f661fb7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -47,6 +47,7 @@ import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.Proxy; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -64,6 +65,7 @@ public class ImportKeysListFragment extends ListFragment implements private ImportKeysAdapter mAdapter; private LoaderState mLoaderState; + private Proxy 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 @@ -183,6 +186,7 @@ public class ImportKeysListFragment extends ListFragment implements static public class CloudLoaderState extends LoaderState { Preferences.CloudSearchPrefs mCloudPrefs; String mServerQuery; + Proxy proxy; CloudLoaderState(String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) { mServerQuery = serverQuery; @@ -258,7 +262,9 @@ public class ImportKeysListFragment extends ListFragment implements mAdapter.notifyDataSetChanged(); } - public void loadNew(LoaderState loaderState) { + public void loadNew(LoaderState loaderState, Proxy proxy) { + mProxy = proxy; + mLoaderState = loaderState; restartLoaders(); @@ -301,7 +307,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/adapter/ImportKeysListCloudLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java index af919f3b6..05d5a19ee 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 @@ -29,6 +29,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; +import java.net.Proxy; import java.util.ArrayList; public class ImportKeysListCloudLoader @@ -38,15 +39,18 @@ public class ImportKeysListCloudLoader Preferences.CloudSearchPrefs mCloudPrefs; String mServerQuery; + private Proxy mProxy; private ArrayList mEntryList = new ArrayList<>(); private AsyncTaskResultWrapper> mEntryListWrapper; - public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) { + public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs, + Proxy proxy) { super(context); mContext = context; mServerQuery = serverQuery; mCloudPrefs = cloudPrefs; + mProxy = proxy; } @Override @@ -97,7 +101,7 @@ public class ImportKeysListCloudLoader private void queryServer(boolean enforceFingerprint) { try { ArrayList searchResult - = CloudSearch.search(mServerQuery, mCloudPrefs); + = CloudSearch.search(mServerQuery, mCloudPrefs, mProxy); mEntryList.clear(); // add result to data 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 bbc08a2aa..f08cba4e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java @@ -28,6 +28,7 @@ import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.service.KeychainService; +import java.net.Proxy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -36,15 +37,15 @@ import java.util.Set; public class EmailKeyHelper { - public static void importContacts(Context context, Messenger messenger) { - importAll(context, messenger, ContactHelper.getContactMails(context)); + public static void importContacts(Context context, Messenger messenger, Proxy proxy) { + importAll(context, messenger, ContactHelper.getContactMails(context), proxy); } - public static void importAll(Context context, Messenger messenger, List mails) { + public static void importAll(Context context, Messenger messenger, List mails, Proxy proxy) { // Collect all candidates as ImportKeysListEntry (set for deduplication) Set 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 @@ -55,7 +56,7 @@ public class EmailKeyHelper { importKeys(context, messenger, keys); } - public static Set getEmailKeys(Context context, String mail) { + public static Set getEmailKeys(Context context, String mail, Proxy proxy) { Set keys = new HashSet<>(); // Try _hkp._tcp SRV record first @@ -63,7 +64,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)); } } @@ -72,7 +73,7 @@ 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; @@ -89,10 +90,10 @@ public class EmailKeyHelper { context.startService(importIntent); } - public static List getEmailKeys(String mail, Keyserver keyServer) { + public static List getEmailKeys(String mail, Keyserver keyServer, Proxy proxy) { Set 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..a24141a69 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.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 int mProxyType; + + private final int TYPE_HTTP = 1; + private final int TYPE_SOCKS = 2; + + public ParcelableProxy(Proxy proxy) { + InetSocketAddress address = (InetSocketAddress) proxy.address(); + + mProxyHost = address.getHostName(); + mProxyPort = address.getPort(); + + switch (proxy.type()) { + case HTTP: { + mProxyType = TYPE_HTTP; + break; + } + case SOCKS: { + mProxyType = TYPE_SOCKS; + break; + } + } + } + + public Proxy getProxy() { + Proxy.Type type = null; + switch (mProxyType) { + case TYPE_HTTP: + type = Proxy.Type.HTTP; + break; + case TYPE_SOCKS: + type = Proxy.Type.SOCKS; + break; + } + return new Proxy(type, new InetSocketAddress(mProxyHost, mProxyPort)); + } + + protected ParcelableProxy(Parcel in) { + mProxyHost = in.readString(); + mProxyPort = in.readInt(); + mProxyType = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mProxyHost); + dest.writeInt(mProxyPort); + dest.writeInt(mProxyType); + } + + @SuppressWarnings("unused") + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @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 4cd361bcb..4aaf8b17b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -302,7 +302,8 @@ public class Preferences { boolean useNormalProxy = getUseNormalProxy(); if (useTor) { - proxy = Constants.Orbot.PROXY; + proxy = new Proxy(Constants.Orbot.PROXY_TYPE, + new InetSocketAddress(Constants.Orbot.PROXY_HOST, Constants.Orbot.PROXY_PORT)); } else if (useNormalProxy) { proxy = new Proxy(getProxyType(), new InetSocketAddress(getProxyHost(), getProxyPort())); 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..b116524ef 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,8 @@ package org.sufficientlysecure.keychain.util; import android.content.res.AssetManager; +import com.squareup.okhttp.CertificatePinner; +import com.squareup.okhttp.OkHttpClient; import org.sufficientlysecure.keychain.Constants; import java.io.ByteArrayInputStream; @@ -85,6 +87,31 @@ public class TlsHelper { return url.openConnection(); } + public static void pinCertificateIfNecessary(OkHttpClient client, URL url) throws TlsHelperException { + if (url.getProtocol().equals("https")) { + for (String domain : sStaticCA.keySet()) { + if (url.getHost().endsWith(domain)) { + pinCertificate(sStaticCA.get(domain), domain, client); + } + } + } + } + + public static void pinCertificate(byte[] certificate, String hostName, OkHttpClient client) + throws TlsHelperException { + try { + // Load CA + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate ca = cf.generateCertificate(new ByteArrayInputStream(certificate)); + String pin = CertificatePinner.pin(ca); + Log.e("PHILIP", "" + ca.getPublicKey() + ":" + pin); + + client.setCertificatePinner(new CertificatePinner.Builder().add(hostName, pin).build()); + } catch (CertificateException e) { + throw new TlsHelperException(e); + } + } + /** * Opens a Connection that will only accept certificates signed with a specific CA and skips common name check. * This is required for some distributed Keyserver networks like sks-keyservers.net