Merge branch 'master' of github.com:open-keychain/open-keychain

This commit is contained in:
Dominik Schürmann
2015-07-06 11:27:57 +02:00
75 changed files with 2270 additions and 430 deletions

View File

@@ -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 {
@@ -44,6 +46,8 @@ public final class Constants {
public static final String PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
public static final String TEMPSTORAGE_AUTHORITY = BuildConfig.APPLICATION_ID + ".tempstorage";
public static final String CLIPBOARD_LABEL = "Keychain";
// as defined in http://tools.ietf.org/html/rfc3156, section 7
public static final String NFC_MIME = "application/pgp-keys";
@@ -93,6 +97,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 {

View File

@@ -17,28 +17,22 @@
package org.sufficientlysecure.keychain.compatibility;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.support.annotation.Nullable;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;
import java.lang.reflect.Method;
public class ClipboardReflection {
private static final String clipboardLabel = "Keychain";
public static void copyToClipboard(Context context, String text) {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(clipboardLabel, text);
clipboard.setPrimaryClip(clip);
}
public static String getClipboardText(Context context) {
@Nullable
public static String getClipboardText(@Nullable Context context) {
if (context == null) {
return null;
}
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = clipboard.getPrimaryClip();

View File

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

View File

@@ -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);
OutputStream os = conn.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
writer.write(params);
writer.flush();
writer.close();
os.close();
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();
conn.connect();
Response response = getClient(url, proxy).newCall(request).execute();
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 {

View File

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

View File

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

View File

@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface;
@@ -76,9 +77,8 @@ public abstract class BaseOperation <T extends Parcelable> implements Passphrase
mCancelled = cancelled;
}
public OperationResult execute(T input, CryptoInputParcel cryptoInput) {
return null;
}
@NonNull
public abstract OperationResult execute(T input, CryptoInputParcel cryptoInput);
public void updateProgress(int message, int current, int total) {
if (mProgressable != null) {

View File

@@ -18,11 +18,11 @@
package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
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;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
@@ -43,27 +43,31 @@ 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.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);
}
@NonNull
@Override
public CertifyResult execute(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput) {
@@ -174,7 +178,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 +191,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 +217,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 +228,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 +248,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);
}
}

View File

@@ -20,6 +20,7 @@
package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
@@ -34,6 +35,7 @@ public class ConsolidateOperation extends BaseOperation<ConsolidateInputParcel>
super(context, providerHelper, progressable);
}
@NonNull
@Override
public ConsolidateResult execute(ConsolidateInputParcel consolidateInputParcel,
CryptoInputParcel cryptoInputParcel) {

View File

@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
@@ -45,6 +46,7 @@ public class DeleteOperation extends BaseOperation<DeleteKeyringParcel> {
super(context, providerHelper, progressable);
}
@NonNull
@Override
public DeleteResult execute(DeleteKeyringParcel deleteKeyringParcel,
CryptoInputParcel cryptoInputParcel) {

View File

@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
@@ -57,6 +58,7 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
super(context, providerHelper, progressable, cancelled);
}
@NonNull
public OperationResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();

View File

@@ -21,12 +21,14 @@ package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import org.spongycastle.bcpg.ArmoredOutputStream;
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 +42,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 +56,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 +68,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 +81,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) {
@@ -311,20 +330,32 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
}
@NonNull
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: {
@@ -335,8 +366,8 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
return exportToUri(exportInput.mMasterKeyIds, exportInput.mExportSecret,
exportInput.mOutputUri);
}
default: { // can't happen
return null;
default: { // can never happen, all enum types must be handled above
throw new AssertionError("must not happen, this is a bug!");
}
}
}

View File

@@ -18,7 +18,24 @@
package org.sufficientlysecure.keychain.operations;
import java.io.IOException;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -39,24 +56,13 @@ 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 java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
/**
* An operation class which implements high level import
@@ -89,39 +95,40 @@ 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);
}
@NonNull
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
@@ -143,10 +150,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
* @param keyServerUri contains uri of keyserver to import from, if it is an import from cloud
* @param progressable Allows multi-threaded import to supply a progressable that ignores the
* progress of a single key being imported
* @return
*/
@NonNull
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 +216,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 +243,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
@@ -373,12 +382,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
importedMasterKeyIdsArray);
}
@NonNull
@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 +394,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 +418,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);
}
}
@@ -407,8 +429,10 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
return result;
}
@NonNull
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);
@@ -421,7 +445,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
new SynchronousQueue<Runnable>());
ExecutorCompletionService<ImportKeyResult> importCompletionService =
new ExecutorCompletionService(importExecutor);
new ExecutorCompletionService<>(importExecutor);
while (keyListIterator.hasNext()) { // submit all key rings to be imported
@@ -436,7 +460,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 +586,4 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
}
}
}
}

View File

@@ -19,7 +19,15 @@
package org.sufficientlysecure.keychain.operations;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.support.annotation.NonNull;
import com.textuality.keybase.lib.Proof;
import com.textuality.keybase.lib.prover.Prover;
@@ -41,11 +49,9 @@ 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 java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificationParcel> {
@@ -54,9 +60,22 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat
super(context, providerHelper, progressable);
}
@NonNull
@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 +95,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);
}
@@ -96,7 +115,7 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
}
Record[] records = dnsQuery.getAnswers();
List<List<byte[]>> extents = new ArrayList<List<byte[]>>();
List<List<byte[]>> extents = new ArrayList<>();
for (Record r : records) {
Data d = r.getPayload();
if (d instanceof TXT) {

View File

@@ -17,7 +17,11 @@
package org.sufficientlysecure.keychain.operations;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
@@ -36,8 +40,6 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.util.concurrent.atomic.AtomicBoolean;
/** An operation which promotes a public key ring to a secret one.
*
* This operation can only be applied to public key rings where no secret key
@@ -52,18 +54,10 @@ public class PromoteKeyOperation extends BaseOperation<PromoteKeyringParcel> {
super(context, providerHelper, progressable, cancelled);
}
@NonNull
@Override
public PromoteKeyResult execute(PromoteKeyringParcel promoteKeyringParcel,
CryptoInputParcel cryptoInputParcel) {
// Input
long masterKeyId = promoteKeyringParcel.mKeyRingId;
byte[] cardAid = promoteKeyringParcel.mCardAid;
long[] subKeyIds = promoteKeyringParcel.mSubKeyIds;
return execute(masterKeyId, cardAid, subKeyIds);
}
public PromoteKeyResult execute(long masterKeyId, byte[] cardAid, long[] subKeyIds) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_PR, 0);
@@ -74,17 +68,17 @@ public class PromoteKeyOperation extends BaseOperation<PromoteKeyringParcel> {
try {
log.add(LogType.MSG_PR_FETCHING, 1,
KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
KeyFormattingUtils.convertKeyIdToHex(promoteKeyringParcel.mKeyRingId));
CanonicalizedPublicKeyRing pubRing =
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
mProviderHelper.getCanonicalizedPublicKeyRing(promoteKeyringParcel.mKeyRingId);
if (subKeyIds == null) {
if (promoteKeyringParcel.mSubKeyIds == null) {
log.add(LogType.MSG_PR_ALL, 1);
} else {
// sort for binary search
for (CanonicalizedPublicKey key : pubRing.publicKeyIterator()) {
long subKeyId = key.getKeyId();
if (naiveIndexOf(subKeyIds, subKeyId) != null) {
if (naiveIndexOf(promoteKeyringParcel.mSubKeyIds, subKeyId) != null) {
log.add(LogType.MSG_PR_SUBKEY_MATCH, 1,
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
} else {
@@ -95,7 +89,8 @@ public class PromoteKeyOperation extends BaseOperation<PromoteKeyringParcel> {
}
// create divert-to-card secret key from public key
promotedRing = pubRing.createDivertSecretRing(cardAid, subKeyIds);
promotedRing = pubRing.createDivertSecretRing(promoteKeyringParcel.mCardAid,
promoteKeyringParcel.mSubKeyIds);
} catch (NotFoundException e) {
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);

View File

@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
@@ -36,6 +37,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;
@@ -61,6 +63,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
super(context, providerHelper, progressable, cancelled);
}
@NonNull
public SignEncryptResult execute(SignEncryptParcel input, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();
@@ -84,7 +87,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);
}
}

View File

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

View File

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

View File

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

View File

@@ -609,6 +609,7 @@ public abstract class OperationResult implements Parcelable {
MSG_DC_ERROR_INTEGRITY_MISSING (LogLevel.ERROR, R.string.msg_dc_error_integrity_missing),
MSG_DC_ERROR_INVALID_DATA (LogLevel.ERROR, R.string.msg_dc_error_invalid_data),
MSG_DC_ERROR_IO (LogLevel.ERROR, R.string.msg_dc_error_io),
MSG_DC_ERROR_INPUT (LogLevel.ERROR, R.string.msg_dc_error_input),
MSG_DC_ERROR_NO_DATA (LogLevel.ERROR, R.string.msg_dc_error_no_data),
MSG_DC_ERROR_NO_KEY (LogLevel.ERROR, R.string.msg_dc_error_no_key),
MSG_DC_ERROR_PGP_EXCEPTION (LogLevel.ERROR, R.string.msg_dc_error_pgp_exception),
@@ -692,6 +693,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 +714,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 +726,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),

View File

@@ -27,6 +27,9 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/** A generic wrapped PGPKeyRing object.
*
@@ -91,6 +94,16 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
return getRing().getPublicKey().isEncryptionKey();
}
public Set<Long> getEncryptIds() {
HashSet<Long> result = new HashSet<>();
for(CanonicalizedPublicKey key : publicKeyIterator()) {
if (key.canEncrypt() && key.isValid()) {
result.add(key.getKeyId());
}
}
return result;
}
public long getEncryptId() throws PgpKeyNotFoundException {
for(CanonicalizedPublicKey key : publicKeyIterator()) {
if (key.canEncrypt() && key.isValid()) {

View File

@@ -62,19 +62,6 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
return mRing;
}
/** Getter that returns the subkey that should be used for signing. */
CanonicalizedPublicKey getEncryptionSubKey() throws PgpKeyNotFoundException {
PGPPublicKey key = getRing().getPublicKey(getEncryptId());
if(key != null) {
CanonicalizedPublicKey cKey = new CanonicalizedPublicKey(this, key);
if(!cKey.canEncrypt()) {
throw new PgpKeyNotFoundException("key error");
}
return cKey;
}
throw new PgpKeyNotFoundException("no encryption key available");
}
public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() {
@SuppressWarnings("unchecked")
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();

View File

@@ -19,6 +19,7 @@
package org.sufficientlysecure.keychain.pgp;
import android.content.Context;
import android.support.annotation.NonNull;
import android.webkit.MimeTypeMap;
import org.openintents.openpgp.OpenPgpMetadata;
@@ -80,9 +81,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
super(context, providerHelper, progressable);
}
/**
* Decrypts and/or verifies data based on parameters of class
*/
/** Decrypts and/or verifies data based on parameters of PgpDecryptVerifyInputParcel. */
@NonNull
public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput) {
InputData inputData;
OutputStream outputStream;
@@ -96,8 +96,10 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
long inputSize = FileHelper.getFileSize(mContext, input.getInputUri(), 0);
inputData = new InputData(inputStream, inputSize);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
Log.e(Constants.TAG, "Input URI could not be opened: " + input.getInputUri(), e);
OperationLog log = new OperationLog();
log.add(LogType.MSG_DC_ERROR_INPUT, 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
}
@@ -107,8 +109,10 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
try {
outputStream = mContext.getContentResolver().openOutputStream(input.getOutputUri());
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
Log.e(Constants.TAG, "Output URI could not be opened: " + input.getOutputUri(), e);
OperationLog log = new OperationLog();
log.add(LogType.MSG_DC_ERROR_IO, 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
}
@@ -122,11 +126,13 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
}
@NonNull
public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputData inputData, OutputStream outputStream) {
return executeInternal(input, cryptoInput, inputData, outputStream);
}
@NonNull
private DecryptVerifyResult executeInternal(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputData inputData, OutputStream outputStream) {
try {
@@ -161,6 +167,13 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
OperationLog log = new OperationLog();
log.add(LogType.MSG_DC_ERROR_PGP_EXCEPTION, 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
} catch (ArrayIndexOutOfBoundsException e) {
// these can happen if assumptions in JcaPGPObjectFactory.nextObject() aren't
// fulfilled, so we need to catch them here to handle this gracefully
Log.d(Constants.TAG, "array index out of bounds", e);
OperationLog log = new OperationLog();
log.add(LogType.MSG_DC_ERROR_IO, 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
} catch (IOException e) {
Log.d(Constants.TAG, "IOException", e);
OperationLog log = new OperationLog();
@@ -169,9 +182,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
}
}
/**
* Verify Keybase.io style signed literal data
*/
/**Verify signed plaintext data (PGP/INLINE). */
@NonNull
private DecryptVerifyResult verifySignedLiteralData(
PgpDecryptVerifyInputParcel input, InputStream in, OutputStream out, int indent)
throws IOException, PGPException {
@@ -301,9 +313,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
}
/**
* Decrypt and/or verifies binary or ascii armored pgp
*/
/** Decrypt and/or verify binary or ascii armored pgp data. */
@NonNull
private DecryptVerifyResult decryptVerify(
PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputStream in, OutputStream out, int indent) throws IOException, PGPException {
@@ -843,6 +854,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
* The method is heavily based on
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
*/
@NonNull
private DecryptVerifyResult verifyCleartextSignature(
ArmoredInputStream aIn, OutputStream outputStream, int indent) throws IOException, PGPException {
@@ -950,6 +962,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
return result;
}
@NonNull
private DecryptVerifyResult verifyDetachedSignature(
PgpDecryptVerifyInputParcel input, InputData inputData, OutputStream out, int indent)
throws IOException, PGPException {
@@ -1042,7 +1055,9 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
return result;
}
private PGPSignature processPGPSignatureList(PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder) throws PGPException {
private PGPSignature processPGPSignatureList(
PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder)
throws PGPException {
CanonicalizedPublicKeyRing signingRing = null;
CanonicalizedPublicKey signingKey = null;
int signatureIndex = -1;

View File

@@ -20,6 +20,8 @@
package org.sufficientlysecure.keychain.pgp;
import android.content.Context;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.BCPGOutputStream;
@@ -36,6 +38,7 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
@@ -63,6 +66,7 @@ import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -99,6 +103,13 @@ public class PgpSignEncryptOperation extends BaseOperation {
super(context, providerHelper, progressable);
}
@NonNull
@Override
// TODO this is horrible, refactor ASAP!!
public OperationResult execute(Parcelable input, CryptoInputParcel cryptoInput) {
return null;
}
/**
* Signs and/or encrypts data based on parameters of class
*/
@@ -263,15 +274,19 @@ public class PgpSignEncryptOperation extends BaseOperation {
try {
CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing(
KeyRings.buildUnifiedKeyRingUri(id));
CanonicalizedPublicKey key = keyRing.getEncryptionSubKey();
cPk.addMethod(key.getPubKeyEncryptionGenerator(input.isHiddenRecipients()));
log.add(LogType.MSG_PSE_KEY_OK, indent + 1,
KeyFormattingUtils.convertKeyIdToHex(id));
} catch (PgpKeyNotFoundException e) {
log.add(LogType.MSG_PSE_KEY_WARN, indent + 1,
KeyFormattingUtils.convertKeyIdToHex(id));
if (input.isFailOnMissingEncryptionKeyIds()) {
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
Set<Long> encryptSubKeyIds = keyRing.getEncryptIds();
for (Long subKeyId : encryptSubKeyIds) {
CanonicalizedPublicKey key = keyRing.getPublicKey(subKeyId);
cPk.addMethod(key.getPubKeyEncryptionGenerator(input.isHiddenRecipients()));
log.add(LogType.MSG_PSE_KEY_OK, indent + 1,
KeyFormattingUtils.convertKeyIdToHex(id));
}
if (encryptSubKeyIds.isEmpty()) {
log.add(LogType.MSG_PSE_KEY_WARN, indent + 1,
KeyFormattingUtils.convertKeyIdToHex(id));
if (input.isFailOnMissingEncryptionKeyIds()) {
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
}
}
} catch (ProviderHelper.NotFoundException e) {
log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1,

View File

@@ -26,6 +26,7 @@ import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.v4.util.LongSparseArray;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
@@ -725,7 +726,7 @@ public class ProviderHelper {
LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<>();
@Override
public int compareTo(UserPacketItem o) {
public int compareTo(@NonNull UserPacketItem o) {
// revoked keys always come last!
//noinspection DoubleNegation
if ((selfRevocation != null) != (o.selfRevocation != null)) {
@@ -906,7 +907,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 +1033,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);
}
@@ -1057,6 +1060,7 @@ public class ProviderHelper {
}
@NonNull
public ConsolidateResult consolidateDatabaseStep1(Progressable progress) {
OperationLog log = new OperationLog();
@@ -1082,7 +1086,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);
@@ -1124,6 +1128,7 @@ public class ProviderHelper {
}
});
cursor.close();
} catch (IOException e) {
Log.e(Constants.TAG, "error saving secret", e);
@@ -1143,7 +1148,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);
@@ -1185,6 +1190,7 @@ public class ProviderHelper {
}
});
cursor.close();
} catch (IOException e) {
Log.e(Constants.TAG, "error saving public", e);
@@ -1200,12 +1206,14 @@ public class ProviderHelper {
return consolidateDatabaseStep2(log, indent, progress, false);
}
@NonNull
public ConsolidateResult consolidateDatabaseStep2(Progressable progress) {
return consolidateDatabaseStep2(new OperationLog(), 0, progress, true);
}
private static boolean mConsolidateCritical = false;
@NonNull
private ConsolidateResult consolidateDatabaseStep2(
OperationLog log, int indent, Progressable progress, boolean recovery) {
@@ -1250,7 +1258,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 +1286,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);

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,6 +23,7 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -385,14 +386,18 @@ public class CreateKeyFinalFragment extends Fragment {
}
public void handleResult(ExportResult result) {
// TODO: ExportOperation UPLOAD_KEYSERVER needs logs!
// TODO: then merge logs here!
//saveKeyResult.getLog().add(result, 0);
Activity activity = getActivity();
if (activity == null) {
return;
}
saveKeyResult.getLog().add(result, 0);
Intent data = new Intent();
data.putExtra(OperationResult.EXTRA_RESULT, saveKeyResult);
getActivity().setResult(Activity.RESULT_OK, data);
getActivity().finish();
activity.setResult(Activity.RESULT_OK, data);
activity.finish();
}
@Override

View File

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

View File

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

View File

@@ -304,7 +304,7 @@ public class DecryptListFragment
}
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setType(type);
intent.setDataAndType(outputUri, type);
final List<ResolveInfo> matches =
context.getPackageManager().queryIntentActivities(intent, 0);

View File

@@ -17,6 +17,10 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -29,9 +33,9 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.ShareHelper;
public class DisplayTextFragment extends DecryptFragment {
@@ -80,8 +84,19 @@ public class DisplayTextFragment extends DecryptFragment {
}
private void copyToClipboard(String text) {
ClipboardReflection.copyToClipboard(getActivity(), text);
Notify.create(getActivity(), R.string.text_copied_to_clipboard, Notify.Style.OK).show();
Activity activity = getActivity();
if (activity == null) {
return;
}
ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipMan == null) {
Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show();
return;
}
clipMan.setPrimaryClip(ClipData.newPlainText(Constants.CLIPBOARD_LABEL, text));
Notify.create(activity, R.string.text_copied_to_clipboard, Style.OK).show();
}
@Override

View File

@@ -124,8 +124,7 @@ public class EncryptDecryptOverviewFragment extends Fragment {
super.onResume();
// get text from clipboard
final CharSequence clipboardText =
ClipboardReflection.getClipboardText(getActivity());
final CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
// if it's null, nothing to do here /o/
if (clipboardText == null) {

View File

@@ -35,6 +35,7 @@ import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -388,6 +389,12 @@ public class EncryptFilesFragment
@Override
public void onCryptoOperationSuccess(final SignEncryptResult result) {
FragmentActivity activity = getActivity();
if (activity == null) {
// it's gone, there's nothing we can do here
return;
}
if (mDeleteAfterEncrypt) {
// TODO make behavior coherent here
DeleteFileDialogFragment deleteFileDialog =
@@ -400,13 +407,18 @@ public class EncryptFilesFragment
// Share encrypted message/file
startActivity(sendWithChooserExcludingEncrypt());
} else {
Activity activity = getActivity();
if (activity == null) {
// it's gone, there's nothing we can do here
return;
}
// Save encrypted file
result.createNotify(getActivity()).show();
result.createNotify(activity).show();
}
}
});
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
deleteFileDialog.show(activity.getSupportFragmentManager(), "deleteDialog");
} else {
switch (mAfterEncryptAction) {
@@ -417,25 +429,24 @@ public class EncryptFilesFragment
break;
case COPY:
Activity activity = getActivity();
if (activity == null) {
// it's gone, there's nothing we can do here
return;
}
ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipMan == null) {
Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show();
break;
}
ClipData clip = new ClipData(getString(R.string.label_clip_title),
// make available as application/pgp-encrypted
new String[] { "text/plain" },
new ClipData.Item(mOutputUris.get(0))
);
clipMan.setPrimaryClip(clip);
result.createNotify(getActivity()).show();
result.createNotify(activity).show();
break;
case SAVE:
// Encrypted file was saved already, just show notification
result.createNotify(getActivity()).show();
result.createNotify(activity).show();
break;
}
}

View File

@@ -18,6 +18,9 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
@@ -33,7 +36,6 @@ import android.widget.TextView;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.PgpConstants;
@@ -265,8 +267,21 @@ public class EncryptTextFragment
return data;
}
private void copyToClipboard(byte[] resultBytes) {
ClipboardReflection.copyToClipboard(getActivity(), new String(resultBytes));
private void copyToClipboard(SignEncryptResult result) {
Activity activity = getActivity();
if (activity == null) {
return;
}
ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipMan == null) {
Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show();
return;
}
ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, new String(result.getResultBytes()));
clipMan.setPrimaryClip(clip);
result.createNotify(activity).show();
}
/**
@@ -323,11 +338,7 @@ public class EncryptTextFragment
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
} else {
// Copy to clipboard
copyToClipboard(result.getResultBytes());
result.createNotify(getActivity()).show();
// Notify.create(EncryptTextActivity.this,
// R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
// .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
copyToClipboard(result);
}
}

View File

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

View File

@@ -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:

View File

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

View File

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

View File

@@ -128,6 +128,11 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
getSupportFragmentManager().addOnBackStackChangedListener(this);
// all further initialization steps are saved as instance state
if (savedInstanceState != null) {
return;
}
Intent data = getIntent();
// If we got an EXTRA_RESULT in the intent, show the notification
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
@@ -135,20 +140,18 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
result.createNotify(this).show();
}
if (savedInstanceState == null) {
// always initialize keys fragment to the bottom of the backstack
onKeysSelected();
// always initialize keys fragment to the bottom of the backstack
onKeysSelected();
if (data != null && data.hasExtra(EXTRA_INIT_FRAG)) {
// initialize FragmentLayout with KeyListFragment at first
switch (data.getIntExtra(EXTRA_INIT_FRAG, -1)) {
case ID_ENCRYPT_DECRYPT:
onEnDecryptSelected();
break;
case ID_APPS:
onAppsSelected();
break;
}
if (data != null && data.hasExtra(EXTRA_INIT_FRAG)) {
// initialize FragmentLayout with KeyListFragment at first
switch (data.getIntExtra(EXTRA_INIT_FRAG, -1)) {
case ID_ENCRYPT_DECRYPT:
onEnDecryptSelected();
break;
case ID_APPS:
onAppsSelected();
break;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,9 @@ import java.io.OutputStreamWriter;
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
@@ -49,7 +52,6 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
@@ -58,6 +60,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.NfcHelper;
@@ -197,7 +200,15 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
if (toClipboard) {
ClipboardReflection.copyToClipboard(activity, content);
ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipMan == null) {
Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR);
return;
}
ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, content);
clipMan.setPrimaryClip(clip);
Notify.create(activity, fingerprintOnly ? R.string.fingerprint_copied_to_clipboard
: R.string.key_copied_to_clipboard, Notify.Style.OK).show();
return;

View File

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

View File

@@ -210,7 +210,7 @@ public class ViewKeyYubiKeyFragment
}
@Override
protected void onCryptoOperationResult(PromoteKeyResult result) {
public void onCryptoOperationSuccess(PromoteKeyResult result) {
result.createNotify(getActivity()).show();
}
}

View File

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

View File

@@ -31,8 +31,12 @@ public abstract class CachingCryptoOperationFragment <T extends Parcelable, S ex
}
@Override
protected void onCryptoOperationResult(S result) {
super.onCryptoOperationResult(result);
public void onCryptoOperationSuccess(S result) {
mCachedActionsParcel = null;
}
@Override
public void onCryptoOperationError(S result) {
mCachedActionsParcel = null;
}

View File

@@ -20,56 +20,88 @@ package org.sufficientlysecure.keychain.ui.base;
import android.content.Intent;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
/**
* All fragments executing crypto operations need to extend this class.
/** This is a base class for fragments which implement a cryptoOperation.
*
* Subclasses of this class can call the cryptoOperation method to execute an
* operation in KeychainService which takes a parcelable of type T as its input
* and returns an OperationResult of type S as a result.
*
* The input (of type T) is not given directly to the cryptoOperation method,
* but must be provided by the overriden createOperationInput method to be
* available upon request during execution of the cryptoOperation.
*
* After running cryptoOperation, one of the onCryptoOperation*() methods will
* be called, depending on the success status of the operation. The subclass
* must override at least onCryptoOperationSuccess to proceed after a
* successful operation.
*
* @see KeychainService
*
*/
public abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult>
extends Fragment implements CryptoOperationHelper.Callback<T, S> {
private CryptoOperationHelper<T, S> mOperationHelper;
final private CryptoOperationHelper<T, S> mOperationHelper;
public CryptoOperationFragment() {
mOperationHelper = new CryptoOperationHelper<>(this, this);
}
public void setProgressMessageResource(int id) {
mOperationHelper.setProgressMessageResource(id);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mOperationHelper.handleActivityResult(requestCode, resultCode, data);
}
@Override
public abstract T createOperationInput();
/** Starts execution of the cryptographic operation.
*
* During this process, the createOperationInput() method will be called,
* this input will be handed to KeychainService, where it is executed in
* the appropriate *Operation class. If the result is a PendingInputResult,
* it is handled accordingly. Otherwise, it is returned in one of the
* onCryptoOperation* callbacks.
*/
protected void cryptoOperation() {
cryptoOperation(new CryptoInputParcel());
mOperationHelper.cryptoOperation();
}
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
cryptoOperation(cryptoInput, true);
mOperationHelper.cryptoOperation(cryptoInput);
}
protected void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) {
mOperationHelper.cryptoOperation(cryptoInput, showProgress);
}
@Override @Nullable
/** Creates input for the crypto operation. Called internally after the
* crypto operation is started by a call to cryptoOperation(). Silently
* cancels operation if this method returns null. */
public abstract T createOperationInput();
/** Returns false, indicating that we did not handle progress ourselves. */
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return false;
}
public void setProgressMessageResource(int id) {
mOperationHelper.setProgressMessageResource(id);
}
@Override
/** Called when the cryptoOperation() was successful. No default behavior
* here, this should always be implemented by a subclass! */
abstract public void onCryptoOperationSuccess(S result);
@Override
public void onCryptoOperationError(S result) {
onCryptoOperationResult(result);
result.createNotify(getActivity()).show();
}
@@ -77,19 +109,4 @@ public abstract class CryptoOperationFragment<T extends Parcelable, S extends Op
public void onCryptoOperationCancelled() {
}
@Override
public void onCryptoOperationSuccess(S result) {
onCryptoOperationResult(result);
}
/**
*
* 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) {
}
}

View File

@@ -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
@@ -78,8 +84,6 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
/**
* If OperationHelper is being integrated into an activity
*
* @param activity
*/
public CryptoOperationHelper(FragmentActivity activity, Callback<T, S> callback, int progressMessageString) {
mActivity = activity;
@@ -90,8 +94,6 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
/**
* if OperationHelper is being integrated into a fragment
*
* @param fragment
*/
public CryptoOperationHelper(Fragment fragment, Callback<T, S> callback, int progressMessageString) {
mFragment = fragment;
@@ -102,8 +104,6 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
/**
* if OperationHelper is being integrated into a fragment with default message for the progress dialog
*
* @param fragment
*/
public CryptoOperationHelper(Fragment fragment, Callback<T, S> callback) {
mFragment = fragment;
@@ -116,11 +116,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 +133,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,22 +146,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;
}
}
throw new RuntimeException("Unhandled pending result!");
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;
}
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.
*
* @param requestCode
* @param resultCode
* @param data
* Attempts the result of an activity started by this helper. Returns true if requestCode is
* recognized, false otherwise.
* @return true if requestCode was recognized, false otherwise
*/
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
@@ -196,6 +209,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 +248,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 +280,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);
}
}
@@ -291,15 +314,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
cryptoOperation(new CryptoInputParcel());
}
protected void onCryptoOperationResult(S result) {
if (result.success()) {
mCallback.onCryptoOperationSuccess(result);
} else {
mCallback.onCryptoOperationError(result);
}
}
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 +322,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
if (pendingResult.isPending()) {
RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel();
initiateInputActivity(requiredInput);
initiateInputActivity(requiredInput, oldCryptoInput);
return;
}
}
@@ -315,8 +330,13 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
dismissProgress();
try {
// noinspection unchecked, because type erasure :(
onCryptoOperationResult((S) result);
if (result.success()) {
// noinspection unchecked, because type erasure :(
mCallback.onCryptoOperationSuccess((S) result);
} else {
// noinspection unchecked, because type erasure :(
mCallback.onCryptoOperationError((S) result);
}
} catch (ClassCastException e) {
throw new AssertionError("bad return class ("
+ result.getClass().getSimpleName() + "), this is a programming error!");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
}
}
/**

View File

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

View File

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