2134 lines
88 KiB
Diff
2134 lines
88 KiB
Diff
From 220fc35de449e03a11d5d836af0de0909d691e16 Mon Sep 17 00:00:00 2001
|
|
From: Arnaud Fontaine <arnaud.fontaine@ssi.gouv.fr>
|
|
Date: Sat, 22 Oct 2016 22:42:22 +0200
|
|
Subject: [PATCH 2/2] Support of SmartPGP secure messaging feature
|
|
|
|
---
|
|
OpenKeychain/src/main/AndroidManifest.xml | 5 +
|
|
.../org/sufficientlysecure/keychain/Constants.java | 2 +
|
|
.../securitytoken/SCP11bSecureMessaging.java | 714 +++++++++++++++++++++
|
|
.../keychain/securitytoken/SecureMessaging.java | 17 +
|
|
.../securitytoken/SecureMessagingException.java | 8 +
|
|
.../securitytoken/SecurityTokenHelper.java | 67 +-
|
|
.../ui/SecurityTokenOperationActivity.java | 1 +
|
|
.../keychain/ui/SettingsActivity.java | 54 ++
|
|
.../ui/SettingsSmartPGPAuthoritiesActivity.java | 124 ++++
|
|
.../ui/SettingsSmartPGPAuthorityFragment.java | 334 ++++++++++
|
|
.../ui/base/BaseSecurityTokenActivity.java | 6 +-
|
|
.../AddEditSmartPGPAuthorityDialogFragment.java | 313 +++++++++
|
|
.../keychain/util/Preferences.java | 5 +-
|
|
.../res/layout/add_smartpgp_authority_dialog.xml | 45 ++
|
|
.../settings_smartpgp_authority_fragment.xml | 7 +
|
|
.../layout/settings_smartpgp_authority_item.xml | 27 +
|
|
.../res/layout/smartpgp_authorities_preference.xml | 17 +
|
|
.../main/res/menu/smartpgp_authority_pref_menu.xml | 10 +
|
|
OpenKeychain/src/main/res/values/strings.xml | 17 +
|
|
.../src/main/res/xml/experimental_preferences.xml | 10 +
|
|
20 files changed, 1773 insertions(+), 10 deletions(-)
|
|
create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SCP11bSecureMessaging.java
|
|
create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecureMessaging.java
|
|
create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecureMessagingException.java
|
|
create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsSmartPGPAuthoritiesActivity.java
|
|
create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsSmartPGPAuthorityFragment.java
|
|
create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditSmartPGPAuthorityDialogFragment.java
|
|
create mode 100644 OpenKeychain/src/main/res/layout/add_smartpgp_authority_dialog.xml
|
|
create mode 100644 OpenKeychain/src/main/res/layout/settings_smartpgp_authority_fragment.xml
|
|
create mode 100644 OpenKeychain/src/main/res/layout/settings_smartpgp_authority_item.xml
|
|
create mode 100644 OpenKeychain/src/main/res/layout/smartpgp_authorities_preference.xml
|
|
create mode 100644 OpenKeychain/src/main/res/menu/smartpgp_authority_pref_menu.xml
|
|
|
|
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
|
|
index 0ab1e6a..f22c59f 100644
|
|
--- a/OpenKeychain/src/main/AndroidManifest.xml
|
|
+++ b/OpenKeychain/src/main/AndroidManifest.xml
|
|
@@ -484,6 +484,11 @@
|
|
android:label="@string/title_key_server_preference"
|
|
android:windowSoftInputMode="stateHidden" />
|
|
<activity
|
|
+ android:name=".ui.SettingsSmartPGPAuthoritiesActivity"
|
|
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
|
+ android:label="@string/title_smartpgp_authorities_preference"
|
|
+ android:windowSoftInputMode="stateHidden" />
|
|
+ <activity
|
|
android:name=".ui.SettingsCacheTTLActivity"
|
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
|
android:label="@string/title_cache_ttl_preference"
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
|
|
index fd6e903..9f40cc5 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
|
|
@@ -123,6 +123,8 @@ public final class Constants {
|
|
public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm";
|
|
public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities";
|
|
public static final String EXPERIMENTAL_ENABLE_KEYBASE = "experimentalEnableKeybase";
|
|
+ public static final String EXPERIMENTAL_SMARTPGP_VERIFY_AUTHORITY = "smartpgp_authorities_pref";
|
|
+ public static final String EXPERIMENTAL_SMARTPGP_AUTHORITIES = "smartpgp_authorities";
|
|
|
|
public static final class Theme {
|
|
public static final String LIGHT = "light";
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SCP11bSecureMessaging.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SCP11bSecureMessaging.java
|
|
new file mode 100644
|
|
index 0000000..6d28749
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SCP11bSecureMessaging.java
|
|
@@ -0,0 +1,714 @@
|
|
+package org.sufficientlysecure.keychain.securitytoken;
|
|
+
|
|
+import android.content.Context;
|
|
+import android.content.SharedPreferences;
|
|
+import android.support.annotation.NonNull;
|
|
+import android.util.Log;
|
|
+
|
|
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
|
+import org.bouncycastle.asn1.x9.ECNamedCurveTable;
|
|
+import org.bouncycastle.asn1.x9.X9ECParameters;
|
|
+import org.bouncycastle.math.ec.ECCurve;
|
|
+import org.bouncycastle.math.ec.ECPoint;
|
|
+import org.bouncycastle.util.Arrays;
|
|
+import org.bouncycastle.util.Iterable;
|
|
+import org.bouncycastle.util.encoders.Hex;
|
|
+import org.sufficientlysecure.keychain.ui.SettingsSmartPGPAuthoritiesActivity;
|
|
+import org.sufficientlysecure.keychain.ui.util.Notify;
|
|
+import org.sufficientlysecure.keychain.util.Iso7816TLV;
|
|
+import org.sufficientlysecure.keychain.util.Preferences;
|
|
+import org.sufficientlysecure.keychain.util.SecurityTokenUtils;
|
|
+
|
|
+import java.io.ByteArrayInputStream;
|
|
+import java.io.ByteArrayOutputStream;
|
|
+import java.io.File;
|
|
+import java.io.FileInputStream;
|
|
+import java.io.FileNotFoundException;
|
|
+import java.io.IOException;
|
|
+import java.security.AlgorithmParameters;
|
|
+import java.security.InvalidAlgorithmParameterException;
|
|
+import java.security.InvalidKeyException;
|
|
+import java.security.KeyFactory;
|
|
+import java.security.KeyPair;
|
|
+import java.security.KeyPairGenerator;
|
|
+import java.security.KeyStore;
|
|
+import java.security.KeyStoreException;
|
|
+import java.security.MessageDigest;
|
|
+import java.security.NoSuchAlgorithmException;
|
|
+import java.security.NoSuchProviderException;
|
|
+import java.security.PublicKey;
|
|
+import java.security.SecureRandom;
|
|
+import java.security.SignatureException;
|
|
+import java.security.cert.CertPathBuilder;
|
|
+import java.security.cert.CertPathBuilderException;
|
|
+import java.security.cert.CertStore;
|
|
+import java.security.cert.Certificate;
|
|
+import java.security.cert.CertificateException;
|
|
+import java.security.cert.CertificateFactory;
|
|
+import java.security.cert.CollectionCertStoreParameters;
|
|
+import java.security.cert.PKIXBuilderParameters;
|
|
+import java.security.cert.PKIXCertPathBuilderResult;
|
|
+import java.security.cert.TrustAnchor;
|
|
+import java.security.cert.X509CertSelector;
|
|
+import java.security.cert.X509Certificate;
|
|
+import java.security.interfaces.ECPrivateKey;
|
|
+import java.security.interfaces.ECPublicKey;
|
|
+import java.security.spec.ECGenParameterSpec;
|
|
+import java.security.spec.ECParameterSpec;
|
|
+import java.security.spec.InvalidKeySpecException;
|
|
+import java.security.spec.InvalidParameterSpecException;
|
|
+import java.util.ArrayList;
|
|
+import java.util.Enumeration;
|
|
+import java.util.HashSet;
|
|
+import java.util.Iterator;
|
|
+import java.util.Set;
|
|
+
|
|
+import javax.crypto.BadPaddingException;
|
|
+import javax.crypto.Cipher;
|
|
+import javax.crypto.IllegalBlockSizeException;
|
|
+import javax.crypto.KeyAgreement;
|
|
+import javax.crypto.Mac;
|
|
+import javax.crypto.NoSuchPaddingException;
|
|
+import javax.crypto.SecretKey;
|
|
+import javax.crypto.spec.IvParameterSpec;
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
+import javax.net.ssl.TrustManagerFactory;
|
|
+import javax.smartcardio.CommandAPDU;
|
|
+import javax.smartcardio.ResponseAPDU;
|
|
+
|
|
+
|
|
+class SCP11bSecureMessaging implements SecureMessaging {
|
|
+
|
|
+ private static final byte OPENPGP_SECURE_MESSAGING_CLA_MASK = (byte)0x04;
|
|
+ private static final byte[] OPENPGP_SECURE_MESSAGING_KEY_CRT = new byte[] { (byte)0xA6, (byte)0 };
|
|
+ private static final byte OPENPGP_SECURE_MESSAGING_KEY_ATTRIBUTES_TAG = (byte)0xD4;
|
|
+
|
|
+ private static final int AES_BLOCK_SIZE = 128 / 8;
|
|
+
|
|
+ private static final int SCP11_MAC_LENGTH = AES_BLOCK_SIZE / 2;
|
|
+
|
|
+ private static final String SCP11_SYMMETRIC_ALGO = "AES";
|
|
+ private static final String SCP11_CIPHER_ALGO = "AES/CBC/NoPadding";
|
|
+ private static final String SCP11_MAC_ALGO = "AESCMAC";
|
|
+
|
|
+ private static final String SCP11B_KEY_AGREEMENT_ALGO = "ECDH";
|
|
+ private static final String SCP11B_KEY_AGREEMENT_KEY_TYPE = "ECDH";
|
|
+ private static final String SCP11B_KEY_AGREEMENT_KEY_ALGO = "EC";
|
|
+ private static final String SCP11B_KEY_DERIVATION_ALGO = "SHA256";
|
|
+
|
|
+ private static final String CERTIFICATE_FORMAT = "X.509";
|
|
+
|
|
+ private static final String PROVIDER = "BC";
|
|
+
|
|
+ private static SecureRandom srand;
|
|
+ private static KeyFactory ecdhFactory;
|
|
+ private static CertificateFactory certFactory;
|
|
+
|
|
+ private SecretKey mSEnc;
|
|
+ private SecretKey mSMac;
|
|
+ private SecretKey mSRMac;
|
|
+
|
|
+ private short mEncryptionCounter;
|
|
+
|
|
+ private byte[] mMacChaining;
|
|
+
|
|
+ private SCP11bSecureMessaging() {
|
|
+ }
|
|
+
|
|
+ private void setKeys(@NonNull final byte[] sEnc,
|
|
+ @NonNull final byte[] sMac,
|
|
+ @NonNull final byte[] sRmac,
|
|
+ @NonNull final byte[] receipt)
|
|
+ throws SecureMessagingException {
|
|
+
|
|
+ if ((sEnc.length != sMac.length)
|
|
+ || (sEnc.length != sRmac.length)
|
|
+ || (receipt.length != AES_BLOCK_SIZE)) {
|
|
+ throw new SecureMessagingException("incoherent SCP11b key set");
|
|
+ }
|
|
+
|
|
+ mSEnc = new SecretKeySpec(sEnc, SCP11_SYMMETRIC_ALGO);
|
|
+ mSMac = new SecretKeySpec(sMac, SCP11_SYMMETRIC_ALGO);
|
|
+ mSRMac = new SecretKeySpec(sRmac, SCP11_SYMMETRIC_ALGO);
|
|
+ mEncryptionCounter = 0;
|
|
+ mMacChaining = receipt;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void clearSession() {
|
|
+ mSEnc = null;
|
|
+ mSMac = null;
|
|
+ mSRMac = null;
|
|
+ mEncryptionCounter = 0;
|
|
+ mMacChaining = null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isEstablished() {
|
|
+ return (mSEnc != null)
|
|
+ && (mSMac != null)
|
|
+ && (mSRMac != null)
|
|
+ && (mMacChaining != null);
|
|
+ }
|
|
+
|
|
+ private static final ECParameterSpec getAlgorithmParameterSpec(final ECKeyFormat kf)
|
|
+ throws NoSuchProviderException, NoSuchAlgorithmException, InvalidParameterSpecException {
|
|
+ final AlgorithmParameters algo_params = AlgorithmParameters.getInstance(SCP11B_KEY_AGREEMENT_KEY_ALGO, PROVIDER);
|
|
+
|
|
+ algo_params.init(new ECGenParameterSpec(ECNamedCurveTable.getName(kf.getCurveOID())));
|
|
+
|
|
+ return algo_params.getParameterSpec(ECParameterSpec.class);
|
|
+ }
|
|
+
|
|
+
|
|
+ private static ECPublicKey newECDHPublicKey(final ECKeyFormat kf, byte[] data)
|
|
+ throws InvalidKeySpecException, NoSuchAlgorithmException,
|
|
+ InvalidParameterSpecException, NoSuchProviderException {
|
|
+ if (ecdhFactory == null) {
|
|
+ ecdhFactory = KeyFactory.getInstance(SCP11B_KEY_AGREEMENT_KEY_TYPE, PROVIDER);
|
|
+ }
|
|
+
|
|
+ final X9ECParameters params = NISTNamedCurves.getByOID(kf.getCurveOID());
|
|
+ if (params == null) {
|
|
+ throw new InvalidParameterSpecException("unsupported curve");
|
|
+ }
|
|
+
|
|
+ final ECCurve curve = params.getCurve();
|
|
+ final ECPoint p = curve.decodePoint(data);
|
|
+ if (!p.isValid()) {
|
|
+ throw new InvalidKeySpecException("invalid EC point");
|
|
+ }
|
|
+
|
|
+ final java.security.spec.ECPublicKeySpec pk = new java.security.spec.ECPublicKeySpec(
|
|
+ new java.security.spec.ECPoint(p.getAffineXCoord().toBigInteger(), p.getAffineYCoord().toBigInteger()),
|
|
+ getAlgorithmParameterSpec(kf));
|
|
+
|
|
+ return (ECPublicKey)(ecdhFactory.generatePublic(pk));
|
|
+ }
|
|
+
|
|
+ private static KeyPair generateECDHKeyPair(final ECKeyFormat kf)
|
|
+ throws NoSuchProviderException, NoSuchAlgorithmException,
|
|
+ InvalidParameterSpecException, InvalidAlgorithmParameterException {
|
|
+ final KeyPairGenerator gen = KeyPairGenerator.getInstance(SCP11B_KEY_AGREEMENT_KEY_ALGO, PROVIDER);
|
|
+
|
|
+ if (srand == null) {
|
|
+ srand = new SecureRandom();
|
|
+ }
|
|
+
|
|
+ gen.initialize(getAlgorithmParameterSpec(kf), srand);
|
|
+
|
|
+ return gen.generateKeyPair();
|
|
+ }
|
|
+
|
|
+ private static ECPublicKey verifyCertificate(final Context ctx,
|
|
+ final ECKeyFormat kf,
|
|
+ final byte[] data) throws IOException {
|
|
+ try {
|
|
+
|
|
+ if (certFactory == null) {
|
|
+ certFactory = CertificateFactory.getInstance(CERTIFICATE_FORMAT, PROVIDER);
|
|
+ }
|
|
+
|
|
+ final ECParameterSpec kfParams = getAlgorithmParameterSpec(kf);
|
|
+
|
|
+ final Certificate _cardCert = certFactory.generateCertificate(new ByteArrayInputStream(data));
|
|
+ if (!(_cardCert instanceof X509Certificate)) {
|
|
+ throw new IOException("invalid card certificate");
|
|
+ }
|
|
+ final X509Certificate cardCert = (X509Certificate) _cardCert;
|
|
+
|
|
+ final PublicKey _cardPk = cardCert.getPublicKey();
|
|
+ if (!(_cardPk instanceof ECPublicKey)) {
|
|
+ throw new IOException("invalid card public key");
|
|
+ }
|
|
+ final ECPublicKey cardPk = (ECPublicKey) _cardPk;
|
|
+ final ECParameterSpec cardPkParams = cardPk.getParams();
|
|
+
|
|
+ if (!kfParams.getCurve().equals(cardPkParams.getCurve())) {
|
|
+ throw new IOException("incoherent card certificate/public key format");
|
|
+ }
|
|
+
|
|
+ final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(ctx);
|
|
+
|
|
+ if (ks == null) {
|
|
+ throw new KeyStoreException("no keystore found");
|
|
+ }
|
|
+
|
|
+ final X509CertSelector targetConstraints = new X509CertSelector();
|
|
+ targetConstraints.setCertificate(cardCert);
|
|
+
|
|
+ final ArrayList al = new ArrayList();
|
|
+ al.add(cardCert);
|
|
+ final CollectionCertStoreParameters certStoreParams = new CollectionCertStoreParameters(al);
|
|
+ final CertStore certStore = CertStore.getInstance("Collection", certStoreParams, PROVIDER);
|
|
+
|
|
+ final PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(ks, targetConstraints);
|
|
+ pkixParams.setRevocationEnabled(false);
|
|
+ pkixParams.addCertStore(certStore);
|
|
+
|
|
+ final CertPathBuilder builder = CertPathBuilder.getInstance(CertPathBuilder.getDefaultType(), PROVIDER);
|
|
+
|
|
+ final PKIXCertPathBuilderResult result =
|
|
+ (PKIXCertPathBuilderResult) builder.build(pkixParams);
|
|
+
|
|
+ return cardPk;
|
|
+
|
|
+ } catch (CertificateException e) {
|
|
+ throw new IOException("invalid card certificate (" + e.getMessage() + ")");
|
|
+ } catch (NoSuchAlgorithmException e) {
|
|
+ throw new IOException("unknown algorithm (" + e.getMessage() + ")");
|
|
+ } catch (InvalidParameterSpecException e) {
|
|
+ throw new IOException("invalid card key parameters (" + e.getMessage() + ")");
|
|
+ } catch (IllegalArgumentException e) {
|
|
+ e.printStackTrace();
|
|
+ throw new IOException("illegal argument (" + e.getMessage() + ")");
|
|
+ } catch (NoSuchProviderException e) {
|
|
+ throw new IOException("unavailable crypto (" + e.getMessage() + ")");
|
|
+ } catch (KeyStoreException e) {
|
|
+ throw new IOException("failed to build keystore (" + e.getMessage() + ")");
|
|
+ } catch (InvalidAlgorithmParameterException e) {
|
|
+ throw new IOException("invalid algorithm parameter (" + e.getMessage() + ")");
|
|
+ } catch (CertPathBuilderException e) {
|
|
+ throw new IOException("invalid certificate path (" + e.getMessage() + ")");
|
|
+ }
|
|
+ }
|
|
+
|
|
+
|
|
+ public static void establish(final SecurityTokenHelper t, final Context ctx)
|
|
+ throws SecureMessagingException, IOException {
|
|
+
|
|
+ final int keySize = t.getOpenPgpCapabilities().getSMAESKeySize();
|
|
+
|
|
+ t.clearSecureMessaging();
|
|
+
|
|
+ if ((keySize != 16)
|
|
+ && (keySize != 32)) {
|
|
+ throw new SecureMessagingException("invalid key size");
|
|
+ }
|
|
+
|
|
+ CommandAPDU cmd;
|
|
+ ResponseAPDU resp;
|
|
+ Iso7816TLV[] tlvs;
|
|
+
|
|
+ // retrieving key algorithm
|
|
+ cmd = new CommandAPDU(0, (byte)0xCA, (byte)0x00,
|
|
+ OPENPGP_SECURE_MESSAGING_KEY_ATTRIBUTES_TAG, SecurityTokenHelper.MAX_APDU_NE_EXT);
|
|
+ resp = t.communicate(cmd);
|
|
+ if (resp.getSW() != SecurityTokenHelper.APDU_SW_SUCCESS) {
|
|
+ throw new SecureMessagingException("failed to retrieve secure messaging key attributes");
|
|
+ }
|
|
+ tlvs = Iso7816TLV.readList(resp.getData(), true);
|
|
+ if ((tlvs == null)
|
|
+ || (tlvs.length != 1)
|
|
+ || ((byte)tlvs[0].mT != OPENPGP_SECURE_MESSAGING_KEY_ATTRIBUTES_TAG)) {
|
|
+ throw new SecureMessagingException("invalid format of secure messaging key attributes");
|
|
+ }
|
|
+
|
|
+ final KeyFormat kf = KeyFormat.fromBytes(tlvs[0].mV);
|
|
+
|
|
+ if (kf.keyFormatType() != KeyFormat.KeyFormatType.ECKeyFormatType) {
|
|
+ throw new SecureMessagingException("invalid format of secure messaging key");
|
|
+ }
|
|
+
|
|
+ final ECKeyFormat eckf = (ECKeyFormat)kf;
|
|
+
|
|
+ if (eckf.getCurveOID() == null) {
|
|
+ throw new SecureMessagingException("unsupported curve");
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ ECPublicKey pkcard = null;
|
|
+
|
|
+ final Preferences prefs = Preferences.getPreferences(ctx);
|
|
+
|
|
+ if (prefs != null && prefs.getExperimentalSmartPGPAuthoritiesEnable()) {
|
|
+ // retrieving certificate
|
|
+ cmd = new CommandAPDU(0, (byte) 0xA5, (byte) 0x03, (byte) 0x04,
|
|
+ new byte[]{(byte) 0x60, (byte) 0x04, (byte) 0x5C, (byte) 0x02, (byte) 0x7F, (byte) 0x21});
|
|
+ resp = t.communicate(cmd);
|
|
+ if (resp.getSW() != SecurityTokenHelper.APDU_SW_SUCCESS) {
|
|
+ throw new SecureMessagingException("failed to select secure messaging certificate");
|
|
+ }
|
|
+ cmd = new CommandAPDU(0, (byte) 0xCA, (byte) 0x7F, (byte) 0x21, SecurityTokenHelper.MAX_APDU_NE_EXT);
|
|
+ resp = t.communicate(cmd);
|
|
+ if (resp.getSW() != SecurityTokenHelper.APDU_SW_SUCCESS) {
|
|
+ throw new SecureMessagingException("failed to retrieve secure messaging certificate");
|
|
+ }
|
|
+
|
|
+ pkcard = verifyCertificate(ctx, eckf, resp.getData());
|
|
+
|
|
+ } else {
|
|
+ // retrieving public key
|
|
+ cmd = new CommandAPDU(0, (byte) 0x47, (byte) 0x81, (byte) 0x00,
|
|
+ OPENPGP_SECURE_MESSAGING_KEY_CRT, SecurityTokenHelper.MAX_APDU_NE_EXT);
|
|
+ resp = t.communicate(cmd);
|
|
+ if (resp.getSW() != SecurityTokenHelper.APDU_SW_SUCCESS) {
|
|
+ throw new SecureMessagingException("failed to retrieve secure messaging public key");
|
|
+ }
|
|
+ tlvs = Iso7816TLV.readList(resp.getData(), true);
|
|
+ if ((tlvs == null)
|
|
+ || (tlvs.length != 1)
|
|
+ || ((short)tlvs[0].mT != (short)0x7f49)) {
|
|
+ throw new SecureMessagingException("invalid format of secure messaging key");
|
|
+ }
|
|
+ tlvs = Iso7816TLV.readList(tlvs[0].mV, true);
|
|
+ if ((tlvs == null)
|
|
+ || (tlvs.length != 1)
|
|
+ || ((byte)tlvs[0].mT != (byte)0x86)) {
|
|
+ throw new SecureMessagingException("invalid format of secure messaging key");
|
|
+ }
|
|
+
|
|
+ pkcard = newECDHPublicKey(eckf, tlvs[0].mV);
|
|
+ }
|
|
+
|
|
+ if (pkcard == null) {
|
|
+ throw new SecureMessagingException("No key in token for secure messaging");
|
|
+ }
|
|
+
|
|
+ final KeyPair ekoce = generateECDHKeyPair(eckf);
|
|
+ final ECPublicKey epkoce = (ECPublicKey)ekoce.getPublic();
|
|
+ final ECPrivateKey eskoce = (ECPrivateKey)ekoce.getPrivate();
|
|
+
|
|
+ final byte[] crt_template = new byte[] {
|
|
+ (byte)0xA6, (byte)0x0D,
|
|
+ (byte)0x90, (byte)0x02, (byte)0x11, (byte)0x00,
|
|
+ (byte)0x95, (byte)0x01, (byte)0x3C,
|
|
+ (byte)0x80, (byte)0x01, (byte)0x88,
|
|
+ (byte)0x81, (byte)0x01, (byte)keySize,
|
|
+ (byte)0x5F, (byte)0x49 };
|
|
+
|
|
+ int csize = (int)Math.ceil(epkoce.getParams().getCurve().getField().getFieldSize() / 8.0);
|
|
+
|
|
+ ByteArrayOutputStream pkout = new ByteArrayOutputStream(), bout = new ByteArrayOutputStream();
|
|
+
|
|
+ pkout.write((byte)0x04);
|
|
+ SecurityTokenUtils.writeBits(pkout, epkoce.getW().getAffineX(), csize);
|
|
+ SecurityTokenUtils.writeBits(pkout, epkoce.getW().getAffineY(), csize);
|
|
+
|
|
+ bout.write(crt_template);
|
|
+ bout.write(SecurityTokenUtils.encodeLength(pkout.size()));
|
|
+ pkout.writeTo(bout);
|
|
+ pkout = bout;
|
|
+
|
|
+ // internal authenticate
|
|
+ cmd = new CommandAPDU(0, (byte)0x88, (byte)0x01, (byte)0x0, pkout.toByteArray(),
|
|
+ SecurityTokenHelper.MAX_APDU_NE_EXT);
|
|
+ resp = t.communicate(cmd);
|
|
+ if (resp.getSW() != SecurityTokenHelper.APDU_SW_SUCCESS) {
|
|
+ throw new SecureMessagingException("failed to initiate internal authenticate");
|
|
+ }
|
|
+
|
|
+ tlvs = Iso7816TLV.readList(resp.getData(), true);
|
|
+ if ((tlvs == null)
|
|
+ || (tlvs.length != 2)
|
|
+ || (tlvs[0].mT == tlvs[1].mT)) {
|
|
+ throw new SecureMessagingException("invalid internal authenticate response");
|
|
+ }
|
|
+
|
|
+ byte[] receipt = null;
|
|
+ ECPublicKey epkcard = null;
|
|
+
|
|
+ for (int i = 0; i < tlvs.length; ++i) {
|
|
+ switch (tlvs[i].mT) {
|
|
+ case 0x86:
|
|
+ if (tlvs[i].mL != AES_BLOCK_SIZE) {
|
|
+ throw new SecureMessagingException("invalid size for receipt");
|
|
+ }
|
|
+ receipt = tlvs[i].mV;
|
|
+ break;
|
|
+
|
|
+ case 0x5F49:
|
|
+ epkcard = newECDHPublicKey(eckf, tlvs[i].mV);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ throw new SecureMessagingException("unexpected data in internal authenticate response");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final KeyAgreement ecdhKa = KeyAgreement.getInstance(SCP11B_KEY_AGREEMENT_ALGO, PROVIDER);
|
|
+ bout = new ByteArrayOutputStream();
|
|
+
|
|
+ //compute ShSe
|
|
+ ecdhKa.init(eskoce);
|
|
+ ecdhKa.doPhase(epkcard, true);
|
|
+ bout.write(ecdhKa.generateSecret());
|
|
+
|
|
+ //compute ShSs
|
|
+ ecdhKa.init(eskoce);
|
|
+ ecdhKa.doPhase(pkcard, true);
|
|
+ bout.write(ecdhKa.generateSecret());
|
|
+
|
|
+ csize = bout.size() + 3;
|
|
+
|
|
+ bout.write(new byte[] {
|
|
+ (byte)0, (byte)0, (byte)0, (byte)0,
|
|
+ crt_template[8], crt_template[11],
|
|
+ (byte)keySize });
|
|
+
|
|
+ byte[] shs = bout.toByteArray();
|
|
+
|
|
+ //key derivation
|
|
+ final MessageDigest h = MessageDigest.getInstance(SCP11B_KEY_DERIVATION_ALGO, PROVIDER);
|
|
+
|
|
+ bout = new ByteArrayOutputStream();
|
|
+ while (bout.size() < 4 * keySize) {
|
|
+ ++shs[csize];
|
|
+ bout.write(h.digest(shs));
|
|
+ }
|
|
+
|
|
+ shs = bout.toByteArray();
|
|
+
|
|
+ final byte[] rkey = Arrays.copyOfRange(shs, 0, keySize);
|
|
+ final byte[] sEnc = Arrays.copyOfRange(shs, keySize, 2 * keySize);
|
|
+ final byte[] sMac = Arrays.copyOfRange(shs, 2 * keySize, 3 * keySize);
|
|
+ final byte[] sRmac = Arrays.copyOfRange(shs, 3 * keySize, 4 * keySize);
|
|
+
|
|
+ //receipt computation
|
|
+ final Mac mac = Mac.getInstance(SCP11_MAC_ALGO, PROVIDER);
|
|
+
|
|
+ mac.init(new SecretKeySpec(rkey, SCP11_SYMMETRIC_ALGO));
|
|
+
|
|
+ shs = resp.getData();
|
|
+ mac.update(pkout.toByteArray());
|
|
+ mac.update(shs, 0, shs.length - 2 - AES_BLOCK_SIZE);
|
|
+ shs = mac.doFinal();
|
|
+
|
|
+ for(int i = 0; i < AES_BLOCK_SIZE; ++i) {
|
|
+ if (shs[i] != receipt[i]) {
|
|
+ throw new SecureMessagingException("corrupted receipt!");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final SCP11bSecureMessaging sm = new SCP11bSecureMessaging();
|
|
+ sm.setKeys(sEnc, sMac, sRmac, receipt);
|
|
+
|
|
+ t.setSecureMessaging(sm);
|
|
+
|
|
+ } catch (InvalidKeySpecException e) {
|
|
+ throw new SecureMessagingException("invalid key specification : " + e.getMessage());
|
|
+ } catch (NoSuchAlgorithmException e) {
|
|
+ throw new SecureMessagingException("unknown EC key algorithm : " + e.getMessage());
|
|
+ } catch (InvalidParameterSpecException e) {
|
|
+ throw new SecureMessagingException("invalid ECDH parameters : " + e.getMessage());
|
|
+ } catch (NoSuchProviderException e) {
|
|
+ throw new SecureMessagingException("unknown provider " + PROVIDER);
|
|
+ } catch (InvalidAlgorithmParameterException e) {
|
|
+ throw new SecureMessagingException("invalid algorithm parameters : " + e.getMessage());
|
|
+ } catch (InvalidKeyException e) {
|
|
+ throw new SecureMessagingException("invalid key : " + e.getMessage());
|
|
+ } catch (IllegalArgumentException e) {
|
|
+ throw new SecureMessagingException("illegal argument (" + e.getMessage() + ")");
|
|
+ }
|
|
+ }
|
|
+
|
|
+
|
|
+
|
|
+ @Override
|
|
+ public CommandAPDU encryptAndSign(CommandAPDU apdu)
|
|
+ throws SecureMessagingException {
|
|
+
|
|
+ if (!isEstablished()) {
|
|
+ throw new SecureMessagingException("not established");
|
|
+ }
|
|
+
|
|
+ ++mEncryptionCounter;
|
|
+ if(mEncryptionCounter <= 0) {
|
|
+ throw new SecureMessagingException("exhausted encryption counter");
|
|
+ }
|
|
+
|
|
+ try {
|
|
+
|
|
+ byte[] data = apdu.getData();
|
|
+
|
|
+ if (data.length > 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 0000000..90436a3
|
|
--- /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 0000000..5b8a8e3
|
|
--- /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 c3619db..0fdd183 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 651780f..c663a2f 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<Void, Void, Void>() {
|
|
@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 894ce78..ddd6d55 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
|
|
@@ -57,11 +57,18 @@ import org.sufficientlysecure.keychain.util.Log;
|
|
import org.sufficientlysecure.keychain.util.Preferences;
|
|
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
|
|
|
+import java.io.IOException;
|
|
+import java.security.KeyStore;
|
|
+import java.security.KeyStoreException;
|
|
+import java.security.NoSuchAlgorithmException;
|
|
+import java.security.cert.CertificateException;
|
|
import java.util.List;
|
|
+import java.util.Set;
|
|
|
|
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;
|
|
@@ -552,6 +559,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);
|
|
@@ -561,6 +570,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 0000000..fe11fb6
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsSmartPGPAuthoritiesActivity.java
|
|
@@ -0,0 +1,124 @@
|
|
+/*
|
|
+ * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
|
+ *
|
|
+ * 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;
|
|
+
|
|
+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 0000000..f3dcca7
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsSmartPGPAuthorityFragment.java
|
|
@@ -0,0 +1,334 @@
|
|
+/*
|
|
+ * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
|
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@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;
|
|
+
|
|
+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<String> 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<String> authorities = new LinkedList();
|
|
+
|
|
+ try {
|
|
+ final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(getActivity());
|
|
+ final Enumeration<String> 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<AuthorityListAdapter.ViewHolder>
|
|
+ implements ItemTouchHelperAdapter {
|
|
+
|
|
+ private final List<String> mAuthorities;
|
|
+
|
|
+ public AuthorityListAdapter(List<String> 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 fca1666..5cf8eb0 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 0000000..b35279c
|
|
--- /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 <dominik@dominikschuermann.de>
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, either version 3 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
+ */
|
|
+
|
|
+package org.sufficientlysecure.keychain.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 6a6d8d1..cc6ecfe 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
|
|
@@ -34,7 +34,6 @@ import org.sufficientlysecure.keychain.Constants.Pref;
|
|
import org.sufficientlysecure.keychain.KeychainApplication;
|
|
import org.sufficientlysecure.keychain.R;
|
|
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
|
|
-import org.sufficientlysecure.keychain.util.orbot.OrbotStatusReceiver;
|
|
|
|
import java.io.Serializable;
|
|
import java.net.Proxy;
|
|
@@ -466,6 +465,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) {
|
|
if (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0) !=
|
|
Constants.Defaults.PREF_VERSION) {
|
|
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 0000000..ce5e472
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/res/layout/add_smartpgp_authority_dialog.xml
|
|
@@ -0,0 +1,45 @@
|
|
+<?xml version="1.0" encoding="utf-8"?>
|
|
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
+ xmlns:custom="http://schemas.android.com/apk/res-auto"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:orientation="vertical"
|
|
+ android:paddingBottom="16dp"
|
|
+ android:paddingLeft="24dp"
|
|
+ android:paddingRight="24dp"
|
|
+ android:paddingTop="16dp">
|
|
+
|
|
+ <android.support.design.widget.TextInputLayout
|
|
+ android:id="@+id/smartpgp_authority_alias_edit_text_layout"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:layout_marginBottom="8dp">
|
|
+
|
|
+ <EditText
|
|
+ android:id="@+id/smartpgp_authority_alias_edit_text"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:layout_gravity="center_horizontal"
|
|
+ android:ems="10"
|
|
+ android:hint="@string/label_enter_smartpgp_authority_name"
|
|
+ android:imeOptions="actionDone"
|
|
+ android:inputType="textUri" />
|
|
+
|
|
+ </android.support.design.widget.TextInputLayout>
|
|
+
|
|
+ <Button
|
|
+ android:id="@+id/smartpgp_authority_filename"
|
|
+ android:paddingLeft="8dp"
|
|
+ android:paddingRight="8dp"
|
|
+ android:textAppearance="?android:attr/textAppearanceMedium"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:minHeight="?android:attr/listPreferredItemHeight"
|
|
+ android:layout_gravity="center"
|
|
+ android:text="@string/btn_add_files"
|
|
+ style="?android:attr/borderlessButtonStyle"
|
|
+ android:drawableLeft="@drawable/ic_folder_grey_24dp"
|
|
+ android:drawablePadding="16dp"
|
|
+ android:gravity="left|center_vertical" />
|
|
+
|
|
+</LinearLayout>
|
|
\ 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 0000000..1451384
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/res/layout/settings_smartpgp_authority_fragment.xml
|
|
@@ -0,0 +1,7 @@
|
|
+<?xml version="1.0" encoding="utf-8"?>
|
|
+
|
|
+<android.support.v7.widget.RecyclerView
|
|
+ xmlns:android="http://schemas.android.com/apk/res/android"
|
|
+ android:id="@+id/smartpgp_authority_recycler_view"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="match_parent" />
|
|
\ 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 0000000..9debd0a
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/res/layout/settings_smartpgp_authority_item.xml
|
|
@@ -0,0 +1,27 @@
|
|
+<?xml version="1.0" encoding="utf-8"?>
|
|
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
+ android:id="@+id/outer_layout"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:minHeight="?listPreferredItemHeight">
|
|
+
|
|
+ <LinearLayout
|
|
+ android:id="@+id/smartpgp_authority_layout"
|
|
+ android:padding="6sp"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:layout_centerVertical="true"
|
|
+ android:orientation="vertical">
|
|
+
|
|
+ <TextView
|
|
+ android:id="@+id/smartpgp_authority_tv"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:textAppearance="?android:attr/textAppearanceMedium" />
|
|
+ </LinearLayout>
|
|
+
|
|
+ <View
|
|
+ android:layout_alignParentBottom="true"
|
|
+ style="@style/Divider"/>
|
|
+
|
|
+</RelativeLayout>
|
|
\ 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 0000000..5eb8bab
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/res/layout/smartpgp_authorities_preference.xml
|
|
@@ -0,0 +1,17 @@
|
|
+<?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">
|
|
+
|
|
+ <include
|
|
+ android:id="@+id/toolbar_include"
|
|
+ layout="@layout/toolbar_standalone" />
|
|
+
|
|
+ <FrameLayout
|
|
+ android:id="@+id/smartpgp_authorities_settings_fragment_container"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="match_parent"
|
|
+ android:orientation="vertical" />
|
|
+
|
|
+</LinearLayout>
|
|
\ 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 0000000..6c2e4c7
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/res/menu/smartpgp_authority_pref_menu.xml
|
|
@@ -0,0 +1,10 @@
|
|
+<?xml version="1.0" encoding="utf-8"?>
|
|
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
+ xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
+
|
|
+ <item
|
|
+ android:id="@+id/menu_add_smartpgp_authority"
|
|
+ android:title="@string/menu_search"
|
|
+ android:icon="@drawable/ic_add_white_24dp"
|
|
+ app:showAsAction="always" />
|
|
+</menu>
|
|
\ 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 770d97c..1d2ced5 100644
|
|
--- a/OpenKeychain/src/main/res/values/strings.xml
|
|
+++ b/OpenKeychain/src/main/res/values/strings.xml
|
|
@@ -19,6 +19,7 @@
|
|
<string name="title_preferences">"Settings"</string>
|
|
<string name="title_api_registered_apps">"Apps"</string>
|
|
<string name="title_key_server_preference">"OpenPGP keyservers"</string>
|
|
+ <string name="title_smartpgp_authorities_preference">"SmartPGP authorities"</string>
|
|
<string name="title_cache_ttl_preference">"Customize 'Remember' choices"</string>
|
|
<string name="title_change_passphrase">"Change Password"</string>
|
|
<string name="title_share_fingerprint_with">"Share fingerprint with…"</string>
|
|
@@ -182,16 +183,22 @@
|
|
<string name="label_keyservers_title">"Keyservers"</string>
|
|
<string name="label_keyserver_settings_hint">"Drag to change order, tap to edit/delete"</string>
|
|
<string name="label_selected_keyserver_title">"Selected keyserver"</string>
|
|
+ <string name="label_selected_smartpgp_authority_title">"Selected authority"</string>
|
|
<string name="label_preferred">"preferred"</string>
|
|
<string name="label_enable_compression">"Enable compression"</string>
|
|
<string name="label_encrypt_filenames">"Encrypt filenames"</string>
|
|
<string name="label_hidden_recipients">"Hide recipients"</string>
|
|
+ <string name="label_smartpgp_verify">SmartPGP verify certificate</string>
|
|
+ <string name="label_smartpgp_verify_summary">"Validate tokens certificates against a set of trusted certification authorities"</string>
|
|
+ <string name="label_smartpgp_roots">SmartPGP trusted authorities</string>
|
|
|
|
<string name="label_verify_keyserver_connection">"Test connection"</string>
|
|
<string name="label_only_trusted_keyserver">"Only trusted keyserver"</string>
|
|
<string name="label_enter_keyserver_url">"URL"</string>
|
|
<string name="label_keyserver_dialog_delete">"Delete keyserver"</string>
|
|
<string name="label_theme">"Theme"</string>
|
|
+ <string name="label_enter_smartpgp_authority_name">Name</string>
|
|
+ <string name="label_smartpgp_authority_dialog_delete">"Delete authority"</string>
|
|
|
|
<string name="pref_keyserver">"OpenPGP keyservers"</string>
|
|
<string name="pref_keyserver_summary">"Search keys on selected OpenPGP keyservers (HKP protocol)"</string>
|
|
@@ -267,6 +274,11 @@
|
|
<item quantity="other">"%d keyservers"</item>
|
|
</plurals>
|
|
|
|
+ <plurals name="n_authorities">
|
|
+ <item quantity="one">"%d authority"</item>
|
|
+ <item quantity="other">"%d authorities"</item>
|
|
+ </plurals>
|
|
+
|
|
<string name="secret_key">"Secret Key:"</string>
|
|
|
|
<!-- choice -->
|
|
@@ -821,6 +833,11 @@
|
|
<string name="keyserver_preference_deleted">"%s deleted"</string>
|
|
<string name="keyserver_preference_cannot_delete_last">"Cannot delete last keyserver. At least one is required!"</string>
|
|
|
|
+ <!-- Add/Edit SmartPGP authority -->
|
|
+ <string name="add_smartpgp_authority_dialog_title">"Add authority"</string>
|
|
+ <string name="show_smartpgp_authority_dialog_title">"Edit authority"</string>
|
|
+ <string name="smartpgp_authority_preference_deleted">"%s deleted"</string>
|
|
+
|
|
<!-- Navigation Drawer -->
|
|
<string name="nav_keys">"Keys"</string>
|
|
<string name="nav_encrypt_decrypt">"Encrypt/Decrypt"</string>
|
|
diff --git a/OpenKeychain/src/main/res/xml/experimental_preferences.xml b/OpenKeychain/src/main/res/xml/experimental_preferences.xml
|
|
index 9312997..8544487 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" />
|
|
|
|
+ <SwitchPreference
|
|
+ android:defaultValue="false"
|
|
+ android:key="smartpgp_authorities_pref"
|
|
+ android:summary="@string/label_smartpgp_verify_summary"
|
|
+ android:title="@string/label_smartpgp_verify" />
|
|
+ <PreferenceScreen
|
|
+ android:dependency="smartpgp_authorities_pref"
|
|
+ android:key="smartpgp_authorities"
|
|
+ android:title="@string/label_smartpgp_roots" />
|
|
+
|
|
</PreferenceScreen>
|
|
--
|
|
2.1.4
|
|
|
|
|