Remove experimental Linked Identities feature
parent
7e5a153a04
commit
2cc22c6b65
|
@ -148,11 +148,6 @@
|
|||
android:name=".ui.EditKeyActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_edit_key" />
|
||||
<activity
|
||||
android:name=".ui.linked.LinkedIdWizard"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_linked_create"
|
||||
android:parentActivityName=".ui.keyview.ViewKeyActivity" />
|
||||
<!-- NOTE: Dont use configChanges for QR Code view! We use a different layout for landscape -->
|
||||
<activity
|
||||
android:name=".ui.QrCodeViewActivity"
|
||||
|
|
|
@ -17,6 +17,12 @@
|
|||
|
||||
package org.sufficientlysecure.keychain;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.net.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||
|
@ -26,11 +32,6 @@ import org.sufficientlysecure.keychain.securitytoken.RSAKeyFormat;
|
|||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public final class Constants {
|
||||
|
||||
|
@ -153,7 +154,6 @@ public final class Constants {
|
|||
public static final String ENABLE_WIFI_SYNC_ONLY = "enableWifiSyncOnly";
|
||||
public static final String SYNC_WORK_UUID = "syncWorkUuid";
|
||||
// other settings
|
||||
public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities";
|
||||
public static final String EXPERIMENTAL_ENABLE_KEYBASE = "experimentalEnableKeybase";
|
||||
public static final String EXPERIMENTAL_USB_ALLOW_UNTESTED = "experimentalUsbAllowUntested";
|
||||
public static final String EXPERIMENTAL_SMARTPGP_VERIFY_AUTHORITY = "smartpgp_authorities_pref";
|
||||
|
@ -179,7 +179,7 @@ public final class Constants {
|
|||
// we generally only track booleans. never snoop around in the user's string settings!!
|
||||
public static final List<String> ANALYTICS_PREFS = Arrays.asList(USE_NORMAL_PROXY, USE_TOR_PROXY,
|
||||
SYNC_CONTACTS, SYNC_KEYSERVER, ENABLE_WIFI_SYNC_ONLY, EXPERIMENTAL_ENABLE_KEYBASE,
|
||||
EXPERIMENTAL_ENABLE_LINKED_IDENTITIES, EXPERIMENTAL_USB_ALLOW_UNTESTED,
|
||||
EXPERIMENTAL_USB_ALLOW_UNTESTED,
|
||||
PASSPHRASE_CACHE_SUBS, SEARCH_KEYSERVER, SEARCH_KEYBASE, SEARCH_WEB_KEY_DIRECTORY,
|
||||
TEXT_USE_COMPRESSION, TEXT_SELF_ENCRYPT, FILE_USE_COMPRESSION, FILE_SELF_ENCRYPT, USE_ARMOR,
|
||||
USE_NUMKEYPAD_FOR_SECURITY_TOKEN_PIN, ENCRYPT_FILENAMES);
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.linked;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.DrawableRes;
|
||||
|
||||
public class LinkedAttribute extends UriAttribute {
|
||||
|
||||
public final LinkedResource mResource;
|
||||
|
||||
protected LinkedAttribute(URI uri, LinkedResource resource) {
|
||||
super(uri);
|
||||
if (resource == null) {
|
||||
throw new AssertionError("resource must not be null in a LinkedIdentity!");
|
||||
}
|
||||
mResource = resource;
|
||||
}
|
||||
|
||||
public @DrawableRes int getDisplayIcon() {
|
||||
return mResource.getDisplayIcon();
|
||||
}
|
||||
|
||||
public String getDisplayTitle(Context context) {
|
||||
return mResource.getDisplayTitle(context);
|
||||
}
|
||||
|
||||
public String getDisplayComment(Context context) {
|
||||
return mResource.getDisplayComment(context);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.linked;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
public abstract class LinkedResource {
|
||||
|
||||
public abstract URI toUri();
|
||||
|
||||
public abstract @DrawableRes int getDisplayIcon();
|
||||
public abstract @StringRes int getVerifiedText(boolean isSecret);
|
||||
public abstract String getDisplayTitle(Context context);
|
||||
public abstract String getDisplayComment(Context context);
|
||||
public boolean isViewable() {
|
||||
return false;
|
||||
}
|
||||
public Intent getViewIntent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,302 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.linked;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import okhttp3.CertificatePinner;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.json.JSONException;
|
||||
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
|
||||
import org.sufficientlysecure.keychain.linked.resources.GithubResource;
|
||||
import org.sufficientlysecure.keychain.linked.resources.TwitterResource;
|
||||
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.network.OkHttpClientFactory;
|
||||
import timber.log.Timber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
public abstract class LinkedTokenResource extends LinkedResource {
|
||||
|
||||
protected final URI mSubUri;
|
||||
protected final Set<String> mFlags;
|
||||
protected final HashMap<String,String> mParams;
|
||||
|
||||
public static Pattern magicPattern =
|
||||
Pattern.compile("\\[Verifying my (?:Open|)?PGP key(?::|) openpgp4fpr:([a-zA-Z0-9]+)]");
|
||||
|
||||
protected LinkedTokenResource(Set<String> flags, HashMap<String, String> params, URI uri) {
|
||||
mFlags = flags;
|
||||
mParams = params;
|
||||
mSubUri = uri;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public URI getSubUri () {
|
||||
return mSubUri;
|
||||
}
|
||||
|
||||
public Set<String> getFlags () {
|
||||
return new HashSet<>(mFlags);
|
||||
}
|
||||
|
||||
public HashMap<String,String> getParams () {
|
||||
return new HashMap<>(mParams);
|
||||
}
|
||||
|
||||
public static String generate (byte[] fingerprint) {
|
||||
return String.format("[Verifying my OpenPGP key: openpgp4fpr:%s]",
|
||||
KeyFormattingUtils.convertFingerprintToHex(fingerprint));
|
||||
}
|
||||
|
||||
protected static LinkedTokenResource fromUri (URI uri) {
|
||||
|
||||
if (!"openpgpid+token".equals(uri.getScheme())
|
||||
&& !"openpgpid+cookie".equals(uri.getScheme())) {
|
||||
Timber.e("unknown uri scheme in (suspected) linked id packet");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!uri.isOpaque()) {
|
||||
Timber.e("non-opaque uri in (suspected) linked id packet");
|
||||
return null;
|
||||
}
|
||||
|
||||
String specific = uri.getSchemeSpecificPart();
|
||||
if (!specific.contains("@")) {
|
||||
Timber.e("unknown uri scheme in linked id packet");
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] pieces = specific.split("@", 2);
|
||||
URI subUri = URI.create(pieces[1]);
|
||||
|
||||
Set<String> flags = new HashSet<>();
|
||||
HashMap<String,String> params = new HashMap<>();
|
||||
if (!pieces[0].isEmpty()) {
|
||||
String[] rawParams = pieces[0].split(";");
|
||||
for (String param : rawParams) {
|
||||
String[] p = param.split("=", 2);
|
||||
if (p.length == 1) {
|
||||
flags.add(param);
|
||||
} else {
|
||||
params.put(p[0], p[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return findResourceType(flags, params, subUri);
|
||||
|
||||
}
|
||||
|
||||
protected static LinkedTokenResource findResourceType (Set<String> flags,
|
||||
HashMap<String,String> params, URI subUri) {
|
||||
|
||||
LinkedTokenResource res;
|
||||
|
||||
res = GenericHttpsResource.create(flags, params, subUri);
|
||||
if (res != null) {
|
||||
return res;
|
||||
}
|
||||
// res = DnsResource.create(flags, params, subUri);
|
||||
// if (res != null) {
|
||||
// return res;
|
||||
// }
|
||||
res = TwitterResource.create(flags, params, subUri);
|
||||
if (res != null) {
|
||||
return res;
|
||||
}
|
||||
res = GithubResource.create(flags, params, subUri);
|
||||
if (res != null) {
|
||||
return res;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public URI toUri () {
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("openpgpid+token:");
|
||||
|
||||
// add flags
|
||||
if (mFlags != null) {
|
||||
boolean first = true;
|
||||
for (String flag : mFlags) {
|
||||
if (!first) {
|
||||
b.append(";");
|
||||
}
|
||||
first = false;
|
||||
b.append(flag);
|
||||
}
|
||||
}
|
||||
|
||||
// add parameters
|
||||
if (mParams != null) {
|
||||
boolean first = true;
|
||||
for (Entry<String, String> stringStringEntry : mParams.entrySet()) {
|
||||
if (!first) {
|
||||
b.append(";");
|
||||
}
|
||||
first = false;
|
||||
b.append(stringStringEntry.getKey()).append("=").append(stringStringEntry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
b.append("@");
|
||||
b.append(mSubUri);
|
||||
|
||||
return URI.create(b.toString());
|
||||
|
||||
}
|
||||
|
||||
public LinkedVerifyResult verify(Context context, byte[] fingerprint) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_LV, 0);
|
||||
|
||||
// Try to fetch resource. Logs for itself
|
||||
String res = null;
|
||||
try {
|
||||
res = fetchResource(context, log, 1);
|
||||
} catch (HttpStatusException e) {
|
||||
// log verbose output to logcat
|
||||
Timber.e("http error (" + e.getStatus() + "): " + e.getReason());
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR, 2, Integer.toString(e.getStatus()));
|
||||
} catch (MalformedURLException e) {
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR_URL, 2);
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "io error");
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR_IO, 2);
|
||||
} catch (JSONException e) {
|
||||
Timber.e(e, "json error");
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR_FORMAT, 2);
|
||||
}
|
||||
|
||||
if (res == null) {
|
||||
// if this is null, an error was recorded in fetchResource above
|
||||
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
Timber.d("Resource data: '" + res + "'");
|
||||
|
||||
return verifyString(log, 1, res, fingerprint);
|
||||
|
||||
}
|
||||
|
||||
protected abstract String fetchResource (Context context, OperationLog log, int indent)
|
||||
throws HttpStatusException, IOException, JSONException;
|
||||
|
||||
protected Matcher matchResource (OperationLog log, int indent, String res) {
|
||||
return magicPattern.matcher(res);
|
||||
}
|
||||
|
||||
protected LinkedVerifyResult verifyString (OperationLog log, int indent,
|
||||
String res,
|
||||
byte[] fingerprint) {
|
||||
|
||||
log.add(LogType.MSG_LV_MATCH, indent);
|
||||
Matcher match = matchResource(log, indent+1, res);
|
||||
if (!match.find()) {
|
||||
log.add(LogType.MSG_LV_MATCH_ERROR, 2);
|
||||
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
String candidateFp = match.group(1).toLowerCase();
|
||||
String fp = KeyFormattingUtils.convertFingerprintToHex(fingerprint);
|
||||
if (!fp.equals(candidateFp)) {
|
||||
log.add(LogType.MSG_LV_FP_ERROR, indent);
|
||||
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
log.add(LogType.MSG_LV_FP_OK, indent);
|
||||
|
||||
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_OK, log);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static CertificatePinner getCertificatePinner(String hostname, String[] pins){
|
||||
CertificatePinner.Builder builder = new CertificatePinner.Builder();
|
||||
for(String pin : pins){
|
||||
builder.add(hostname,pin);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
public static String getResponseBody(Request request, String... pins)
|
||||
throws IOException, HttpStatusException {
|
||||
|
||||
Timber.d("");
|
||||
OkHttpClient client;
|
||||
if (pins != null) {
|
||||
client = OkHttpClientFactory.getSimpleClientPinned(getCertificatePinner(request.url().url().getHost(), pins));
|
||||
} else {
|
||||
client = OkHttpClientFactory.getSimpleClient();
|
||||
}
|
||||
|
||||
Response response = client.newCall(request).execute();
|
||||
|
||||
|
||||
int statusCode = response.code();
|
||||
String reason = response.message();
|
||||
|
||||
if (statusCode != 200) {
|
||||
throw new HttpStatusException(statusCode, reason);
|
||||
}
|
||||
|
||||
return response.body().string();
|
||||
}
|
||||
|
||||
public static class HttpStatusException extends Throwable {
|
||||
|
||||
private final int mStatusCode;
|
||||
private final String mReason;
|
||||
|
||||
HttpStatusException(int statusCode, String reason) {
|
||||
super("http status " + statusCode + ": " + reason);
|
||||
mStatusCode = statusCode;
|
||||
mReason = reason;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return mStatusCode;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return mReason;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.linked;
|
||||
|
||||
import org.bouncycastle.util.Strings;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import timber.log.Timber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.DrawableRes;
|
||||
|
||||
/** The RawLinkedIdentity contains raw parsed data from a Linked Identity subpacket. */
|
||||
public class UriAttribute {
|
||||
|
||||
public final URI mUri;
|
||||
|
||||
protected UriAttribute(URI uri) {
|
||||
mUri = uri;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() {
|
||||
return Strings.toUTF8ByteArray(mUri.toASCIIString());
|
||||
}
|
||||
|
||||
public static UriAttribute fromAttributeData(byte[] data) throws IOException {
|
||||
WrappedUserAttribute att = WrappedUserAttribute.fromData(data);
|
||||
|
||||
byte[][] subpackets = att.getSubpackets();
|
||||
if (subpackets.length >= 1) {
|
||||
return fromSubpacketData(subpackets[0]);
|
||||
}
|
||||
|
||||
throw new IOException("no subpacket data");
|
||||
}
|
||||
|
||||
static UriAttribute fromSubpacketData(byte[] data) {
|
||||
|
||||
try {
|
||||
String uriStr = Strings.fromUTF8ByteArray(data);
|
||||
URI uri = URI.create(uriStr);
|
||||
|
||||
LinkedResource res = LinkedTokenResource.fromUri(uri);
|
||||
if (res == null) {
|
||||
return new UriAttribute(uri);
|
||||
}
|
||||
|
||||
return new LinkedAttribute(uri, res);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Timber.e("error parsing uri in (suspected) linked id packet");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static UriAttribute fromResource (LinkedTokenResource res) {
|
||||
return new UriAttribute(res.toUri());
|
||||
}
|
||||
|
||||
|
||||
public WrappedUserAttribute toUserAttribute () {
|
||||
return WrappedUserAttribute.fromSubpacket(WrappedUserAttribute.UAT_URI_ATTRIBUTE, getEncoded());
|
||||
}
|
||||
|
||||
public @DrawableRes int getDisplayIcon() {
|
||||
return R.drawable.ic_warning_grey_24dp;
|
||||
}
|
||||
|
||||
public String getDisplayTitle(Context context) {
|
||||
return "Unknown Identity";
|
||||
}
|
||||
|
||||
public String getDisplayComment(Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.linked.resources;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import de.measite.minidns.Client;
|
||||
import de.measite.minidns.DNSMessage;
|
||||
import de.measite.minidns.Question;
|
||||
import de.measite.minidns.Record;
|
||||
import de.measite.minidns.Record.CLASS;
|
||||
import de.measite.minidns.Record.TYPE;
|
||||
import de.measite.minidns.record.TXT;
|
||||
|
||||
public class DnsResource extends LinkedTokenResource {
|
||||
|
||||
final static Pattern magicPattern =
|
||||
Pattern.compile("openpgpid\\+token=([a-zA-Z0-9]+)(?:#|;)([a-zA-Z0-9]+)");
|
||||
|
||||
String mFqdn;
|
||||
CLASS mClass;
|
||||
TYPE mType;
|
||||
|
||||
DnsResource(Set<String> flags, HashMap<String, String> params, URI uri,
|
||||
String fqdn, CLASS clazz, TYPE type) {
|
||||
super(flags, params, uri);
|
||||
|
||||
mFqdn = fqdn;
|
||||
mClass = clazz;
|
||||
mType = type;
|
||||
}
|
||||
|
||||
public static String generateText(byte[] fingerprint) {
|
||||
|
||||
return String.format("openpgp4fpr=%s",
|
||||
KeyFormattingUtils.convertFingerprintToHex(fingerprint));
|
||||
|
||||
}
|
||||
|
||||
public static DnsResource createNew (String domain) {
|
||||
HashSet<String> flags = new HashSet<>();
|
||||
HashMap<String,String> params = new HashMap<>();
|
||||
URI uri = URI.create("dns:" + domain + "?TYPE=TXT");
|
||||
return create(flags, params, uri);
|
||||
}
|
||||
|
||||
public static DnsResource create(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||
if ( ! ("dns".equals(uri.getScheme())
|
||||
&& (flags == null || flags.isEmpty())
|
||||
&& (params == null || params.isEmpty()))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
String spec = uri.getSchemeSpecificPart();
|
||||
// If there are // at the beginning, this includes an authority - we don't support those!
|
||||
if (spec.startsWith("//")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] pieces = spec.split("\\?", 2);
|
||||
// In either case, part before a ? is the fqdn
|
||||
String fqdn = pieces[0];
|
||||
// There may be a query part
|
||||
/*
|
||||
if (pieces.length > 1) {
|
||||
// parse CLASS and TYPE query parameters
|
||||
}
|
||||
*/
|
||||
|
||||
CLASS clazz = CLASS.IN;
|
||||
TYPE type = TYPE.TXT;
|
||||
|
||||
return new DnsResource(flags, params, uri, fqdn, clazz, type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public String getFqdn() {
|
||||
return mFqdn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String fetchResource (Context context, OperationLog log, int indent) {
|
||||
|
||||
Client c = new Client();
|
||||
DNSMessage msg = c.query(new Question(mFqdn, mType, mClass));
|
||||
Record aw = msg.getAnswers()[0];
|
||||
TXT txt = (TXT) aw.getPayload();
|
||||
return txt.getText().toLowerCase();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Matcher matchResource(OperationLog log, int indent, String res) {
|
||||
return magicPattern.matcher(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @StringRes
|
||||
int getVerifiedText(boolean isSecret) {
|
||||
return isSecret ? R.string.linked_verified_secret_dns : R.string.linked_verified_dns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @DrawableRes int getDisplayIcon() {
|
||||
return R.drawable.linked_dns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayTitle(Context context) {
|
||||
return context.getString(R.string.linked_title_dns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayComment(Context context) {
|
||||
return mFqdn;
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.linked.resources;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
import okhttp3.Request;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class GenericHttpsResource extends LinkedTokenResource {
|
||||
|
||||
GenericHttpsResource(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||
super(flags, params, uri);
|
||||
}
|
||||
|
||||
public static String generateText (Context context, byte[] fingerprint) {
|
||||
String token = LinkedTokenResource.generate(fingerprint);
|
||||
|
||||
return String.format(context.getResources().getString(R.string.linked_id_generic_text),
|
||||
token, "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprint).substring(24));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String fetchResource (Context context, OperationLog log, int indent)
|
||||
throws HttpStatusException, IOException {
|
||||
|
||||
log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString());
|
||||
Request request = new Request.Builder()
|
||||
.url(mSubUri.toURL())
|
||||
.addHeader("User-Agent", "OpenKeychain")
|
||||
.build();
|
||||
return getResponseBody(request);
|
||||
|
||||
}
|
||||
|
||||
public static GenericHttpsResource createNew (URI uri) {
|
||||
HashSet<String> flags = new HashSet<>();
|
||||
flags.add("generic");
|
||||
HashMap<String,String> params = new HashMap<>();
|
||||
return create(flags, params, uri);
|
||||
}
|
||||
|
||||
public static GenericHttpsResource create(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||
if ( ! ("https".equals(uri.getScheme())
|
||||
&& flags != null && flags.size() == 1 && flags.contains("generic")
|
||||
&& (params == null || params.isEmpty()))) {
|
||||
return null;
|
||||
}
|
||||
return new GenericHttpsResource(flags, params, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @DrawableRes
|
||||
int getDisplayIcon() {
|
||||
return R.drawable.linked_https;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @StringRes
|
||||
int getVerifiedText(boolean isSecret) {
|
||||
return isSecret ? R.string.linked_verified_secret_https : R.string.linked_verified_https;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayTitle(Context context) {
|
||||
return context.getString(R.string.linked_title_https);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayComment(Context context) {
|
||||
return mSubUri.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getViewIntent() {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(mSubUri.toString()));
|
||||
return intent;
|
||||
}
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.linked.resources;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
import okhttp3.Request;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import timber.log.Timber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
public class GithubResource extends LinkedTokenResource {
|
||||
|
||||
final String mHandle;
|
||||
final String mGistId;
|
||||
|
||||
GithubResource(Set<String> flags, HashMap<String,String> params, URI uri,
|
||||
String handle, String gistId) {
|
||||
super(flags, params, uri);
|
||||
|
||||
mHandle = handle;
|
||||
mGistId = gistId;
|
||||
}
|
||||
|
||||
public static String generate(Context context, byte[] fingerprint) {
|
||||
String token = LinkedTokenResource.generate(fingerprint);
|
||||
|
||||
return String.format(context.getResources().getString(R.string.linked_id_github_text), token);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String fetchResource (Context context, OperationLog log, int indent)
|
||||
throws HttpStatusException, IOException, JSONException {
|
||||
|
||||
log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString());
|
||||
indent += 1;
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url("https://api.github.com/gists/" + mGistId)
|
||||
.addHeader("User-Agent", "OpenKeychain")
|
||||
.build();
|
||||
String response = getResponseBody(request);
|
||||
|
||||
JSONObject obj = new JSONObject(response);
|
||||
|
||||
JSONObject owner = obj.getJSONObject("owner");
|
||||
if (!mHandle.equals(owner.getString("login"))) {
|
||||
log.add(LogType.MSG_LV_ERROR_GITHUB_HANDLE, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
JSONObject files = obj.getJSONObject("files");
|
||||
Iterator<String> it = files.keys();
|
||||
if (it.hasNext()) {
|
||||
// TODO can there be multiple candidates?
|
||||
JSONObject file = files.getJSONObject(it.next());
|
||||
return file.getString("content");
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_LV_ERROR_GITHUB_NOT_FOUND, indent);
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings({ "deprecation", "unused" })
|
||||
public static GithubResource searchInGithubStream(
|
||||
Context context, String screenName, String needle, OperationLog log) {
|
||||
|
||||
// narrow the needle down to important part
|
||||
Matcher matcher = magicPattern.matcher(needle);
|
||||
if (!matcher.find()) {
|
||||
throw new AssertionError("Needle must contain token pattern! This is a programming error, please report.");
|
||||
}
|
||||
needle = matcher.group();
|
||||
|
||||
try {
|
||||
|
||||
JSONArray array; {
|
||||
Request request = new Request.Builder()
|
||||
.url("https://api.github.com/users/" + screenName + "/gists")
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeader("User-Agent", "OpenKeychain")
|
||||
.build();
|
||||
String response = getResponseBody(request);
|
||||
array = new JSONArray(response);
|
||||
}
|
||||
|
||||
for (int i = 0, j = Math.min(array.length(), 5); i < j; i++) {
|
||||
JSONObject obj = array.getJSONObject(i);
|
||||
|
||||
JSONObject files = obj.getJSONObject("files");
|
||||
Iterator<String> it = files.keys();
|
||||
if (it.hasNext()) {
|
||||
|
||||
JSONObject file = files.getJSONObject(it.next());
|
||||
String type = file.getString("type");
|
||||
if (!"text/plain".equals(type)) {
|
||||
continue;
|
||||
}
|
||||
String id = obj.getString("id");
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url("https://api.github.com/gists/" + id)
|
||||
.addHeader("User-Agent", "OpenKeychain")
|
||||
.build();
|
||||
|
||||
JSONObject gistObj = new JSONObject(getResponseBody(request));
|
||||
JSONObject gistFiles = gistObj.getJSONObject("files");
|
||||
Iterator<String> gistIt = gistFiles.keys();
|
||||
if (!gistIt.hasNext()) {
|
||||
continue;
|
||||
}
|
||||
// TODO can there be multiple candidates?
|
||||
JSONObject gistFile = gistFiles.getJSONObject(gistIt.next());
|
||||
String content = gistFile.getString("content");
|
||||
if (!content.contains(needle)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
URI uri = URI.create("https://gist.github.com/" + screenName + "/" + id);
|
||||
return create(uri);
|
||||
}
|
||||
}
|
||||
|
||||
// update the results with the body of the response
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR_NOTHING, 2);
|
||||
return null;
|
||||
|
||||
} catch (HttpStatusException e) {
|
||||
// log verbose output to logcat
|
||||
Timber.e("http error (" + e.getStatus() + "): " + e.getReason());
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR, 2, Integer.toString(e.getStatus()));
|
||||
} catch (MalformedURLException e) {
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR_URL, 2);
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "io error");
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR_IO, 2);
|
||||
} catch (JSONException e) {
|
||||
Timber.e(e, "json error");
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR_FORMAT, 2);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static GithubResource create(URI uri) {
|
||||
return create(new HashSet<String>(), new HashMap<String,String>(), uri);
|
||||
}
|
||||
|
||||
public static GithubResource create(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||
|
||||
// no params or flags
|
||||
if (!flags.isEmpty() || !params.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Pattern p = Pattern.compile("https://gist\\.github\\.com/([a-zA-Z0-9_-]+)/([0-9a-f]+)");
|
||||
Matcher match = p.matcher(uri.toString());
|
||||
if (!match.matches()) {
|
||||
return null;
|
||||
}
|
||||
String handle = match.group(1);
|
||||
String gistId = match.group(2);
|
||||
|
||||
return new GithubResource(flags, params, uri, handle, gistId);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public @DrawableRes
|
||||
int getDisplayIcon() {
|
||||
return R.drawable.linked_github;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @StringRes
|
||||
int getVerifiedText(boolean isSecret) {
|
||||
return isSecret ? R.string.linked_verified_secret_github : R.string.linked_verified_github;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayTitle(Context context) {
|
||||
return context.getString(R.string.linked_title_github);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayComment(Context context) {
|
||||
return mHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getViewIntent() {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(mSubUri.toString()));
|
||||
return intent;
|
||||
}
|
||||
}
|
|
@ -1,276 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.linked.resources;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.util.Log;
|
||||
|
||||
import com.textuality.keybase.lib.JWalk;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import timber.log.Timber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TwitterResource extends LinkedTokenResource {
|
||||
|
||||
public static final String[] CERT_PINS = null; /*(new String[] {
|
||||
// Symantec Class 3 Secure Server CA - G4
|
||||
"513fb9743870b73440418d30930699ff"
|
||||
};*/
|
||||
|
||||
final String mHandle;
|
||||
final String mTweetId;
|
||||
|
||||
TwitterResource(Set<String> flags, HashMap<String,String> params,
|
||||
URI uri, String handle, String tweetId) {
|
||||
super(flags, params, uri);
|
||||
|
||||
mHandle = handle;
|
||||
mTweetId = tweetId;
|
||||
}
|
||||
|
||||
public static TwitterResource create(URI uri) {
|
||||
return create(new HashSet<String>(), new HashMap<String,String>(), uri);
|
||||
}
|
||||
|
||||
public static TwitterResource create(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||
|
||||
// no params or flags
|
||||
if (!flags.isEmpty() || !params.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Pattern p = Pattern.compile("https://twitter\\.com/([a-zA-Z0-9_]+)/status/([0-9]+)");
|
||||
Matcher match = p.matcher(uri.toString());
|
||||
if (!match.matches()) {
|
||||
return null;
|
||||
}
|
||||
String handle = match.group(1);
|
||||
String tweetId = match.group(2);
|
||||
|
||||
return new TwitterResource(flags, params, uri, handle, tweetId);
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
protected String fetchResource(Context context, OperationLog log, int indent)
|
||||
throws IOException, HttpStatusException, JSONException {
|
||||
|
||||
String authToken;
|
||||
try {
|
||||
authToken = getAuthToken(context);
|
||||
} catch (IOException | HttpStatusException | JSONException e) {
|
||||
log.add(LogType.MSG_LV_ERROR_TWITTER_AUTH, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
// construct a normal HTTPS request and include an Authorization
|
||||
// header with the value of Bearer <>
|
||||
Request request = new Request.Builder()
|
||||
.url("https://api.twitter.com/1.1/statuses/show.json"
|
||||
+ "?id=" + mTweetId
|
||||
+ "&include_entities=false")
|
||||
.addHeader("Authorization", "Bearer " + authToken)
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeader("User-Agent", "OpenKeychain")
|
||||
.build();
|
||||
|
||||
try {
|
||||
String response = getResponseBody(request, CERT_PINS);
|
||||
JSONObject obj = new JSONObject(response);
|
||||
JSONObject user = obj.getJSONObject("user");
|
||||
if (!mHandle.equalsIgnoreCase(user.getString("screen_name"))) {
|
||||
log.add(LogType.MSG_LV_ERROR_TWITTER_HANDLE, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
// update the results with the body of the response
|
||||
return obj.getString("text");
|
||||
} catch (JSONException e) {
|
||||
log.add(LogType.MSG_LV_ERROR_TWITTER_RESPONSE, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @DrawableRes int getDisplayIcon() {
|
||||
return R.drawable.linked_twitter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @StringRes
|
||||
int getVerifiedText(boolean isSecret) {
|
||||
return isSecret ? R.string.linked_verified_secret_twitter : R.string.linked_verified_twitter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayTitle(Context context) {
|
||||
return context.getString(R.string.linked_title_twitter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayComment(Context context) {
|
||||
return "@" + mHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getViewIntent() {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(mSubUri.toString()));
|
||||
return intent;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static TwitterResource searchInTwitterStream(
|
||||
Context context, String screenName, String needle, OperationLog log) {
|
||||
|
||||
String authToken;
|
||||
try {
|
||||
authToken = getAuthToken(context);
|
||||
} catch (IOException | HttpStatusException | JSONException e) {
|
||||
log.add(LogType.MSG_LV_ERROR_TWITTER_AUTH, 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url("https://api.twitter.com/1.1/statuses/user_timeline.json"
|
||||
+ "?screen_name=" + screenName
|
||||
+ "&count=15"
|
||||
+ "&include_rts=false"
|
||||
+ "&trim_user=true"
|
||||
+ "&exclude_replies=true")
|
||||
.addHeader("Authorization", "Bearer " + authToken)
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeader("User-Agent", "OpenKeychain")
|
||||
.build();
|
||||
|
||||
try {
|
||||
String response = getResponseBody(request, CERT_PINS);
|
||||
JSONArray array = new JSONArray(response);
|
||||
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
JSONObject obj = array.getJSONObject(i);
|
||||
String tweet = obj.getString("text");
|
||||
if (tweet.contains(needle)) {
|
||||
String id = obj.getString("id_str");
|
||||
URI uri = URI.create("https://twitter.com/" + screenName + "/status/" + id);
|
||||
return create(uri);
|
||||
}
|
||||
}
|
||||
|
||||
// update the results with the body of the response
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR_NOTHING, 1);
|
||||
return null;
|
||||
|
||||
} catch (HttpStatusException e) {
|
||||
// log verbose output to logcat
|
||||
Timber.e("http error (" + e.getStatus() + "): " + e.getReason());
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR, 1, Integer.toString(e.getStatus()));
|
||||
} catch (MalformedURLException e) {
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR_URL, 1);
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "io error");
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR_IO, 1);
|
||||
} catch (JSONException e) {
|
||||
Timber.e(e, "json error");
|
||||
log.add(LogType.MSG_LV_FETCH_ERROR_FORMAT, 1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String cachedAuthToken;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static String getAuthToken(Context context)
|
||||
throws IOException, HttpStatusException, JSONException {
|
||||
if (cachedAuthToken != null) {
|
||||
return cachedAuthToken;
|
||||
}
|
||||
String base64Encoded = rot13("D293FQqanH0jH29KIaWJER5DomqSGRE2Ewc1LJACn3cbD1c"
|
||||
+ "Fq1bmqSAQAz5MI2cIHKOuo3cPoRAQI1OyqmIVFJS6LHMXq2g6MRLkIj") + "==";
|
||||
|
||||
RequestBody requestBody = RequestBody.create(
|
||||
MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"),
|
||||
"grant_type=client_credentials");
|
||||
|
||||
// Step 2: Obtain a bearer token
|
||||
Request request = new Request.Builder()
|
||||
.url("https://api.twitter.com/oauth2/token")
|
||||
.addHeader("Authorization", "Basic " + base64Encoded)
|
||||
.addHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
|
||||
.addHeader("User-Agent", "OpenKeychain")
|
||||
.post(requestBody)
|
||||
.build();
|
||||
|
||||
JSONObject rawAuthorization = new JSONObject(getResponseBody(request, CERT_PINS));
|
||||
|
||||
// Applications should verify that the value associated with the
|
||||
// token_type key of the returned object is bearer
|
||||
if (!"bearer".equals(JWalk.getString(rawAuthorization, "token_type"))) {
|
||||
throw new JSONException("Expected bearer token in response!");
|
||||
}
|
||||
|
||||
cachedAuthToken = rawAuthorization.getString("access_token");
|
||||
return cachedAuthToken;
|
||||
|
||||
}
|
||||
|
||||
public static String rot13(String input) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
char c = input.charAt(i);
|
||||
if (c >= 'a' && c <= 'm') c += 13;
|
||||
else if (c >= 'A' && c <= 'M') c += 13;
|
||||
else if (c >= 'n' && c <= 'z') c -= 13;
|
||||
else if (c >= 'N' && c <= 'Z') c -= 13;
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.operations.results;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
public class LinkedVerifyResult extends OperationResult {
|
||||
|
||||
public LinkedVerifyResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
}
|
||||
|
||||
/** Construct from a parcel - trivial because we have no extra data. */
|
||||
public LinkedVerifyResult(Parcel source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
}
|
||||
|
||||
public static Creator<LinkedVerifyResult> CREATOR = new Creator<LinkedVerifyResult>() {
|
||||
public LinkedVerifyResult createFromParcel(final Parcel source) {
|
||||
return new LinkedVerifyResult(source);
|
||||
}
|
||||
|
||||
public LinkedVerifyResult[] newArray(final int size) {
|
||||
return new LinkedVerifyResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -20,11 +20,8 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -34,41 +31,30 @@ import android.widget.ImageView;
|
|||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.UriAttribute;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter.ViewHolder;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.AutocryptPeerInfo;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.LinkedIdInfo;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.UserIdInfo;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
|
||||
|
||||
|
||||
public class IdentityAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||
private static final int VIEW_TYPE_USER_ID = 0;
|
||||
private static final int VIEW_TYPE_LINKED_ID = 1;
|
||||
|
||||
|
||||
private final Context context;
|
||||
private final LayoutInflater layoutInflater;
|
||||
private final IdentityClickListener identityClickListener;
|
||||
|
||||
private List<IdentityInfo> data;
|
||||
private boolean isSecret;
|
||||
|
||||
|
||||
public IdentityAdapter(Context context, IdentityClickListener identityClickListener) {
|
||||
super();
|
||||
this.layoutInflater = LayoutInflater.from(context);
|
||||
this.context = context;
|
||||
this.identityClickListener = identityClickListener;
|
||||
}
|
||||
|
||||
public void setData(List<IdentityInfo> data, boolean isSecret) {
|
||||
public void setData(List<IdentityInfo> data) {
|
||||
this.data = data;
|
||||
this.isSecret = isSecret;
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
@ -83,8 +69,6 @@ public class IdentityAdapter extends RecyclerView.Adapter<ViewHolder> {
|
|||
} else {
|
||||
((UserIdViewHolder) holder).bind((UserIdInfo) info);
|
||||
}
|
||||