diff options
43 files changed, 837 insertions, 817 deletions
diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 3eeeb9503..cd8142174 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -55,8 +55,8 @@ dependencies { compile 'org.sufficientlysecure:html-textview:1.3' compile 'org.sufficientlysecure:donations:2.4' compile 'com.nispok:snackbar:2.11.0' - compile 'com.squareup.okhttp:okhttp:2.7.5' - compile 'com.squareup.okhttp:okhttp-urlconnection:2.7.5' + compile 'com.squareup.okhttp3:okhttp:3.2.0' + compile 'com.squareup.okhttp3:okhttp-urlconnection:3.2.0' compile 'org.apache.james:apache-mime4j-core:0.7.2' compile 'org.apache.james:apache-mime4j-dom:0.7.2' compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0' @@ -105,10 +105,10 @@ dependencyVerification { 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', 'org.sufficientlysecure:donations:96f8197bab26dfe41900d824f10f8f1914519cd62eedb77bdac5b223eccdf0a6', 'org.sufficientlysecure:html-textview:39048e35894e582adada388e6c00631803283f8defed8e07ad58a5f284f272ee', - 'com.squareup.okhttp:okhttp:88ac9fd1bb51f82bcc664cc1eb9c225c90dc4389d660231b4cc737bebfe7d0aa', + 'com.squareup.okhttp3:okhttp:a41cdb7b024c56436a21e38f00b4d12e3b7e01451ffe6c4f545acba805bba03b', + 'com.squareup.okhttp3:okhttp-urlconnection:7d6598a6665c166e2d4b78956a96056b9be7de192b3c923ccf4695d08e580833', 'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f', 'org.apache.james:apache-mime4j-core:4d7434c68f94b81a253c12f28e6bbb4d6239c361d6086a46e22e594bb43ac660', - 'com.squareup.okhttp:okhttp-urlconnection:6ae7c527abd2253e7c499ded790174f8f37d9676add63d9e23a76f3ba0588c07', 'org.thoughtcrime.ssl.pinning:AndroidPinning:afa1d74e699257fa75cb109ff29bac50726ef269c6e306bdeffe8223cee06ef4', 'org.apache.james:apache-mime4j-dom:7e6b06ee164a1c21b7e477249ea0b74a18fddce44764e5764085f58dd8c34633', 'com.mikepenz:materialdrawer:4e2644f454cc2ce48b956536d3339957c3f592adb2e0b6dad72d477da29f7677', diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java index faa2a1848..6217d1a01 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java @@ -23,10 +23,10 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.Response; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; @@ -34,6 +34,8 @@ import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.OkHttpClientFactory; +import org.sufficientlysecure.keychain.util.TlsHelper; import java.io.IOException; import java.net.Proxy; @@ -104,11 +106,10 @@ public class FacebookKeyserver extends Keyserver { String request = String.format(FB_KEY_URL_FORMAT, fbUsername); Log.d(Constants.TAG, "fetching from Facebook with: " + request + " proxy: " + mProxy); - OkHttpClient client = new OkHttpClient(); - client.setProxy(mProxy); - URL url = new URL(request); + OkHttpClient client = OkHttpClientFactory.getClientPinnedIfAvailable(url, mProxy); + Response response = client.newCall(new Request.Builder().url(url).build()).execute(); // contains body both in case of success or failure @@ -126,6 +127,9 @@ public class FacebookKeyserver extends Keyserver { throw new QueryFailedException("Cannot connect to Facebook. " + "Check your Internet connection!" + (mProxy == Proxy.NO_PROXY ? "" : " Using proxy " + mProxy)); + } catch (TlsHelper.TlsHelperException e) { + Log.e(Constants.TAG, "Exception in cert pinning", e); + throw new QueryFailedException("Exception in cert pinning. "); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index c2190318b..5e3d2ebc6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -18,16 +18,18 @@ package org.sufficientlysecure.keychain.keyimport; -import com.squareup.okhttp.MediaType; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.RequestBody; -import com.squareup.okhttp.Response; + +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.OkHttpClientFactory; import org.sufficientlysecure.keychain.util.TlsHelper; import java.io.IOException; @@ -42,7 +44,6 @@ import java.util.Comparator; import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; -import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -199,43 +200,12 @@ public class HkpKeyserver extends Keyserver { return mSecure ? "https://" : "http://"; } - /** - * returns a client with pinned certificate if necessary - * - * @param url url to be queried by client - * @param proxy proxy to be used by client - * @return client with a pinned certificate if necessary - */ - public static OkHttpClient getClient(URL url, Proxy proxy) throws IOException { - OkHttpClient client = new OkHttpClient(); - - try { - TlsHelper.usePinnedCertificateIfAvailable(client, url); - } catch (TlsHelper.TlsHelperException e) { - Log.w(Constants.TAG, e); - } - - // don't follow any redirects - client.setFollowRedirects(false); - client.setFollowSslRedirects(false); - - if (proxy != null) { - client.setProxy(proxy); - client.setConnectTimeout(30000, TimeUnit.MILLISECONDS); - } else { - client.setProxy(Proxy.NO_PROXY); - client.setConnectTimeout(5000, TimeUnit.MILLISECONDS); - } - client.setReadTimeout(45000, TimeUnit.MILLISECONDS); - - return client; - } private String query(String request, @NonNull Proxy proxy) throws QueryFailedException, HttpError { try { URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request); Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy); - OkHttpClient client = getClient(url, proxy); + OkHttpClient client = OkHttpClientFactory.getClientPinnedIfAvailable(url, proxy); Response response = client.newCall(new Request.Builder().url(url).build()).execute(); String responseBody = response.body().string(); // contains body both in case of success or failure @@ -249,6 +219,9 @@ public class HkpKeyserver extends Keyserver { Log.e(Constants.TAG, "IOException at HkpKeyserver", e); throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!" + (proxy == Proxy.NO_PROXY ? "" : " Using proxy " + proxy)); + } catch (TlsHelper.TlsHelperException e) { + Log.e(Constants.TAG, "Exception in pinning certs", e); + throw new QueryFailedException("Exception in pinning certs"); } } @@ -413,6 +386,7 @@ public class HkpKeyserver extends Keyserver { Log.d(Constants.TAG, "hkp keyserver add: " + url); Log.d(Constants.TAG, "params: " + params); + RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), params); Request request = new Request.Builder() @@ -422,7 +396,7 @@ public class HkpKeyserver extends Keyserver { .post(body) .build(); - Response response = getClient(url, mProxy).newCall(request).execute(); + Response response = OkHttpClientFactory.getClientPinnedIfAvailable(url, mProxy).newCall(request).execute(); Log.d(Constants.TAG, "response code: " + response.code()); Log.d(Constants.TAG, "answer: " + response.body().string()); @@ -434,6 +408,9 @@ public class HkpKeyserver extends Keyserver { } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); throw new AddKeyException(); + } catch (TlsHelper.TlsHelperException e) { + Log.e(Constants.TAG, "Exception in pinning certs", e); + throw new AddKeyException(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java index 6a584a939..a5f882dd0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java @@ -2,10 +2,10 @@ package org.sufficientlysecure.keychain.linked; import android.content.Context; -import com.squareup.okhttp.CertificatePinner; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.Response; +import okhttp3.CertificatePinner; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.json.JSONException; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource; @@ -16,6 +16,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.OkHttpClientFactory; import java.io.IOException; import java.net.MalformedURLException; @@ -236,13 +237,16 @@ public abstract class LinkedTokenResource extends LinkedResource { return builder.build(); } + public static String getResponseBody(Request request, String... pins) throws IOException, HttpStatusException { - Log.d("Connection to: "+request.url().getHost(),""); - OkHttpClient client = new OkHttpClient(); - if(pins !=null){ - client.setCertificatePinner(getCertificatePinner(request.url().getHost(),pins)); + Log.d("Connection to: " + request.url().url().getHost(), ""); + OkHttpClient client; + if (pins != null) { + client = OkHttpClientFactory.getSimpleClientPinned(getCertificatePinner(request.url().url().getHost(), pins)); + } else { + client = OkHttpClientFactory.getSimpleClient(); } Response response = client.newCall(request).execute(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java index d6b8640e3..da531e8fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java @@ -6,7 +6,7 @@ import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; -import com.squareup.okhttp.Request; +import okhttp3.Request; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java index 134582ae1..0e87ca6e5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java @@ -6,7 +6,7 @@ import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; -import com.squareup.okhttp.Request; +import okhttp3.Request; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java index b0f02c43c..db3b64225 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java @@ -7,11 +7,11 @@ import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; import android.util.Log; -import com.squareup.okhttp.MediaType; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.RequestBody; 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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index 3845e07cb..09149716c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -28,7 +28,8 @@ import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class CertifyKeyActivity extends BaseActivity { public static final String EXTRA_RESULT = "operation_result"; - public static final String EXTRA_KEY_IDS = "extra_key_ids"; + // For sending masterKeyIds to MultiUserIdsFragment to display list of keys + public static final String EXTRA_KEY_IDS = MultiUserIdsFragment.EXTRA_KEY_IDS ; public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id"; @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 357b445f0..ad39ff43d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -62,58 +62,26 @@ import java.util.ArrayList; import java.util.Date; public class CertifyKeyFragment - extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult> - implements LoaderManager.LoaderCallbacks<Cursor> { - - public static final String ARG_CHECK_STATES = "check_states"; + extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult> { private CheckBox mUploadKeyCheckbox; - ListView mUserIds; private CertifyKeySpinner mCertifyKeySpinner; - private long[] mPubMasterKeyIds; - - public static final String[] USER_IDS_PROJECTION = new String[]{ - UserPackets._ID, - UserPackets.MASTER_KEY_ID, - UserPackets.USER_ID, - UserPackets.IS_PRIMARY, - UserPackets.IS_REVOKED - }; - private static final int INDEX_MASTER_KEY_ID = 1; - private static final int INDEX_USER_ID = 2; - @SuppressWarnings("unused") - private static final int INDEX_IS_PRIMARY = 3; - @SuppressWarnings("unused") - private static final int INDEX_IS_REVOKED = 4; - - private MultiUserIdsAdapter mUserIdsAdapter; + private MultiUserIdsFragment mMultiUserIdsFragment; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS); - if (mPubMasterKeyIds == null) { - Log.e(Constants.TAG, "List of key ids to certify missing!"); - getActivity().finish(); - return; - } - - ArrayList<Boolean> checkedStates; - if (savedInstanceState != null) { - checkedStates = (ArrayList<Boolean>) savedInstanceState.getSerializable(ARG_CHECK_STATES); - // key spinner and the checkbox keep their own state - } else { - checkedStates = null; - + if (savedInstanceState == null) { // preselect certify key id if given long certifyKeyId = getActivity().getIntent() .getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); if (certifyKeyId != Constants.key.none) { try { - CachedPublicKeyRing key = (new ProviderHelper(getActivity())).getCachedPublicKeyRing(certifyKeyId); + CachedPublicKeyRing key = (new ProviderHelper(getActivity())) + .getCachedPublicKeyRing(certifyKeyId); if (key.canCertify()) { mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId); } @@ -121,15 +89,8 @@ public class CertifyKeyFragment Log.e(Constants.TAG, "certify certify check failed", e); } } - } - mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates); - mUserIds.setAdapter(mUserIdsAdapter); - mUserIds.setDividerHeight(0); - - getLoaderManager().initLoader(0, null, this); - OperationResult result = getActivity().getIntent().getParcelableExtra(CertifyKeyActivity.EXTRA_RESULT); if (result != null) { // display result from import @@ -138,21 +99,13 @@ public class CertifyKeyFragment } @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - ArrayList<Boolean> states = mUserIdsAdapter.getCheckStates(); - // no proper parceling method available :( - outState.putSerializable(ARG_CHECK_STATES, states); - } - - @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.certify_key_fragment, null); mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner); mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox); - mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + mMultiUserIdsFragment = (MultiUserIdsFragment) + getChildFragmentManager().findFragmentById(R.id.multi_user_ids_fragment); // make certify image gray, like action icons ImageView vActionCertifyImage = @@ -184,127 +137,10 @@ public class CertifyKeyFragment } @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - Uri uri = UserPackets.buildUserIdsUri(); - - String selection, ids[]; - { - // generate placeholders and string selection args - ids = new String[mPubMasterKeyIds.length]; - StringBuilder placeholders = new StringBuilder("?"); - for (int i = 0; i < mPubMasterKeyIds.length; i++) { - ids[i] = Long.toString(mPubMasterKeyIds[i]); - if (i != 0) { - placeholders.append(",?"); - } - } - // put together selection string - selection = UserPackets.IS_REVOKED + " = 0" + " AND " - + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID - + " IN (" + placeholders + ")"; - } - - return new CursorLoader(getActivity(), uri, - USER_IDS_PROJECTION, selection, ids, - Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC" - + ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC" - ); - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - - MatrixCursor matrix = new MatrixCursor(new String[]{ - "_id", "user_data", "grouped" - }) { - @Override - public byte[] getBlob(int column) { - return super.getBlob(column); - } - }; - data.moveToFirst(); - - long lastMasterKeyId = 0; - String lastName = ""; - ArrayList<String> uids = new ArrayList<>(); - - boolean header = true; - - // Iterate over all rows - while (!data.isAfterLast()) { - long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - String userId = data.getString(INDEX_USER_ID); - KeyRing.UserId pieces = KeyRing.splitUserId(userId); - - // Two cases: - - boolean grouped = masterKeyId == lastMasterKeyId; - boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name); - // Remember for next loop - lastName = pieces.name; - - Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped")); - - if (!subGrouped) { - // 1. This name should NOT be grouped with the previous, so we flush the buffer - - Parcel p = Parcel.obtain(); - p.writeStringList(uids); - byte[] d = p.marshall(); - p.recycle(); - - matrix.addRow(new Object[]{ - lastMasterKeyId, d, header ? 1 : 0 - }); - // indicate that we have a header for this masterKeyId - header = false; - - // Now clear the buffer, and add the new user id, for the next round - uids.clear(); - - } - - // 2. This name should be grouped with the previous, just add to buffer - uids.add(userId); - lastMasterKeyId = masterKeyId; - - // If this one wasn't grouped, the next one's gotta be a header - if (!grouped) { - header = true; - } - - // Regardless of the outcome, move to next entry - data.moveToNext(); - - } - - // If there is anything left in the buffer, flush it one last time - if (!uids.isEmpty()) { - - Parcel p = Parcel.obtain(); - p.writeStringList(uids); - byte[] d = p.marshall(); - p.recycle(); - - matrix.addRow(new Object[]{ - lastMasterKeyId, d, header ? 1 : 0 - }); - - } - - mUserIdsAdapter.swapCursor(matrix); - } - - @Override - public void onLoaderReset(Loader<Cursor> loader) { - mUserIdsAdapter.swapCursor(null); - } - - @Override public CertifyActionsParcel createOperationInput() { // Bail out if there is not at least one user id selected - ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions(); + ArrayList<CertifyAction> certifyActions = mMultiUserIdsFragment.getSelectedCertifyActions(); if (certifyActions.isEmpty()) { Notify.create(getActivity(), "No identities selected!", Notify.Style.ERROR).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index 58a28da51..cbf862074 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -281,11 +281,11 @@ public class CreateKeyFinalFragment extends Fragment { saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase()); } else { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 4096, null, KeyFlags.CERTIFY_OTHER, 0L)); + 3072, null, KeyFlags.CERTIFY_OTHER, 0L)); saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 4096, null, KeyFlags.SIGN_DATA, 0L)); + 3072, null, KeyFlags.SIGN_DATA, 0L)); saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); + 3072, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); saveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null ? new ChangeUnlockParcel(createKeyActivity.mPassphrase) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 2184f91f1..4cbb4724e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -561,15 +561,9 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring } private void addSubkey() { - boolean willBeMasterKey; - if (mSubkeysAdapter != null) { - willBeMasterKey = mSubkeysAdapter.getCount() == 0 && mSubkeysAddedAdapter.getCount() == 0; - } else { - willBeMasterKey = mSubkeysAddedAdapter.getCount() == 0; - } - + // new subkey will never be a masterkey, as masterkey cannot be removed AddSubkeyDialogFragment addSubkeyDialogFragment = - AddSubkeyDialogFragment.newInstance(willBeMasterKey); + AddSubkeyDialogFragment.newInstance(false); addSubkeyDialogFragment .setOnAlgorithmSelectedListener( new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java new file mode 100644 index 000000000..8ba695cf7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java @@ -0,0 +1,223 @@ +package org.sufficientlysecure.keychain.ui; + +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcel; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel; +import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; + +public class MultiUserIdsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>{ + public static final String ARG_CHECK_STATES = "check_states"; + public static final String EXTRA_KEY_IDS = "extra_key_ids"; + private boolean checkboxVisibility = true; + + ListView mUserIds; + private MultiUserIdsAdapter mUserIdsAdapter; + + private long[] mPubMasterKeyIds; + + public static final String[] USER_IDS_PROJECTION = new String[]{ + KeychainContract.UserPackets._ID, + KeychainContract.UserPackets.MASTER_KEY_ID, + KeychainContract.UserPackets.USER_ID, + KeychainContract.UserPackets.IS_PRIMARY, + KeychainContract.UserPackets.IS_REVOKED + }; + private static final int INDEX_MASTER_KEY_ID = 1; + private static final int INDEX_USER_ID = 2; + @SuppressWarnings("unused") + private static final int INDEX_IS_PRIMARY = 3; + @SuppressWarnings("unused") + private static final int INDEX_IS_REVOKED = 4; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.multi_user_ids_fragment, null); + + mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + + return view; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(EXTRA_KEY_IDS); + if (mPubMasterKeyIds == null) { + Log.e(Constants.TAG, "List of key ids to certify missing!"); + getActivity().finish(); + return; + } + + ArrayList<Boolean> checkedStates = null; + if (savedInstanceState != null) { + checkedStates = (ArrayList<Boolean>) savedInstanceState.getSerializable(ARG_CHECK_STATES); + } + + mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates, checkboxVisibility); + mUserIds.setAdapter(mUserIdsAdapter); + mUserIds.setDividerHeight(0); + + getLoaderManager().initLoader(0, null, this); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + ArrayList<Boolean> states = mUserIdsAdapter.getCheckStates(); + // no proper parceling method available :( + outState.putSerializable(ARG_CHECK_STATES, states); + } + + public ArrayList<CertifyActionsParcel.CertifyAction> getSelectedCertifyActions() { + if (!checkboxVisibility) { + throw new AssertionError("Item selection not allowed"); + } + + return mUserIdsAdapter.getSelectedCertifyActions(); + } + + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + Uri uri = KeychainContract.UserPackets.buildUserIdsUri(); + + String selection, ids[]; + { + // generate placeholders and string selection args + ids = new String[mPubMasterKeyIds.length]; + StringBuilder placeholders = new StringBuilder("?"); + for (int i = 0; i < mPubMasterKeyIds.length; i++) { + ids[i] = Long.toString(mPubMasterKeyIds[i]); + if (i != 0) { + placeholders.append(",?"); + } + } + // put together selection string + selection = KeychainContract.UserPackets.IS_REVOKED + " = 0" + " AND " + + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID + + " IN (" + placeholders + ")"; + } + + return new CursorLoader(getActivity(), uri, + USER_IDS_PROJECTION, selection, ids, + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID + " ASC" + + ", " + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.USER_ID + " ASC" + ); + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + + MatrixCursor matrix = new MatrixCursor(new String[]{ + "_id", "user_data", "grouped" + }) { + @Override + public byte[] getBlob(int column) { + return super.getBlob(column); + } + }; + data.moveToFirst(); + + long lastMasterKeyId = 0; + String lastName = ""; + ArrayList<String> uids = new ArrayList<>(); + + boolean header = true; + + // Iterate over all rows + while (!data.isAfterLast()) { + long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); + String userId = data.getString(INDEX_USER_ID); + KeyRing.UserId pieces = KeyRing.splitUserId(userId); + + // Two cases: + + boolean grouped = masterKeyId == lastMasterKeyId; + boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name); + // Remember for next loop + lastName = pieces.name; + + Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped")); + + if (!subGrouped) { + // 1. This name should NOT be grouped with the previous, so we flush the buffer + + Parcel p = Parcel.obtain(); + p.writeStringList(uids); + byte[] d = p.marshall(); + p.recycle(); + + matrix.addRow(new Object[]{ + lastMasterKeyId, d, header ? 1 : 0 + }); + // indicate that we have a header for this masterKeyId + header = false; + + // Now clear the buffer, and add the new user id, for the next round + uids.clear(); + + } + + // 2. This name should be grouped with the previous, just add to buffer + uids.add(userId); + lastMasterKeyId = masterKeyId; + + // If this one wasn't grouped, the next one's gotta be a header + if (!grouped) { + header = true; + } + + // Regardless of the outcome, move to next entry + data.moveToNext(); + + } + + // If there is anything left in the buffer, flush it one last time + if (!uids.isEmpty()) { + + Parcel p = Parcel.obtain(); + p.writeStringList(uids); + byte[] d = p.marshall(); + p.recycle(); + + matrix.addRow(new Object[]{ + lastMasterKeyId, d, header ? 1 : 0 + }); + + } + + mUserIdsAdapter.swapCursor(matrix); + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + mUserIdsAdapter.swapCursor(null); + } + + public void setCheckboxVisibility(boolean checkboxVisibility) { + this.checkboxVisibility = checkboxVisibility; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index f38e4928d..306b022c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -31,10 +31,7 @@ import android.widget.Spinner; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.UploadResult; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; @@ -53,7 +50,6 @@ public class UploadKeyActivity extends BaseActivity // CryptoOperationHelper.Callback vars private String mKeyserver; - private long mMasterKeyId; private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper; @Override @@ -63,6 +59,10 @@ public class UploadKeyActivity extends BaseActivity mUploadButton = findViewById(R.id.upload_key_action_upload); mKeyServerSpinner = (Spinner) findViewById(R.id.upload_key_keyserver); + MultiUserIdsFragment mMultiUserIdsFragment = (MultiUserIdsFragment) + getSupportFragmentManager().findFragmentById(R.id.multi_user_ids_fragment); + mMultiUserIdsFragment.setCheckboxVisibility(false); + ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, Preferences.getPreferences(this) .getKeyServers() @@ -89,15 +89,6 @@ public class UploadKeyActivity extends BaseActivity return; } - try { - mMasterKeyId = new ProviderHelper(this).getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingUri(mDataUri)).getMasterKeyId(); - } catch (PgpKeyNotFoundException e) { - Log.e(Constants.TAG, "Intent data pointed to bad key!"); - finish(); - return; - } - } @Override @@ -136,7 +127,9 @@ public class UploadKeyActivity extends BaseActivity @Override public UploadKeyringParcel createOperationInput() { - return new UploadKeyringParcel(mKeyserver, mMasterKeyId); + long[] masterKeyIds = getIntent().getLongArrayExtra(MultiUserIdsFragment.EXTRA_KEY_IDS); + + return new UploadKeyringParcel(mKeyserver, masterKeyIds[0]); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 35c1615c7..d81797454 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -32,6 +32,8 @@ import android.app.ActivityOptions; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PorterDuff; import android.net.Uri; import android.nfc.NfcAdapter; import android.os.AsyncTask; @@ -923,6 +925,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements } mPhoto.setImageBitmap(photo); + mPhoto.setColorFilter(getResources().getColor(R.color.toolbar_photo_tint), PorterDuff.Mode.SRC_ATOP); mPhotoLayout.setVisibility(View.VISIBLE); } }; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index 0aa8330ea..02eae1b2b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -455,8 +455,19 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements } private void uploadToKeyserver() { + long keyId; + try { + keyId = new ProviderHelper(getActivity()) + .getCachedPublicKeyRing(mDataUri) + .extractOrGetMasterKeyId(); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "key not found!", e); + Notify.create(getActivity(), "key not found", Style.ERROR).show(); + return; + } Intent uploadIntent = new Intent(getActivity(), UploadKeyActivity.class); uploadIntent.setData(mDataUri); + uploadIntent.putExtra(MultiUserIdsFragment.EXTRA_KEY_IDS, new long[]{keyId}); startActivityForResult(uploadIntent, 0); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java index b91abf076..d247faddc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java @@ -39,6 +39,7 @@ import java.util.ArrayList; public class MultiUserIdsAdapter extends CursorAdapter { private LayoutInflater mInflater; private final ArrayList<Boolean> mCheckStates; + private boolean checkboxVisibility = true; public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates) { super(context, c, flags); @@ -46,6 +47,11 @@ public class MultiUserIdsAdapter extends CursorAdapter { mCheckStates = preselectStates == null ? new ArrayList<Boolean>() : preselectStates; } + public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates, boolean checkboxVisibility) { + this(context,c,flags,preselectStates); + this.checkboxVisibility = checkboxVisibility; + } + @Override public Cursor swapCursor(Cursor newCursor) { if (newCursor != null) { @@ -138,6 +144,7 @@ public class MultiUserIdsAdapter extends CursorAdapter { } }); vCheckBox.setClickable(false); + vCheckBox.setVisibility(checkboxVisibility?View.VISIBLE:View.GONE); View vUidBody = view.findViewById(R.id.user_id_body); vUidBody.setClickable(true); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java index 8b2481c29..717299b55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.app.Activity; import android.content.Context; import android.graphics.Typeface; +import android.support.v4.app.FragmentActivity; import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.View; @@ -32,6 +33,7 @@ import android.widget.TextView; import org.bouncycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import java.util.Calendar; @@ -65,10 +67,11 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd public SaveKeyringParcel.SubkeyAdd mModel; } + public View getView(final int position, View convertView, ViewGroup parent) { if (convertView == null) { // Not recycled, inflate a new view - convertView = mInflater.inflate(R.layout.view_key_adv_subkey_item, null); + convertView = mInflater.inflate(R.layout.view_key_adv_subkey_item, parent, false); final ViewHolder holder = new ViewHolder(); holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id); holder.vKeyDetails = (TextView) convertView.findViewById(R.id.subkey_item_details); @@ -88,16 +91,8 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd vStatus.setVisibility(View.GONE); convertView.setTag(holder); - - holder.vDelete.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - // remove reference model item from adapter (data and notify about change) - SubkeysAddedAdapter.this.remove(holder.mModel); - } - }); - } + final ViewHolder holder = (ViewHolder) convertView.getTag(); // save reference to model item @@ -113,8 +108,41 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd boolean isMasterKey = mNewKeyring && position == 0; if (isMasterKey) { holder.vKeyId.setTypeface(null, Typeface.BOLD); + holder.vDelete.setImageResource(R.drawable.ic_change_grey_24dp); + holder.vDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // swapping out the old master key with newly set master key + AddSubkeyDialogFragment addSubkeyDialogFragment = + AddSubkeyDialogFragment.newInstance(true); + addSubkeyDialogFragment + .setOnAlgorithmSelectedListener( + new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() { + @Override + public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey) { + // calculate manually as the provided position variable + // is not always accurate + int pos = SubkeysAddedAdapter.this.getPosition(holder.mModel); + SubkeysAddedAdapter.this.remove(holder.mModel); + SubkeysAddedAdapter.this.insert(newSubkey, pos); + } + } + ); + addSubkeyDialogFragment.show( + ((FragmentActivity)mActivity).getSupportFragmentManager() + , "addSubkeyDialog"); + } + }); } else { holder.vKeyId.setTypeface(null, Typeface.NORMAL); + holder.vDelete.setImageResource(R.drawable.ic_close_grey_24dp); + holder.vDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // remove reference model item from adapter (data and notify about change) + SubkeysAddedAdapter.this.remove(holder.mModel); + } + }); } holder.vKeyId.setText(R.string.edit_key_new_subkey); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java index 3d96f3c6d..3dcc2f58b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java @@ -50,14 +50,13 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; +import okhttp3.OkHttpClient; +import okhttp3.Request; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; -import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.OkHttpClientFactory; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.TlsHelper; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; @@ -354,19 +353,15 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On Log.d("Converted URL", newKeyserver.toString()); - OkHttpClient client = HkpKeyserver.getClient(newKeyserver.toURL(), proxy); - - // don't follow any redirects - client.setFollowRedirects(false); - client.setFollowSslRedirects(false); - if (onlyTrustedKeyserver - && !TlsHelper.usePinnedCertificateIfAvailable(client, newKeyserver.toURL())) { + && TlsHelper.getPinnedSslSocketFactory(newKeyserver.toURL()) == null) { Log.w(Constants.TAG, "No pinned certificate for this host in OpenKeychain's assets."); reason = FailureReason.NO_PINNED_CERTIFICATE; return reason; } + OkHttpClient client = OkHttpClientFactory.getClientPinnedIfAvailable(newKeyserver.toURL(), proxy); + client.newCall(new Request.Builder().url(newKeyserver.toURL()).build()).execute(); } catch (TlsHelper.TlsHelperException e) { reason = FailureReason.CONNECTION_FAILED; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java index 5b75723fb..ce1665382 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java @@ -17,25 +17,26 @@ package org.sufficientlysecure.keychain.ui.dialog; -import android.annotation.TargetApi; +import android.annotation.SuppressLint; import android.app.Dialog; -import android.os.Build; +import android.content.Context; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.support.v7.app.AlertDialog; -import android.text.Editable; -import android.text.TextWatcher; +import android.text.Html; import android.view.LayoutInflater; import android.view.View; -import android.view.inputmethod.InputMethodManager; +import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.DatePicker; -import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.RadioGroup; import android.widget.Spinner; import android.widget.TableRow; import android.widget.TextView; @@ -49,14 +50,18 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.util.Choice; import java.util.ArrayList; -import java.util.Arrays; import java.util.Calendar; +import java.util.List; import java.util.TimeZone; public class AddSubkeyDialogFragment extends DialogFragment { public interface OnAlgorithmSelectedListener { - public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey); + void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey); + } + + public enum SupportedKeyType { + RSA_2048, RSA_3072, RSA_4096, ECC_P256, ECC_P521 } private static final String ARG_WILL_BE_MASTER_KEY = "will_be_master_key"; @@ -66,18 +71,12 @@ public class AddSubkeyDialogFragment extends DialogFragment { private CheckBox mNoExpiryCheckBox; private TableRow mExpiryRow; private DatePicker mExpiryDatePicker; - private Spinner mAlgorithmSpinner; - private View mKeySizeRow; - private Spinner mKeySizeSpinner; - private View mCurveRow; - private Spinner mCurveSpinner; - private TextView mCustomKeyTextView; - private EditText mCustomKeyEditText; - private TextView mCustomKeyInfoTextView; - private CheckBox mFlagCertify; - private CheckBox mFlagSign; - private CheckBox mFlagEncrypt; - private CheckBox mFlagAuthenticate; + private Spinner mKeyTypeSpinner; + private RadioGroup mUsageRadioGroup; + private RadioButton mUsageNone; + private RadioButton mUsageSign; + private RadioButton mUsageEncrypt; + private RadioButton mUsageSignAndEncrypt; private boolean mWillBeMasterKey; @@ -96,6 +95,8 @@ public class AddSubkeyDialogFragment extends DialogFragment { return frag; } + + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final FragmentActivity context = getActivity(); @@ -106,25 +107,27 @@ public class AddSubkeyDialogFragment extends DialogFragment { CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context); + @SuppressLint("InflateParams") View view = mInflater.inflate(R.layout.add_subkey_dialog, null); dialog.setView(view); - dialog.setTitle(R.string.title_add_subkey); mNoExpiryCheckBox = (CheckBox) view.findViewById(R.id.add_subkey_no_expiry); mExpiryRow = (TableRow) view.findViewById(R.id.add_subkey_expiry_row); mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker); - mAlgorithmSpinner = (Spinner) view.findViewById(R.id.add_subkey_algorithm); - mKeySizeSpinner = (Spinner) view.findViewById(R.id.add_subkey_size); - mCurveSpinner = (Spinner) view.findViewById(R.id.add_subkey_curve); - mKeySizeRow = view.findViewById(R.id.add_subkey_row_size); - mCurveRow = view.findViewById(R.id.add_subkey_row_curve); - mCustomKeyTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_label); - mCustomKeyEditText = (EditText) view.findViewById(R.id.add_subkey_custom_key_size_input); - mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_info); - mFlagCertify = (CheckBox) view.findViewById(R.id.add_subkey_flag_certify); - mFlagSign = (CheckBox) view.findViewById(R.id.add_subkey_flag_sign); - mFlagEncrypt = (CheckBox) view.findViewById(R.id.add_subkey_flag_encrypt); - mFlagAuthenticate = (CheckBox) view.findViewById(R.id.add_subkey_flag_authenticate); + mKeyTypeSpinner = (Spinner) view.findViewById(R.id.add_subkey_type); + mUsageRadioGroup = (RadioGroup) view.findViewById(R.id.add_subkey_usage_group); + mUsageNone = (RadioButton) view.findViewById(R.id.add_subkey_usage_none); + mUsageSign = (RadioButton) view.findViewById(R.id.add_subkey_usage_sign); + mUsageEncrypt = (RadioButton) view.findViewById(R.id.add_subkey_usage_encrypt); + mUsageSignAndEncrypt = (RadioButton) view.findViewById(R.id.add_subkey_usage_sign_and_encrypt); + + if(mWillBeMasterKey) { + dialog.setTitle(R.string.title_change_master_key); + mUsageNone.setVisibility(View.VISIBLE); + mUsageNone.setChecked(true); + } else { + dialog.setTitle(R.string.title_add_subkey); + } mNoExpiryCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override @@ -143,65 +146,24 @@ public class AddSubkeyDialogFragment extends DialogFragment { mExpiryDatePicker.setMinDate(minDateCal.getTime().getTime()); { - ArrayList<Choice<Algorithm>> choices = new ArrayList<>(); - choices.add(new Choice<>(Algorithm.DSA, getResources().getString( - R.string.dsa))); - if (!mWillBeMasterKey) { - choices.add(new Choice<>(Algorithm.ELGAMAL, getResources().getString( - R.string.elgamal))); - } - choices.add(new Choice<>(Algorithm.RSA, getResources().getString( - R.string.rsa))); - choices.add(new Choice<>(Algorithm.ECDSA, getResources().getString( - R.string.ecdsa))); - choices.add(new Choice<>(Algorithm.ECDH, getResources().getString( - R.string.ecdh))); - ArrayAdapter<Choice<Algorithm>> adapter = new ArrayAdapter<>(context, + ArrayList<Choice<SupportedKeyType>> choices = new ArrayList<>(); + choices.add(new Choice<>(SupportedKeyType.RSA_2048, getResources().getString( + R.string.rsa_2048), getResources().getString(R.string.rsa_2048_description_html))); + choices.add(new Choice<>(SupportedKeyType.RSA_3072, getResources().getString( + R.string.rsa_3072), getResources().getString(R.string.rsa_3072_description_html))); + choices.add(new Choice<>(SupportedKeyType.RSA_4096, getResources().getString( + R.string.rsa_4096), getResources().getString(R.string.rsa_4096_description_html))); + choices.add(new Choice<>(SupportedKeyType.ECC_P256, getResources().getString( + R.string.ecc_p256), getResources().getString(R.string.ecc_p256_description_html))); + choices.add(new Choice<>(SupportedKeyType.ECC_P521, getResources().getString( + R.string.ecc_p521), getResources().getString(R.string.ecc_p521_description_html))); + TwoLineArrayAdapter adapter = new TwoLineArrayAdapter(context, android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mAlgorithmSpinner.setAdapter(adapter); - // make RSA the default + mKeyTypeSpinner.setAdapter(adapter); + // make RSA 3072 the default for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == Algorithm.RSA) { - mAlgorithmSpinner.setSelection(i); - break; - } - } - } - - // dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change - ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, - new ArrayList<CharSequence>(Arrays.asList(getResources().getStringArray(R.array.rsa_key_size_spinner_values)))); - keySizeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mKeySizeSpinner.setAdapter(keySizeAdapter); - mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length - - { - ArrayList<Choice<Curve>> choices = new ArrayList<>(); - - choices.add(new Choice<>(Curve.NIST_P256, getResources().getString( - R.string.key_curve_nist_p256))); - choices.add(new Choice<>(Curve.NIST_P384, getResources().getString( - R.string.key_curve_nist_p384))); - choices.add(new Choice<>(Curve.NIST_P521, getResources().getString( - R.string.key_curve_nist_p521))); - - /* @see SaveKeyringParcel - choices.add(new Choice<Curve>(Curve.BRAINPOOL_P256, getResources().getString( - R.string.key_curve_bp_p256))); - choices.add(new Choice<Curve>(Curve.BRAINPOOL_P384, getResources().getString( - R.string.key_curve_bp_p384))); - choices.add(new Choice<Curve>(Curve.BRAINPOOL_P512, getResources().getString( - R.string.key_curve_bp_p512))); - */ - - ArrayAdapter<Choice<Curve>> adapter = new ArrayAdapter<>(context, - android.R.layout.simple_spinner_item, choices); - mCurveSpinner.setAdapter(adapter); - // make NIST P-256 the default - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == Curve.NIST_P256) { - mCurveSpinner.setSelection(i); + if (choices.get(i).getId() == SupportedKeyType.RSA_3072) { + mKeyTypeSpinner.setSelection(i); break; } } @@ -215,45 +177,35 @@ public class AddSubkeyDialogFragment extends DialogFragment { final AlertDialog alertDialog = dialog.show(); - mCustomKeyEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } + mKeyTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override - public void afterTextChanged(Editable s) { - setOkButtonAvailability(alertDialog); - } - }); - - mKeySizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - setCustomKeyVisibility(); - setOkButtonAvailability(alertDialog); - } + // noinspection unchecked + SupportedKeyType keyType = ((Choice<SupportedKeyType>) parent.getSelectedItem()).getId(); - @Override - public void onNothingSelected(AdapterView<?> parent) { - } - }); + // RadioGroup.getCheckedRadioButtonId() gives the wrong RadioButton checked + // when programmatically unchecking children radio buttons. Clearing all is the only option. + mUsageRadioGroup.clearCheck(); - mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - updateUiForAlgorithm(((Choice<Algorithm>) parent.getSelectedItem()).getId()); + if(mWillBeMasterKey) { + mUsageNone.setChecked(true); + } - setCustomKeyVisibility(); - setOkButtonAvailability(alertDialog); + if (keyType == SupportedKeyType.ECC_P521 || keyType == SupportedKeyType.ECC_P256) { + mUsageSignAndEncrypt.setEnabled(false); + if (mWillBeMasterKey) { + mUsageEncrypt.setEnabled(false); + } + } else { + // need to enable if previously disabled for ECC masterkey + mUsageEncrypt.setEnabled(true); + mUsageSignAndEncrypt.setEnabled(true); + } } @Override - public void onNothingSelected(AdapterView<?> parent) { - } + public void onNothingSelected(AdapterView<?> parent) {} }); return alertDialog; @@ -269,36 +221,74 @@ public class AddSubkeyDialogFragment extends DialogFragment { positiveButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (!mFlagCertify.isChecked() && !mFlagSign.isChecked() - && !mFlagEncrypt.isChecked() && !mFlagAuthenticate.isChecked()) { - Toast.makeText(getActivity(), R.string.edit_key_select_flag, Toast.LENGTH_LONG).show(); + if (mUsageRadioGroup.getCheckedRadioButtonId() == -1) { + Toast.makeText(getActivity(), R.string.edit_key_select_usage, Toast.LENGTH_LONG).show(); return; } - Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId(); + // noinspection unchecked + SupportedKeyType keyType = ((Choice<SupportedKeyType>) mKeyTypeSpinner.getSelectedItem()).getId(); Curve curve = null; Integer keySize = null; - // For EC keys, add a curve - if (algorithm == Algorithm.ECDH || algorithm == Algorithm.ECDSA) { - curve = ((Choice<Curve>) mCurveSpinner.getSelectedItem()).getId(); - // Otherwise, get a keysize - } else { - keySize = getProperKeyLength(algorithm, getSelectedKeyLength()); + Algorithm algorithm = null; + + // set keysize & curve, for RSA & ECC respectively + switch (keyType) { + case RSA_2048: { + keySize = 2048; + break; + } + case RSA_3072: { + keySize = 3072; + break; + } + case RSA_4096: { + keySize = 4096; + break; + } + case ECC_P256: { + curve = Curve.NIST_P256; + break; + } + case ECC_P521: { + curve = Curve.NIST_P521; + break; + } } + // set algorithm + switch (keyType) { + case RSA_2048: + case RSA_3072: + case RSA_4096: { + algorithm = Algorithm.RSA; + break; + } + + case ECC_P256: + case ECC_P521: { + if(mUsageEncrypt.isChecked()) { + algorithm = Algorithm.ECDH; + } else { + algorithm = Algorithm.ECDSA; + } + break; + } + } + + // set flags int flags = 0; - if (mFlagCertify.isChecked()) { + if (mWillBeMasterKey) { flags |= KeyFlags.CERTIFY_OTHER; } - if (mFlagSign.isChecked()) { + if (mUsageSign.isChecked()) { flags |= KeyFlags.SIGN_DATA; - } - if (mFlagEncrypt.isChecked()) { + } else if (mUsageEncrypt.isChecked()) { flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; + } else if (mUsageSignAndEncrypt.isChecked()) { + flags |= KeyFlags.SIGN_DATA | KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; } - if (mFlagAuthenticate.isChecked()) { - flags |= KeyFlags.AUTHENTICATION; - } + long expiry; if (mNoExpiryCheckBox.isChecked()) { @@ -332,206 +322,29 @@ public class AddSubkeyDialogFragment extends DialogFragment { } } - private int getSelectedKeyLength() { - final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem(); - final String customLengthString = getResources().getString(R.string.key_size_custom); - final boolean customSelected = customLengthString.equals(selectedItemString); - String keyLengthString = customSelected ? mCustomKeyEditText.getText().toString() : selectedItemString; - int keySize; - try { - keySize = Integer.parseInt(keyLengthString); - } catch (NumberFormatException e) { - keySize = 0; - } - return keySize; - } - - /** - * <h3>RSA</h3> - * <p>for RSA algorithm, key length must be greater than 2048. Possibility to generate keys bigger - * than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check - * <a href="http://www.javamex.com/tutorials/cryptography/rsa_key_length.shtml">RSA key length plot</a> and - * <a href="http://www.keylength.com/">Cryptographic Key Length Recommendation</a>). Also, key length must be a - * multiplicity of 8.</p> - * <h3>ElGamal</h3> - * <p>For ElGamal algorithm, supported key lengths are 2048, 3072, 4096 or 8192 bits.</p> - * <h3>DSA</h3> - * <p>For DSA algorithm key length must be between 2048 and 3072. Also, it must me dividable by 64.</p> - * - * @return correct key length, according to BouncyCastle specification. Returns <code>-1</code>, if key length is - * inappropriate. - */ - private int getProperKeyLength(Algorithm algorithm, int currentKeyLength) { - final int[] elGamalSupportedLengths = {2048, 3072, 4096, 8192}; - int properKeyLength = -1; - switch (algorithm) { - case RSA: { - if (currentKeyLength >= 2048 && currentKeyLength <= 16384) { - properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8); - } - break; - } - case ELGAMAL: { - int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length]; - for (int i = 0; i < elGamalSupportedLengths.length; i++) { - elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength); - } - int minimalValue = Integer.MAX_VALUE; - int minimalIndex = -1; - for (int i = 0; i < elGammalKeyDiff.length; i++) { - if (elGammalKeyDiff[i] <= minimalValue) { - minimalValue = elGammalKeyDiff[i]; - minimalIndex = i; - } - } - properKeyLength = elGamalSupportedLengths[minimalIndex]; - break; - } - case DSA: { - // Bouncy Castle supports 4096 maximum - if (currentKeyLength >= 2048 && currentKeyLength <= 4096) { - properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64); - } - break; - } + private class TwoLineArrayAdapter extends ArrayAdapter<Choice<SupportedKeyType>> { + public TwoLineArrayAdapter(Context context, int resource, List<Choice<SupportedKeyType>> objects) { + super(context, resource, objects); } - return properKeyLength; - } - private void setOkButtonAvailability(AlertDialog alertDialog) { - Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId(); - boolean enabled = algorithm == Algorithm.ECDSA || algorithm == Algorithm.ECDH - || getProperKeyLength(algorithm, getSelectedKeyLength()) > 0; - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled); - } - private void setCustomKeyVisibility() { - final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem(); - final String customLengthString = getResources().getString(R.string.key_size_custom); - final boolean customSelected = customLengthString.equals(selectedItemString); - final int visibility = customSelected ? View.VISIBLE : View.GONE; - - mCustomKeyEditText.setVisibility(visibility); - mCustomKeyTextView.setVisibility(visibility); - mCustomKeyInfoTextView.setVisibility(visibility); - - // hide keyboard after setting visibility to gone - if (visibility == View.GONE) { - InputMethodManager imm = (InputMethodManager) - getActivity().getSystemService(FragmentActivity.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mCustomKeyEditText.getWindowToken(), 0); - } - } + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + // inflate view if not given one + if (convertView == null) { + convertView = getActivity().getLayoutInflater() + .inflate(R.layout.two_line_spinner_dropdown_item, parent, false); + } - private void updateUiForAlgorithm(Algorithm algorithm) { - final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) mKeySizeSpinner.getAdapter(); - keySizeAdapter.clear(); - switch (algorithm) { - case RSA: { - replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values); - mKeySizeSpinner.setSelection(1); - mKeySizeRow.setVisibility(View.VISIBLE); - mCurveRow.setVisibility(View.GONE); - mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_rsa)); - // allowed flags: - mFlagSign.setEnabled(true); - mFlagEncrypt.setEnabled(true); - mFlagAuthenticate.setEnabled(true); - - if (mWillBeMasterKey) { - mFlagCertify.setEnabled(true); - - mFlagCertify.setChecked(true); - mFlagSign.setChecked(false); - mFlagEncrypt.setChecked(false); - } else { - mFlagCertify.setEnabled(false); + Choice c = this.getItem(position); - mFlagCertify.setChecked(false); - mFlagSign.setChecked(true); - mFlagEncrypt.setChecked(true); - } - mFlagAuthenticate.setChecked(false); - break; - } - case ELGAMAL: { - replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values); - mKeySizeSpinner.setSelection(3); - mKeySizeRow.setVisibility(View.VISIBLE); - mCurveRow.setVisibility(View.GONE); - mCustomKeyInfoTextView.setText(""); // ElGamal does not support custom key length - // allowed flags: - mFlagCertify.setChecked(false); - mFlagCertify.setEnabled(false); - mFlagSign.setChecked(false); - mFlagSign.setEnabled(false); - mFlagEncrypt.setChecked(true); - mFlagEncrypt.setEnabled(true); - mFlagAuthenticate.setChecked(false); - mFlagAuthenticate.setEnabled(false); - break; - } - case DSA: { - replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values); - mKeySizeSpinner.setSelection(2); - mKeySizeRow.setVisibility(View.VISIBLE); - mCurveRow.setVisibility(View.GONE); - mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_dsa)); - // allowed flags: - mFlagCertify.setChecked(false); - mFlagCertify.setEnabled(false); - mFlagSign.setChecked(true); - mFlagSign.setEnabled(true); - mFlagEncrypt.setChecked(false); - mFlagEncrypt.setEnabled(false); - mFlagAuthenticate.setChecked(false); - mFlagAuthenticate.setEnabled(false); - break; - } - case ECDSA: { - mKeySizeRow.setVisibility(View.GONE); - mCurveRow.setVisibility(View.VISIBLE); - mCustomKeyInfoTextView.setText(""); - // allowed flags: - mFlagCertify.setEnabled(mWillBeMasterKey); - mFlagCertify.setChecked(mWillBeMasterKey); - mFlagSign.setEnabled(true); - mFlagSign.setChecked(!mWillBeMasterKey); - mFlagEncrypt.setEnabled(false); - mFlagEncrypt.setChecked(false); - mFlagAuthenticate.setEnabled(true); - mFlagAuthenticate.setChecked(false); - break; - } - case ECDH: { - mKeySizeRow.setVisibility(View.GONE); - mCurveRow.setVisibility(View.VISIBLE); - mCustomKeyInfoTextView.setText(""); - // allowed flags: - mFlagCertify.setChecked(false); - mFlagCertify.setEnabled(false); - mFlagSign.setChecked(false); - mFlagSign.setEnabled(false); - mFlagEncrypt.setChecked(true); - mFlagEncrypt.setEnabled(true); - mFlagAuthenticate.setChecked(false); - mFlagAuthenticate.setEnabled(false); - break; - } - } - keySizeAdapter.notifyDataSetChanged(); + TextView text1 = (TextView) convertView.findViewById(android.R.id.text1); + TextView text2 = (TextView) convertView.findViewById(android.R.id.text2); - } + text1.setText(c.getName()); + text2.setText(Html.fromHtml(c.getDescription())); - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - private void replaceArrayAdapterContent(ArrayAdapter<CharSequence> arrayAdapter, int stringArrayResourceId) { - final String[] spinnerValuesStringArray = getResources().getStringArray(stringArrayResourceId); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - arrayAdapter.addAll(spinnerValuesStringArray); - } else { - for (final String value : spinnerValuesStringArray) { - arrayAdapter.add(value); - } + return convertView; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java index b51648740..9f5ffc358 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java @@ -36,6 +36,10 @@ public class EditSubkeyDialogFragment extends DialogFragment { public static final int MESSAGE_REVOKE = 2; public static final int MESSAGE_STRIP = 3; public static final int MESSAGE_MOVE_KEY_TO_CARD = 4; + public static final int SUBKEY_MENU_CHANGE_EXPIRY = 0; + public static final int SUBKEY_MENU_REVOKE_SUBKEY = 1; + public static final int SUBKEY_MENU_STRIP_SUBKEY = 2; + public static final int SUBKEY_MENU_MOVE_TO_SECURITY_TOKEN = 3; private Messenger mMessenger; @@ -68,16 +72,16 @@ public class EditSubkeyDialogFragment extends DialogFragment { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { - case 0: + case SUBKEY_MENU_CHANGE_EXPIRY: sendMessageToHandler(MESSAGE_CHANGE_EXPIRY, null); break; - case 1: + case SUBKEY_MENU_REVOKE_SUBKEY: sendMessageToHandler(MESSAGE_REVOKE, null); break; - case 2: - sendMessageToHandler(MESSAGE_STRIP, null); + case SUBKEY_MENU_STRIP_SUBKEY: + showAlertDialog(); break; - case 3: + case SUBKEY_MENU_MOVE_TO_SECURITY_TOKEN: sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null); break; default: @@ -95,6 +99,25 @@ public class EditSubkeyDialogFragment extends DialogFragment { return builder.show(); } + private void showAlertDialog() { + CustomAlertDialogBuilder stripAlertDialog = new CustomAlertDialogBuilder(getActivity()); + stripAlertDialog.setTitle(R.string.title_alert_strip). + setMessage(R.string.alert_strip).setCancelable(true); + stripAlertDialog.setPositiveButton(R.string.strip, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + sendMessageToHandler(MESSAGE_STRIP, null); + } + }); + stripAlertDialog.setNegativeButton(R.string.btn_do_not_save, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dismiss(); + } + }); + stripAlertDialog.show(); + } + /** * Send message back to handler which is initialized in a activity * diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java index c25f775b0..334a7361b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java @@ -119,7 +119,7 @@ public class LinkedIdCreateTwitterStep1Fragment extends Fragment { private static Boolean checkHandle(String handle) { try { HttpURLConnection nection = - (HttpURLConnection) new URL("https://twitter.com/" + handle).openConnection(); + (HttpURLConnection) new URL("https://twitter.com/" + handle).getUrlResponse(); nection.setRequestMethod("HEAD"); nection.setRequestProperty("User-Agent", "OpenKeychain"); return nection.getResponseCode() == 200; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/spinner/FocusFirstItemSpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/spinner/FocusFirstItemSpinner.java new file mode 100644 index 000000000..7919a0918 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/spinner/FocusFirstItemSpinner.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 Alex Fong Jie Wen <alexfongg@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.util.spinner; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Spinner; + +/** + * Custom spinner which uses a hack to + * always set focus on first item in list + * + */ +public class FocusFirstItemSpinner extends Spinner { + /** + * Spinner is originally designed to set focus on the currently selected item. + * When Spinner is selected to show dropdown, 'performClick()' is called internally. + * 'getSelectedItemPosition()' is then called to obtain the item to focus on. + * We use a toggle to have 'getSelectedItemPosition()' return the 0th index + * for this particular case. + */ + + private boolean mToggleFlag = true; + + public FocusFirstItemSpinner(Context context, AttributeSet attrs, + int defStyle, int mode) { + super(context, attrs, defStyle, mode); + } + + public FocusFirstItemSpinner(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + } + + public FocusFirstItemSpinner(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FocusFirstItemSpinner(Context context, int mode) { + super(context, mode); + } + + public FocusFirstItemSpinner(Context context) { + super(context); + } + + @Override + public int getSelectedItemPosition() { + if (!mToggleFlag) { + return 0; + } + return super.getSelectedItemPosition(); + } + + @Override + public boolean performClick() { + mToggleFlag = false; + boolean result = super.performClick(); + mToggleFlag = true; + return result; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java index 6a51085f3..63afb1e8b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -113,11 +113,6 @@ public class CertifyKeySpinner extends KeySpinner { @Override boolean isItemEnabled(Cursor cursor) { - // "none" entry is always enabled! - if (cursor.getPosition() == 0) { - return true; - } - if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java index 8fb9e38aa..0c2d93ad9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java @@ -72,11 +72,6 @@ public class SignKeySpinner extends KeySpinner { @Override boolean isItemEnabled(Cursor cursor) { - // "none" entry is always enabled! - if (cursor.getPosition() == 0) { - return true; - } - if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java index 48f10d4b9..5ffce9f24 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java @@ -18,12 +18,15 @@ package org.sufficientlysecure.keychain.util; public class Choice <E> { + private String mName; private E mId; + private String mDescription; - public Choice(E id, String name) { + public Choice(E id, String name, String description) { mId = id; mName = name; + mDescription = description; } public E getId() { @@ -34,6 +37,10 @@ public class Choice <E> { return mName; } + public String getDescription() { + return mDescription; + } + @Override public String toString() { return mName; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java new file mode 100644 index 000000000..ea2ae8368 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 Michał Kępkowski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.util; + +import java.io.IOException; +import java.net.Proxy; +import java.net.URL; +import java.util.concurrent.TimeUnit; + +import okhttp3.CertificatePinner; +import okhttp3.OkHttpClient; + +public class OkHttpClientFactory { + private static OkHttpClient client; + + public static OkHttpClient getSimpleClient() { + if (client == null) { + client = new OkHttpClient.Builder() + .connectTimeout(5000, TimeUnit.MILLISECONDS) + .readTimeout(25000, TimeUnit.MILLISECONDS) + .build(); + } + return client; + } + + public static OkHttpClient getSimpleClientPinned(CertificatePinner pinner) { + return new OkHttpClient.Builder() + .connectTimeout(5000, TimeUnit.MILLISECONDS) + .readTimeout(25000, TimeUnit.MILLISECONDS) + .certificatePinner(pinner) + .build(); + } + + public static OkHttpClient getClientPinnedIfAvailable(URL url, Proxy proxy) throws IOException, + TlsHelper.TlsHelperException { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + + // don't follow any redirects for keyservers, as discussed in the security audit + builder.followRedirects(false) + .followSslRedirects(false); + + if (proxy != null) { + // set proxy and higher timeouts for Tor + builder.proxy(proxy); + builder.connectTimeout(30000, TimeUnit.MILLISECONDS) + .readTimeout(45000, TimeUnit.MILLISECONDS); + } else { + builder.connectTimeout(5000, TimeUnit.MILLISECONDS) + .readTimeout(25000, TimeUnit.MILLISECONDS); + } + + // If a pinned cert is available, use it! + // NOTE: this fails gracefully back to "no pinning" if no cert is available. + if (url != null && TlsHelper.getPinnedSslSocketFactory(url) != null) { + builder.sslSocketFactory(TlsHelper.getPinnedSslSocketFactory(url)); + } + + return builder.build(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java index d2c90cfcd..8d3eb6963 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java @@ -17,55 +17,42 @@ package org.sufficientlysecure.keychain.util; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.OkUrlFactory; + import com.textuality.keybase.lib.KeybaseUrlConnectionClient; +import okhttp3.OkHttpClient; +import okhttp3.Request; + import org.sufficientlysecure.keychain.Constants; import java.io.IOException; import java.net.Proxy; import java.net.URL; -import java.net.URLConnection; -import java.util.concurrent.TimeUnit; /** * Wrapper for Keybase Lib */ public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient { - private OkUrlFactory generateUrlFactory() { - OkHttpClient client = new OkHttpClient(); - return new OkUrlFactory(client); - } - @Override - public URLConnection openConnection(URL url, Proxy proxy, boolean isKeybase) throws IOException { - OkUrlFactory factory = generateUrlFactory(); - if (proxy != null) { - factory.client().setProxy(proxy); - factory.client().setConnectTimeout(30000, TimeUnit.MILLISECONDS); - factory.client().setReadTimeout(40000, TimeUnit.MILLISECONDS); - } else { - factory.client().setConnectTimeout(5000, TimeUnit.MILLISECONDS); - factory.client().setReadTimeout(25000, TimeUnit.MILLISECONDS); - } - - factory.client().setFollowSslRedirects(false); - - // forced the usage of api.keybase.io pinned certificate - if (isKeybase) { - try { - if (!TlsHelper.usePinnedCertificateIfAvailable(factory.client(), url)) { - throw new IOException("no pinned certificate found for URL!"); - } - } catch (TlsHelper.TlsHelperException e) { - Log.e(Constants.TAG, "TlsHelper failed", e); - throw new IOException("TlsHelper failed"); + public Response getUrlResponse(URL url, Proxy proxy, boolean isKeybase) throws IOException { + OkHttpClient client = null; + + try { + if (proxy != null) { + client = OkHttpClientFactory.getClientPinnedIfAvailable(url, proxy); + } else { + client = OkHttpClientFactory.getSimpleClient(); } + } catch (TlsHelper.TlsHelperException e) { + Log.e(Constants.TAG, "TlsHelper failed", e); + throw new IOException("TlsHelper failed"); } - return factory.open(url); + Request request = new Request.Builder() + .url(url).build(); + okhttp3.Response okResponse = client.newCall(request).execute(); + return new Response(okResponse.body().byteStream(), okResponse.code(), okResponse.message(), okResponse.headers().toMultimap()); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java index 1492abdeb..fe62eff55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java @@ -19,8 +19,6 @@ package org.sufficientlysecure.keychain.util; import android.content.res.AssetManager; -import com.squareup.okhttp.OkHttpClient; - import org.sufficientlysecure.keychain.Constants; import java.io.ByteArrayInputStream; @@ -39,16 +37,11 @@ import java.util.HashMap; import java.util.Map; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; public class TlsHelper { - public static class TlsHelperException extends Exception { - public TlsHelperException(Exception e) { - super(e); - } - } - private static Map<String, byte[]> sPinnedCertificates = new HashMap<>(); /** @@ -80,30 +73,29 @@ public class TlsHelper { * @throws TlsHelperException * @throws IOException */ - public static boolean usePinnedCertificateIfAvailable(OkHttpClient client, URL url) throws TlsHelperException, IOException { + public static SSLSocketFactory getPinnedSslSocketFactory(URL url) throws TlsHelperException, IOException { if (url.getProtocol().equals("https")) { // use certificate PIN from assets if we have one for (String host : sPinnedCertificates.keySet()) { if (url.getHost().endsWith(host)) { - pinCertificate(sPinnedCertificates.get(host), client); - return true; + return pinCertificate(sPinnedCertificates.get(host)); } } } - return false; + return null; } /** - * Modifies the client to accept only requests with a given certificate. Applies to all URLs requested by the - * client. - * Therefore a client that is pinned this way should be used to only make requests to URLs with passed certificate. + * Modifies the builder to accept only requests with a given certificate. + * Applies to all URLs requested by the builder. + * Therefore a builder that is pinned this way should be used to only make requests + * to URLs with passed certificate. * * @param certificate certificate to pin - * @param client OkHttpClient to enforce pinning on * @throws TlsHelperException * @throws IOException */ - private static void pinCertificate(byte[] certificate, OkHttpClient client) + private static SSLSocketFactory pinCertificate(byte[] certificate) throws TlsHelperException, IOException { // We don't use OkHttp's CertificatePinner since it can not be used to pin self-signed // certificate if such certificate is not accepted by TrustManager. @@ -130,10 +122,16 @@ public class TlsHelper { SSLContext context = SSLContext.getInstance("TLS"); context.init(null, tmf.getTrustManagers(), null); - client.setSslSocketFactory(context.getSocketFactory()); + return context.getSocketFactory(); } catch (CertificateException | KeyStoreException | KeyManagementException | NoSuchAlgorithmException e) { throw new TlsHelperException(e); } } + public static class TlsHelperException extends Exception { + public TlsHelperException(Exception e) { + super(e); + } + } + } diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_change_grey_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_change_grey_24dp.png Binary files differnew file mode 100644 index 000000000..f625ba425 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_change_grey_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_change_grey_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_change_grey_24dp.png Binary files differnew file mode 100644 index 000000000..6cf9d044a --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_change_grey_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_change_grey_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_change_grey_24dp.png Binary files differnew file mode 100644 index 000000000..1c30b6f22 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_change_grey_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_change_grey_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_change_grey_24dp.png Binary files differnew file mode 100644 index 000000000..672a9c96c --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_change_grey_24dp.png diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_change_grey_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_change_grey_24dp.png Binary files differnew file mode 100644 index 000000000..5be101001 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_change_grey_24dp.png diff --git a/OpenKeychain/src/main/res/layout/add_subkey_dialog.xml b/OpenKeychain/src/main/res/layout/add_subkey_dialog.xml index 4b5058a81..b232ed423 100644 --- a/OpenKeychain/src/main/res/layout/add_subkey_dialog.xml +++ b/OpenKeychain/src/main/res/layout/add_subkey_dialog.xml @@ -13,147 +13,68 @@ android:paddingRight="24dp" android:stretchColumns="1"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="4dp" - android:text="@string/key_creation_el_gamal_info" /> - <TableRow> - <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:text="@string/label_algorithm" /> + android:paddingRight="10dp" + android:text="@string/label_key_type" /> - <Spinner - android:id="@+id/add_subkey_algorithm" + <!-- custom spinner for fixing focus on first item in list at all times --> + <org.sufficientlysecure.keychain.ui.util.spinner.FocusFirstItemSpinner + android:id="@+id/add_subkey_type" android:layout_width="match_parent" android:layout_height="wrap_content" - android:padding="4dp" /> - </TableRow> - - <TableRow android:id="@+id/add_subkey_row_size"> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:text="@string/label_key_size" /> - - <Spinner - android:id="@+id/add_subkey_size" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="right" + android:dropDownWidth="wrap_content" android:padding="4dp" /> </TableRow> <TableRow - android:id="@+id/add_subkey_row_curve" - android:visibility="gone"> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:text="@string/label_ecc_curve"/> - - <Spinner - android:id="@+id/add_subkey_curve" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="right" - android:padding="4dp"/> - </TableRow> - - <TextView - android:id="@+id/add_subkey_custom_key_size_label" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:text="@string/key_size_custom_info" - android:visibility="gone" /> - - <EditText - android:id="@+id/add_subkey_custom_key_size_input" - android:layout_width="0dip" - android:layout_height="wrap_content" - android:layout_weight="1" - android:inputType="number" - android:visibility="gone" /> - - <TextView - android:id="@+id/add_subkey_custom_key_size_info" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" /> - - <TableRow> + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp"> <TextView android:id="@+id/label_usage" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:paddingRight="10dip" + android:layout_gravity="center_vertical|top" + android:paddingRight="10dp" android:text="@string/label_usage" /> - - <CheckBox - android:id="@+id/add_subkey_flag_certify" - android:enabled="false" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/flag_certify" /> - </TableRow> - - <TableRow> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:paddingRight="10dip" /> - - <CheckBox - android:id="@+id/add_subkey_flag_sign" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/flag_sign" /> - </TableRow> - - <TableRow> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:paddingRight="10dip" /> - - <CheckBox - android:id="@+id/add_subkey_flag_encrypt" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/flag_encrypt" /> + <RadioGroup + android:id="@+id/add_subkey_usage_group" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="wrap_content"> + + <RadioButton + android:id="@+id/add_subkey_usage_none" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:text="@string/usage_none" /> + + <RadioButton + android:id="@+id/add_subkey_usage_sign" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/usage_sign" /> + + <RadioButton + android:id="@+id/add_subkey_usage_encrypt" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/usage_encrypt" /> + + <RadioButton + android:id="@+id/add_subkey_usage_sign_and_encrypt" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/usage_sign_and_encrypt" /> + </RadioGroup> </TableRow> - <TableRow> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:paddingRight="10dip" /> - - <CheckBox - android:id="@+id/add_subkey_flag_authenticate" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/flag_authenticate" /> - </TableRow> <TableRow android:layout_marginTop="8dp" @@ -164,7 +85,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:paddingRight="10dip" + android:paddingRight="10dp" android:text="@string/label_expiry" /> <CheckBox diff --git a/OpenKeychain/src/main/res/layout/certify_item.xml b/OpenKeychain/src/main/res/layout/certify_item.xml index 71838c2fc..8aff5823e 100644 --- a/OpenKeychain/src/main/res/layout/certify_item.xml +++ b/OpenKeychain/src/main/res/layout/certify_item.xml @@ -13,7 +13,6 @@ android:layout_height="wrap_content" android:orientation="vertical" android:clickable="true" - android:layout_marginLeft="8dip" android:layout_marginTop="8dip"/> <LinearLayout android:id="@+id/user_id_body" @@ -21,7 +20,6 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:singleLine="true" - android:layout_marginLeft="8dip" android:layout_marginTop="4dip"> <CheckBox diff --git a/OpenKeychain/src/main/res/layout/certify_key_fragment.xml b/OpenKeychain/src/main/res/layout/certify_key_fragment.xml index 23685ce15..01837240b 100644 --- a/OpenKeychain/src/main/res/layout/certify_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/certify_key_fragment.xml @@ -22,8 +22,9 @@ android:id="@+id/textView" android:layout_weight="1" /> - <org.sufficientlysecure.keychain.ui.widget.FixedListView - android:id="@+id/view_key_user_ids" + <fragment + android:id="@+id/multi_user_ids_fragment" + android:name="org.sufficientlysecure.keychain.ui.MultiUserIdsFragment" android:layout_width="match_parent" android:layout_height="wrap_content" /> diff --git a/OpenKeychain/src/main/res/layout/multi_user_ids_fragment.xml b/OpenKeychain/src/main/res/layout/multi_user_ids_fragment.xml new file mode 100644 index 000000000..560934f50 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/multi_user_ids_fragment.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<org.sufficientlysecure.keychain.ui.widget.FixedListView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/view_key_user_ids" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> diff --git a/OpenKeychain/src/main/res/layout/two_line_spinner_dropdown_item.xml b/OpenKeychain/src/main/res/layout/two_line_spinner_dropdown_item.xml new file mode 100644 index 000000000..ae325eaab --- /dev/null +++ b/OpenKeychain/src/main/res/layout/two_line_spinner_dropdown_item.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingLeft="10dp" + android:paddingTop="6dp" + android:paddingRight="10dp" + android:paddingBottom="6dp" + android:background="?android:attr/selectableItemBackground"> + + <TextView android:id="@android:id/text1" + style="?android:attr/spinnerDropDownItemStyle" + android:textStyle="bold" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="6dp" + android:paddingRight="6dp" /> + + <TextView android:id="@android:id/text2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:paddingTop="2dp" /> + +</LinearLayout> + + + + diff --git a/OpenKeychain/src/main/res/layout/upload_key_activity.xml b/OpenKeychain/src/main/res/layout/upload_key_activity.xml index 6a0be9ce5..6acb22c3a 100644 --- a/OpenKeychain/src/main/res/layout/upload_key_activity.xml +++ b/OpenKeychain/src/main/res/layout/upload_key_activity.xml @@ -41,6 +41,12 @@ android:layout_marginBottom="4dp" android:layout_marginTop="4dp" /> + <fragment + android:id="@+id/multi_user_ids_fragment" + android:name="org.sufficientlysecure.keychain.ui.MultiUserIdsFragment" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + <TextView style="@style/SectionHeader" android:layout_width="wrap_content" diff --git a/OpenKeychain/src/main/res/values/colors.xml b/OpenKeychain/src/main/res/values/colors.xml index 93cf126f7..d9075b587 100644 --- a/OpenKeychain/src/main/res/values/colors.xml +++ b/OpenKeychain/src/main/res/values/colors.xml @@ -35,5 +35,6 @@ <!-- linked ID view --> <color name="card_view_button">#7bad45</color> + <color name="toolbar_photo_tint">#1E7bad45</color> </resources> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index f9838ba72..18007c53f 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -13,6 +13,7 @@ <string name="title_encrypt_files">"Encrypt"</string> <string name="title_decrypt">"Decrypt"</string> <string name="title_add_subkey">"Add subkey"</string> + <string name="title_change_master_key">Change master key</string> <string name="title_edit_key">"Edit Key"</string> <string name="title_linked_create">"Create a Linked Identity"</string> <string name="title_preferences">"Settings"</string> @@ -40,6 +41,7 @@ <string name="title_advanced_key_info">"Advanced"</string> <string name="title_delete_secret_key">"Delete YOUR key '%s'?"</string> <string name="title_manage_my_keys">"Manage my keys"</string> + <string name="title_alert_strip">"Strip this subkey"</string> <!-- section --> <string name="section_user_ids">"Identities"</string> @@ -165,6 +167,7 @@ <string name="label_keyservers">"Select OpenPGP keyservers"</string> <string name="label_key_id">"Key ID"</string> <string name="label_key_created">"Key created %s"</string> + <string name="label_key_type">"Type"</string> <string name="label_creation">"Creation"</string> <string name="label_expiry">"Expiry"</string> <string name="label_usage">"Usage"</string> @@ -282,22 +285,26 @@ <string name="choice_8hours">"8 hours"</string> <string name="choice_forever">"forever"</string> <string name="choice_select_cert">"Select a Key"</string> - <string name="dsa">"DSA"</string> - <string name="elgamal">"ElGamal"</string> - <string name="rsa">"RSA"</string> - <string name="ecdh">"ECDH"</string> - <string name="ecdsa">"ECDSA"</string> <string name="filemanager_title_open">"Open…"</string> + <string name="rsa_2048">"RSA 2048"</string> + <string name="rsa_2048_description_html">"smaller filesize, considered secure until 2030"</string> + <string name="rsa_3072">"RSA 3072"</string> + <string name="rsa_3072_description_html">"recommended, considered secure until 2040"</string> + <string name="rsa_4096">"RSA 4096"</string> + <string name="rsa_4096_description_html">"larger file size, considered secure until 2040+"</string> + <string name="ecc_p256">"ECC P-256"</string> + <string name="ecc_p256_description_html">"very tiny filesize, considered secure until 2040 <br/> <u>experimental and not supported by all implementations</u>"</string> + <string name="ecc_p521">"ECC P-521"</string> + <string name="ecc_p521_description_html">"tiny filesize, considered secure until 2040+ <br/> <u>experimental and not supported by all implementations"</u></string> + <string name="usage_none">"None (subkey binding only)"</string> + <string name="usage_sign">"Sign"</string> + <string name="usage_encrypt">"Encrypt"</string> + <string name="usage_sign_and_encrypt">"Sign & Encrypt"</string> <string name="error">"Error"</string> <string name="error_message">"Error: %s"</string> <string name="theme_dark">"Dark"</string> <string name="theme_light">"Light"</string> - - <!-- key flags --> - <string name="flag_certify">"Certify"</string> - <string name="flag_sign">"Sign"</string> - <string name="flag_encrypt">"Encrypt"</string> - <string name="flag_authenticate">"Authenticate"</string> + <string name="strip">"Strip it"</string> <!-- sentences --> <string name="wrong_passphrase">"Wrong password."</string> @@ -332,6 +339,7 @@ <string name="public_key_deletetion_confirmation">"Delete key '%s'?"</string> <string name="also_export_secret_keys">"Also export secret keys"</string> <string name="reinstall_openkeychain">"You encountered a known bug with Android. Please reinstall OpenKeychain if you want to link your contacts with keys."</string> + <string name="alert_strip">"Stripping this subkey will make it unusable on this device!"</string> <string name="key_exported">"Successfully exported 1 key."</string> <string name="keys_exported">"Successfully exported %d keys."</string> @@ -748,7 +756,7 @@ <item>"Move Subkey to Security Token"</item> </string-array> <string name="edit_key_new_subkey">"new subkey"</string> - <string name="edit_key_select_flag">"Please select at least one flag!"</string> + <string name="edit_key_select_usage">"Please select key usage!"</string> <string name="edit_key_error_add_identity">"Add at least one identity!"</string> <string name="edit_key_error_add_subkey">"Add at least one subkey!"</string> <string name="edit_key_error_bad_security_token_algo">"Algorithm not supported by Security Token!"</string> @@ -1546,7 +1554,7 @@ <string name="snack_security_token_import">"Import"</string> <string name="button_bind_key">"Bind Key"</string> <string name="security_token_serial_no">"Serial No: %s"</string> - <string name="security_token_key_holder">"Key holder: "</string> + <string name="security_token_key_holder">"Key holder: %s"</string> <string name="security_token_key_holder_not_set"><![CDATA[Key holder: <not set>]]></string> <string name="security_token_status_bound">"Security Token matches and is bound to key"</string> <string name="security_token_status_unbound">"Security Token matches, can be bound to key"</string> @@ -74,7 +74,7 @@ We are using the newest [Android Studio](http://developer.android.com/sdk/instal OpenKeychain uses a forked version with some small changes. These changes will been sent to Bouncy Castle. see -* Fork: https://github.com/openpgp-keychain/bouncycastle +* Fork: https://github.com/open-keychain/bouncycastle #### Bouncy Castle resources diff --git a/extern/KeybaseLib b/extern/KeybaseLib -Subproject bc02742a59f4cc984cd497e14ac48cb61fe6e8c +Subproject 0a99c130e6d1bc25a313a113d7f7777bf65b2e3 |