Code formatting and package re-structuring
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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/";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user