Remove experimental Linked Identities feature

This commit is contained in:
Vincent Breitmoser 2018-10-22 12:49:52 +02:00
parent 7e5a153a04
commit 2cc22c6b65
59 changed files with 19 additions and 5054 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
} else if (viewType == VIEW_TYPE_LINKED_ID) {
((LinkedIdViewHolder) holder).bind(context, (LinkedIdInfo) info, isSecret);
} else {
throw new IllegalStateException("unhandled identitytype!");
}
@ -96,9 +80,6 @@ public class IdentityAdapter extends RecyclerView.Adapter<ViewHolder> {
if (viewType == VIEW_TYPE_USER_ID) {
return new UserIdViewHolder(
layoutInflater.inflate(R.layout.view_key_identity_user_id, parent, false), identityClickListener);
} else if (viewType == VIEW_TYPE_LINKED_ID) {
return new LinkedIdViewHolder(layoutInflater.inflate(R.layout.linked_id_item, parent, false),
identityClickListener);
} else {
throw new IllegalStateException("unhandled identitytype!");
}
@ -109,8 +90,6 @@ public class IdentityAdapter extends RecyclerView.Adapter<ViewHolder> {
IdentityInfo info = data.get(position);
if (info instanceof UserIdInfo || info instanceof AutocryptPeerInfo) {
return VIEW_TYPE_USER_ID;
} else if (info instanceof LinkedIdInfo) {
return VIEW_TYPE_LINKED_ID;
} else {
throw new IllegalStateException("unhandled identitytype!");
}
@ -131,69 +110,6 @@ public class IdentityAdapter extends RecyclerView.Adapter<ViewHolder> {
}
}
public static class LinkedIdViewHolder extends ViewHolder {
public final ImageView vVerified;
final private ImageView vIcon;
final private TextView vTitle;
final private TextView vComment;
public LinkedIdViewHolder(View view, final IdentityClickListener identityClickListener) {
super(view);
vVerified = view.findViewById(R.id.linked_id_certified_icon);
vIcon = view.findViewById(R.id.linked_id_type_icon);
vTitle = view.findViewById(R.id.linked_id_title);
vComment = view.findViewById(R.id.linked_id_comment);
view.setOnClickListener(v -> {
if (identityClickListener != null) {
identityClickListener.onClickIdentity(getAdapterPosition());
}
});
}
public void bind(Context context, LinkedIdInfo info, boolean isSecret) {
bindVerified(context, info, isSecret);
UriAttribute uriAttribute = info.getLinkedAttribute();
bind(context, uriAttribute);
}
public void bind(Context context, UriAttribute uriAttribute) {
vTitle.setText(uriAttribute.getDisplayTitle(context));
String comment = uriAttribute.getDisplayComment(context);
if (comment != null) {
vComment.setVisibility(View.VISIBLE);
vComment.setText(comment);
} else {
vComment.setVisibility(View.GONE);
}
vIcon.setImageResource(uriAttribute.getDisplayIcon());
}
private void bindVerified(Context context, IdentityInfo info, boolean isSecret) {
if (!isSecret) {
if (info.isVerified()) {
KeyFormattingUtils.setStatusImage(context, vVerified,
null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
} else {
KeyFormattingUtils.setStatusImage(context, vVerified,
null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
}
}
}
public void seekAttention() {
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000);
anim.setStartDelay(200);
anim.start();
}
}
}
private static class UserIdViewHolder extends ViewHolder {
private final TextView vName;
private final TextView vAddress;

View File

@ -8,10 +8,10 @@ import android.arch.lifecycle.Transformations;
import android.arch.lifecycle.ViewModel;
import android.content.Context;
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
import org.sufficientlysecure.keychain.livedata.GenericLiveData;
import org.sufficientlysecure.keychain.model.KeyMetadata;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao;
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo;
import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao;
@ -26,13 +26,12 @@ public class KeyFragmentViewModel extends ViewModel {
private LiveData<SystemContactInfo> systemContactInfo;
private LiveData<KeyMetadata> keyserverStatus;
LiveData<List<IdentityInfo>> getIdentityInfo(Context context, LiveData<UnifiedKeyInfo> unifiedKeyInfoLiveData,
boolean showLinkedIds) {
LiveData<List<IdentityInfo>> getIdentityInfo(Context context, LiveData<UnifiedKeyInfo> unifiedKeyInfoLiveData) {
if (identityInfo == null) {
IdentityDao identityDao = IdentityDao.getInstance(context);
identityInfo = Transformations.switchMap(unifiedKeyInfoLiveData,
(unifiedKeyInfo) -> unifiedKeyInfo == null ? null : new GenericLiveData<>(context,
() -> identityDao.getIdentityInfos(unifiedKeyInfo.master_key_id(), showLinkedIds)));
() -> identityDao.getIdentityInfos(unifiedKeyInfo.master_key_id())));
}
return identityInfo;
}

View File

@ -1,529 +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.ui.keyview;
import java.util.Collections;
import java.util.List;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.support.v4.content.ContextCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextSwitcher;
import android.widget.TextView;
import android.widget.ViewAnimator;
import org.sufficientlysecure.keychain.Constants.key;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
import org.sufficientlysecure.keychain.linked.LinkedResource;
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
import org.sufficientlysecure.keychain.linked.UriAttribute;
import org.sufficientlysecure.keychain.daos.CertificationDao;
import org.sufficientlysecure.keychain.livedata.GenericLiveData;
import org.sufficientlysecure.keychain.model.Certification.CertDetails;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.keyview.LinkedIdViewFragment.ViewHolder.VerifyState;
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao;
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.LinkedIdInfo;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
import org.sufficientlysecure.keychain.ui.widget.CertListWidget;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
import timber.log.Timber;
public class LinkedIdViewFragment extends CryptoOperationFragment implements OnBackStackChangedListener {
private static final String ARG_LID_RANK = "rank";
private static final String ARG_IS_SECRET = "verified";
private static final String ARG_MASTER_KEY_ID = "master_key_id";
private long masterKeyId;
private boolean isSecret;
private UriAttribute linkedId;
private LinkedTokenResource linkedResource;
private AsyncTask taskInProgress;
private ViewHolder viewHolder;
private int lidRank;
private long certifyKeyId;
public static LinkedIdViewFragment newInstance(long masterKeyId, int rank, boolean isSecret) {
LinkedIdViewFragment frag = new LinkedIdViewFragment();
Bundle args = new Bundle();
args.putInt(ARG_LID_RANK, rank);
args.putBoolean(ARG_IS_SECRET, isSecret);
args.putLong(ARG_MASTER_KEY_ID, masterKeyId);
frag.setArguments(args);
return frag;
}
public LinkedIdViewFragment() {
// IMPORTANT: the id must be unique in the ViewKeyActivity CryptoOperationHelper id namespace!
// no initial progress message -> we handle progress ourselves!
super(5, null);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
lidRank = args.getInt(ARG_LID_RANK);
isSecret = args.getBoolean(ARG_IS_SECRET);
masterKeyId = args.getLong(ARG_MASTER_KEY_ID);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
LinkedIdViewModel viewModel = ViewModelProviders.of(this).get(LinkedIdViewModel.class);
viewModel.getLinkedIdInfo(requireContext(), masterKeyId, lidRank).observe(this, this::onLinkedIdInfoLoaded);
viewModel.getCertifyingKeys(requireContext()).observe(this, viewHolder.vKeySpinner::setData);
}
private void onLinkedIdInfoLoaded(LinkedIdInfo linkedIdInfo) {
if (linkedIdInfo == null) {
Timber.e("error loading identity");
Notify.create(getActivity(), "Error loading linked identity!",
Notify.LENGTH_LONG, Style.ERROR).show();
finishFragment();
return;
}
loadIdentity(linkedIdInfo.getLinkedAttribute(), linkedIdInfo.isVerified());
}
public void finishFragment() {
new Handler().post(() -> {
FragmentManager manager = getFragmentManager();
manager.removeOnBackStackChangedListener(LinkedIdViewFragment.this);
manager.popBackStack("linked_id", FragmentManager.POP_BACK_STACK_INCLUSIVE);
});
}
private void loadIdentity(LinkedAttribute linkedId, boolean isVerified) {
this.linkedId = linkedId;
LinkedResource res = ((LinkedAttribute) this.linkedId).mResource;
linkedResource = (LinkedTokenResource) res;
if (!isSecret) {
if (isVerified) {
KeyFormattingUtils.setStatusImage(getContext(), viewHolder.mLinkedIdHolder.vVerified,
null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
} else {
KeyFormattingUtils.setStatusImage(getContext(), viewHolder.mLinkedIdHolder.vVerified,
null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
}
} else {
viewHolder.mLinkedIdHolder.vVerified.setImageResource(R.drawable.octo_link_24dp);
}
viewHolder.mLinkedIdHolder.bind(getContext(), this.linkedId);
setShowVerifying(false);
if (linkedResource.isViewable()) {
viewHolder.vButtonView.setVisibility(View.VISIBLE);
viewHolder.vButtonView.setOnClickListener(v -> {
Intent intent = linkedResource.getViewIntent();
if (intent == null) {
return;
}
startActivity(intent);
});
} else {
viewHolder.vButtonView.setVisibility(View.GONE);
}
}
static class ViewHolder {
private final View vButtonView;
private final ViewAnimator vVerifyingContainer;
private final ViewAnimator vItemCertified;
private final View vKeySpinnerContainer;
IdentityAdapter.LinkedIdViewHolder mLinkedIdHolder;
private ViewAnimator vButtonSwitcher;
private CertListWidget vLinkedCerts;
private KeySpinner vKeySpinner;
private final View vButtonVerify;
private final View vButtonRetry;
private final View vButtonConfirm;
private final ViewAnimator vProgress;
private final TextSwitcher vText;
ViewHolder(View root) {
vLinkedCerts = root.findViewById(R.id.linked_id_certs);
vKeySpinner = root.findViewById(R.id.cert_key_spinner);
vKeySpinnerContainer = root.findViewById(R.id.cert_key_spincontainer);
vButtonSwitcher = root.findViewById(R.id.button_animator);
mLinkedIdHolder = new IdentityAdapter.LinkedIdViewHolder(root, null);
vButtonVerify = root.findViewById(R.id.button_verify);
vButtonRetry = root.findViewById(R.id.button_retry);
vButtonConfirm = root.findViewById(R.id.button_confirm);
vButtonView = root.findViewById(R.id.button_view);
vVerifyingContainer = root.findViewById(R.id.linked_verify_container);
vItemCertified = root.findViewById(R.id.linked_id_certified);
vProgress = root.findViewById(R.id.linked_cert_progress);
vText = root.findViewById(R.id.linked_cert_text);
vKeySpinner.setShowNone(R.string.choice_select_cert);
}
enum VerifyState {
VERIFYING, VERIFY_OK, VERIFY_ERROR, CERTIFYING
}
void setVerifyingState(Context context, VerifyState state, boolean isSecret) {
switch (state) {
case VERIFYING:
vProgress.setDisplayedChild(0);
vText.setText(context.getString(R.string.linked_text_verifying));
vKeySpinnerContainer.setVisibility(View.GONE);
break;
case VERIFY_OK:
vProgress.setDisplayedChild(1);
if (!isSecret) {
showButton(2);
if (!vKeySpinner.isSingleEntry()) {
vKeySpinnerContainer.setVisibility(View.VISIBLE);
}
} else {
showButton(1);
vKeySpinnerContainer.setVisibility(View.GONE);
}
break;
case VERIFY_ERROR:
showButton(1);
vProgress.setDisplayedChild(2);
vText.setText(context.getString(R.string.linked_text_error));
vKeySpinnerContainer.setVisibility(View.GONE);
break;
case CERTIFYING:
vProgress.setDisplayedChild(0);
vText.setText(context.getString(R.string.linked_text_confirming));
vKeySpinnerContainer.setVisibility(View.GONE);
vButtonConfirm.setEnabled(false);
break;
}
}
void showVerifyingContainer(Context context, boolean show, boolean isSecret) {
if (vVerifyingContainer.getDisplayedChild() == (show ? 1 : 0)) {
return;
}
vVerifyingContainer.setInAnimation(context, show ? R.anim.fade_in_up : R.anim.fade_in_down);
vVerifyingContainer.setOutAnimation(context, show ? R.anim.fade_out_up : R.anim.fade_out_down);
vVerifyingContainer.setDisplayedChild(show ? 1 : 0);
vItemCertified.setInAnimation(context, show ? R.anim.fade_in_up : R.anim.fade_in_down);
vItemCertified.setOutAnimation(context, show ? R.anim.fade_out_up : R.anim.fade_out_down);
vItemCertified.setDisplayedChild(show || isSecret ? 1 : 0);
}
void showButton(int which) {
if (vButtonSwitcher.getDisplayedChild() == which) {
return;
}
vButtonSwitcher.setDisplayedChild(which);
}
}
private boolean mVerificationState = false;
/** Switches between the 'verifying' ui bit and certificate status. This method
* must behave correctly in all states, showing or hiding the appropriate views
* and cancelling pending operations where necessary.
*
* This method also handles back button functionality in combination with
* onBackStateChanged.
*/
void setShowVerifying(boolean show) {
if (!show) {
if (taskInProgress != null) {
taskInProgress.cancel(false);
taskInProgress = null;
}
getFragmentManager().removeOnBackStackChangedListener(this);
new Handler().post(() -> getFragmentManager().popBackStack("verification",
FragmentManager.POP_BACK_STACK_INCLUSIVE));
if (!mVerificationState) {
return;
}
mVerificationState = false;
viewHolder.showButton(0);
viewHolder.vKeySpinnerContainer.setVisibility(View.GONE);
viewHolder.showVerifyingContainer(getContext(), false, isSecret);
return;
}
if (mVerificationState) {
return;
}
mVerificationState = true;
FragmentManager manager = getFragmentManager();
manager.beginTransaction().addToBackStack("verification").commit();
manager.executePendingTransactions();
manager.addOnBackStackChangedListener(this);
viewHolder.showVerifyingContainer(getContext(), true, isSecret);
}
@Override
public void onBackStackChanged() {
setShowVerifying(false);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.linked_id_view_fragment, superContainer, false);
Context context = getContext();
if (context == null) {
throw new NullPointerException();
}
viewHolder = new ViewHolder(root);
root.setTag(viewHolder);
((ImageView) root.findViewById(R.id.status_icon_verified))
.setColorFilter(ContextCompat.getColor(context, R.color.android_green_light),
PorterDuff.Mode.SRC_IN);
((ImageView) root.findViewById(R.id.status_icon_invalid))
.setColorFilter(ContextCompat.getColor(context, R.color.android_red_light),
PorterDuff.Mode.SRC_IN);
viewHolder.vButtonVerify.setOnClickListener(v -> verifyResource());
viewHolder.vButtonRetry.setOnClickListener(v -> verifyResource());
viewHolder.vButtonConfirm.setOnClickListener(v -> initiateCertifying());
LinkedIdViewModel viewModel = ViewModelProviders.of(this).get(LinkedIdViewModel.class);
viewModel.getCertDetails(context, masterKeyId, lidRank).observe(this, this::onLoadCertDetails);
return root;
}
private void onLoadCertDetails(CertDetails certDetails) {
viewHolder.vLinkedCerts.setData(certDetails, isSecret);
}
void verifyResource() {
// only one at a time (no sync needed, taskInProgress is only touched in ui thread)
if (taskInProgress != null) {
return;
}
setShowVerifying(true);
viewHolder.vKeySpinnerContainer.setVisibility(View.GONE);
viewHolder.setVerifyingState(getContext(), VerifyState.VERIFYING, isSecret);
taskInProgress = new AsyncTask<Void,Void,LinkedVerifyResult>() {
@Override
protected LinkedVerifyResult doInBackground(Void... params) {
FragmentActivity activity = getActivity();
byte[] fingerprint;
try {
fingerprint = KeyRepository.create(activity).getFingerprintByKeyId(masterKeyId);
} catch (NotFoundException e) {
throw new IllegalStateException("Key to verify linked id for must exist in db!");
}
long timer = System.currentTimeMillis();
LinkedVerifyResult result = linkedResource.verify(activity, fingerprint);
// ux flow: this operation should take at last a second
timer = System.currentTimeMillis() -timer;
if (timer < 1000) try {
Thread.sleep(1000 -timer);
} catch (InterruptedException e) {
// never mind
}
return result;
}
@Override
protected void onPostExecute(LinkedVerifyResult result) {
if (isCancelled()) {
return;
}
if (result.success()) {
viewHolder.vText.setText(getString(linkedResource.getVerifiedText(isSecret)));
// hack to preserve bold text
((TextView) viewHolder.vText.getCurrentView()).setText(
linkedResource.getVerifiedText(isSecret));
viewHolder.setVerifyingState(getContext(), VerifyState.VERIFY_OK, isSecret);
viewHolder.mLinkedIdHolder.seekAttention();
} else {
viewHolder.setVerifyingState(getContext(), VerifyState.VERIFY_ERROR, isSecret);
result.createNotify(getActivity()).show();
}
taskInProgress = null;
}
}.execute();
}
private void initiateCertifying() {
if (isSecret) {
return;
}
// get the user's passphrase for this key (if required)
certifyKeyId = viewHolder.vKeySpinner.getSelectedKeyId();
if (certifyKeyId == key.none || certifyKeyId == key.symmetric) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
SubtleAttentionSeeker.tintBackground(viewHolder.vKeySpinnerContainer, 600).start();
} else {
Notify.create(getActivity(), R.string.select_key_to_certify, Style.ERROR).show();
}
return;
}
viewHolder.setVerifyingState(getContext(), VerifyState.CERTIFYING, false);
cryptoOperation();
}
@Override
public void onCryptoOperationCancelled() {
super.onCryptoOperationCancelled();
// go back to 'verified ok'
setShowVerifying(false);
}
@Nullable
@Override
public Parcelable createOperationInput() {
CertifyAction action = CertifyAction.createForUserAttributes(masterKeyId,
Collections.singletonList(linkedId.toUserAttribute()));
// fill values for this action
CertifyActionsParcel.Builder builder = CertifyActionsParcel.builder(certifyKeyId);
builder.addActions(Collections.singletonList(action));
return builder.build();
}
@Override
public void onCryptoOperationSuccess(OperationResult result) {
result.createNotify(getActivity()).show();
// no need to do anything else, we will get a loader refresh!
}
@Override
public void onCryptoOperationError(OperationResult result) {
result.createNotify(getActivity()).show();
}
@Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return true;
}
public static class LinkedIdViewModel extends ViewModel {
LiveData<List<UnifiedKeyInfo>> certifyingKeysLiveData;
LiveData<CertDetails> certDetailsLiveData;
LiveData<LinkedIdInfo> linkedIfInfoLiveData;
LiveData<List<UnifiedKeyInfo>> getCertifyingKeys(Context context) {
if (certifyingKeysLiveData == null) {
certifyingKeysLiveData = new GenericLiveData<>(context, () -> {
KeyRepository keyRepository = KeyRepository.create(context);
return keyRepository.getAllUnifiedKeyInfoWithSecret();
});
}
return certifyingKeysLiveData;
}
LiveData<CertDetails> getCertDetails(Context context, long masterKeyId, int lidRank) {
if (certDetailsLiveData == null) {
CertificationDao certificationDao = CertificationDao.getInstance(context);
certDetailsLiveData = new GenericLiveData<>(context, masterKeyId,
() -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank));
}
return certDetailsLiveData;
}
public LiveData<LinkedIdInfo> getLinkedIdInfo(Context context, long masterKeyId, int lidRank) {
if (linkedIfInfoLiveData == null) {
IdentityDao identityDao = IdentityDao.getInstance(context);
linkedIfInfoLiveData = new GenericLiveData<>(context, masterKeyId,
() -> identityDao.getLinkedIdInfo(masterKeyId, lidRank));
}
return linkedIfInfoLiveData;
}
}
}

View File

@ -41,17 +41,16 @@ import android.view.ViewGroup;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
import org.sufficientlysecure.keychain.model.KeyMetadata;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter;
import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter.IdentityClickListener;
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
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.keyview.loader.SubkeyStatusDao.KeyHealthStatus;
import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus;
@ -62,8 +61,6 @@ import org.sufficientlysecure.keychain.ui.keyview.view.KeyHealthView;
import org.sufficientlysecure.keychain.ui.keyview.view.KeyStatusList.KeyDisplayStatus;
import org.sufficientlysecure.keychain.ui.keyview.view.KeyserverStatusView;
import org.sufficientlysecure.keychain.ui.keyview.view.SystemContactCardView;
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
import org.sufficientlysecure.keychain.util.Preferences;
import timber.log.Timber;
@ -128,8 +125,7 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
KeyFragmentViewModel model = ViewModelProviders.of(this).get(KeyFragmentViewModel.class);
boolean showLinkedIds = Preferences.getPreferences(context).getExperimentalEnableLinkedIdentities();
model.getIdentityInfo(context, unifiedKeyInfoLiveData, showLinkedIds).observe(this, this::onLoadIdentityInfo);
model.getIdentityInfo(context, unifiedKeyInfoLiveData).observe(this, this::onLoadIdentityInfo);
model.getKeyserverStatus(context, unifiedKeyInfoLiveData).observe(this, this::onLoadKeyMetadata);
model.getSystemContactInfo(context, unifiedKeyInfoLiveData).observe(this, this::onLoadSystemContact);
model.getSubkeyStatus(context, unifiedKeyInfoLiveData).observe(this, this::onLoadSubkeyStatus);
@ -238,21 +234,12 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
return;
}
Context context = requireContext();
this.unifiedKeyInfo = unifiedKeyInfo;
boolean showLinkedIds = Preferences.getPreferences(context).getExperimentalEnableLinkedIdentities();
boolean isSecret = unifiedKeyInfo.has_any_secret();
identitiesCardView.setAddLinkedIdButtonVisible(showLinkedIds && isSecret);
identitiesCardView.setIdentitiesCardListener((v) -> addLinkedIdentity());
}
private void showIdentityInfo(final int position) {
IdentityInfo info = identitiesAdapter.getInfo(position);
if (info instanceof LinkedIdInfo) {
showLinkedId((LinkedIdInfo) info);
} else if (info instanceof UserIdInfo) {
if (info instanceof UserIdInfo) {
showUserIdInfo((UserIdInfo) info);
} else if (info instanceof AutocryptPeerInfo) {
Intent autocryptPeerIntent = ((AutocryptPeerInfo) info).getAutocryptPeerIntent();
@ -266,12 +253,6 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
showContextMenu(position, anchor);
}
private void showLinkedId(final LinkedIdInfo info) {
LinkedIdViewFragment frag = LinkedIdViewFragment.newInstance(info.getMasterKeyId(), info.getRank(), unifiedKeyInfo.has_any_secret());
switchToFragment(frag, "linked_id");
}
private void showUserIdInfo(UserIdInfo info) {
if (!unifiedKeyInfo.has_any_secret()) {
UserIdInfoDialogFragment dialogFragment = UserIdInfoDialogFragment.newInstance(false, info.isVerified());
@ -279,12 +260,6 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
}
}
private void addLinkedIdentity() {
Intent intent = new Intent(requireContext(), LinkedIdWizard.class);
intent.putExtra(LinkedIdWizard.EXTRA_MASTER_KEY_ID, unifiedKeyInfo.master_key_id());
startActivity(intent);
}
public void onClickForgetIdentity(int position) {
AutocryptPeerInfo info = (AutocryptPeerInfo) identitiesAdapter.getInfo(position);
if (info == null) {
@ -296,7 +271,7 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
}
private void onLoadIdentityInfo(List<IdentityInfo> identityInfos) {
identitiesAdapter.setData(identityInfos, unifiedKeyInfo.has_any_secret());
identitiesAdapter.setData(identityInfos);
}
private void onLoadSystemContact(SystemContactInfo systemContactInfo) {

View File

@ -18,7 +18,6 @@
package org.sufficientlysecure.keychain.ui.keyview.loader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -35,17 +34,12 @@ import android.support.annotation.Nullable;
import com.google.auto.value.AutoValue;
import com.squareup.sqldelight.SqlDelightQuery;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
import org.sufficientlysecure.keychain.linked.UriAttribute;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
import org.sufficientlysecure.keychain.model.AutocryptPeer;
import org.sufficientlysecure.keychain.model.UserPacket;
import org.sufficientlysecure.keychain.model.UserPacket.UserAttribute;
import org.sufficientlysecure.keychain.model.UserPacket.UserId;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.ui.util.PackageIconGetter;
import timber.log.Timber;
public class IdentityDao {
@ -71,12 +65,9 @@ public class IdentityDao {
this.autocryptPeerDao = autocryptPeerDao;
}
public List<IdentityInfo> getIdentityInfos(long masterKeyId, boolean showLinkedIds) {
public List<IdentityInfo> getIdentityInfos(long masterKeyId) {
ArrayList<IdentityInfo> identities = new ArrayList<>();
if (showLinkedIds) {
loadLinkedIds(identities, masterKeyId);
}
loadUserIds(identities, masterKeyId);
correlateOrAddAutocryptPeers(identities, masterKeyId);
@ -132,46 +123,6 @@ public class IdentityDao {
return null;
}
private void loadLinkedIds(ArrayList<IdentityInfo> identities, long masterKeyId) {
SqlDelightQuery query = UserPacket.FACTORY.selectUserAttributesByTypeAndMasterKeyId(
(long) WrappedUserAttribute.UAT_URI_ATTRIBUTE, masterKeyId);
try (Cursor cursor = db.query(query)) {
while (cursor.moveToNext()) {
UserAttribute userAttribute = UserPacket.USER_ATTRIBUTE_MAPPER.map(cursor);
LinkedIdInfo linkedIdInfo = parseLinkedIdInfo(userAttribute);
identities.add(linkedIdInfo);
}
}
}
public LinkedIdInfo getLinkedIdInfo(long masterKeyId, int rank) {
SqlDelightQuery query = UserPacket.FACTORY.selectSpecificUserAttribute(
(long) WrappedUserAttribute.UAT_URI_ATTRIBUTE, masterKeyId, rank);
try (Cursor cursor = db.query(query)) {
if (cursor.moveToFirst()) {
UserAttribute userAttribute = UserPacket.USER_ATTRIBUTE_MAPPER.map(cursor);
return parseLinkedIdInfo(userAttribute);
}
}
return null;
}
@Nullable
private LinkedIdInfo parseLinkedIdInfo(UserAttribute userAttribute) {
try {
UriAttribute uriAttribute = LinkedAttribute.fromAttributeData(userAttribute.attribute_data());
if (uriAttribute instanceof LinkedAttribute) {
return LinkedIdInfo.create(userAttribute.master_key_id(), userAttribute.rank(),
userAttribute.isVerified(), userAttribute.is_primary(), (LinkedAttribute) uriAttribute);
}
} catch (IOException e) {
Timber.e(e, "Failed parsing uri attribute");
}
return null;
}
private void loadUserIds(ArrayList<IdentityInfo> identities, long... masterKeyId) {
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyId);
try (Cursor cursor = db.query(query)) {
@ -214,20 +165,6 @@ public class IdentityDao {
}
}
@AutoValue
public abstract static class LinkedIdInfo implements IdentityInfo {
public abstract long getMasterKeyId();
public abstract int getRank();
public abstract boolean isVerified();
public abstract boolean isPrimary();
public abstract LinkedAttribute getLinkedAttribute();
static LinkedIdInfo create(long masterKeyId, int rank, boolean isVerified, boolean isPrimary, LinkedAttribute linkedAttribute) {
return new AutoValue_IdentityDao_LinkedIdInfo(masterKeyId, rank, isVerified, isPrimary, linkedAttribute);
}
}
@AutoValue
public abstract static class AutocryptPeerInfo implements IdentityInfo {
public abstract long getMasterKeyId();

View File

@ -36,8 +36,6 @@ import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoratio
public class IdentitiesCardView extends CardView {
private final RecyclerView vIdentities;
private final Button linkedIdsAddButton;
public IdentitiesCardView(Context context, AttributeSet attrs) {
super(context, attrs);
@ -46,8 +44,6 @@ public class IdentitiesCardView extends CardView {
vIdentities = view.findViewById(R.id.view_key_user_ids);
vIdentities.setLayoutManager(new LinearLayoutManager(context));
vIdentities.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL_LIST, false));
linkedIdsAddButton = view.findViewById(R.id.view_key_card_linked_ids_add);
}
public void setIdentitiesAdapter(IdentityAdapter identityAdapter) {
@ -59,12 +55,4 @@ public class IdentitiesCardView extends CardView {
});
vIdentities.setAdapter(identityAdapter);
}
public void setIdentitiesCardListener(OnClickListener identitiesCardListener) {
linkedIdsAddButton.setOnClickListener(identitiesCardListener);
}
public void setAddLinkedIdButtonVisible(boolean show) {
linkedIdsAddButton.setVisibility(show ? View.VISIBLE : View.GONE);
}
}

View File

@ -1,221 +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.ui.linked;
import android.arch.lifecycle.ViewModelProviders;
import android.graphics.PorterDuff;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ViewAnimator;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel;
import org.sufficientlysecure.keychain.ui.util.Notify;
public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragment {
private ImageView mVerifyImage;
private TextView mVerifyStatus;
private ViewAnimator mVerifyAnimator;
private long masterKeyId;
byte[] fingerprint;
// This is a resource, set AFTER it has been verified
LinkedTokenResource mVerifiedResource = null;
private ViewAnimator mVerifyButtonAnimator;
protected abstract View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(requireActivity()).get(UnifiedKeyInfoViewModel.class);
viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo);
}
private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) {
this.masterKeyId = unifiedKeyInfo.master_key_id();
this.fingerprint = unifiedKeyInfo.fingerprint();
}
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = newView(inflater, container, savedInstanceState);
View nextButton = view.findViewById(R.id.next_button);
if (nextButton != null) {
nextButton.setOnClickListener(v -> cryptoOperation());
}
view.findViewById(R.id.back_button).setOnClickListener(
v -> ((LinkedIdWizard) requireActivity()).loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT));
mVerifyAnimator = view.findViewById(R.id.verify_progress);
mVerifyImage = view.findViewById(R.id.verify_image);
mVerifyStatus = view.findViewById(R.id.verify_status);
mVerifyButtonAnimator = view.findViewById(R.id.verify_buttons);
view.findViewById(R.id.button_verify).setOnClickListener(v -> proofVerify());
view.findViewById(R.id.button_retry).setOnClickListener(v -> proofVerify());
setVerifyProgress(false, null);
mVerifyStatus.setText(R.string.linked_verify_pending);
return view;
}
abstract LinkedTokenResource getResource(OperationLog log);
private void setVerifyProgress(boolean on, Boolean success) {
if (success == null) {
mVerifyStatus.setText(R.string.linked_verifying);
displayButton(on ? 2 : 0);
} else if (success) {
mVerifyStatus.setText(R.string.linked_verify_success);
mVerifyImage.setImageResource(R.drawable.status_signature_verified_cutout_24dp);
mVerifyImage.setColorFilter(getResources().getColor(R.color.android_green_dark),
PorterDuff.Mode.SRC_IN);
displayButton(2);
} else {
mVerifyStatus.setText(R.string.linked_verify_error);
mVerifyImage.setImageResource(R.drawable.status_signature_unknown_cutout_24dp);
mVerifyImage.setColorFilter(getResources().getColor(R.color.android_red_dark),
PorterDuff.Mode.SRC_IN);
displayButton(1);
}
mVerifyAnimator.setDisplayedChild(on ? 1 : 0);
}
public void displayButton(int button) {
if (mVerifyButtonAnimator.getDisplayedChild() == button) {
return;
}
mVerifyButtonAnimator.setDisplayedChild(button);
}
protected void proofVerify() {
setVerifyProgress(true, null);
new AsyncTask<Void,Void,LinkedVerifyResult>() {
@Override
protected LinkedVerifyResult doInBackground(Void... params) {
long timer = System.currentTimeMillis();
OperationLog log = new OperationLog();
LinkedTokenResource resource = getResource(log);
if (resource == null) {
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log);
}
LinkedVerifyResult result = resource.verify(getActivity(), fingerprint);
// ux flow: this operation should take at last a second
timer = System.currentTimeMillis() -timer;
if (timer < 1000) try {
Thread.sleep(1000 -timer);
} catch (InterruptedException e) {
// never mind
}
if (result.success()) {
mVerifiedResource = resource;
}
return result;
}
@Override
protected void onPostExecute(LinkedVerifyResult result) {
super.onPostExecute(result);
if (result.success()) {
setVerifyProgress(false, true);
} else {
setVerifyProgress(false, false);
// on error, show error message
result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this);
}
}
}.execute();
}
@Override
protected void cryptoOperation() {
if (mVerifiedResource == null) {
Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
.show(LinkedIdCreateFinalFragment.this);
return;
}
super.cryptoOperation();
}
@Override
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
if (mVerifiedResource == null) {
Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
.show(LinkedIdCreateFinalFragment.this);
return;
}
super.cryptoOperation(cryptoInput);
}
@Nullable
@Override
public Parcelable createOperationInput() {
SaveKeyringParcel.Builder builder=
SaveKeyringParcel.buildChangeKeyringParcel(masterKeyId, fingerprint);
WrappedUserAttribute ua = LinkedAttribute.fromResource(mVerifiedResource).toUserAttribute();
builder.addUserAttribute(ua);
return builder.build();
}
@Override
public void onCryptoOperationSuccess(OperationResult result) {
requireActivity().finish();
}
@Override
public void onCryptoOperationError(OperationResult result) {
result.createNotify(getActivity()).show(this);
}
}

View File

@ -1,671 +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.ui.linked;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URL;
import java.util.Random;
import android.app.Activity;
import android.app.Dialog;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityOptionsCompat;
import android.util.Base64;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ViewAnimator;
import javax.net.ssl.HttpsURLConnection;
import org.bouncycastle.util.encoders.Hex;
import org.json.JSONException;
import org.json.JSONObject;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
import org.sufficientlysecure.keychain.linked.resources.GithubResource;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel;
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator;
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator.Status;
import timber.log.Timber;
public class LinkedIdCreateGithubFragment extends CryptoOperationFragment<SaveKeyringParcel,EditKeyResult> {
public static final String ARG_GITHUB_COOKIE = "github_cookie";
private Button mRetryButton;
enum State {
IDLE, AUTH_PROCESS, AUTH_ERROR, POST_PROCESS, POST_ERROR, LID_PROCESS, LID_ERROR, DONE
}
ViewAnimator mButtonContainer;
StatusIndicator mStatus1, mStatus2, mStatus3;
byte[] mFingerprint;
long mMasterKeyId;
private SaveKeyringParcel.Builder mSkpBuilder;
private TextView mLinkedIdTitle, mLinkedIdComment;
private boolean mFinishOnStop;
public static LinkedIdCreateGithubFragment newInstance() {
return new LinkedIdCreateGithubFragment();
}
public LinkedIdCreateGithubFragment() {
super(null);
}
@Override @NonNull
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.linked_create_github_fragment, container, false);
mButtonContainer = view.findViewById(R.id.button_container);
mStatus1 = view.findViewById(R.id.linked_status_step1);
mStatus2 = view.findViewById(R.id.linked_status_step2);
mStatus3 = view.findViewById(R.id.linked_status_step3);
mRetryButton = view.findViewById(R.id.button_retry);
((ImageView) view.findViewById(R.id.linked_id_type_icon)).setImageResource(R.drawable.linked_github);
((ImageView) view.findViewById(R.id.linked_id_certified_icon)).setImageResource(R.drawable.octo_link_24dp);
mLinkedIdTitle = view.findViewById(R.id.linked_id_title);
mLinkedIdComment = view.findViewById(R.id.linked_id_comment);
view.findViewById(R.id.back_button).setOnClickListener(v -> {
LinkedIdWizard activity = (LinkedIdWizard) requireActivity();
activity.loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
});
view.findViewById(R.id.button_send).setOnClickListener(v -> {
step1GetOAuthCode();
// for animation testing
// onCryptoOperationSuccess(null);
});
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(requireActivity()).get(UnifiedKeyInfoViewModel.class);
viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo);
}
private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) {
this.mMasterKeyId = unifiedKeyInfo.master_key_id();
this.mFingerprint = unifiedKeyInfo.fingerprint();
}
private void step1GetOAuthCode() {
setState(State.AUTH_PROCESS);
mButtonContainer.setDisplayedChild(1);
new Handler().postDelayed(
() -> oAuthRequest("github.com/login/oauth/authorize", BuildConfig.GITHUB_CLIENT_ID, "gist"), 300);
}
private void showRetryForOAuth() {
mRetryButton.setOnClickListener(v -> {
v.setOnClickListener(null);
step1GetOAuthCode();
});
mButtonContainer.setDisplayedChild(3);
}
private void step1GetOAuthToken() {
if (mOAuthCode == null) {
setState(State.AUTH_ERROR);
showRetryForOAuth();
return;
}
Activity activity = getActivity();
if (activity == null) {
return;
}
final String gistText = GithubResource.generate(activity, mFingerprint);
new AsyncTask<Void,Void,JSONObject>() {
Exception mException;
@Override
protected JSONObject doInBackground(Void... dummy) {
try {
JSONObject params = new JSONObject();
params.put("client_id", BuildConfig.GITHUB_CLIENT_ID);
params.put("client_secret", BuildConfig.GITHUB_CLIENT_SECRET);
params.put("code", mOAuthCode);
params.put("state", mOAuthState);
return jsonHttpRequest("https://github.com/login/oauth/access_token", params, null);
} catch (IOException | HttpResultException e) {
mException = e;
} catch (JSONException e) {
throw new AssertionError("json error, this is a bug!");
}
return null;
}
@Override
protected void onPostExecute(JSONObject result) {
super.onPostExecute(result);
Activity activity = getActivity();
if (activity == null) {
// we couldn't show an error anyways
return;
}
Timber.d("response: " + result);
if (result == null || result.optString("access_token", null) == null) {
setState(State.AUTH_ERROR);
showRetryForOAuth();
if (result != null) {
Notify.create(activity, R.string.linked_error_auth_failed, Style.ERROR).show();
return;
}
if (mException instanceof SocketTimeoutException) {
Notify.create(activity, R.string.linked_error_timeout, Style.ERROR).show();
} else if (mException instanceof HttpResultException) {
Notify.create(activity, activity.getString(R.string.linked_error_http,
((HttpResultException) mException).mResponse),
Style.ERROR).show();
} else if (mException instanceof IOException) {
Notify.create(activity, R.string.linked_error_network, Style.ERROR).show();
}
return;
}
step2PostGist(result.optString("access_token"), gistText);
}
}.execute();
}
private void step2PostGist(final String accessToken, final String gistText) {
setState(State.POST_PROCESS);
new AsyncTask<Void,Void,JSONObject>() {
Exception mException;
@Override
protected JSONObject doInBackground(Void... dummy) {
try {
long timer = System.currentTimeMillis();
JSONObject file = new JSONObject();
file.put("content", gistText);
JSONObject files = new JSONObject();
files.put("openpgp.txt", file);
JSONObject params = new JSONObject();
params.put("public", true);
params.put("description", getString(R.string.linked_gist_description));
params.put("files", files);
JSONObject result = jsonHttpRequest("https://api.github.com/gists", params, accessToken);
// ux flow: this operation should take at last a second
timer = System.currentTimeMillis() -timer;
if (timer < 1000) try {
Thread.sleep(1000 -timer);
} catch (InterruptedException e) {
// never mind
}
return result;
} catch (IOException | HttpResultException e) {
mException = e;
} catch (JSONException e) {
throw new AssertionError("json error, this is a bug!");
}
return null;
}
@Override
protected void onPostExecute(JSONObject result) {
super.onPostExecute(result);
Timber.d("response: " + result);
Activity activity = getActivity();
if (activity == null) {
// we couldn't show an error anyways
return;
}
if (result == null) {
setState(State.POST_ERROR);
showRetryForOAuth();
if (mException instanceof SocketTimeoutException) {
Notify.create(activity, R.string.linked_error_timeout, Style.ERROR).show();
} else if (mException instanceof HttpResultException) {
Notify.create(activity, activity.getString(R.string.linked_error_http,
((HttpResultException) mException).mResponse),
Style.ERROR).show();
} else if (mException instanceof IOException) {
Notify.create(activity, R.string.linked_error_network, Style.ERROR).show();
}
return;
}
GithubResource resource;
try {
String gistId = result.getString("id");
JSONObject owner = result.getJSONObject("owner");
String gistLogin = owner.getString("login");
URI uri = URI.create("https://gist.github.com/" + gistLogin + "/" + gistId);
resource = GithubResource.create(uri);
} catch (JSONException e) {
setState(State.POST_ERROR);
return;
}
View linkedItem = mButtonContainer.getChildAt(2);
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
linkedItem.setTransitionName(resource.toUri().toString());
}
// we only need authorization for this one operation, drop it afterwards
revokeToken(accessToken);
step3EditKey(resource);
}
}.execute();
}
private void revokeToken(final String token) {
new AsyncTask<Void,Void,Void>() {
@Override
protected Void doInBackground(Void... dummy) {
try {
HttpsURLConnection nection = (HttpsURLConnection) new URL(
"https://api.github.com/applications/" + BuildConfig.GITHUB_CLIENT_ID + "/tokens/" + token)
.openConnection();
nection.setRequestMethod("DELETE");
String encoded = Base64.encodeToString(
(BuildConfig.GITHUB_CLIENT_ID + ":" + BuildConfig.GITHUB_CLIENT_SECRET).getBytes(), Base64.NO_WRAP);
nection.setRequestProperty("Authorization", "Basic " + encoded);
nection.connect();
} catch (IOException e) {
// nvm
}
return null;
}
}.execute();
}
private void step3EditKey(final GithubResource resource) {
// set item data while we're there
{
Context context = getActivity();
mLinkedIdTitle.setText(resource.getDisplayTitle(context));
mLinkedIdComment.setText(resource.getDisplayComment(context));
}
setState(State.LID_PROCESS);
new Handler().postDelayed(() -> {
WrappedUserAttribute ua = LinkedAttribute.fromResource(resource).toUserAttribute();
mSkpBuilder = SaveKeyringParcel.buildChangeKeyringParcel(mMasterKeyId, mFingerprint);
mSkpBuilder.addUserAttribute(ua);
cryptoOperation();
}, 250);
}
@Nullable
@Override
public SaveKeyringParcel createOperationInput() {
// if this is null, the cryptoOperation silently aborts - which is what we want in that case
return mSkpBuilder.build();
}
@Override
public void onCryptoOperationSuccess(EditKeyResult result) {
setState(State.DONE);
mButtonContainer.getInAnimation().setDuration(750);
mButtonContainer.setDisplayedChild(2);
new Handler().postDelayed(() -> {
Activity activity = requireActivity();
Intent intent = ViewKeyActivity.getViewKeyActivityIntent(requireActivity(), mMasterKeyId);
// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
intent.putExtra(ViewKeyActivity.EXTRA_LINKED_TRANSITION, true);
View linkedItem = mButtonContainer.getChildAt(2);
Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(
activity, linkedItem, linkedItem.getTransitionName()).toBundle();
activity.startActivity(intent, options);
mFinishOnStop = true;
} else {
activity.startActivity(intent);
activity.finish();
}
}, 1000);
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
// cookies are automatically saved, we don't want that
CookieManager cookieManager = CookieManager.getInstance();
String cookie = cookieManager.getCookie("https://github.com/");
outState.putString(ARG_GITHUB_COOKIE, cookie);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState != null) {
String cookie = savedInstanceState.getString(ARG_GITHUB_COOKIE);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setCookie("https://github.com/", cookie);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
try {
// cookies are automatically saved, we don't want that
CookieManager cookieManager = CookieManager.getInstance();
// noinspection deprecation (replacement is api lvl 21)
cookieManager.removeAllCookie();
} catch (Exception e) {
// no biggie if this fails
}
}
@Override
public void onStop() {
super.onStop();
if (mFinishOnStop) {
Activity activity = requireActivity();
activity.setResult(Activity.RESULT_OK);
activity.finish();
}
}
@Override
public void onCryptoOperationError(EditKeyResult result) {
result.createNotify(getActivity()).show(this);
setState(State.LID_ERROR);
}
@Override
public void onCryptoOperationCancelled() {
mRetryButton.setOnClickListener(v -> {
v.setOnClickListener(null);
mButtonContainer.setDisplayedChild(1);
setState(State.LID_PROCESS);
cryptoOperation();
});
mButtonContainer.setDisplayedChild(3);
setState(State.LID_ERROR);
}
private String mOAuthCode, mOAuthState;
public void oAuthRequest(String hostAndPath, String clientId, String scope) {
Activity activity = getActivity();
if (activity == null) {
return;
}
byte[] buf = new byte[16];
new Random().nextBytes(buf);
mOAuthState = new String(Hex.encode(buf));
mOAuthCode = null;
final Dialog auth_dialog = new Dialog(activity);
auth_dialog.setContentView(R.layout.oauth_webview);
WebView web = auth_dialog.findViewById(R.id.web_view);
web.getSettings().setSaveFormData(false);
web.getSettings().setJavaScriptEnabled(true);
web.getSettings().setUserAgentString("OpenKeychain " + BuildConfig.VERSION_NAME);
web.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
if ("oauth-openkeychain".equals(uri.getScheme())) {
if (mOAuthCode != null) {
return true;
}
if (uri.getQueryParameter("error") != null) {
Timber.i("got oauth error: " + uri.getQueryParameter("error"));
auth_dialog.dismiss();
return true;
}
// check if mOAuthState == queryParam[state]
mOAuthCode = uri.getQueryParameter("code");
auth_dialog.dismiss();
return true;
}
// don't surf away from github!
if (!"github.com".equals(uri.getHost())) {
auth_dialog.dismiss();
return true;
}
return false;
}
});
auth_dialog.setTitle(R.string.linked_webview_title_github);
auth_dialog.setCancelable(true);
auth_dialog.setOnDismissListener(dialog -> step1GetOAuthToken());
auth_dialog.show();
web.loadUrl("https://" + hostAndPath +
"?client_id=" + clientId +
"&scope=" + scope +
"&redirect_uri=oauth-openkeychain://linked/" +
"&state=" + mOAuthState);
}
public void setState(State state) {
switch (state) {
case IDLE:
mStatus1.setDisplayedChild(Status.IDLE);
mStatus2.setDisplayedChild(Status.IDLE);
mStatus3.setDisplayedChild(Status.IDLE);
break;
case AUTH_PROCESS:
mStatus1.setDisplayedChild(Status.PROGRESS);
mStatus2.setDisplayedChild(Status.IDLE);
mStatus3.setDisplayedChild(Status.IDLE);
break;
case AUTH_ERROR:
mStatus1.setDisplayedChild(Status.ERROR);
mStatus2.setDisplayedChild(Status.IDLE);
mStatus3.setDisplayedChild(Status.IDLE);
break;
case POST_PROCESS:
mStatus1.setDisplayedChild(Status.OK);
mStatus2.setDisplayedChild(Status.PROGRESS);
mStatus3.setDisplayedChild(Status.IDLE);
break;
case POST_ERROR:
mStatus1.setDisplayedChild(Status.OK);
mStatus2.setDisplayedChild(Status.ERROR);
mStatus3.setDisplayedChild(Status.IDLE);
break;
case LID_PROCESS:
mStatus1.setDisplayedChild(Status.OK);
mStatus2.setDisplayedChild(Status.OK);
mStatus3.setDisplayedChild(Status.PROGRESS);
break;
case LID_ERROR:
mStatus1.setDisplayedChild(Status.OK);
mStatus2.setDisplayedChild(Status.OK);
mStatus3.setDisplayedChild(Status.ERROR);
break;
case DONE:
mStatus1.setDisplayedChild(Status.OK);
mStatus2.setDisplayedChild(Status.OK);
mStatus3.setDisplayedChild(Status.OK);
}
}
private static JSONObject jsonHttpRequest(String url, JSONObject params, String accessToken)
throws IOException, HttpResultException {
HttpsURLConnection nection = (HttpsURLConnection) new URL(url).openConnection();
nection.setDoInput(true);
nection.setDoOutput(true);
nection.setConnectTimeout(3000);
nection.setReadTimeout(5000);
nection.setRequestProperty("Content-Type", "application/json");
nection.setRequestProperty("Accept", "application/json");
nection.setRequestProperty("User-Agent", "OpenKeychain " + BuildConfig.VERSION_NAME);
if (accessToken != null) {
nection.setRequestProperty("Authorization", "token " + accessToken);
}
try {
nection.connect();
OutputStream os = nection.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
writer.write(params.toString());
writer.flush();
writer.close();
os.close();
int code = nection.getResponseCode();
if (code != HttpsURLConnection.HTTP_CREATED && code != HttpsURLConnection.HTTP_OK) {
throw new HttpResultException(nection.getResponseCode(), nection.getResponseMessage());
}
InputStream in = new BufferedInputStream(nection.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
response.append(line);
}
try {
return new JSONObject(response.toString());
} catch (JSONException e) {
throw new IOException(e);
}
} finally {
nection.disconnect();
}
}
static class HttpResultException extends Exception {
final int mCode;
final String mResponse;
HttpResultException(int code, String response) {
mCode = code;
mResponse = response;
}
}
}

View File

@ -1,104 +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.ui.linked;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import org.sufficientlysecure.keychain.R;
public class LinkedIdCreateHttpsStep1Fragment extends Fragment {
EditText mEditUri;
public static LinkedIdCreateHttpsStep1Fragment newInstance() {
LinkedIdCreateHttpsStep1Fragment frag = new LinkedIdCreateHttpsStep1Fragment();
Bundle args = new Bundle();
frag.setArguments(args);
return frag;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.linked_create_https_fragment_step1, container, false);
view.findViewById(R.id.next_button).setOnClickListener(v -> {
String uri = "https://" + mEditUri.getText();
if (!checkUri(uri)) {
return;
}
LinkedIdCreateHttpsStep2Fragment frag = LinkedIdCreateHttpsStep2Fragment.newInstance(uri);
((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
});
view.findViewById(R.id.back_button).setOnClickListener(
v -> ((LinkedIdWizard) requireActivity()).loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT));
mEditUri = view.findViewById(R.id.linked_create_https_uri);
mEditUri.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@Override
public void afterTextChanged(Editable editable) {
String uri = "https://" + editable;
if (uri.length() > 0) {
if (checkUri(uri)) {
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
R.drawable.ic_stat_retyped_ok, 0);
} else {
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
R.drawable.ic_stat_retyped_bad, 0);
}
} else {
// remove drawable if email is empty
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
}
});
// mEditUri.setText("mugenguild.com/pgpkey.txt");
return view;
}
private static boolean checkUri(String uri) {
return Patterns.WEB_URL.matcher(uri).matches();
}
}

View File

@ -1,153 +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.ui.linked;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.FileHelper;
import timber.log.Timber;
public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragment {
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
public static final String ARG_URI = "uri";
EditText mEditUri;
URI mResourceUri;
public static LinkedIdCreateHttpsStep2Fragment newInstance(String uri) {
LinkedIdCreateHttpsStep2Fragment frag = new LinkedIdCreateHttpsStep2Fragment();
Bundle args = new Bundle();
args.putString(ARG_URI, uri);
frag.setArguments(args);
return frag;
}
@Override
GenericHttpsResource getResource(OperationLog log) {
return GenericHttpsResource.createNew(mResourceUri);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
mResourceUri = new URI(getArguments().getString(ARG_URI));
} catch (URISyntaxException e) {
Timber.e(e);
requireActivity().finish();
}
}
@Override
protected View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.linked_create_https_fragment_step2, container, false);
}
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
view.findViewById(R.id.button_send).setOnClickListener(v -> proofSend());
view.findViewById(R.id.button_save).setOnClickListener(v -> proofSave());
mEditUri = view.findViewById(R.id.linked_create_https_uri);
mEditUri.setText(mResourceUri.toString());
return view;
}
private String getResourceString() {
return GenericHttpsResource.generateText(requireActivity(), fingerprint);
}
private void proofSend() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, getResourceString());
sendIntent.setType("text/plain");
startActivity(sendIntent);
}
private void proofSave() {
String state = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(state)) {
Notify.create(getActivity(), "External storage not available!", Style.ERROR).show();
return;
}
String targetName = "pgpkey.txt";
// TODO: not supported on Android < 4.4
FileHelper.saveDocument(this, targetName, "text/plain", REQUEST_CODE_OUTPUT);
}
private void saveFile(Uri uri) {
try {
PrintWriter out = new PrintWriter(requireActivity().getContentResolver().openOutputStream(uri));
out.print(getResourceString());
if (out.checkError()) {
Notify.create(getActivity(), "Error writing file!", Style.ERROR).show();
}
} catch (FileNotFoundException e) {
Notify.create(getActivity(), "File could not be opened for writing!", Style.ERROR).show();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
// For saving a file
case REQUEST_CODE_OUTPUT:
if (data == null) {
return;
}
Uri uri = data.getData();
saveFile(uri);
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
}

View File

@ -1,113 +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.ui.linked;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.EditText;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.util.Notify;
public class LinkedIdCreateTwitterStep1Fragment extends Fragment {
EditText mEditHandle;
public static LinkedIdCreateTwitterStep1Fragment newInstance() {
return new LinkedIdCreateTwitterStep1Fragment();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.linked_create_twitter_fragment_step1, container, false);
view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final String handle = mEditHandle.getText().toString();
if ("".equals(handle)) {
mEditHandle.setError("Please input a Twitter handle!");
return;
}
new AsyncTask<Void,Void,Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
return true;
// return checkHandle(handle);
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (result == null) {
Notify.create(getActivity(),
"Connection error while checking username!",
Notify.Style.ERROR).show(LinkedIdCreateTwitterStep1Fragment.this);
return;
}
if (!result) {
Notify.create(getActivity(),
"This handle does not exist on Twitter!",
Notify.Style.ERROR).show(LinkedIdCreateTwitterStep1Fragment.this);
return;
}
LinkedIdCreateTwitterStep2Fragment frag =
LinkedIdCreateTwitterStep2Fragment.newInstance(handle);
((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
}
}.execute();
}
});
view.findViewById(R.id.back_button).setOnClickListener(
v -> ((LinkedIdWizard) requireActivity()).loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT));
mEditHandle = view.findViewById(R.id.linked_create_twitter_handle);
return view;
}
/* not used at this point, too many problems
private static Boolean checkHandle(String handle) {
try {
HttpURLConnection nection =
(HttpURLConnection) new URL("https://twitter.com/" + handle).getUrlResponse();
nection.setRequestMethod("HEAD");
nection.setRequestProperty("User-Agent", "OpenKeychain");
return nection.getResponseCode() == 200;
} catch (IOException e) {
return null;
}
}
*/
}

View File

@ -1,106 +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.ui.linked;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Html;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
import org.sufficientlysecure.keychain.linked.resources.TwitterResource;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
public class LinkedIdCreateTwitterStep2Fragment extends LinkedIdCreateFinalFragment {
public static final String ARG_HANDLE = "handle";
String mResourceHandle;
public static LinkedIdCreateTwitterStep2Fragment newInstance(String handle) {
LinkedIdCreateTwitterStep2Fragment frag = new LinkedIdCreateTwitterStep2Fragment();
Bundle args = new Bundle();
args.putString(ARG_HANDLE, handle);
frag.setArguments(args);
return frag;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mResourceHandle = getArguments().getString(ARG_HANDLE);
}
private String getResourceString() {
return TwitterResource.generate(fingerprint);
}
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
view.findViewById(R.id.button_send).setOnClickListener(v -> proofSend());
view.findViewById(R.id.button_share).setOnClickListener(v -> proofShare());
Spanned tweetText = Html.fromHtml(getString(R.string.linked_create_twitter_2_3, mResourceHandle));
((TextView) view.findViewById(R.id.linked_tweet_published)).setText(tweetText);
return view;
}
@Override
LinkedTokenResource getResource(OperationLog log) {
return TwitterResource.searchInTwitterStream(getActivity(), mResourceHandle, getResourceString(), log);
}
@Override
protected View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.linked_create_twitter_fragment_step2, container, false);
}
private void proofShare() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, getResourceString());
sendIntent.setType("text/plain");
startActivity(sendIntent);
}
private void proofSend() {
Uri.Builder builder = Uri.parse("https://twitter.com/intent/tweet").buildUpon();
builder.appendQueryParameter("text", getResourceString());
Uri uri = builder.build();
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
}

View File

@ -1,59 +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.ui.linked;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.sufficientlysecure.keychain.R;
public class LinkedIdSelectFragment extends Fragment {
public static LinkedIdSelectFragment newInstance() {
return new LinkedIdSelectFragment();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.linked_select_fragment, container, false);
view.findViewById(R.id.linked_create_https_button).setOnClickListener(v -> {
LinkedIdCreateHttpsStep1Fragment frag = LinkedIdCreateHttpsStep1Fragment.newInstance();
((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
});
view.findViewById(R.id.linked_create_twitter_button).setOnClickListener(v -> {
LinkedIdCreateTwitterStep1Fragment frag = LinkedIdCreateTwitterStep1Fragment.newInstance();
((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
});
view.findViewById(R.id.linked_create_github_button).setOnClickListener(v -> {
LinkedIdCreateGithubFragment frag = LinkedIdCreateGithubFragment.newInstance();
((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
});
return view;
}
}

View File

@ -1,118 +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.ui.linked;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel;
import timber.log.Timber;
public class LinkedIdWizard extends BaseActivity {
public static final String EXTRA_MASTER_KEY_ID = "master_key_id";
public static final int FRAG_ACTION_START = 0;
public static final int FRAG_ACTION_TO_RIGHT = 1;
public static final int FRAG_ACTION_TO_LEFT = 2;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(getString(R.string.title_linked_id_create));
Bundle extras = getIntent().getExtras();
if (extras == null || !extras.containsKey(EXTRA_MASTER_KEY_ID)) {
Timber.e("Missing required extra master_key_id!");
finish();
return;
}
long masterKeyId = extras.getLong(EXTRA_MASTER_KEY_ID);
UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(this).get(UnifiedKeyInfoViewModel.class);
viewModel.setMasterKeyId(masterKeyId);
viewModel.getUnifiedKeyInfoLiveData(this).observe(this, this::onLoadUnifiedKeyInfo);
hideKeyboard();
// pass extras into fragment
if (savedInstanceState == null) {
LinkedIdSelectFragment frag = LinkedIdSelectFragment.newInstance();
loadFragment(frag, FRAG_ACTION_START);
}
}
private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) {
if (!unifiedKeyInfo.has_any_secret()) {
Timber.e("Linked Identities can only be added to secret keys!");
finish();
}
}
@Override
protected void initLayout() {
setContentView(R.layout.create_key_activity);
}
public void loadFragment(Fragment fragment, int action) {
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
switch (action) {
case FRAG_ACTION_START:
transaction.setCustomAnimations(0, 0);
transaction.replace(R.id.create_key_fragment_container, fragment)
.commitAllowingStateLoss();
break;
case FRAG_ACTION_TO_LEFT:
getSupportFragmentManager().popBackStackImmediate();
break;
case FRAG_ACTION_TO_RIGHT:
transaction.setCustomAnimations(R.anim.frag_slide_in_from_right, R.anim.frag_slide_out_to_left,
R.anim.frag_slide_in_from_left, R.anim.frag_slide_out_to_right);
transaction.addToBackStack(null);
transaction.replace(R.id.create_key_fragment_container, fragment)
.commitAllowingStateLoss();
break;
}
getSupportFragmentManager().executePendingTransactions();
}
private void hideKeyboard() {
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
View v = getCurrentFocus();
if (v == null || inputManager == null) {
return;
}
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
}

View File

@ -432,10 +432,6 @@ public class Preferences {
// experimental prefs
public boolean getExperimentalEnableLinkedIdentities() {
return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES, false);
}
public boolean getExperimentalEnableKeybase() {
return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_KEYBASE, false);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -18,30 +18,4 @@
android:layout_height="wrap_content"
android:layout_marginBottom="4dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<LinearLayout
android:id="@+id/view_key_card_user_ids_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right|end"
android:orientation="horizontal"
style="?buttonBarStyle">
<Button
android:id="@+id/view_key_card_linked_ids_add"
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_linked_add_identity"
android:textColor="@color/card_view_button"
android:visibility="gone"
tools:visibility="visible"
/>
</LinearLayout>
</LinearLayout>

View File

@ -1,235 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="false"
android:layout_above="@+id/create_key_button_divider">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:src="@drawable/linked_github" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_margin="8dp"
android:src="@drawable/octo_link_24dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_margin="8dp"
android:src="@drawable/account_key" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/linked_github_text"
style="?android:textAppearanceSmall"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<org.sufficientlysecure.keychain.ui.widget.StatusIndicator
android:layout_width="24dp"
android:layout_height="24dp"
android:id="@+id/linked_status_step1"
android:layout_margin="4dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/linked_progress_auth_github"
style="?android:textAppearanceMedium"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<org.sufficientlysecure.keychain.ui.widget.StatusIndicator
android:layout_width="24dp"
android:layout_height="24dp"
android:id="@+id/linked_status_step2"
android:layout_margin="4dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/linked_progress_post_gist"
style="?android:textAppearanceMedium"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<org.sufficientlysecure.keychain.ui.widget.StatusIndicator
android:layout_width="24dp"
android:layout_height="24dp"
android:id="@+id/linked_status_step3"
android:layout_margin="4dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/linked_progress_update_key"
style="?android:textAppearanceMedium"
/>
</LinearLayout>
</LinearLayout>
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button_container"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginRight="4dp"
android:layout_marginLeft="4dp"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
android:clipChildren="false"
custom:initialView="3">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:drawableLeft="@drawable/link_24dp"
android:drawableStart="@drawable/link_24dp"
android:drawablePadding="12dp"
android:text="@string/linked_button_start"
android:id="@+id/button_send"
/>
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<include layout="@layout/linked_id_item" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:drawableLeft="@drawable/ic_repeat_black_24dp"
android:drawableStart="@drawable/ic_repeat_black_24dp"
android:drawablePadding="12dp"
android:text="@string/linked_button_retry_step"
android:id="@+id/button_retry"
/>
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
</LinearLayout>
</ScrollView>
<View
android:id="@+id/create_key_button_divider"
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:background="?android:attr/listDivider"
android:layout_alignTop="@+id/create_key_buttons"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:id="@+id/create_key_buttons">
<TextView
android:id="@+id/back_button"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_back"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAllCaps="true"
style="?android:attr/borderlessButtonStyle"
android:drawableLeft="@drawable/ic_chevron_left_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:clickable="true"
android:layout_gravity="center_vertical" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?android:attr/listDivider" />
<TextView
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_finish"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAllCaps="true"
android:drawableRight="@drawable/ic_person_add_grey_24dp"
android:drawablePadding="8dp"
style="?android:attr/borderlessButtonStyle"
android:gravity="center_vertical|right"
android:layout_gravity="center_vertical"
android:visibility="invisible" />
</LinearLayout>
</RelativeLayout>

View File

@ -1,129 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="false"
android:layout_above="@+id/create_key_button_divider">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/linked_https"
android:drawablePadding="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_https_1_1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_https_1_2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_https_1_3" />
<org.sufficientlysecure.keychain.ui.widget.PrefixedEditText
android:id="@+id/linked_create_https_uri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionNext"
android:layout_marginTop="16dp"
android:ems="10"
android:inputType="textUri"
android:layout_gravity="center_horizontal"
custom:prefix="https://"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_https_1_4" />
</LinearLayout>
</ScrollView>
<View
android:id="@+id/create_key_button_divider"
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:background="?android:attr/listDivider"
android:layout_alignTop="@+id/create_key_buttons"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:id="@+id/create_key_buttons">
<TextView
android:id="@+id/back_button"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_back"
android:clickable="true"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAllCaps="true"
style="?android:attr/borderlessButtonStyle"
android:drawableLeft="@drawable/ic_chevron_left_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:layout_gravity="center_vertical" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/next_button"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_next"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAllCaps="true"
android:drawableRight="@drawable/ic_chevron_right_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical|right"
style="?android:attr/borderlessButtonStyle"
android:clickable="true"
android:layout_gravity="center_vertical" />
</LinearLayout>
</RelativeLayout>

View File

@ -1,165 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="false"
android:layout_above="@+id/create_key_button_divider">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_https_2_1" />
<EditText
android:id="@+id/linked_create_https_uri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionNext"
android:layout_marginTop="8dp"
android:hint="uri"
android:ems="10"
android:layout_gravity="center_horizontal"
android:inputType="textUri|none"
android:enabled="false"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_https_2_2" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_gravity="center_horizontal"
style="?android:buttonBarStyle"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
style="?android:buttonBarButtonStyle"
android:drawableLeft="@android:drawable/ic_menu_save"
android:drawableStart="@android:drawable/ic_menu_save"
android:text="Save"
android:id="@+id/button_save"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
style="?android:buttonBarButtonStyle"
android:drawableLeft="@android:drawable/ic_menu_share"
android:drawableStart="@android:drawable/ic_menu_share"
android:text="Share"
android:id="@+id/button_send"
/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_https_2_3" />
<include layout="@layout/linked_create_verify" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_https_2_4" />
</LinearLayout>
</ScrollView>
<View
android:id="@+id/create_key_button_divider"
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:background="?android:attr/listDivider"
android:layout_alignTop="@+id/create_key_buttons"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:id="@+id/create_key_buttons">
<TextView
android:id="@+id/back_button"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_back"
android:minHeight="?android:attr/listPreferredItemHeight"
android:clickable="true"
android:textAllCaps="true"
style="?android:attr/borderlessButtonStyle"
android:drawableLeft="@drawable/ic_chevron_left_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:layout_gravity="center_vertical" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/next_button"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_finish"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAllCaps="true"
android:drawableRight="@drawable/ic_person_add_grey_24dp"
android:drawablePadding="8dp"
style="?android:attr/borderlessButtonStyle"
android:gravity="center_vertical|right"
android:layout_gravity="center_vertical" />
</LinearLayout>
</RelativeLayout>

View File

@ -1,123 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="match_parent"
>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="false"
android:layout_above="@+id/create_key_button_divider">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/linked_twitter"
android:drawablePadding="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_twitter_1_1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_twitter_1_2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_twitter_1_3" />
<org.sufficientlysecure.keychain.ui.widget.PrefixedEditText
android:id="@+id/linked_create_twitter_handle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionNext"
android:layout_marginTop="16dp"
android:ems="10"
android:layout_gravity="center_horizontal"
android:inputType="text"
android:hint="@string/linked_create_twitter_handle"
custom:prefix="\@"
/>
</LinearLayout>
</ScrollView>
<View
android:id="@+id/create_key_button_divider"
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:background="?android:attr/listDivider"
android:layout_alignTop="@+id/create_key_buttons"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:id="@+id/create_key_buttons">
<TextView
android:id="@+id/back_button"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_back"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAllCaps="true"
style="?android:attr/borderlessButtonStyle"
android:drawableLeft="@drawable/ic_chevron_left_grey_24dp"
android:drawablePadding="8dp"
android:clickable="true"
android:gravity="center_vertical"
android:layout_gravity="center_vertical" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/next_button"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_next"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAllCaps="true"
style="?android:attr/borderlessButtonStyle"
android:drawableRight="@drawable/ic_chevron_right_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical|right"
android:layout_gravity="center_vertical" />
</LinearLayout>
</RelativeLayout>

View File

@ -1,154 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="false"
android:layout_above="@+id/create_key_button_divider">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_twitter_2_1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/linked_create_twitter_2_2" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_gravity="center_horizontal"
style="?android:buttonBarStyle">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
style="?android:buttonBarButtonStyle"
android:drawableLeft="@android:drawable/ic_menu_send"
android:drawableStart="@android:drawable/ic_menu_send"
android:text="Tweet"
android:id="@+id/button_send"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
style="?android:buttonBarButtonStyle"
android:drawableLeft="@android:drawable/ic_menu_share"
android:drawableStart="@android:drawable/ic_menu_share"
android:text="Share"
android:id="@+id/button_share"
/>
</LinearLayout>
<TextView
android:id="@+id/linked_tweet_published"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
tools:text="@string/linked_create_twitter_2_3" />
<include layout="@layout/linked_create_verify" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_create_twitter_2_4" />
</LinearLayout>
</ScrollView>
<View
android:id="@+id/create_key_button_divider"
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:background="?android:attr/listDivider"
android:layout_alignTop="@+id/create_key_buttons"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:id="@+id/create_key_buttons">
<TextView
android:id="@+id/back_button"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_back"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAllCaps="true"
style="?android:attr/borderlessButtonStyle"
android:drawableLeft="@drawable/ic_chevron_left_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:clickable="true"
android:layout_gravity="center_vertical" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/next_button"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_finish"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAllCaps="true"
android:drawableRight="@drawable/ic_person_add_grey_24dp"
android:drawablePadding="8dp"
style="?android:attr/borderlessButtonStyle"
android:gravity="center_vertical|right"
android:layout_gravity="center_vertical" />
</LinearLayout>
</RelativeLayout>

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:showIn="@layout/linked_create_github_fragment_step2">
<ViewAnimator
android:id="@+id/verify_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dip"
android:layout_marginStart="16dip"
android:layout_gravity="center_vertical"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:id="@+id/verify_image"
android:src="@drawable/status_signature_unverified_cutout_24dp"
/>
<ProgressBar
android:layout_width="24dp"
android:layout_height="24dp"
android:indeterminateOnly="true" />
</ViewAnimator>
<TextView
android:id="@+id/verify_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_verify_pending"
android:layout_marginLeft="16dip"
android:layout_marginStart="16dip"
android:layout_weight="1"
/>
<ViewAnimator
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dip"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
android:id="@+id/verify_buttons"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:buttonBarButtonStyle"
android:text="@string/linked_button_verify"
android:id="@+id/button_verify"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:buttonBarButtonStyle"
android:text="@string/linked_button_retry"
android:id="@+id/button_retry"
/>
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</ViewAnimator>
</LinearLayout>

View File

@ -1,75 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:singleLine="true"
android:background="?selectableItemBackground"
tools:showIn="@layout/linked_id_view_fragment">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:id="@+id/linked_id_type_icon"
android:layout_marginLeft="14dp"
android:layout_marginStart="14dp"
tools:src="@drawable/linked_dns"
android:layout_gravity="center_vertical"
android:scaleType="fitCenter" />
<LinearLayout
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:layout_width="0dip"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<TextView
android:id="@+id/linked_id_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Title"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<TextView
android:id="@+id/linked_id_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="comment"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
</LinearLayout>
<ViewAnimator
android:id="@+id/linked_id_certified"
android:layout_width="22dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:outAnimation="@anim/fade_out_down"
android:inAnimation="@anim/fade_in_up"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<ImageView
android:id="@+id/linked_id_certified_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
tools:src="@drawable/status_signature_unknown_cutout_24dp"
/>
<Space
android:layout_height="wrap_content"
android:layout_width="wrap_content" />
</ViewAnimator>
</LinearLayout>

View File

@ -1,193 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<android.support.v7.widget.CardView
android:id="@+id/card_linked_ids"
android:transitionName="card_linked_ids"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardBackgroundColor="?attr/colorCardViewBackground"
card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true"
card_view:cardCornerRadius="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<TextView
style="@style/CardViewHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/card_linked_identity" />
<include layout="@layout/linked_id_item" />
<ViewAnimator
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/linked_verify_container"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:measureAllChildren="false"
>
<include layout="@layout/cert_list_widget" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false"
android:animateLayoutChanges="true"
>
<TextSwitcher
android:id="@+id/linked_cert_text"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:inAnimation="@anim/fade_in_quick"
android:outAnimation="@anim/fade_out_quick"
android:measureAllChildren="false"
android:layout_weight="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
</TextSwitcher>
<ViewAnimator
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="4dp"
android:layout_gravity="center"
android:id="@+id/linked_cert_progress"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out">
<ProgressBar
android:layout_width="22dp"
android:layout_height="22dp"
android:indeterminate="true"
/>
<ImageView
android:id="@+id/status_icon_verified"
android:layout_width="22dp"
android:layout_height="wrap_content"
android:src="@drawable/status_signature_verified_inner_24dp"
/>
<ImageView
android:id="@+id/status_icon_invalid"
android:layout_width="22dp"
android:layout_height="wrap_content"
android:src="@drawable/status_signature_invalid_cutout_24dp"
/>
</ViewAnimator>
</LinearLayout>
</ViewAnimator>
<!-- this layout is used for a highlight thing, so we use padding instead of margin -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="14dp"
android:paddingRight="14dp"
android:id="@+id/cert_key_spincontainer"
android:visibility="gone"
tools:visibility="visible"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginRight="4dp"
android:layout_marginEnd="4dp"
android:text="@string/add_keys_my_key" />
<org.sufficientlysecure.keychain.ui.widget.KeySpinner
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/cert_key_spinner">
</org.sufficientlysecure.keychain.ui.widget.KeySpinner>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="left|start">
<Button
android:id="@+id/button_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/linked_button_view"
android:textColor="@color/card_view_button"
style="?android:attr/borderlessButtonStyle"
/>
<ViewAnimator
android:id="@+id/button_animator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out">
<Button
android:id="@+id/button_verify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/linked_button_verify"
android:textColor="@color/card_view_button"
style="?android:attr/borderlessButtonStyle" />
<Button
android:id="@+id/button_retry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/linked_button_retry"
android:textColor="@color/card_view_button"
style="?android:attr/borderlessButtonStyle" />
<Button
android:id="@+id/button_confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/linked_button_confirm"
android:textColor="@color/card_view_button"
style="?android:attr/borderlessButtonStyle" />
</ViewAnimator>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
</ScrollView>

View File

@ -1,188 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_select_1"
android:id="@+id/textView"
android:layout_weight="1" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/linked_select_2"
android:layout_weight="1" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<LinearLayout
android:id="@+id/linked_create_github_button"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:clickable="true"
android:background="?android:selectableItemBackground"
android:orientation="horizontal">
<!-- separate ImageView required for recoloring -->
<ImageView
android:layout_width="60dip"
android:layout_height="60dip"
android:padding="8dp"
android:src="@drawable/linked_github"
android:layout_gravity="center"
/>
<TextView
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dip"
android:layout_height="match_parent"
android:text="@string/linked_title_github"
android:layout_weight="1"
android:gravity="center_vertical" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<LinearLayout
android:id="@+id/linked_create_twitter_button"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:clickable="true"
android:background="?android:selectableItemBackground"
android:orientation="horizontal">
<!-- separate ImageView required for recoloring -->
<ImageView
android:layout_width="60dip"
android:layout_height="60dip"
android:padding="8dp"
android:src="@drawable/linked_twitter"
android:layout_gravity="center"
/>
<TextView
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dip"
android:layout_height="match_parent"
android:text="@string/linked_title_twitter"
android:layout_weight="1"
android:gravity="center_vertical" />
</LinearLayout>
<!--
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginBottom="4dp"
android:background="?android:attr/listDivider" />
<LinearLayout
android:id="@+id/linked_create_dns_button"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:clickable="true"
android:background="?android:selectableItemBackground"
android:orientation="horizontal">
<!- separate ImageView required for recoloring ->
<ImageView
android:layout_width="60dip"
android:layout_height="60dip"
android:padding="8dp"
android:src="@drawable/linked_dns"
android:layout_gravity="center"
/>
<TextView
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dip"
android:layout_height="match_parent"
android:text="@string/linked_title_dns"
android:layout_weight="1"
android:gravity="center_vertical" />
</LinearLayout>
-->
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<LinearLayout
android:id="@+id/linked_create_https_button"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:clickable="true"
android:background="?android:selectableItemBackground"
android:orientation="horizontal">
<!-- separate ImageView required for recoloring -->
<ImageView
android:id="@+id/certify_key_action_certify_image"
android:layout_width="60dip"
android:layout_height="60dip"
android:padding="8dp"
android:src="@drawable/linked_https"
android:layout_gravity="center_vertical" />
<TextView
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dip"
android:layout_height="match_parent"
android:text="@string/linked_title_https"
android:layout_weight="1"
android:gravity="center_vertical" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginBottom="4dp"
android:background="?android:attr/listDivider" />
</LinearLayout>
</ScrollView>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds />
</transitionSet>

View File

@ -1647,81 +1647,13 @@
<string name="error_scan_match">"Fingerprints did not match!"</string>
<string name="error_expiry_past">"Expiry date is in the past!"</string>
<string name="linked_create_https_1_1">"By creating a Linked Identity of this type, you can link your key to a website you control."</string>
<string name="linked_create_https_1_2">"To do this, you publish a text file on this website, then create a Linked Identity which links to it."</string>
<string name="linked_create_https_1_3">"Please enter a URL where you are able to place a text file for proof. Note that your server must support https and have a valid TLS certificate!"</string>
<string name="linked_create_https_1_4">"Example: https://example.com/pgpkey.txt"</string>
<string name="linked_create_https_created">"The proof file has been created. For the next step, you should save and upload it to the URI you indicated:"</string>
<string name="linked_create_https_2_1">"A proof file for this URI has been created:"</string>
<string name="linked_create_https_2_2">"For the next step, you should save and upload this file."</string>
<string name="linked_create_https_2_3">"Make sure the file is reachable at the correct URI, then verify your setup."</string>
<string name="linked_create_https_2_4">"After successful verification, touch Finish button to add the Linked Identity to your keyring and finish the process."</string>
<string name="linked_create_twitter_1_1">"By creating a Linked Identity of this type, you can link your key to a Twitter account you control."</string>
<string name="linked_create_twitter_1_2">"To do this, you publish a specific Tweet on your timeline, then create a Linked Identity which links to this Tweet."</string>
<string name="linked_create_twitter_1_3">"Please enter your Twitter screen name to proceed."</string>
<string name="linked_create_twitter_handle">Twitter Handle</string>
<string name="linked_create_twitter_2_1">"Touch either button to tweet the message!"</string>
<string name="linked_create_twitter_2_2">"You can edit the Tweet before posting it, so long as the text inside the brackets is unmodified."</string>
<string name="linked_create_twitter_2_3">"Once your Tweet is published as <b>@%s</b>, touch the Verify button to scan your timeline for it."</string>
<string name="linked_create_twitter_2_4">"After successful verification, touch Finish button to add the Linked Identity to your keyring and finish the process."</string>
<string name="linked_create_verify">"Verify"</string>
<string name="linked_text_clipboard">Text has been copied to clipboard</string>
<string name="linked_verified_https">"The link between this Website and key was securely verified. <b>If you believe the Website is genuine</b>, confirm this verification with your key."</string>
<string name="linked_verified_github">"The link between this GitHub account and key was securely verified. <b>If you believe the account is genuine</b>, confirm this verification with your key."</string>
<string name="linked_verified_dns">"The link between this Domain Name and key was securely verified. <b>If you believe the Domain is genuine</b>, confirm this verification with your key."</string>
<string name="linked_verified_twitter">"The link between this Twitter account and key was securely verified. <b>If you believe the account is genuine</b>, confirm this verification with your key."</string>
<string name="linked_verified_secret_https">"Everything looks in order."</string>
<string name="linked_verified_secret_github">"Everything looks in order."</string>
<string name="linked_verified_secret_dns">"Everything looks in order."</string>
<string name="linked_verified_secret_twitter">"Everything looks in order."</string>
<plurals name="linked_id_expand">
<item quantity="one">"There is one more unknown identity type"</item>
<item quantity="other">"There are %d more unknown identity types"</item>
</plurals>
<!-- Other Linked Identity strings -->
<string name="linked_select_1">"A 'linked identity' connects your PGP key to a resource on the web."</string>
<string name="linked_select_2">"Please select a type:"</string>
<string name="linked_id_generic_text">"This file claims ownership of the OpenPGP key with long id %2$s.\n\nToken for proof:\n%1$s"</string>
<string name="linked_id_github_text" translatable="false">"This Gist confirms the Linked Identity in my OpenPGP key, and links it to this GitHub account.\n\nToken for proof:\n%1$s"</string>
<string name="linked_verifying">"Verifying…"</string>
<string name="linked_verify_success">"Verified!"</string>
<string name="linked_verify_error">"Verification error!"</string>
<string name="linked_verify_pending">"Not yet verified"</string>
<string name="linked_need_verify">The resource needs to be verified before you can proceed!</string>
<string name="menu_linked_add_identity">"Link to Account"</string>
<string name="section_linked_identities">"Linked Identities"</string>
<string name="btn_finish">"Finish"</string>
<string name="linked_title_https">"Website (HTTPS)"</string>
<string name="linked_title_dns">"Domain Name (DNS)"</string>
<string name="linked_title_github">"GitHub"</string>
<string name="linked_title_twitter">"Twitter"</string>
<string name="card_linked_identity">"Linked Identity"</string>
<string name="linked_button_verify">"Verify"</string>
<string name="linked_button_retry">"Retry"</string>
<string name="linked_button_retry_step">"Retry last step"</string>
<string name="linked_button_confirm">"Confirm"</string>
<string name="linked_button_view">"View"</string>
<string name="linked_text_verifying">"Verifying…"</string>
<string name="linked_text_error">"Error"</string>
<string name="linked_text_confirming">"Confirming…"</string>
<string name="linked_ids_more_unknown">"%d more unknown identity types"</string>
<string name="title_linked_id_create">"Create Linked Identity"</string>
<string name="linked_github_text">"This operation links your key to your GitHub account.\nJust touch the button to continue."</string>
<string name="linked_progress_auth_github">"Authorize with GitHub…"</string>
<string name="linked_progress_post_gist">"Post Gist…"</string>
<string name="linked_progress_update_key">"Update Key…"</string>
<string name="linked_button_start">"Link to GitHub account"</string>
<string name="linked_error_auth_failed">"Authorization failed!"</string>
<string name="linked_error_timeout">"Connection timeout!"</string>
<string name="linked_error_network">"Network error!"</string>
<string name="linked_error_http">"Communication error: %s"</string>
<string name="linked_webview_title_github">"GitHub Authorization"</string>
<string name="linked_gist_description">"OpenKeychain Linked Identity"</string>
<string name="linked_empty">"Link your key to GitHub, Twitter or other websites!"</string>
<string name="snack_btn_overwrite">"Overwrite"</string>
<string name="backup_code_explanation">"The backup will be secured with a backup code. Write it down before you proceed!"</string>
<string name="backup_code_enter">"Please enter the backup code:"</string>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M7,9A2,2 0 0,1 5,7A2,2 0 0,1 7,5A2,2 0 0,1 9,7A2,2 0 0,1 7,9M20,3H4A1,1 0 0,0 3,4V10A1,1 0 0,0 4,11H20A1,1 0 0,0 21,10V4A1,1 0 0,0 20,3M7,19A2,2 0 0,1 5,17A2,2 0 0,1 7,15A2,2 0 0,1 9,17A2,2 0 0,1 7,19M20,13H4A1,1 0 0,0 3,14V20A1,1 0 0,0 4,21H20A1,1 0 0,0 21,20V14A1,1 0 0,0 20,13Z" />
</svg>

Before

Width:  |  Height:  |  Size: 401 B

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" />
</svg>

Before

Width:  |  Height:  |  Size: 852 B

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M16.36,14C16.44,13.34 16.5,12.68 16.5,12C16.5,11.32 16.44,10.66 16.36,10H19.74C19.9,10.64 20,11.31 20,12C20,12.69 19.9,13.36 19.74,14M14.59,19.56C15.19,18.45 15.65,17.25 15.97,16H18.92C17.96,17.65 16.43,18.93 14.59,19.56M14.34,14H9.66C9.56,13.34 9.5,12.68 9.5,12C9.5,11.32 9.56,10.65 9.66,10H14.34C14.43,10.65 14.5,11.32 14.5,12C14.5,12.68 14.43,13.34 14.34,14M12,19.96C11.17,18.76 10.5,17.43 10.09,16H13.91C13.5,17.43 12.83,18.76 12,19.96M8,8H5.08C6.03,6.34 7.57,5.06 9.4,4.44C8.8,5.55 8.35,6.75 8,8M5.08,16H8C8.35,17.25 8.8,18.45 9.4,19.56C7.57,18.93 6.03,17.65 5.08,16M4.26,14C4.1,13.36 4,12.69 4,12C4,11.31 4.1,10.64 4.26,10H7.64C7.56,10.66 7.5,11.32 7.5,12C7.5,12.68 7.56,13.34 7.64,14M12,4.03C12.83,5.23 13.5,6.57 13.91,8H10.09C10.5,6.57 11.17,5.23 12,4.03M18.92,8H15.97C15.65,6.75 15.19,5.55 14.59,4.44C16.43,5.07 17.96,6.34 18.92,8M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M22.46,6C21.69,6.35 20.86,6.58 20,6.69C20.88,6.16 21.56,5.32 21.88,4.31C21.05,4.81 20.13,5.16 19.16,5.36C18.37,4.5 17.26,4 16,4C13.65,4 11.73,5.92 11.73,8.29C11.73,8.63 11.77,8.96 11.84,9.27C8.28,9.09 5.11,7.38 3,4.79C2.63,5.42 2.42,6.16 2.42,6.94C2.42,8.43 3.17,9.75 4.33,10.5C3.62,10.5 2.96,10.3 2.38,10C2.38,10 2.38,10 2.38,10.03C2.38,12.11 3.86,13.85 5.82,14.24C5.46,14.34 5.08,14.39 4.69,14.39C4.42,14.39 4.15,14.36 3.89,14.31C4.43,16 6,17.26 7.89,17.29C6.43,18.45 4.58,19.13 2.56,19.13C2.22,19.13 1.88,19.11 1.54,19.07C3.44,20.29 5.7,21 8.12,21C16,21 20.33,14.46 20.33,8.79C20.33,8.6 20.33,8.42 20.32,8.23C21.16,7.63 21.88,6.87 22.46,6Z" />
</svg>

Before

Width:  |  Height:  |  Size: 763 B

View File

@ -41,7 +41,7 @@ inkscape -w 192 -h 192 -e "$XDPI_DIR/${NAME}_96dp.png" "$SRC_DIR/$NAME.svg"
inkscape -w 256 -h 256 -e "$XXDPI_DIR/${NAME}_96dp.png" "$SRC_DIR/$NAME.svg"
done
for NAME in "create_key_robot" "linked_dns" "linked_https" "linked_github" "linked_twitter" "account_key"
for NAME in "create_key_robot" "account_key"
do
echo $NAME
inkscape -w 48 -h 48 -e "$MDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"