Code formatting and package re-structuring

This commit is contained in:
Dominik Schürmann
2017-01-05 13:56:09 +01:00
parent b89ba85313
commit 63244a113a
35 changed files with 131 additions and 142 deletions

View File

@@ -1,195 +0,0 @@
/* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.keychain.util;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/** TLV data structure and parser implementation.
*
* This class is used for parsing and working with TLV packets as specified in
* the Functional Specification of the OpenPGP application on ISO Smart Card
* Operating Systems, and ISO 7816-4.
*
* Objects of this class are immutable data structs parsed from BER-TLV data.
* They include at least the T, L and V fields they were originally parsed
* from, subclasses may also carry more specific information.
*
* @see { @linktourl http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-d.aspx}
*
*/
public class Iso7816TLV {
public final int mT;
public final int mL;
public final byte[] mV;
private Iso7816TLV(int T, int L, byte[] V) {
mT = T;
mL = L;
mV = V;
}
public String prettyPrint() {
return prettyPrint(0);
}
public String prettyPrint(int indent) {
// lol
String padding = " ".substring(0, indent*2);
return padding + String.format("tag T %4x L %04d", mT, mL);
}
/** Read a single Iso7816 TLV packet from a given ByteBuffer, either
* recursively or flat.
*
* This is a convenience wrapper for readSingle with ByteBuffer argument.
*/
public static Iso7816TLV readSingle(byte[] data, boolean recursive) throws IOException {
return readSingle(ByteBuffer.wrap(data), recursive);
}
/** Read a single Iso7816 TLV packet from a given ByteBuffer, either
* recursively or flat.
*
* If the recursive flag is true, a composite packet will be parsed and
* returned as an Iso7816CompositeTLV. Otherwise, a regular Iso7816TLV
* object will be returned regardless of actual packet type.
*
* This method is fail-fast, if any parsing error occurs it will throw an
* exception.
*
*/
public static Iso7816TLV readSingle(ByteBuffer data, boolean recursive) throws IOException {
int T = data.get() & 0xff;
boolean composite = (T & 0x20) == 0x20;
if ((T & 0x1f) == 0x1f) {
int T2 = data.get() & 0xff;
if ((T2 & 0x1f) == 0x1f) {
throw new IOException("Only tags up to two bytes are supported!");
}
T = (T << 8) | (T2 & 0x7f);
}
// Log.d(Constants.TAG, String.format("T %02x", T));
// parse length, according to ISO 7816-4 (openpgp card 2.0 specs, page 24)
int L = data.get() & 0xff;
if (L == 0x81) {
L = data.get() & 0xff;
} else if (L == 0x82) {
L = data.get() & 0xff;
L = (L << 8) | (data.get() & 0xff);
} else if (L >= 0x80) {
throw new IOException("Invalid length field!");
}
// Log.d(Constants.TAG, String.format("L %02x", L));
// read L bytes into new buffer
byte[] V = new byte[L];
data.get(V);
// if we are supposed to parse composites, do that
if (recursive && composite) {
// Log.d(Constants.TAG, "parsing composite TLV");
Iso7816TLV[] subs = readList(V, true);
return new Iso7816CompositeTLV(T, L, V, subs);
}
return new Iso7816TLV(T, L, V);
}
/** Parse a list of TLV packets from byte data, recursively or flat.
*
* This method is fail-fast, if any parsing error occurs it will throw an
* exception.
*
*/
public static Iso7816TLV[] readList(byte[] data, boolean recursive) throws IOException {
ByteBuffer buf = ByteBuffer.wrap(data);
ArrayList<Iso7816TLV> result = new ArrayList<>();
// read while data is available. this will fail if there is trailing data!
while (buf.hasRemaining()) {
// skip 0x00 and 0xFF filler bytes
buf.mark();
byte peek = buf.get();
if (peek == 0xff || peek == 0x00) {
continue;
}
buf.reset();
Iso7816TLV packet = readSingle(buf, recursive);
result.add(packet);
}
Iso7816TLV[] resultX = new Iso7816TLV[result.size()];
result.toArray(resultX);
return resultX;
}
/** This class represents a composite TLV packet.
*
* Note that only actual composite TLV packets are instances of this class.
* A regular non-composite Iso7816TLV object may however be a composite
* packet if it was parsed non-recursively.
*
*/
public static class Iso7816CompositeTLV extends Iso7816TLV {
public final Iso7816TLV[] mSubs;
public Iso7816CompositeTLV(int T, int L, byte[] V, Iso7816TLV[] subs) {
super(T, L, V);
mSubs = subs;
}
public String prettyPrint(int indent) {
StringBuilder result = new StringBuilder();
// lol
result.append(" ".substring(0, indent*2));
result.append(String.format("composite tag T %4x L %04d", mT, mL));
for (Iso7816TLV sub : mSubs) {
result.append('\n');
result.append(sub.prettyPrint(indent+1));
}
return result.toString();
}
}
/** Recursively searches for a specific tag in a composite TLV packet structure, depth first. */
public static Iso7816TLV findRecursive(Iso7816TLV in, int tag) {
if (in.mT == tag) {
return in;
} else if (in instanceof Iso7816CompositeTLV) {
for (Iso7816TLV sub : ((Iso7816CompositeTLV) in).mSubs) {
Iso7816TLV result = findRecursive(sub, tag);
if (result != null) {
return result;
}
}
}
return null;
}
}

View File

@@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import java.lang.ref.WeakReference;

View File

@@ -1,76 +0,0 @@
/*
* Copyright (C) 2016 Michał Kępkowski
*
* 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 java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import okhttp3.CertificatePinner;
import okhttp3.OkHttpClient;
public class OkHttpClientFactory {
private static OkHttpClient client;
public static OkHttpClient getSimpleClient() {
if (client == null) {
client = new OkHttpClient.Builder()
.connectTimeout(5000, TimeUnit.MILLISECONDS)
.readTimeout(25000, TimeUnit.MILLISECONDS)
.build();
}
return client;
}
public static OkHttpClient getSimpleClientPinned(CertificatePinner pinner) {
return new OkHttpClient.Builder()
.connectTimeout(5000, TimeUnit.MILLISECONDS)
.readTimeout(25000, TimeUnit.MILLISECONDS)
.certificatePinner(pinner)
.build();
}
public static OkHttpClient getClientPinnedIfAvailable(URL url, Proxy proxy)
throws IOException, TlsHelper.TlsHelperException {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
// don't follow any redirects for keyservers, as discussed in the security audit
builder.followRedirects(false)
.followSslRedirects(false);
if (proxy != null) {
// set proxy and higher timeouts for Tor
builder.proxy(proxy);
builder.connectTimeout(30000, TimeUnit.MILLISECONDS)
.readTimeout(45000, TimeUnit.MILLISECONDS);
} else {
builder.connectTimeout(5000, TimeUnit.MILLISECONDS)
.readTimeout(25000, TimeUnit.MILLISECONDS);
}
// If a pinned cert is available, use it!
// NOTE: this fails gracefully back to "no pinning" if no cert is available.
if (url != null && TlsHelper.getPinnedSslSocketFactory(url) != null) {
builder.sslSocketFactory(TlsHelper.getPinnedSslSocketFactory(url));
}
return builder.build();
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright (C) 2015 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 com.textuality.keybase.lib.KeybaseUrlConnectionClient;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.sufficientlysecure.keychain.Constants;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
/**
* Wrapper for Keybase Lib
*/
public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient {
@Override
public Response getUrlResponse(URL url, Proxy proxy, boolean isKeybase) throws IOException {
OkHttpClient client;
try {
if (proxy != null) {
client = OkHttpClientFactory.getClientPinnedIfAvailable(url, proxy);
} else {
client = OkHttpClientFactory.getSimpleClient();
}
} catch (TlsHelper.TlsHelperException e) {
Log.e(Constants.TAG, "TlsHelper failed", e);
throw new IOException("TlsHelper failed");
}
Request request = new Request.Builder()
.url(url).build();
okhttp3.Response okResponse = client.newCall(request).execute();
return new Response(okResponse.body().byteStream(), okResponse.code(), okResponse.message(), okResponse.headers().toMultimap());
}
@Override
public String getKeybaseBaseUrl() {
return "https://api.keybase.io/";
}
}

View File

@@ -1,242 +0,0 @@
/*
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@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.util;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.securitytoken.ECKeyFormat;
import org.sufficientlysecure.keychain.securitytoken.RSAKeyFormat;
import org.sufficientlysecure.keychain.securitytoken.KeyType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
public class SecurityTokenUtils {
public static byte[] attributesFromSecretKey(final KeyType slot, final CanonicalizedSecretKey secretKey) throws IOException, PgpGeneralException {
if (secretKey.isRSA()) {
final int mModulusLength = secretKey.getBitStrength();
final int mExponentLength = secretKey.getSecurityTokenRSASecretKey().getPublicExponent().bitLength();
final byte[] attrs = new byte[6];
int i = 0;
attrs[i++] = (byte)0x01;
attrs[i++] = (byte)((mModulusLength >> 8) & 0xff);
attrs[i++] = (byte)(mModulusLength & 0xff);
attrs[i++] = (byte)((mExponentLength >> 8) & 0xff);
attrs[i++] = (byte)(mExponentLength & 0xff);
attrs[i++] = RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS.getValue();
return attrs;
} else if (secretKey.isEC()) {
final byte[] oid = new ASN1ObjectIdentifier(secretKey.getCurveOid()).getEncoded();
final byte[] attrs = new byte[1 + (oid.length - 2) + 1];
if (slot.equals(KeyType.SIGN))
attrs[0] = ECKeyFormat.ECAlgorithmFormat.ECDSA_WITH_PUBKEY.getValue();
else {
attrs[0] = ECKeyFormat.ECAlgorithmFormat.ECDH_WITH_PUBKEY.getValue();
}
System.arraycopy(oid, 2, attrs, 1, (oid.length - 2));
attrs[attrs.length - 1] = (byte)0xff;
return attrs;
} else {
throw new IOException("Unsupported key type");
}
}
public static byte[] createRSAPrivKeyTemplate(RSAPrivateCrtKey secretKey, KeyType slot,
RSAKeyFormat format) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream(),
template = new ByteArrayOutputStream(),
data = new ByteArrayOutputStream(),
res = new ByteArrayOutputStream();
int expLengthBytes = (format.getExponentLength() + 7) / 8;
// Public exponent
template.write(new byte[]{(byte) 0x91, (byte) expLengthBytes});
writeBits(data, secretKey.getPublicExponent(), expLengthBytes);
// Prime P, length 128
template.write(Hex.decode("928180"));
writeBits(data, secretKey.getPrimeP(), 128);
// Prime Q, length 128
template.write(Hex.decode("938180"));
writeBits(data, secretKey.getPrimeQ(), 128);
if (format.getAlgorithmFormat().isIncludeCrt()) {
// Coefficient (1/q mod p), length 128
template.write(Hex.decode("948180"));
writeBits(data, secretKey.getCrtCoefficient(), 128);
// Prime exponent P (d mod (p - 1)), length 128
template.write(Hex.decode("958180"));
writeBits(data, secretKey.getPrimeExponentP(), 128);
// Prime exponent Q (d mod (1 - 1)), length 128
template.write(Hex.decode("968180"));
writeBits(data, secretKey.getPrimeExponentQ(), 128);
}
if (format.getAlgorithmFormat().isIncludeModulus()) {
// Modulus, length 256, last item in private key template
template.write(Hex.decode("97820100"));
writeBits(data, secretKey.getModulus(), 256);
}
// Bundle up
// Ext header list data
// Control Reference Template to indicate the private key
stream.write(slot.getSlot());
stream.write(0);
// Cardholder private key template
stream.write(Hex.decode("7F48"));
stream.write(encodeLength(template.size()));
stream.write(template.toByteArray());
// Concatenation of key data as defined in DO 7F48
stream.write(Hex.decode("5F48"));
stream.write(encodeLength(data.size()));
stream.write(data.toByteArray());
// Result tlv
res.write(Hex.decode("4D"));
res.write(encodeLength(stream.size()));
res.write(stream.toByteArray());
return res.toByteArray();
}
public static byte[] createECPrivKeyTemplate(ECPrivateKey secretKey, ECPublicKey publicKey, KeyType slot,
ECKeyFormat format) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream(),
template = new ByteArrayOutputStream(),
data = new ByteArrayOutputStream(),
res = new ByteArrayOutputStream();
final int csize = (int)Math.ceil(publicKey.getParams().getCurve().getField().getFieldSize() / 8.0);
writeBits(data, secretKey.getS(), csize);
template.write(Hex.decode("92"));
template.write(encodeLength(data.size()));
if (format.getAlgorithmFormat().isWithPubkey()) {
data.write(Hex.decode("04"));
writeBits(data, publicKey.getW().getAffineX(), csize);
writeBits(data, publicKey.getW().getAffineY(), csize);
template.write(Hex.decode("99"));
template.write(encodeLength(1 + 2 * csize));
}
// Bundle up
// Ext header list data
// Control Reference Template to indicate the private key
stream.write(slot.getSlot());
stream.write(0);
// Cardholder private key template
stream.write(Hex.decode("7F48"));
stream.write(encodeLength(template.size()));
stream.write(template.toByteArray());
// Concatenation of key data as defined in DO 7F48
stream.write(Hex.decode("5F48"));
stream.write(encodeLength(data.size()));
stream.write(data.toByteArray());
// Result tlv
res.write(Hex.decode("4D"));
res.write(encodeLength(stream.size()));
res.write(stream.toByteArray());
return res.toByteArray();
}
public static byte[] encodeLength(int len) {
if (len < 0) {
throw new IllegalArgumentException("length is negative");
} else if (len >= 16777216) {
throw new IllegalArgumentException("length is too big: " + len);
}
byte[] res;
if (len < 128) {
res = new byte[1];
res[0] = (byte) len;
} else if (len < 256) {
res = new byte[2];
res[0] = -127;
res[1] = (byte) len;
} else if (len < 65536) {
res = new byte[3];
res[0] = -126;
res[1] = (byte) (len / 256);
res[2] = (byte) (len % 256);
} else {
res = new byte[4];
res[0] = -125;
res[1] = (byte) (len / 65536);
res[2] = (byte) (len / 256);
res[3] = (byte) (len % 256);
}
return res;
}
public static void writeBits(ByteArrayOutputStream stream, BigInteger value, int width) {
if (value.signum() == -1) {
throw new IllegalArgumentException("value is negative");
} else if (width <= 0) {
throw new IllegalArgumentException("width <= 0");
}
final byte[] prime = value.toByteArray();
int skip = 0;
while((skip < prime.length) && (prime[skip] == 0)) ++skip;
if ((prime.length - skip) > width) {
throw new IllegalArgumentException("not enough width to fit value: "
+ (prime.length - skip) + "/" + width);
}
byte[] res = new byte[width];
System.arraycopy(prime, skip,
res, width - (prime.length - skip),
prime.length - skip);
stream.write(res, 0, width);
Arrays.fill(res, (byte) 0);
Arrays.fill(prime, (byte) 0);
}
}

View File

@@ -1,137 +0,0 @@
/*
* Copyright (C) 2013-2015 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.content.res.AssetManager;
import org.sufficientlysecure.keychain.Constants;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
public class TlsHelper {
private static Map<String, byte[]> sPinnedCertificates = new HashMap<>();
/**
* Add certificate from assets to pinned certificate map.
*/
public static void addPinnedCertificate(String host, AssetManager assetManager, String cerFilename) {
try {
InputStream is = assetManager.open(cerFilename);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int reads = is.read();
while (reads != -1) {
baos.write(reads);
reads = is.read();
}
is.close();
sPinnedCertificates.put(host, baos.toByteArray());
} catch (IOException e) {
Log.w(Constants.TAG, e);
}
}
/**
* Use pinned certificate for OkHttpClient if we have one.
*
* @return true, if certificate is available, false if not
* @throws TlsHelperException
* @throws IOException
*/
public static SSLSocketFactory getPinnedSslSocketFactory(URL url) throws TlsHelperException, IOException {
if (url.getProtocol().equals("https")) {
// use certificate PIN from assets if we have one
for (String host : sPinnedCertificates.keySet()) {
if (url.getHost().endsWith(host)) {
return pinCertificate(sPinnedCertificates.get(host));
}
}
}
return null;
}
/**
* Modifies the builder to accept only requests with a given certificate.
* Applies to all URLs requested by the builder.
* Therefore a builder that is pinned this way should be used to only make requests
* to URLs with passed certificate.
*
* @param certificate certificate to pin
* @throws TlsHelperException
* @throws IOException
*/
private static SSLSocketFactory pinCertificate(byte[] certificate)
throws TlsHelperException, IOException {
// We don't use OkHttp's CertificatePinner since it can not be used to pin self-signed
// certificate if such certificate is not accepted by TrustManager.
// (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);
return context.getSocketFactory();
} catch (CertificateException | KeyStoreException | KeyManagementException | NoSuchAlgorithmException e) {
throw new TlsHelperException(e);
}
}
public static class TlsHelperException extends Exception {
public TlsHelperException(Exception e) {
super(e);
}
}
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@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.util;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity;
public class UsbConnectionDispatcher {
private Activity mActivity;
private OnDiscoveredUsbDeviceListener mListener;
private UsbManager mUsbManager;
/**
* Receives broadcast when a supported USB device get permission.
*/
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case UsbEventReceiverActivity.ACTION_USB_PERMISSION: {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,
false);
if (permission) {
Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName());
mListener.usbDeviceDiscovered(usbDevice);
}
break;
}
}
}
};
public UsbConnectionDispatcher(final Activity activity, final OnDiscoveredUsbDeviceListener listener) {
this.mActivity = activity;
this.mListener = listener;
this.mUsbManager = (UsbManager) activity.getSystemService(Context.USB_SERVICE);
}
public void onStart() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION);
mActivity.registerReceiver(mUsbReceiver, intentFilter);
}
public void onStop() {
mActivity.unregisterReceiver(mUsbReceiver);
}
/**
* Rescans devices and triggers {@link OnDiscoveredUsbDeviceListener}
*/
public void rescanDevices() {
// Note: we don't check devices VID/PID because
// we check for permission instead.
// We should have permission only for matching devices
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
if (mUsbManager.hasPermission(device)) {
if (mListener != null) {
mListener.usbDeviceDiscovered(device);
}
break;
}
}
}
public interface OnDiscoveredUsbDeviceListener {
void usbDeviceDiscovered(UsbDevice usbDevice);
}
}

View File

@@ -1,469 +0,0 @@
/* 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.app.Activity;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
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 android.text.TextUtils;
import org.sufficientlysecure.keychain.Constants;
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.ui.util.ThemeChanger;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences;
import java.util.List;
/**
* This class is taken from the NetCipher library: https://github.com/guardianproject/NetCipher/
*/
public class OrbotHelper {
public interface DialogActions {
void onOrbotStarted();
void onNeutralButton();
void onCancel();
}
private final static int REQUEST_CODE_STATUS = 100;
public final static String ORBOT_PACKAGE_NAME = "org.torproject.android";
public final static String ORBOT_MARKET_URI = "market://details?id=" + ORBOT_PACKAGE_NAME;
public final static String ORBOT_FDROID_URI = "https://f-droid.org/repository/browse/?fdid="
+ ORBOT_PACKAGE_NAME;
public final static String ORBOT_PLAY_URI = "https://play.google.com/store/apps/details?id="
+ ORBOT_PACKAGE_NAME;
/**
* A request to Orbot to transparently start Tor services
*/
public final static String ACTION_START = "org.torproject.android.intent.action.START";
/**
* {@link Intent} send by Orbot with {@code ON/OFF/STARTING/STOPPING} status
*/
public final static String ACTION_STATUS = "org.torproject.android.intent.action.STATUS";
/**
* {@code String} that contains a status constant: {@link #STATUS_ON},
* {@link #STATUS_OFF}, {@link #STATUS_STARTING}, or
* {@link #STATUS_STOPPING}
*/
public final static String EXTRA_STATUS = "org.torproject.android.intent.extra.STATUS";
/**
* A {@link String} {@code packageName} for Orbot to direct its status reply
* to, used in {@link #ACTION_START} {@link Intent}s sent to Orbot
*/
public final static String EXTRA_PACKAGE_NAME = "org.torproject.android.intent.extra.PACKAGE_NAME";
/**
* All tor-related services and daemons are stopped
*/
@SuppressWarnings("unused") // we might use this later, sent by Orbot
public final static String STATUS_OFF = "OFF";
/**
* All tor-related services and daemons have completed starting
*/
public final static String STATUS_ON = "ON";
@SuppressWarnings("unused") // we might use this later, sent by Orbot
public final static String STATUS_STARTING = "STARTING";
@SuppressWarnings("unused") // we might use this later, sent by Orbot
public final static String STATUS_STOPPING = "STOPPING";
/**
* The user has disabled the ability for background starts triggered by
* apps. Fallback to the old Intent that brings up Orbot.
*/
public final static String STATUS_STARTS_DISABLED = "STARTS_DISABLED";
public final static String ACTION_START_TOR = "org.torproject.android.START_TOR";
/**
* request code used to start tor
*/
public final static int START_TOR_RESULT = 0x9234;
private final static String FDROID_PACKAGE_NAME = "org.fdroid.fdroid";
private final static String PLAY_PACKAGE_NAME = "com.android.vending";
private OrbotHelper() {
// only static utility methods, do not instantiate
}
/**
* Initialize the OrbotStatusReceiver (if not already happened) and check, whether Orbot is
* running or not.
*
* @param context context
* @return if Orbot is running
*/
public static boolean isOrbotRunning(Context context) {
return OrbotStatusReceiver.getInstance().isTorRunning(context);
}
public static boolean isOrbotInstalled(Context context) {
return isAppInstalled(context, ORBOT_PACKAGE_NAME);
}
private static boolean isAppInstalled(Context context, String uri) {
try {
PackageManager pm = context.getPackageManager();
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
/**
* First, checks whether Orbot is installed, then checks whether Orbot is
* running. If Orbot is installed and not running, then an {@link Intent} is
* sent to request Orbot to start, which will show the main Orbot screen.
* The result will be returned in
* {@link Activity#onActivityResult(int requestCode, int resultCode, Intent data)}
* with a {@code requestCode} of {@code START_TOR_RESULT}
*
* @param activity the {@link Activity} that gets the
* {@code START_TOR_RESULT} result
* @return whether the start request was sent to Orbot
*/
public static boolean requestShowOrbotStart(Activity activity) {
if (OrbotHelper.isOrbotInstalled(activity)) {
if (!OrbotHelper.isOrbotRunning(activity)) {
Intent intent = getShowOrbotStartIntent();
activity.startActivityForResult(intent, START_TOR_RESULT);
return true;
}
}
return false;
}
public static Intent getShowOrbotStartIntent() {
Intent intent = new Intent(ACTION_START_TOR);
intent.setPackage(ORBOT_PACKAGE_NAME);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}
/**
* First, checks whether Orbot is installed. If Orbot is installed, then a
* broadcast {@link Intent} is sent to request Orbot to start transparently
* in the background. When Orbot receives this {@code Intent}, it will
* immediately reply to this all with its status via an
* {@link #ACTION_STATUS} {@code Intent} that is broadcast to the
* {@code packageName} of the provided {@link Context} (i.e.
* {@link Context#getPackageName()}.
*
* @param context the app {@link Context} will receive the reply
* @return whether the start request was sent to Orbot
*/
public static boolean requestStartTor(Context context) {
if (OrbotHelper.isOrbotInstalled(context)) {
Log.i("OrbotHelper", "requestStartTor " + context.getPackageName());
Intent intent = getOrbotStartIntent();
intent.putExtra(EXTRA_PACKAGE_NAME, context.getPackageName());
context.sendBroadcast(intent);
return true;
}
return false;
}
public static Intent getOrbotStartIntent() {
Intent intent = new Intent(ACTION_START);
intent.setPackage(ORBOT_PACKAGE_NAME);
return intent;
}
public static Intent getOrbotInstallIntent(Context context) {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(ORBOT_MARKET_URI));
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);
String foundPackageName = null;
for (ResolveInfo r : resInfos) {
Log.i("OrbotHelper", "market: " + r.activityInfo.packageName);
if (TextUtils.equals(r.activityInfo.packageName, FDROID_PACKAGE_NAME)
|| TextUtils.equals(r.activityInfo.packageName, PLAY_PACKAGE_NAME)) {
foundPackageName = r.activityInfo.packageName;
break;
}
}
if (foundPackageName == null) {
intent.setData(Uri.parse(ORBOT_FDROID_URI));
} else {
intent.setPackage(foundPackageName);
}
return intent;
}
/**
* hack to get around the fact that PreferenceActivity still supports only android.app.DialogFragment
*/
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 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);
}
/**
* checks preferences to see if Orbot is required, and if yes, if it is installed and running
*
* @param context used to retrieve preferences
* @return false if Tor is selected proxy and Orbot is not installed or running, true
* otherwise
*/
public static boolean isOrbotInRequiredState(Context context) {
ParcelableProxy proxy = Preferences.getPreferences(context).getParcelableProxy();
if (!proxy.isTorEnabled()) {
return true;
} else if (!OrbotHelper.isOrbotInstalled(context) || !OrbotHelper.isOrbotRunning(context)) {
return false;
}
return true;
}
/**
* checks if Tor is enabled and if it is, that Orbot is installed and running. Generates appropriate dialogs.
*
* @param middleButton resourceId of string to display as the middle button of install and enable dialogs
* @param proxy proxy preferences used to determine if Tor is required to be started
* @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 DialogActions dialogActions,
ParcelableProxy proxy,
final FragmentActivity fragmentActivity) {
if (!proxy.isTorEnabled()) {
return true;
}
if (!OrbotHelper.isOrbotInstalled(fragmentActivity)) {
Handler ignoreTorHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SupportInstallDialogFragment.MESSAGE_MIDDLE_CLICKED:
dialogActions.onNeutralButton();
break;
case SupportInstallDialogFragment.MESSAGE_DIALOG_DISMISSED:
// both install and cancel buttons mean we don't go ahead with an
// operation, so it's okay to cancel
dialogActions.onCancel();
break;
}
}
};
OrbotHelper.getInstallDialogFragmentWithThirdButton(
new Messenger(ignoreTorHandler),
middleButton
).show(fragmentActivity.getSupportFragmentManager(), "OrbotHelperOrbotInstallDialog");
return false;
} else if (!OrbotHelper.isOrbotRunning(fragmentActivity)) {
final Handler dialogHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case OrbotStartDialogFragment.MESSAGE_MIDDLE_BUTTON:
dialogActions.onNeutralButton();
break;
case OrbotStartDialogFragment.MESSAGE_DIALOG_CANCELLED:
dialogActions.onCancel();
break;
case OrbotStartDialogFragment.MESSAGE_ORBOT_STARTED:
dialogActions.onOrbotStarted();
break;
}
}
};
new SilentStartManager() {
@Override
protected void onOrbotStarted() {
dialogActions.onOrbotStarted();
}
@Override
protected void onSilentStartDisabled() {
getOrbotStartDialogFragment(new Messenger(dialogHandler), middleButton)
.show(fragmentActivity.getSupportFragmentManager(),
"OrbotHelperOrbotStartDialog");
}
}.startOrbotAndListen(fragmentActivity, true);
return false;
} else {
return true;
}
}
public static boolean putOrbotInRequiredState(DialogActions dialogActions,
FragmentActivity fragmentActivity) {
return putOrbotInRequiredState(R.string.orbot_ignore_tor,
dialogActions,
Preferences.getPreferences(fragmentActivity).getParcelableProxy(),
fragmentActivity);
}
/**
* will attempt a silent start, which if disabled will fallback to the
* {@link #requestShowOrbotStart(Activity) requestShowOrbotStart} method, which returns the
* result in {@link Activity#onActivityResult(int requestCode, int resultCode, Intent data)}
* with a {@code requestCode} of {@code START_TOR_RESULT}, which will have to be implemented by
* activities wishing to respond to a change in Orbot state.
*/
public static void bestPossibleOrbotStart(final DialogActions dialogActions,
final Activity activity,
boolean showProgress) {
new SilentStartManager() {
@Override
protected void onOrbotStarted() {
dialogActions.onOrbotStarted();
}
@Override
protected void onSilentStartDisabled() {
requestShowOrbotStart(activity);
}
}.startOrbotAndListen(activity, showProgress);
}
/**
* base class for listening to silent orbot starts. Also handles display of progress dialog.
*/
public static abstract class SilentStartManager {
private ProgressDialog mProgressDialog;
public void startOrbotAndListen(final Context context, final boolean showProgress) {
Log.d(Constants.TAG, "starting orbot listener");
if (showProgress) {
showProgressDialog(context);
}
final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getStringExtra(OrbotHelper.EXTRA_STATUS)) {
case OrbotHelper.STATUS_ON:
context.unregisterReceiver(this);
// generally Orbot starts working a little after this status is received
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (showProgress) {
mProgressDialog.dismiss();
}
onOrbotStarted();
}
}, 1000);
break;
case OrbotHelper.STATUS_STARTS_DISABLED:
context.unregisterReceiver(this);
if (showProgress) {
mProgressDialog.dismiss();
}
onSilentStartDisabled();
break;
}
Log.d(Constants.TAG, "Orbot silent start broadcast: " +
intent.getStringExtra(OrbotHelper.EXTRA_STATUS));
}
};
context.registerReceiver(receiver, new IntentFilter(OrbotHelper.ACTION_STATUS));
requestStartTor(context);
}
private void showProgressDialog(Context context) {
mProgressDialog = new ProgressDialog(ThemeChanger.getDialogThemeWrapper(context));
mProgressDialog.setMessage(context.getString(R.string.progress_starting_orbot));
mProgressDialog.setCancelable(false);
mProgressDialog.show();
}
protected abstract void onOrbotStarted();
protected abstract void onSilentStartDisabled();
}
}

View File

@@ -1,93 +0,0 @@
/*
* 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.orbot;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;
/**
* BroadcastReceiver that receives Orbots status
*/
public class OrbotStatusReceiver extends BroadcastReceiver {
//TODO: These two Strings are missing in older versions of NetCipher.
//TODO: Once they are present in OrbotHelper (not ProxyHelper) point to OrbotHelpers Strings instead.
public final static String EXTRA_PROXY_PORT_HTTP = "org.torproject.android.intent.extra.HTTP_PROXY_PORT";
public final static String EXTRA_PROXY_PORT_SOCKS = "org.torproject.android.intent.extra.SOCKS_PROXY_PORT";
//Variables representing Orbots status
private boolean torRunning;
private int proxy_port_http;
private int proxy_port_socks;
private static OrbotStatusReceiver instance;
public OrbotStatusReceiver() {
instance = this;
}
public static OrbotStatusReceiver getInstance() {
if (instance == null) {
instance = new OrbotStatusReceiver();
}
return instance;
}
@Override
public void onReceive(Context context, Intent intent) {
if (OrbotHelper.ACTION_STATUS.equals(intent.getAction())) {
Log.i(Constants.TAG, context.getPackageName() + " received intent : " + intent.getAction() + " " + intent.getPackage());
String status = intent.getStringExtra(OrbotHelper.EXTRA_STATUS) + " (" + intent.getStringExtra(OrbotHelper.EXTRA_PACKAGE_NAME) + ")";
this.torRunning = (intent.getStringExtra(OrbotHelper.EXTRA_STATUS).equals(OrbotHelper.STATUS_ON));
Log.d(Constants.TAG, "Orbot status: " + status);
if (torRunning) {
Bundle extras = intent.getExtras();
if (extras.containsKey(EXTRA_PROXY_PORT_HTTP)) {
this.proxy_port_http = extras.getInt(EXTRA_PROXY_PORT_HTTP, -1);
Log.i(Constants.TAG, "Http proxy set to " + proxy_port_http);
}
if (extras.containsKey(EXTRA_PROXY_PORT_SOCKS)) {
this.proxy_port_socks = extras.getInt(EXTRA_PROXY_PORT_SOCKS, -1);
Log.i(Constants.TAG, "Socks proxy set to " + proxy_port_socks);
}
}
}
}
public boolean isTorRunning(Context context) {
OrbotHelper.requestStartTor(context);
return this.torRunning;
}
public int getProxyPortHttp(Context context) {
OrbotHelper.requestStartTor(context);
return this.proxy_port_http;
}
public int getProxyPortSocks(Context context) {
OrbotHelper.requestStartTor(context);
return this.proxy_port_socks;
}
}