diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index 0ab1e6aff..f22c59fa0 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -483,6 +483,11 @@
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_key_server_preference"
android:windowSoftInputMode="stateHidden" />
+
0) {
+ final Cipher cipher = Cipher.getInstance(SCP11_CIPHER_ALGO);
+
+ byte[] iv = new byte[AES_BLOCK_SIZE];
+ Arrays.fill(iv, (byte)0);
+ cipher.init(Cipher.ENCRYPT_MODE, mSEnc, new IvParameterSpec(iv));
+
+ iv[AES_BLOCK_SIZE - 2] = (byte)((mEncryptionCounter >> 8) & 0xff);
+ iv[AES_BLOCK_SIZE - 1] = (byte)(mEncryptionCounter & 0xff);
+
+ iv = cipher.doFinal(iv);
+
+ cipher.init(Cipher.ENCRYPT_MODE, mSEnc, new IvParameterSpec(iv));
+
+ final byte[] pdata = new byte[data.length + AES_BLOCK_SIZE - (data.length % AES_BLOCK_SIZE)];
+ System.arraycopy(data, 0, pdata, 0, data.length);
+ pdata[data.length] = (byte)0x80;
+
+ Arrays.fill(data, (byte)0);
+
+ data = cipher.doFinal(pdata);
+
+ Arrays.fill(pdata, (byte)0);
+ Arrays.fill(iv, (byte)0);
+ }
+
+
+ final int lcc = data.length + SCP11_MAC_LENGTH;
+
+ final byte[] odata = new byte[4 + 3 + lcc + 3];
+ int ooff = 0;
+
+ odata[ooff++] = (byte) (((byte) apdu.getCLA()) | OPENPGP_SECURE_MESSAGING_CLA_MASK);
+ odata[ooff++] = (byte) apdu.getINS();
+ odata[ooff++] = (byte) apdu.getP1();
+ odata[ooff++] = (byte) apdu.getP2();
+
+ if (lcc > 0xff) {
+ odata[ooff++] = (byte) 0;
+ odata[ooff++] = (byte) ((lcc >> 8) & 0xff);
+ }
+ odata[ooff++] = (byte) (lcc & 0xff);
+
+ System.arraycopy(data, 0, odata, ooff, data.length);
+ ooff += data.length;
+
+ Arrays.fill(data, (byte)0);
+
+ final Mac mac = Mac.getInstance(SCP11_MAC_ALGO, PROVIDER);
+ mac.init(mSMac);
+ mac.update(mMacChaining);
+ mac.update(odata, 0, ooff);
+ mMacChaining = mac.doFinal();
+
+ System.arraycopy(mMacChaining, 0, odata, ooff, SCP11_MAC_LENGTH);
+ ooff += SCP11_MAC_LENGTH;
+
+ if (lcc > 0xff) {
+ odata[ooff++] = (byte) 0;
+ }
+ odata[ooff++] = (byte) 0;
+
+ apdu = new CommandAPDU(odata, 0, ooff);
+
+ Arrays.fill(odata, (byte)0);
+
+ return apdu;
+
+ } catch (NoSuchAlgorithmException e) {
+ throw new SecureMessagingException("unavailable algorithm : " + e.getMessage());
+ } catch (NoSuchProviderException e) {
+ throw new SecureMessagingException("unavailable provider : " + e.getMessage());
+ } catch (NoSuchPaddingException e) {
+ throw new SecureMessagingException("unavailable padding algorithm : " + e.getMessage());
+ } catch (InvalidKeyException e) {
+ throw new SecureMessagingException("invalid key : " + e.getMessage());
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new SecureMessagingException("invalid IV : " + e.getMessage());
+ } catch (BadPaddingException e) {
+ throw new SecureMessagingException("invalid IV : " + e.getMessage());
+ } catch (IllegalBlockSizeException e) {
+ throw new SecureMessagingException("invalid block size : " + e.getMessage());
+ }
+ }
+
+
+ @Override
+ public ResponseAPDU verifyAndDecrypt(ResponseAPDU apdu)
+ throws SecureMessagingException {
+
+ if (!isEstablished()) {
+ throw new SecureMessagingException("not established");
+ }
+
+ byte[] data = apdu.getData();
+
+ if ((data.length == 0) &&
+ (apdu.getSW() != 0x9000) &&
+ (apdu.getSW1() != 0x62) &&
+ (apdu.getSW1() != 0x63)) {
+ return apdu;
+ }
+
+ if (data.length < SCP11_MAC_LENGTH) {
+ throw new SecureMessagingException("missing or incomplete MAC in response");
+ }
+
+ try {
+
+ final Mac mac = Mac.getInstance(SCP11_MAC_ALGO, PROVIDER);
+ mac.init(mSRMac);
+
+ mac.update(mMacChaining);
+ if ((data.length - SCP11_MAC_LENGTH) > 0) {
+ mac.update(data, 0, data.length - SCP11_MAC_LENGTH);
+ }
+ mac.update((byte) apdu.getSW1());
+ mac.update((byte) apdu.getSW2());
+
+ final byte[] sig = mac.doFinal();
+
+ for (int i = 0; i < SCP11_MAC_LENGTH; ++i) {
+ if ((i >= sig.length)
+ || (sig[i] != data[data.length - SCP11_MAC_LENGTH + i])) {
+ throw new SecureMessagingException("corrupted integrity");
+ }
+ }
+
+ if (((data.length - SCP11_MAC_LENGTH) % AES_BLOCK_SIZE) != 0) {
+ throw new SecureMessagingException("invalid encrypted data size");
+ }
+
+ if (data.length > SCP11_MAC_LENGTH) {
+ final Cipher cipher = Cipher.getInstance(SCP11_CIPHER_ALGO);
+
+ byte[] iv = new byte[AES_BLOCK_SIZE];
+ Arrays.fill(iv,(byte)0);
+ cipher.init(Cipher.ENCRYPT_MODE, mSEnc, new IvParameterSpec(iv));
+
+ iv[0] = (byte) 0x80;
+ iv[AES_BLOCK_SIZE - 2] = (byte) ((mEncryptionCounter >> 8) & 0xff);
+ iv[AES_BLOCK_SIZE - 1] = (byte) (mEncryptionCounter & 0xff);
+
+ iv = cipher.doFinal(iv);
+
+ cipher.init(Cipher.DECRYPT_MODE, mSEnc, new IvParameterSpec(iv));
+ data = cipher.doFinal(data, 0, data.length - SCP11_MAC_LENGTH);
+
+ int i = data.length - 1;
+ while ((0 < i) && (data[i] == (byte) 0)) --i;
+
+ if ((i <= 0) || (data[i] != (byte) 0x80)) {
+ throw new SecureMessagingException("invalid data padding after decryption");
+ }
+
+ final byte[] datasw = new byte[i + 2];
+ System.arraycopy(data, 0, datasw, 0, i);
+ datasw[datasw.length - 2] = (byte) apdu.getSW1();
+ datasw[datasw.length - 1] = (byte) apdu.getSW2();
+
+ Arrays.fill(data, (byte) 0);
+
+ data = datasw;
+ } else {
+ data = new byte[2];
+ data[0] = (byte) apdu.getSW1();
+ data[1] = (byte) apdu.getSW2();
+ }
+
+ apdu = new ResponseAPDU(data);
+
+ return apdu;
+
+ } catch (NoSuchAlgorithmException e) {
+ throw new SecureMessagingException("unavailable algorithm : " + e.getMessage());
+ } catch (NoSuchProviderException e) {
+ throw new SecureMessagingException("unknown provider : " + e.getMessage());
+ } catch (NoSuchPaddingException e) {
+ throw new SecureMessagingException("unavailable padding algorithm : " + e.getMessage());
+ } catch (InvalidKeyException e) {
+ throw new SecureMessagingException("invalid key : " + e.getMessage());
+ } catch (BadPaddingException e) {
+ throw new SecureMessagingException("invalid IV : " + e.getMessage());
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new SecureMessagingException("invalid IV : " + e.getMessage());
+ } catch (IllegalBlockSizeException e) {
+ throw new SecureMessagingException("invalid block size : " + e.getMessage());
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecureMessaging.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecureMessaging.java
new file mode 100644
index 000000000..90436a323
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecureMessaging.java
@@ -0,0 +1,17 @@
+package org.sufficientlysecure.keychain.securitytoken;
+
+import java.io.IOException;
+
+import javax.smartcardio.CommandAPDU;
+import javax.smartcardio.ResponseAPDU;
+
+public interface SecureMessaging {
+
+ void clearSession();
+
+ boolean isEstablished();
+
+ CommandAPDU encryptAndSign(CommandAPDU apdu) throws SecureMessagingException;
+
+ ResponseAPDU verifyAndDecrypt(ResponseAPDU apdu) throws SecureMessagingException;
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecureMessagingException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecureMessagingException.java
new file mode 100644
index 000000000..5b8a8e300
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecureMessagingException.java
@@ -0,0 +1,8 @@
+package org.sufficientlysecure.keychain.securitytoken;
+
+public final class SecureMessagingException extends Exception {
+
+ public SecureMessagingException(String msg) {
+ super(msg);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java
index c3619dba8..0fdd183df 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java
@@ -21,6 +21,7 @@
package org.sufficientlysecure.keychain.securitytoken;
+import android.content.Context;
import android.support.annotation.NonNull;
import org.bouncycastle.asn1.ASN1Encodable;
@@ -75,9 +76,9 @@ public class SecurityTokenHelper {
private static final int MAX_APDU_NC_EXT = 65535;
private static final int MAX_APDU_NE = 256;
- private static final int MAX_APDU_NE_EXT = 65536;
+ static final int MAX_APDU_NE_EXT = 65536;
- private static final int APDU_SW_SUCCESS = 0x9000;
+ static final int APDU_SW_SUCCESS = 0x9000;
private static final int APDU_SW1_RESPONSE_AVAILABLE = 0x61;
private static final int MASK_CLA_CHAINING = 1 << 4;
@@ -92,6 +93,7 @@ public class SecurityTokenHelper {
private Transport mTransport;
private CardCapabilities mCardCapabilities;
private OpenPgpCapabilities mOpenPgpCapabilities;
+ private SecureMessaging mSecureMessaging;
private Passphrase mPin;
private Passphrase mAdminPin;
@@ -181,7 +183,7 @@ public class SecurityTokenHelper {
*
* @throws IOException
*/
- public void connectToDevice() throws IOException {
+ public void connectToDevice(final Context ctx) throws IOException {
// Connect on transport layer
mCardCapabilities = new CardCapabilities();
@@ -202,6 +204,16 @@ public class SecurityTokenHelper {
mPw1ValidatedForSignature = false;
mPw1ValidatedForDecrypt = false;
mPw3Validated = false;
+
+ if (mOpenPgpCapabilities.isHasSM()) {
+ try {
+ SCP11bSecureMessaging.establish(this, ctx);
+ } catch(SecureMessagingException e) {
+ mSecureMessaging = null;
+ Log.e(Constants.TAG, "failed to establish secure messaging", e);
+ }
+ }
+
}
/**
@@ -699,7 +711,16 @@ public class SecurityTokenHelper {
* @return response from the card
* @throws IOException
*/
- private ResponseAPDU communicate(CommandAPDU apdu) throws IOException {
+ ResponseAPDU communicate(CommandAPDU apdu) throws IOException {
+ if ((mSecureMessaging != null) && mSecureMessaging.isEstablished()) {
+ try {
+ apdu = mSecureMessaging.encryptAndSign(apdu);
+ } catch (SecureMessagingException e) {
+ clearSecureMessaging();
+ throw new IOException("secure messaging encrypt/sign failure : " + e. getMessage());
+ }
+ }
+
ByteArrayOutputStream result = new ByteArrayOutputStream();
ResponseAPDU lastResponse = null;
@@ -746,7 +767,18 @@ public class SecurityTokenHelper {
result.write(lastResponse.getSW1());
result.write(lastResponse.getSW2());
- return new ResponseAPDU(result.toByteArray());
+ lastResponse = new ResponseAPDU(result.toByteArray());
+
+ if ((mSecureMessaging != null) && mSecureMessaging.isEstablished()) {
+ try {
+ lastResponse = mSecureMessaging.verifyAndDecrypt(lastResponse);
+ } catch (SecureMessagingException e) {
+ clearSecureMessaging();
+ throw new IOException("secure messaging verify/decrypt failure : " + e. getMessage());
+ }
+ }
+
+ return lastResponse;
}
public Transport getTransport() {
@@ -754,6 +786,7 @@ public class SecurityTokenHelper {
}
public void setTransport(Transport mTransport) {
+ clearSecureMessaging();
this.mTransport = mTransport;
}
@@ -833,6 +866,9 @@ public class SecurityTokenHelper {
}
}
+ // secure messaging must be disabled before reactivation
+ clearSecureMessaging();
+
// reactivate token!
// NOTE: keep the order here! First execute _both_ reactivate commands. Before checking _both_ responses
// If a token is in a bad state and reactivate1 fails, it could still be reactivated with reactivate2
@@ -871,13 +907,32 @@ public class SecurityTokenHelper {
}
public boolean isPersistentConnectionAllowed() {
- return mTransport != null && mTransport.isPersistentConnectionAllowed();
+ return mTransport != null &&
+ mTransport.isPersistentConnectionAllowed() &&
+ (mSecureMessaging == null ||
+ !mSecureMessaging.isEstablished());
}
public boolean isConnected() {
return mTransport != null && mTransport.isConnected();
}
+ public void clearSecureMessaging() {
+ if(mSecureMessaging != null) {
+ mSecureMessaging.clearSession();
+ }
+ mSecureMessaging = null;
+ }
+
+ void setSecureMessaging(final SecureMessaging sm) {
+ clearSecureMessaging();
+ mSecureMessaging = sm;
+ }
+
+ OpenPgpCapabilities getOpenPgpCapabilities() {
+ return mOpenPgpCapabilities;
+ }
+
private static class LazyHolder {
private static final SecurityTokenHelper SECURITY_TOKEN_HELPER = new SecurityTokenHelper();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java
index 651780f7f..c663a2f18 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java
@@ -298,6 +298,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
// Just close
finish();
} else {
+ mSecurityTokenHelper.clearSecureMessaging();
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
index aa1d3647b..d199080f7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
@@ -58,12 +58,15 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
import java.util.ArrayList;
import java.util.List;
public class SettingsActivity extends AppCompatPreferenceActivity {
public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005;
+ public static final int REQUEST_CODE_SMARTPGP_AUTHORITIES_PREF = 0x00007006;
private static final int REQUEST_PERMISSION_READ_CONTACTS = 13;
private static Preferences sPreferences;
@@ -554,6 +557,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
*/
public static class ExperimentalPrefsFragment extends PresetPreferenceFragment {
+ private PreferenceScreen mSmartPGPAuthoritiesPreference = null;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -563,6 +568,51 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
+ mSmartPGPAuthoritiesPreference = (PreferenceScreen) findPreference(Constants.Pref.EXPERIMENTAL_SMARTPGP_AUTHORITIES);
+
+ final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(getActivity());
+ int size = 0;
+ try {
+ if (ks != null) {
+ size = ks.size();
+ }
+ } catch (KeyStoreException e) {}
+
+ mSmartPGPAuthoritiesPreference.setSummary(getActivity().getResources().getQuantityString(R.plurals.n_authorities, size, size));
+
+ mSmartPGPAuthoritiesPreference
+ .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ public boolean onPreferenceClick(Preference preference) {
+ Intent intent = new Intent(getActivity(),
+ SettingsSmartPGPAuthoritiesActivity.class);
+ startActivityForResult(intent, REQUEST_CODE_SMARTPGP_AUTHORITIES_PREF);
+ return false;
+ }
+ });
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_CODE_SMARTPGP_AUTHORITIES_PREF: {
+ // update preference, in case it changed
+ final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(getActivity());
+ int size = 0;
+ try {
+ if (ks != null) {
+ size = ks.size();
+ }
+ } catch (KeyStoreException e) {}
+
+ mSmartPGPAuthoritiesPreference.setSummary(getActivity().getResources().getQuantityString(R.plurals.n_authorities, size, size));
+ break;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ break;
+ }
+ }
}
private static void initializeTheme(final ListPreference themePref) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsSmartPGPAuthoritiesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsSmartPGPAuthoritiesActivity.java
new file mode 100644
index 000000000..fe11fb6d7
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsSmartPGPAuthoritiesActivity.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2010-2014 Thialfihar
+ *
+ * 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 .
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+public class SettingsSmartPGPAuthoritiesActivity extends BaseActivity {
+
+ public static final String EXTRA_SMARTPGP_AUTHORITIES = "smartpgp_authorities";
+
+ private static final String KEYSTORE_FILE = "smartpgp_authorities.keystore";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ String authorities[] = intent.getStringArrayExtra(EXTRA_SMARTPGP_AUTHORITIES);
+ loadFragment(savedInstanceState, authorities);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void initLayout() {
+ setContentView(R.layout.smartpgp_authorities_preference);
+ }
+
+ private void loadFragment(Bundle savedInstanceState, String[] authorities) {
+ // However, if we're being restored from a previous state,
+ // then we don't need to do anything and should return or else
+ // we could end up with overlapping fragments.
+ if (savedInstanceState != null) {
+ return;
+ }
+
+ SettingsSmartPGPAuthorityFragment fragment = SettingsSmartPGPAuthorityFragment.newInstance(authorities);
+
+ // Add the fragment to the 'fragment_container' FrameLayout
+ // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.smartpgp_authorities_settings_fragment_container, fragment)
+ .commitAllowingStateLoss();
+ // do it immediately!
+ getSupportFragmentManager().executePendingTransactions();
+ }
+
+ public static final KeyStore readKeystore(final Context ctx) {
+ try {
+ final File kf = new File(ctx.getFilesDir(), KEYSTORE_FILE);
+ final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+
+ ks.load(null, null);
+
+ if (kf.exists()) {
+ final FileInputStream fis = new FileInputStream(kf);
+ ks.load(fis, null);
+ fis.close();
+ }
+
+ return ks;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public static final void writeKeystore(final Context ctx, final KeyStore ks) {
+ try {
+ final File kf = new File(ctx.getFilesDir(), KEYSTORE_FILE);
+
+ if (kf.exists()) {
+ kf.delete();
+ }
+
+ final FileOutputStream fos = new FileOutputStream(kf);
+ ks.store(fos, null);
+ fos.flush();
+ fos.close();
+ } catch (Exception e) {
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsSmartPGPAuthorityFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsSmartPGPAuthorityFragment.java
new file mode 100644
index 000000000..f3dcca7b9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsSmartPGPAuthorityFragment.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2012-2015 Dominik Schürmann
+ * Copyright (C) 2015 Adithya Abraham Philip
+ *
+ * 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 .
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.dialog.AddEditSmartPGPAuthorityDialogFragment;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+
+
+public class SettingsSmartPGPAuthorityFragment extends Fragment implements RecyclerItemClickListener.OnItemClickListener {
+
+ private ItemTouchHelper mItemTouchHelper;
+
+ private ArrayList mAuthorities;
+ private AuthorityListAdapter mAdapter;
+
+ public static SettingsSmartPGPAuthorityFragment newInstance(String[] authorities) {
+ return new SettingsSmartPGPAuthorityFragment();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
+ savedInstanceState) {
+
+ return inflater.inflate(R.layout.settings_smartpgp_authority_fragment, null);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ List authorities = new LinkedList();
+
+ try {
+ final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(getActivity());
+ final Enumeration it = ks.aliases();
+
+ while (it.hasMoreElements()) {
+ authorities.add(it.nextElement());
+ }
+
+ } catch (Exception e) {
+ }
+
+ mAuthorities = new ArrayList<>(authorities);
+ mAdapter = new AuthorityListAdapter(mAuthorities);
+
+ RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.smartpgp_authority_recycler_view);
+ recyclerView.setAdapter(mAdapter);
+ recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
+
+ ItemTouchHelper.Callback callback = new ItemTouchHelperDragCallback(mAdapter);
+ mItemTouchHelper = new ItemTouchHelper(callback);
+ mItemTouchHelper.attachToRecyclerView(recyclerView);
+
+ // for clicks
+ recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), this));
+
+ // can't use item decoration because it doesn't move with drag and drop
+ // recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null));
+
+ // We have a menu item to show in action bar.
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.smartpgp_authority_pref_menu, menu);
+
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+
+ case R.id.menu_add_smartpgp_authority:
+ startAddAuthorityDialog();
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void startAddAuthorityDialog() {
+ startEditAuthorityDialog(AddEditSmartPGPAuthorityDialogFragment.Action.ADD, null, null, -1);
+ }
+
+ private void startEditAuthorityDialog(AddEditSmartPGPAuthorityDialogFragment.Action action,
+ final String old_alias, final Uri uri, final int position) {
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ Bundle data = message.getData();
+ final String new_alias = data.getString(AddEditSmartPGPAuthorityDialogFragment.OUT_ALIAS);
+ final int position = data.getInt(AddEditSmartPGPAuthorityDialogFragment.OUT_POSITION);
+ final String uri = data.getString(AddEditSmartPGPAuthorityDialogFragment.OUT_URI);
+
+ final AddEditSmartPGPAuthorityDialogFragment.Action action =
+ (AddEditSmartPGPAuthorityDialogFragment.Action)
+ data.getSerializable(AddEditSmartPGPAuthorityDialogFragment.OUT_ACTION);
+
+ switch(action) {
+ case ADD:
+ if (editAuthority(old_alias, new_alias, position, uri)) {
+ Notify.create(getActivity(), "Authority " + new_alias + " added",
+ Notify.LENGTH_SHORT, Notify.Style.OK).show();
+ }
+ break;
+
+ case EDIT:
+ if (editAuthority(old_alias, new_alias, position, uri)){
+ Notify.create(getActivity(), "Authority " + old_alias + " modified",
+ Notify.LENGTH_SHORT, Notify.Style.OK).show();
+ }
+ break;
+
+ case DELETE:
+ if (deleteAuthority(position)) {
+ Notify.create(getActivity(), "Authority " + old_alias + " deleted",
+ Notify.LENGTH_SHORT, Notify.Style.OK).show();
+ }
+ break;
+
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+ AddEditSmartPGPAuthorityDialogFragment dialogFragment = AddEditSmartPGPAuthorityDialogFragment
+ .newInstance(messenger, action, old_alias, uri, position);
+ dialogFragment.show(getFragmentManager(), "addSmartPGPAuthorityDialog");
+ }
+
+
+ private boolean editAuthority(final String old_alias, final String new_alias, final int position, final String uri) {
+ try {
+ final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(getContext());
+
+ if (ks == null) {
+ throw new KeyStoreException("no keystore found");
+ }
+
+ Certificate old_cert = null;
+ if (old_alias != null) {
+ old_cert = ks.getCertificate(old_alias);
+ ks.deleteEntry(old_alias);
+ mAuthorities.remove(old_alias);
+ mAdapter.notifyItemRemoved(position);
+ }
+
+ Certificate new_cert = null;
+ if (uri == null) {
+ new_cert = old_cert;
+ } else {
+ final InputStream fis = getContext().getContentResolver().openInputStream(Uri.parse(uri));
+
+ final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ new_cert = cf.generateCertificate(fis);
+ if (!(new_cert instanceof X509Certificate)) {
+ Notify.create(getActivity(), "Invalid certificate", Notify.LENGTH_LONG, Notify.Style.ERROR).show();
+ return false;
+ }
+
+ fis.close();
+ }
+
+ if (new_alias == null || new_cert == null) {
+ Notify.create(getActivity(), "Missing alias or certificate", Notify.LENGTH_LONG, Notify.Style.ERROR).show();
+ return false;
+ }
+
+ final X509Certificate x509cert = (X509Certificate)new_cert;
+
+ x509cert.checkValidity();
+
+ ks.setCertificateEntry(new_alias, x509cert);
+
+ SettingsSmartPGPAuthoritiesActivity.writeKeystore(getContext(), ks);
+
+ mAuthorities.add(new_alias);
+ mAdapter.notifyItemInserted(mAuthorities.size() - 1);
+
+ return true;
+
+ } catch (IOException e) {
+ Notify.create(getActivity(), "failed to open certificate (" + e.getMessage() + ")", Notify.LENGTH_LONG, Notify.Style.ERROR).show();
+ } catch (CertificateException e) {
+ Notify.create(getActivity(), "invalid certificate (" + e.getMessage() + ")", Notify.LENGTH_LONG, Notify.Style.ERROR).show();
+ } catch (KeyStoreException e) {
+ Notify.create(getActivity(), "invalid keystore (" + e.getMessage() + ")", Notify.LENGTH_LONG, Notify.Style.ERROR).show();
+ }
+
+ return false;
+ }
+
+ private boolean deleteAuthority(final int position) {
+ try {
+ final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(getContext());
+
+ if (ks == null) {
+ return false;
+ }
+
+ ks.deleteEntry(mAuthorities.get(position));
+
+ SettingsSmartPGPAuthoritiesActivity.writeKeystore(getContext(), ks);
+
+ mAuthorities.remove(mAuthorities.get(position));
+ mAdapter.notifyItemRemoved(position);
+
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void onItemClick(View view, int position) {
+ startEditAuthorityDialog(AddEditSmartPGPAuthorityDialogFragment.Action.EDIT,
+ mAuthorities.get(position), null, position);
+ }
+
+
+ public class AuthorityListAdapter extends RecyclerView.Adapter
+ implements ItemTouchHelperAdapter {
+
+ private final List mAuthorities;
+
+ public AuthorityListAdapter(List authorities) {
+ mAuthorities = authorities;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.settings_smartpgp_authority_item, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ holder.authorityName.setText(mAuthorities.get(position));
+ }
+
+ @Override
+ public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target,
+ int fromPosition, int toPosition) {
+ Collections.swap(mAuthorities, fromPosition, toPosition);
+ notifyItemMoved(fromPosition, toPosition);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mAuthorities.size();
+ }
+
+
+ public class ViewHolder extends RecyclerView.ViewHolder implements
+ ItemTouchHelperViewHolder {
+
+ public final ViewGroup outerLayout;
+ public final TextView authorityName;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ outerLayout = (ViewGroup) itemView.findViewById(R.id.outer_layout);
+ authorityName = (TextView) itemView.findViewById(R.id.smartpgp_authority_tv);
+ itemView.setClickable(true);
+ }
+
+ @Override
+ public void onItemSelected() {
+ }
+
+ @Override
+ public void onItemClear() {
+ }
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java
index fca166603..5cf8eb000 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java
@@ -168,7 +168,7 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
@Override
protected IOException doInBackground(Void... params) {
try {
- handleSecurityToken(transport);
+ handleSecurityToken(transport, BaseSecurityTokenActivity.this);
} catch (IOException e) {
return e;
}
@@ -428,13 +428,13 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
}
}
- protected void handleSecurityToken(Transport transport) throws IOException {
+ protected void handleSecurityToken(Transport transport, Context ctx) throws IOException {
// Don't reconnect if device was already connected
if (!(mSecurityTokenHelper.isPersistentConnectionAllowed()
&& mSecurityTokenHelper.isConnected()
&& mSecurityTokenHelper.getTransport().equals(transport))) {
mSecurityTokenHelper.setTransport(transport);
- mSecurityTokenHelper.connectToDevice();
+ mSecurityTokenHelper.connectToDevice(ctx);
}
doSecurityTokenInBackground();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditSmartPGPAuthorityDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditSmartPGPAuthorityDialogFragment.java
new file mode 100644
index 000000000..b35279c0c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditSmartPGPAuthorityDialogFragment.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann
+ *
+ * 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 .
+ */
+
+package org.sufficientlysecure.keychain.ui.dialog;
+
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.annotation.NonNull;
+import android.support.design.widget.TextInputLayout;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AlertDialog;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.EncryptFilesFragment;
+import org.sufficientlysecure.keychain.util.FileHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+public class AddEditSmartPGPAuthorityDialogFragment extends DialogFragment implements OnEditorActionListener {
+ private static final String IN_MESSENGER = "in_messenger";
+ private static final String IN_ACTION = "in_dialog_action";
+ private static final String IN_POSITION = "in_position";
+ private static final String IN_ALIAS = "in_authority";
+ private static final String IN_URI = "in_uri";
+
+ public static final String OUT_ACTION = "out_action";
+ public static final String OUT_ALIAS = "out_alias";
+ public static final String OUT_POSITION = "out_position";
+ public static final String OUT_URI = "out_uri";
+
+ private Messenger mMessenger;
+ private Action mAction;
+ private int mPosition;
+ private Uri mURI;
+
+ private EditText mAuthorityAliasText;
+ private TextInputLayout mAuthorityAliasTextLayout;
+ private Button mAuthorityAdd;
+
+ public enum Action {
+ ADD,
+ EDIT,
+ DELETE
+ }
+
+ public static AddEditSmartPGPAuthorityDialogFragment newInstance(Messenger messenger,
+ Action action,
+ String alias,
+ Uri uri,
+ int position) {
+ AddEditSmartPGPAuthorityDialogFragment frag = new AddEditSmartPGPAuthorityDialogFragment();
+ Bundle args = new Bundle();
+ args.putParcelable(IN_MESSENGER, messenger);
+ args.putSerializable(IN_ACTION, action);
+ args.putString(IN_ALIAS, alias);
+ args.putInt(IN_POSITION, position);
+ if (uri != null) {
+ args.putString(IN_URI, uri.toString());
+ }
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+
+ mMessenger = getArguments().getParcelable(IN_MESSENGER);
+ mAction = (Action) getArguments().getSerializable(IN_ACTION);
+ mPosition = getArguments().getInt(IN_POSITION);
+ if (getArguments().getString(IN_URI) == null)
+ mURI = null;
+ else
+ mURI = Uri.parse(getArguments().getString(IN_URI));
+
+ CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
+
+ LayoutInflater inflater = activity.getLayoutInflater();
+ View view = inflater.inflate(R.layout.add_smartpgp_authority_dialog, null);
+ alert.setView(view);
+
+ mAuthorityAliasText = (EditText) view.findViewById(R.id.smartpgp_authority_alias_edit_text);
+ mAuthorityAliasTextLayout = (TextInputLayout) view.findViewById(R.id.smartpgp_authority_alias_edit_text_layout);
+ mAuthorityAdd = (Button) view.findViewById(R.id.smartpgp_authority_filename);
+
+ mAuthorityAdd.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ FileHelper.openDocument(AddEditSmartPGPAuthorityDialogFragment.this, null, "*/*", false,
+ EncryptFilesFragment.REQUEST_CODE_INPUT);
+ }
+ });
+
+ mAuthorityAliasText.setText(getArguments().getString(IN_ALIAS));
+
+ switch (mAction) {
+ case ADD:
+ alert.setTitle(R.string.add_smartpgp_authority_dialog_title);
+ break;
+ case EDIT:
+ case DELETE:
+ alert.setTitle(R.string.show_smartpgp_authority_dialog_title);
+ break;
+ }
+
+ // we don't want dialog to be dismissed on click for keyserver addition or edit,
+ // thereby requiring the hack seen below and in onStart
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ // we need to have an empty listener to prevent errors on some devices as mentioned
+ // at http://stackoverflow.com/q/13746412/3000919
+ // actual listener set in onStart for adding keyservers or editing them
+ dismiss();
+ }
+ });
+
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dismiss();
+ }
+ });
+
+ switch (mAction) {
+ case EDIT:
+ case DELETE:
+ alert.setNeutralButton(R.string.label_smartpgp_authority_dialog_delete,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ deleteAuthority();
+ }
+ });
+ break;
+ }
+
+ // Hack to open keyboard.
+ // This is the only method that I found to work across all Android versions
+ // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
+ // Notes: * onCreateView can't be used because we want to add buttons to the dialog
+ // * opening in onActivityCreated does not work on Android 4.4
+ mAuthorityAliasText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ mAuthorityAliasText.post(new Runnable() {
+ @Override
+ public void run() {
+ InputMethodManager imm = (InputMethodManager) getActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mAuthorityAliasText, InputMethodManager.SHOW_IMPLICIT);
+ }
+ });
+ }
+ });
+ mAuthorityAliasText.requestFocus();
+
+ mAuthorityAliasText.setImeActionLabel(getString(android.R.string.ok),
+ EditorInfo.IME_ACTION_DONE);
+ mAuthorityAliasText.setOnEditorActionListener(this);
+
+ return alert.show();
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case EncryptFilesFragment.REQUEST_CODE_INPUT:
+ if (data != null) {
+ mURI = data.getData();
+ } else {
+ mURI = null;
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ AlertDialog addKeyserverDialog = (AlertDialog) getDialog();
+ if (addKeyserverDialog != null) {
+ Button positiveButton = addKeyserverDialog.getButton(Dialog.BUTTON_POSITIVE);
+ positiveButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mAuthorityAliasTextLayout.setErrorEnabled(false);
+
+ dismiss();
+ // return unverified keyserver back to activity
+ authorityEdited();
+ }
+ });
+ }
+ }
+
+ public void authorityEdited() {
+ dismiss();
+ Bundle data = new Bundle();
+ data.putSerializable(OUT_ACTION, mAction);
+ data.putString(OUT_ALIAS, mAuthorityAliasText.getText().toString());
+ data.putInt(OUT_POSITION, mPosition);
+ if (mURI != null) {
+ data.putString(OUT_URI, mURI.toString());
+ }
+
+ sendMessageToHandler(data);
+ }
+
+ public void deleteAuthority() {
+ dismiss();
+ Bundle data = new Bundle();
+ data.putSerializable(OUT_ACTION, Action.DELETE);
+ data.putString(OUT_ALIAS, mAuthorityAliasText.getText().toString());
+ data.putInt(OUT_POSITION, mPosition);
+
+ sendMessageToHandler(data);
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ // hide keyboard on dismiss
+ hideKeyboard();
+
+ super.onDismiss(dialog);
+ }
+
+ private void hideKeyboard() {
+ if (getActivity() == null) {
+ return;
+ }
+ InputMethodManager inputManager = (InputMethodManager) getActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ //check if no view has focus:
+ View v = getActivity().getCurrentFocus();
+ if (v == null)
+ return;
+
+ inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+
+ /**
+ * Associate the "done" button on the soft keyboard with the okay button in the view
+ */
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (EditorInfo.IME_ACTION_DONE == actionId) {
+ AlertDialog dialog = ((AlertDialog) getDialog());
+ Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+
+ bt.performClick();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Send message back to handler which is initialized in a activity
+ *
+ */
+ private void sendMessageToHandler(Bundle data) {
+ Message msg = Message.obtain();
+ if (data != null) {
+ msg.setData(data);
+ }
+
+ try {
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
+ } catch (NullPointerException e) {
+ Log.w(Constants.TAG, "Messenger is null!", e);
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
index a649b17a6..67e3624ca 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
@@ -450,6 +450,10 @@ public class Preferences {
return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_KEYBASE, false);
}
+ public boolean getExperimentalSmartPGPAuthoritiesEnable() {
+ return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_SMARTPGP_VERIFY_AUTHORITY, false);
+ }
+
public void upgradePreferences(Context context) {
Log.d(Constants.TAG, "Upgrading preferences…");
int oldVersion = mSharedPreferences.getInt(Constants.Pref.PREF_VERSION, 0);
diff --git a/OpenKeychain/src/main/res/layout/add_smartpgp_authority_dialog.xml b/OpenKeychain/src/main/res/layout/add_smartpgp_authority_dialog.xml
new file mode 100644
index 000000000..ce5e472cc
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/add_smartpgp_authority_dialog.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/settings_smartpgp_authority_fragment.xml b/OpenKeychain/src/main/res/layout/settings_smartpgp_authority_fragment.xml
new file mode 100644
index 000000000..145138468
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/settings_smartpgp_authority_fragment.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/settings_smartpgp_authority_item.xml b/OpenKeychain/src/main/res/layout/settings_smartpgp_authority_item.xml
new file mode 100644
index 000000000..9debd0a53
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/settings_smartpgp_authority_item.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/smartpgp_authorities_preference.xml b/OpenKeychain/src/main/res/layout/smartpgp_authorities_preference.xml
new file mode 100644
index 000000000..5eb8babbe
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/smartpgp_authorities_preference.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/menu/smartpgp_authority_pref_menu.xml b/OpenKeychain/src/main/res/menu/smartpgp_authority_pref_menu.xml
new file mode 100644
index 000000000..6c2e4c7e6
--- /dev/null
+++ b/OpenKeychain/src/main/res/menu/smartpgp_authority_pref_menu.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
index 64c8d4a8c..6cdb18d12 100644
--- a/OpenKeychain/src/main/res/values/strings.xml
+++ b/OpenKeychain/src/main/res/values/strings.xml
@@ -19,6 +19,7 @@
"Settings"
"Apps"
"OpenPGP keyservers"
+ "SmartPGP authorities"
"Customize 'Remember' choices"
"Change Password"
"Share fingerprint with…"
@@ -187,10 +188,14 @@
"Keyservers"
"Drag to change order, tap to edit/delete"
"Selected keyserver"
+ "Selected authority"
"preferred"
"Enable compression"
"Encrypt filenames"
"Hide recipients"
+ SmartPGP verify certificate
+ "Validate tokens certificates against a set of trusted certification authorities"
+ SmartPGP trusted authorities
"Test connection"
"Only trusted keyserver"
@@ -198,6 +203,8 @@
"Optional Tor .onion URL"
"Delete keyserver"
"Theme"
+ Name
+ "Delete authority"
"OpenPGP keyservers"
"Search keys on selected OpenPGP keyservers (HKP protocol)"
@@ -272,6 +279,11 @@
- "%d keyservers"
+
+ - "%d authority"
+ - "%d authorities"
+
+
"Secret Key:"
@@ -829,6 +841,11 @@
"%s deleted"
"Cannot delete last keyserver. At least one is required!"
+
+ "Add authority"
+ "Edit authority"
+ "%s deleted"
+
"Keys"
"Encrypt/Decrypt"
diff --git a/OpenKeychain/src/main/res/xml/experimental_preferences.xml b/OpenKeychain/src/main/res/xml/experimental_preferences.xml
index 9312997b0..8544487d2 100644
--- a/OpenKeychain/src/main/res/xml/experimental_preferences.xml
+++ b/OpenKeychain/src/main/res/xml/experimental_preferences.xml
@@ -35,4 +35,14 @@
android:persistent="true"
android:title="@string/label_theme" />
+
+
+