diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 0ab1e6aff..b80e21cb7 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -97,7 +97,7 @@ android:theme="@style/Theme.Keychain.Light"> @@ -105,7 +105,7 @@ - + @@ -483,6 +483,11 @@ android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_key_server_preference" android:windowSoftInputMode="stateHidden" /> + . */ -package org.sufficientlysecure.keychain.receiver; +package org.sufficientlysecure.keychain.network; import android.content.BroadcastReceiver; import android.content.ComponentName; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/OkHttpClientFactory.java similarity index 88% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/OkHttpClientFactory.java index c4044aa25..636e5ebae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/OkHttpClientFactory.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.util; +package org.sufficientlysecure.keychain.network; import java.io.IOException; import java.net.Proxy; @@ -47,7 +47,7 @@ public class OkHttpClientFactory { } public static OkHttpClient getClientPinnedIfAvailable(URL url, Proxy proxy) - throws IOException, TlsHelper.TlsHelperException { + throws IOException, TlsCertificatePinning.TlsCertificatePinningException { OkHttpClient.Builder builder = new OkHttpClient.Builder(); // don't follow any redirects for keyservers, as discussed in the security audit @@ -66,8 +66,8 @@ public class OkHttpClientFactory { // If a pinned cert is available, use it! // NOTE: this fails gracefully back to "no pinning" if no cert is available. - if (url != null && TlsHelper.getPinnedSslSocketFactory(url) != null) { - builder.sslSocketFactory(TlsHelper.getPinnedSslSocketFactory(url)); + if (url != null && TlsCertificatePinning.getPinnedSslSocketFactory(url) != null) { + builder.sslSocketFactory(TlsCertificatePinning.getPinnedSslSocketFactory(url)); } return builder.build(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/OkHttpKeybaseClient.java similarity index 91% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/OkHttpKeybaseClient.java index 467e2493a..2d1de85ba 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/OkHttpKeybaseClient.java @@ -15,8 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.util; - +package org.sufficientlysecure.keychain.network; import com.textuality.keybase.lib.KeybaseUrlConnectionClient; @@ -24,6 +23,7 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; import java.net.Proxy; @@ -44,7 +44,7 @@ public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient { } else { client = OkHttpClientFactory.getSimpleClient(); } - } catch (TlsHelper.TlsHelperException e) { + } catch (TlsCertificatePinning.TlsCertificatePinningException e) { Log.e(Constants.TAG, "TlsHelper failed", e); throw new IOException("TlsHelper failed"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/TlsCertificatePinning.java similarity index 89% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/TlsCertificatePinning.java index fe62eff55..668b472fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/TlsCertificatePinning.java @@ -15,11 +15,12 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.util; +package org.sufficientlysecure.keychain.network; import android.content.res.AssetManager; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -40,7 +41,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; -public class TlsHelper { +public class TlsCertificatePinning { private static Map sPinnedCertificates = new HashMap<>(); @@ -70,10 +71,10 @@ public class TlsHelper { * Use pinned certificate for OkHttpClient if we have one. * * @return true, if certificate is available, false if not - * @throws TlsHelperException + * @throws TlsCertificatePinningException * @throws IOException */ - public static SSLSocketFactory getPinnedSslSocketFactory(URL url) throws TlsHelperException, IOException { + public static SSLSocketFactory getPinnedSslSocketFactory(URL url) throws TlsCertificatePinningException, IOException { if (url.getProtocol().equals("https")) { // use certificate PIN from assets if we have one for (String host : sPinnedCertificates.keySet()) { @@ -92,11 +93,11 @@ public class TlsHelper { * to URLs with passed certificate. * * @param certificate certificate to pin - * @throws TlsHelperException + * @throws TlsCertificatePinningException * @throws IOException */ private static SSLSocketFactory pinCertificate(byte[] certificate) - throws TlsHelperException, IOException { + throws TlsCertificatePinningException, IOException { // We don't use OkHttp's CertificatePinner since it can not be used to pin self-signed // certificate if such certificate is not accepted by TrustManager. // (Refer to note at end of description: @@ -124,12 +125,12 @@ public class TlsHelper { return context.getSocketFactory(); } catch (CertificateException | KeyStoreException | KeyManagementException | NoSuchAlgorithmException e) { - throw new TlsHelperException(e); + throw new TlsCertificatePinningException(e); } } - public static class TlsHelperException extends Exception { - public TlsHelperException(Exception e) { + public static class TlsCertificatePinningException extends Exception { + TlsCertificatePinningException(Exception e) { super(e); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/orbot/OrbotHelper.java similarity index 99% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/orbot/OrbotHelper.java index f3aabe3f0..0782e7411 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/orbot/OrbotHelper.java @@ -47,7 +47,7 @@ ***** */ -package org.sufficientlysecure.keychain.util.orbot; +package org.sufficientlysecure.keychain.network.orbot; import android.app.Activity; import android.app.ProgressDialog; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotStatusReceiver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/orbot/OrbotStatusReceiver.java similarity index 98% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotStatusReceiver.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/orbot/OrbotStatusReceiver.java index 052f867c5..d91ba8e52 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/orbot/OrbotStatusReceiver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/orbot/OrbotStatusReceiver.java @@ -13,7 +13,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.util.orbot; +package org.sufficientlysecure.keychain.network.orbot; import android.content.BroadcastReceiver; import android.content.Context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java index 3c2ed142a..2d38533e4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -50,10 +50,9 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ProgressScaler; -import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import java.io.IOException; -import java.net.Proxy; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.Iterator; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java index 50a67c627..2bcb84592 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java @@ -40,9 +40,9 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.util.OkHttpKeybaseClient; +import org.sufficientlysecure.keychain.network.OkHttpKeybaseClient; import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import java.io.ByteArrayInputStream; import java.io.InputStream; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java index 490bbdfb7..9e60f22e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java @@ -45,7 +45,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 7a2286fdc..1164d3ff6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -565,7 +565,8 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT(LogLevel.ERROR, R.string.msg_mf_error_duplicate_keytocard_for_slot), MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD(LogLevel.ERROR, R.string.msg_mf_error_invalid_flags_for_keytocard), MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_algo), - MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_size), + MSG_MF_ERROR_BAD_SECURITY_TOKEN_RSA_KEY_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_size), + MSG_MF_ERROR_BAD_SECURITY_TOKEN_CURVE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_curve), MSG_MF_ERROR_BAD_SECURITY_TOKEN_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_stripped), MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master), MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java index 177fe1562..a18da2601 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java @@ -18,14 +18,27 @@ package org.sufficientlysecure.keychain.pgp; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import org.bouncycastle.bcpg.ECDHPublicBCPGKey; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.bouncycastle.openpgp.operator.RFC6637Utils; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter; import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; +import java.io.IOException; +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; @@ -189,4 +202,62 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { return !isRevoked() && !isExpired(); } + // For use only in card export; returns the public key. + public ECPublicKey getSecurityTokenECPublicKey() + throws PgpGeneralException { + JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + PublicKey retVal; + try { + retVal = keyConverter.getPublicKey(mPublicKey); + } catch (PGPException e) { + throw new PgpGeneralException("Error converting public key!", e); + } + + return (ECPublicKey) retVal; + } + + public ASN1ObjectIdentifier getSecurityTokenHashAlgorithm() + throws PGPException { + if (!isEC()) { + throw new PGPException("Key encryption OID is valid only for EC key!"); + } + + final ECDHPublicBCPGKey eck = (ECDHPublicBCPGKey)mPublicKey.getPublicKeyPacket().getKey(); + + switch (eck.getHashAlgorithm()) { + case HashAlgorithmTags.SHA256: + return NISTObjectIdentifiers.id_sha256; + case HashAlgorithmTags.SHA384: + return NISTObjectIdentifiers.id_sha384; + case HashAlgorithmTags.SHA512: + return NISTObjectIdentifiers.id_sha512; + default: + throw new PGPException("Invalid hash algorithm for EC key : " + eck.getHashAlgorithm()); + } + } + + public int getSecurityTokenSymmetricKeySize() + throws PGPException { + if (!isEC()) { + throw new PGPException("Key encryption OID is valid only for EC key!"); + } + + final ECDHPublicBCPGKey eck = (ECDHPublicBCPGKey)mPublicKey.getPublicKeyPacket().getKey(); + + switch (eck.getSymmetricKeyAlgorithm()) { + case SymmetricKeyAlgorithmTags.AES_128: + return 128; + case SymmetricKeyAlgorithmTags.AES_192: + return 192; + case SymmetricKeyAlgorithmTags.AES_256: + return 256; + default: + throw new PGPException("Invalid symmetric encryption algorithm for EC key : " + eck.getSymmetricKeyAlgorithm()); + } + } + + public byte[] createUserKeyingMaterial(KeyFingerPrintCalculator fingerPrintCalculator) + throws IOException, PGPException { + return RFC6637Utils.createUserKeyingMaterial(mPublicKey.getPublicKeyPacket(), fingerPrintCalculator); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index a02ff6685..29ea17e64 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp; import java.nio.ByteBuffer; import java.security.PrivateKey; +import java.security.interfaces.ECPrivateKey; import java.security.interfaces.RSAPrivateCrtKey; import java.util.Date; import java.util.HashMap; @@ -299,7 +300,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } // For use only in card export; returns the secret key in Chinese Remainder Theorem format. - public RSAPrivateCrtKey getCrtSecretKey() throws PgpGeneralException { + public RSAPrivateCrtKey getSecurityTokenRSASecretKey() throws PgpGeneralException { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { throw new PgpGeneralException("Cannot get secret key attributes while key is locked."); } @@ -319,6 +320,28 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { return (RSAPrivateCrtKey)retVal; } + // For use only in card export; returns the secret key. + public ECPrivateKey getSecurityTokenECSecretKey() + throws PgpGeneralException { + if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { + throw new PgpGeneralException("Cannot get secret key attributes while key is locked."); + } + + if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { + throw new PgpGeneralException("Cannot get secret key attributes of divert-to-card key."); + } + + JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + PrivateKey retVal; + try { + retVal = keyConverter.getPrivateKey(mPrivateKey); + } catch (PGPException e) { + throw new PgpGeneralException("Error converting private key! " + e.getMessage(), e); + } + + return (ECPrivateKey) retVal; + } + public byte[] getIv() { return mSecretKey.getIV(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 404e07230..cae4eca08 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -35,6 +35,10 @@ import java.util.Iterator; import java.util.Stack; import java.util.concurrent.atomic.AtomicBoolean; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.bcpg.ECDHPublicBCPGKey; +import org.bouncycastle.bcpg.ECDSAPublicBCPGKey; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.sig.Features; @@ -1645,20 +1649,44 @@ public class PgpKeyOperation { } private static boolean checkSecurityTokenCompatibility(PGPSecretKey key, OperationLog log, int indent) { - PGPPublicKey publicKey = key.getPublicKey(); - int algorithm = publicKey.getAlgorithm(); - if (algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT && - algorithm != PublicKeyAlgorithmTags.RSA_SIGN && - algorithm != PublicKeyAlgorithmTags.RSA_GENERAL) { - log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO, indent + 1); - return false; - } - // Key size must be 2048 - int keySize = publicKey.getBitStrength(); - if (keySize != 2048) { - log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE, indent + 1); - return false; + final PGPPublicKey publicKey = key.getPublicKey(); + ASN1ObjectIdentifier curve; + + switch (publicKey.getAlgorithm()) { + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + case PublicKeyAlgorithmTags.RSA_SIGN: + case PublicKeyAlgorithmTags.RSA_GENERAL: + // Key size must be at least 2048 since OpenPGP card specification 3.x + if (publicKey.getBitStrength() < 2048) { + log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_RSA_KEY_SIZE, indent + 1); + return false; + } + break; + + case PublicKeyAlgorithmTags.ECDH: + curve = ((ECDHPublicBCPGKey)(publicKey.getPublicKeyPacket().getKey())).getCurveOID(); + if (!curve.equals(NISTNamedCurves.getOID("P-256")) && + !curve.equals(NISTNamedCurves.getOID("P-384")) && + !curve.equals(NISTNamedCurves.getOID("P-521"))) { + log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_CURVE, indent + 1); + return false; + } + break; + + case PublicKeyAlgorithmTags.ECDSA: + curve = ((ECDSAPublicBCPGKey)(publicKey.getPublicKeyPacket().getKey())).getCurveOID(); + if (!curve.equals(NISTNamedCurves.getOID("P-256")) && + !curve.equals(NISTNamedCurves.getOID("P-384")) && + !curve.equals(NISTNamedCurves.getOID("P-521"))) { + log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_CURVE, indent + 1); + return false; + } + break; + + default: + log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO, indent + 1); + return false; } // Secret key parts must be available diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java new file mode 100644 index 000000000..2e77b97ae --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * Copyright (C) 2016-2017 Arnaud Fontaine + * + * 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.securitytoken; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.math.ec.ECCurve; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; + +// 4.3.3.6 Algorithm Attributes +public class ECKeyFormat extends KeyFormat { + + private final ECAlgorithmFormat mECAlgorithmFormat; + private final ASN1ObjectIdentifier mECCurveOID; + + public ECKeyFormat(final ASN1ObjectIdentifier ecCurveOid, + final ECAlgorithmFormat ecAlgorithmFormat) { + super(KeyFormatType.ECKeyFormatType); + mECAlgorithmFormat = ecAlgorithmFormat; + mECCurveOID = ecCurveOid; + } + + public ECKeyFormat.ECAlgorithmFormat getAlgorithmFormat() { + return mECAlgorithmFormat; + } + + public ASN1ObjectIdentifier getCurveOID() { + return mECCurveOID; + } + + public enum ECAlgorithmFormat { + ECDH((byte) 18, true, false), + ECDH_WITH_PUBKEY((byte) 18, true, true), + ECDSA((byte) 19, false, false), + ECDSA_WITH_PUBKEY((byte) 19, false, true); + + private final byte mValue; + private final boolean mIsECDH; + private final boolean mWithPubkey; + + ECAlgorithmFormat(final byte value, final boolean isECDH, final boolean withPubkey) { + mValue = value; + mIsECDH = isECDH; + mWithPubkey = withPubkey; + } + + public static ECKeyFormat.ECAlgorithmFormat from(final byte bFirst, final byte bLast) { + for (ECKeyFormat.ECAlgorithmFormat format : values()) { + if (format.mValue == bFirst && ((bLast == (byte) 0xff) == format.isWithPubkey())) { + return format; + } + } + return null; + } + + public final byte getValue() { + return mValue; + } + + public final boolean isECDH() { + return mIsECDH; + } + + public final boolean isWithPubkey() { + return mWithPubkey; + } + } + + public void addToSaveKeyringParcel(SaveKeyringParcel keyring, int keyFlags) { + final X9ECParameters params = NISTNamedCurves.getByOID(mECCurveOID); + final ECCurve curve = params.getCurve(); + + SaveKeyringParcel.Algorithm algo = SaveKeyringParcel.Algorithm.ECDSA; + if (((keyFlags & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS) + || ((keyFlags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE)) { + algo = SaveKeyringParcel.Algorithm.ECDH; + } + + SaveKeyringParcel.Curve scurve; + if (mECCurveOID.equals(NISTNamedCurves.getOID("P-256"))) { + scurve = SaveKeyringParcel.Curve.NIST_P256; + } else if (mECCurveOID.equals(NISTNamedCurves.getOID("P-384"))) { + scurve = SaveKeyringParcel.Curve.NIST_P384; + } else if (mECCurveOID.equals(NISTNamedCurves.getOID("P-521"))) { + scurve = SaveKeyringParcel.Curve.NIST_P521; + } else { + throw new IllegalArgumentException("Unsupported curve " + mECCurveOID); + } + + keyring.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(algo, + curve.getFieldSize(), scurve, keyFlags, 0L)); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Iso7816TLV.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Iso7816TLV.java similarity index 99% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Iso7816TLV.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Iso7816TLV.java index c0483ad04..ec2bc8041 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Iso7816TLV.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Iso7816TLV.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package org.sufficientlysecure.keychain.util; +package org.sufficientlysecure.keychain.securitytoken; import java.io.IOException; import java.nio.ByteBuffer; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java index 3b2e93f0a..6fdd91daa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java @@ -17,71 +17,83 @@ package org.sufficientlysecure.keychain.securitytoken; -// 4.3.3.6 Algorithm Attributes -public class KeyFormat { - private int mAlgorithmId; - private int mModulusLength; - private int mExponentLength; - private AlgorithmFormat mAlgorithmFormat; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.CreateSecurityTokenAlgorithmFragment; - public KeyFormat(byte[] bytes) { - mAlgorithmId = bytes[0]; - mModulusLength = bytes[1] << 8 | bytes[2]; - mExponentLength = bytes[3] << 8 | bytes[4]; - mAlgorithmFormat = AlgorithmFormat.from(bytes[5]); +public abstract class KeyFormat { - if (mAlgorithmId != 1) { // RSA - throw new IllegalArgumentException("Unsupported Algorithm id " + mAlgorithmId); - } + public enum KeyFormatType { + RSAKeyFormatType, + ECKeyFormatType + }; + + private final KeyFormatType mKeyFormatType; + + public KeyFormat(final KeyFormatType keyFormatType) { + mKeyFormatType = keyFormatType; } - public int getAlgorithmId() { - return mAlgorithmId; + public final KeyFormatType keyFormatType() { + return mKeyFormatType; } - public int getModulusLength() { - return mModulusLength; - } - - public int getExponentLength() { - return mExponentLength; - } - - public AlgorithmFormat getAlgorithmFormat() { - return mAlgorithmFormat; - } - - public enum AlgorithmFormat { - STANDARD(0, false, false), - STANDARD_WITH_MODULUS(1, false, true), - CRT(2, true, false), - CRT_WITH_MODULUS(3, true, true); - - private int mValue; - private boolean mIncludeModulus; - private boolean mIncludeCrt; - - AlgorithmFormat(int value, boolean includeCrt, boolean includeModulus) { - mValue = value; - mIncludeModulus = includeModulus; - mIncludeCrt = includeCrt; - } - - public static AlgorithmFormat from(byte b) { - for (AlgorithmFormat format : values()) { - if (format.mValue == b) { - return format; + public static KeyFormat fromBytes(byte[] bytes) { + switch (bytes[0]) { + case PublicKeyAlgorithmTags.RSA_GENERAL: + if (bytes.length < 6) { + throw new IllegalArgumentException("Bad length for RSA attributes"); } - } - return null; - } + return new RSAKeyFormat(bytes[1] << 8 | bytes[2], + bytes[3] << 8 | bytes[4], + RSAKeyFormat.RSAAlgorithmFormat.from(bytes[5])); - public boolean isIncludeModulus() { - return mIncludeModulus; - } + case PublicKeyAlgorithmTags.ECDH: + case PublicKeyAlgorithmTags.ECDSA: + if (bytes.length < 2) { + throw new IllegalArgumentException("Bad length for RSA attributes"); + } + int len = bytes.length - 1; + if (bytes[bytes.length - 1] == (byte)0xff) { + len -= 1; + } + final byte[] boid = new byte[2 + len]; + boid[0] = (byte)0x06; + boid[1] = (byte)len; + System.arraycopy(bytes, 1, boid, 2, len); + final ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(boid); + return new ECKeyFormat(oid, ECKeyFormat.ECAlgorithmFormat.from(bytes[0], bytes[bytes.length - 1])); - public boolean isIncludeCrt() { - return mIncludeCrt; + default: + throw new IllegalArgumentException("Unsupported Algorithm id " + bytes[0]); } } + + public static KeyFormat fromCreationKeyType(CreateSecurityTokenAlgorithmFragment.SupportedKeyType t, boolean forEncryption) { + final int elen = 17; //65537 + final ECKeyFormat.ECAlgorithmFormat kf = + forEncryption ? ECKeyFormat.ECAlgorithmFormat.ECDH_WITH_PUBKEY : ECKeyFormat.ECAlgorithmFormat.ECDSA_WITH_PUBKEY; + + switch (t) { + case RSA_2048: + return new RSAKeyFormat(2048, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS); + case RSA_3072: + return new RSAKeyFormat(3072, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS); + case RSA_4096: + return new RSAKeyFormat(4096, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS); + case ECC_P256: + return new ECKeyFormat(NISTNamedCurves.getOID("P-256"), kf); + case ECC_P384: + return new ECKeyFormat(NISTNamedCurves.getOID("P-384"), kf); + case ECC_P521: + return new ECKeyFormat(NISTNamedCurves.getOID("P-521"), kf); + } + + throw new IllegalArgumentException("Unsupported Algorithm id " + t); + } + + public abstract void addToSaveKeyringParcel(SaveKeyringParcel keyring, int keyFlags); + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java index 7a87a0a97..71fd3f022 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java @@ -17,8 +17,6 @@ package org.sufficientlysecure.keychain.securitytoken; -import org.sufficientlysecure.keychain.util.Iso7816TLV; - import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -36,15 +34,19 @@ public class OpenPgpCapabilities { private boolean mAttriburesChangable; private boolean mHasKeyImport; - private byte mSMAlgo; + private int mSMAESKeySize; private int mMaxCmdLen; private int mMaxRspLen; private Map mKeyFormats; public OpenPgpCapabilities(byte[] data) throws IOException { - Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true); mKeyFormats = new HashMap<>(); + updateWithData(data); + } + + public void updateWithData(byte[] data) throws IOException { + Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true); if (tlvs.length == 1 && tlvs[0].mT == 0x6E) { tlvs = ((Iso7816TLV.Iso7816CompositeTLV) tlvs[0]).mSubs; } @@ -64,13 +66,13 @@ public class OpenPgpCapabilities { parseExtendedCaps(tlv.mV); break; case 0xC1: - mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV)); break; case 0xC2: - mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV)); break; case 0xC3: - mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV)); break; case 0xC4: mPw1ValidForMultipleSignatures = tlv.mV[0] == 1; @@ -86,13 +88,13 @@ public class OpenPgpCapabilities { parseExtendedCaps(tlv.mV); break; case 0xC1: - mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV)); break; case 0xC2: - mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV)); break; case 0xC3: - mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV)); break; case 0xC4: mPw1ValidForMultipleSignatures = tlv.mV[0] == 1; @@ -104,9 +106,9 @@ public class OpenPgpCapabilities { private void parseExtendedCaps(byte[] v) { mHasSM = (v[0] & MASK_SM) != 0; mHasKeyImport = (v[0] & MASK_KEY_IMPORT) != 0; - mAttriburesChangable =(v[0] & MASK_ATTRIBUTES_CHANGABLE) != 0; + mAttriburesChangable = (v[0] & MASK_ATTRIBUTES_CHANGABLE) != 0; - mSMAlgo = v[1]; + mSMAESKeySize = (v[1] == 1) ? 16 : 32; mMaxCmdLen = (v[6] << 8) + v[7]; mMaxRspLen = (v[8] << 8) + v[9]; @@ -128,7 +130,7 @@ public class OpenPgpCapabilities { return mHasSM; } - public boolean isAttriburesChangable() { + public boolean isAttributesChangable() { return mAttriburesChangable; } @@ -136,8 +138,8 @@ public class OpenPgpCapabilities { return mHasKeyImport; } - public byte getSMAlgo() { - return mSMAlgo; + public int getSMAESKeySize() { + return mSMAESKeySize; } public int getMaxCmdLen() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java new file mode 100644 index 000000000..5087ec99b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.securitytoken; + +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; + +// 4.3.3.6 Algorithm Attributes +public class RSAKeyFormat extends KeyFormat { + private int mModulusLength; + private int mExponentLength; + private RSAAlgorithmFormat mRSAAlgorithmFormat; + + public RSAKeyFormat(int modulusLength, + int exponentLength, + RSAAlgorithmFormat rsaAlgorithmFormat) { + super(KeyFormatType.RSAKeyFormatType); + mModulusLength = modulusLength; + mExponentLength = exponentLength; + mRSAAlgorithmFormat = rsaAlgorithmFormat; + } + + public int getModulusLength() { + return mModulusLength; + } + + public int getExponentLength() { + return mExponentLength; + } + + public RSAAlgorithmFormat getAlgorithmFormat() { + return mRSAAlgorithmFormat; + } + + public enum RSAAlgorithmFormat { + STANDARD((byte) 0, false, false), + STANDARD_WITH_MODULUS((byte) 1, false, true), + CRT((byte) 2, true, false), + CRT_WITH_MODULUS((byte) 3, true, true); + + private byte mValue; + private boolean mIncludeModulus; + private boolean mIncludeCrt; + + RSAAlgorithmFormat(byte value, boolean includeCrt, boolean includeModulus) { + mValue = value; + mIncludeModulus = includeModulus; + mIncludeCrt = includeCrt; + } + + public static RSAAlgorithmFormat from(byte b) { + for (RSAAlgorithmFormat format : values()) { + if (format.mValue == b) { + return format; + } + } + return null; + } + + public byte getValue() { + return mValue; + } + + public boolean isIncludeModulus() { + return mIncludeModulus; + } + + public boolean isIncludeCrt() { + return mIncludeCrt; + } + } + + public void addToSaveKeyringParcel(SaveKeyringParcel keyring, int keyFlags) { + keyring.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(SaveKeyringParcel.Algorithm.RSA, + mModulusLength, null, keyFlags, 0L)); + } +} 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 000000000..6a3aa1e8c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SCP11bSecureMessaging.java @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2016-2017 Arnaud Fontaine + * + * 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.securitytoken; + +import android.content.Context; +import android.support.annotation.NonNull; + +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.sufficientlysecure.keychain.ui.SettingsSmartPGPAuthoritiesActivity; +import org.sufficientlysecure.keychain.util.Preferences; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +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.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.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 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.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 algoParams = AlgorithmParameters.getInstance(SCP11B_KEY_AGREEMENT_KEY_ALGO, PROVIDER); + + algoParams.init(new ECGenParameterSpec(ECNamedCurveTable.getName(kf.getCurveOID()))); + + return algoParams.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 cert = certFactory.generateCertificate(new ByteArrayInputStream(data)); + if (!(cert instanceof X509Certificate)) { + throw new IOException("invalid card certificate"); + } + final X509Certificate cardCert = (X509Certificate) cert; + + final PublicKey pk = cardCert.getPublicKey(); + if (!(pk instanceof ECPublicKey)) { + throw new IOException("invalid card public key"); + } + final ECPublicKey cardPk = (ECPublicKey) pk; + 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 000000000..4107a3e18 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecureMessaging.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016-2017 Arnaud Fontaine + * + * 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.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..9c2cd7f38 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecureMessagingException.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2016-2017 Arnaud Fontaine + * + * 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.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 878bf9df3..ceae990e4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2016 Nikita Mikhailov - * Copyright (C) 2013-2015 Dominik Schürmann + * Copyright (C) 2013-2017 Dominik Schürmann * Copyright (C) 2015 Vincent Breitmoser * Copyright (C) 2013-2014 Signe Rüsch * Copyright (C) 2013-2014 Philipp Jakubeit @@ -21,26 +21,48 @@ package org.sufficientlysecure.keychain.securitytoken; +import android.content.Context; import android.support.annotation.NonNull; +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1OutputStream; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.jcajce.util.MessageDigestUtils; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.operator.PGPPad; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; import javax.smartcardio.CommandAPDU; import javax.smartcardio.ResponseAPDU; + import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; -import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; -import org.sufficientlysecure.keychain.util.SecurityTokenUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateCrtKey; /** @@ -53,9 +75,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; @@ -64,9 +86,13 @@ public class SecurityTokenHelper { private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + private final JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator(); + private Transport mTransport; private CardCapabilities mCardCapabilities; private OpenPgpCapabilities mOpenPgpCapabilities; + private SecureMessaging mSecureMessaging; private Passphrase mPin; private Passphrase mAdminPin; @@ -74,7 +100,13 @@ public class SecurityTokenHelper { private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? private boolean mPw3Validated; - protected SecurityTokenHelper() { + private SecurityTokenHelper() { + } + + public static double parseOpenPgpVersion(final byte[] aid) { + float minv = aid[7]; + while (minv > 0) minv /= 10.0; + return aid[6] + minv; } public static SecurityTokenHelper getInstance() { @@ -136,12 +168,14 @@ public class SecurityTokenHelper { private boolean isSlotEmpty(KeyType keyType) throws IOException { // Note: special case: This should not happen, but happens with // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true - if (getKeyFingerprint(keyType) == null) return true; + if (getKeyFingerprint(keyType) == null) { + return true; + } return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); } - public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { + private boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { return java.util.Arrays.equals(getKeyFingerprint(keyType), fingerprint); } @@ -150,7 +184,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(); @@ -171,6 +205,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); + } + } + } /** @@ -218,17 +262,62 @@ public class SecurityTokenHelper { * Call DECIPHER command * * @param encryptedSessionKey the encoded session key + * @param publicKey * @return the decoded session key */ - public byte[] decryptSessionKey(@NonNull byte[] encryptedSessionKey) throws IOException { + public byte[] decryptSessionKey(@NonNull byte[] encryptedSessionKey, + CanonicalizedPublicKey publicKey) + throws IOException { + final KeyFormat kf = mOpenPgpCapabilities.getFormatForKeyType(KeyType.ENCRYPT); + if (!mPw1ValidatedForDecrypt) { verifyPin(0x82); // (Verify PW1 with mode 82 for decryption) } - // Transmit - byte[] data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length); - if (data[0] != 0) { - data = Arrays.prepend(data, (byte) 0x00); + byte[] data; + int pLen = 0; + + X9ECParameters x9Params; + + switch (kf.keyFormatType()) { + case RSAKeyFormatType: + data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length); + if (data[0] != 0) { + data = Arrays.prepend(data, (byte) 0x00); + } + break; + + case ECKeyFormatType: + pLen = ((((encryptedSessionKey[0] & 0xff) << 8) + (encryptedSessionKey[1] & 0xff)) + 7) / 8; + data = new byte[pLen]; + + System.arraycopy(encryptedSessionKey, 2, data, 0, pLen); + + final ECKeyFormat eckf = (ECKeyFormat) kf; + x9Params = NISTNamedCurves.getByOID(eckf.getCurveOID()); + + final ECPoint p = x9Params.getCurve().decodePoint(data); + if (!p.isValid()) { + throw new CardException("Invalid EC point!"); + } + + data = p.getEncoded(false); + data = Arrays.concatenate( + Hex.decode("86"), + new byte[]{(byte) data.length}, + data); + data = Arrays.concatenate( + Hex.decode("7F49"), + new byte[]{(byte) data.length}, + data); + data = Arrays.concatenate( + Hex.decode("A6"), + new byte[]{(byte) data.length}, + data); + break; + + default: + throw new CardException("Unknown encryption key type!"); } CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x80, 0x86, data, MAX_APDU_NE_EXT); @@ -238,7 +327,60 @@ public class SecurityTokenHelper { throw new CardException("Deciphering with Security token failed on receive", response.getSW()); } - return response.getData(); + switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.ENCRYPT).keyFormatType()) { + case RSAKeyFormatType: + return response.getData(); + + /* From 3.x OpenPGP card specification : + In case of ECDH the card supports a partial decrypt only. + With its own private key and the given public key the card calculates a shared secret + in compliance with the Elliptic Curve Key Agreement Scheme from Diffie-Hellman. + The shared secret is returned in the response, all other calculation for deciphering + are done outside of the card. + + The shared secret obtained is a KEK (Key Encryption Key) that is used to wrap the + session key. + + From rfc6637#section-13 : + This document explicitly discourages the use of algorithms other than AES as a KEK algorithm. + */ + case ECKeyFormatType: + data = response.getData(); + + final byte[] keyEnc = new byte[encryptedSessionKey[pLen + 2]]; + + System.arraycopy(encryptedSessionKey, 2 + pLen + 1, keyEnc, 0, keyEnc.length); + + try { + final MessageDigest kdf = MessageDigest.getInstance(MessageDigestUtils.getDigestName(publicKey.getSecurityTokenHashAlgorithm())); + + kdf.update(new byte[]{(byte) 0, (byte) 0, (byte) 0, (byte) 1}); + kdf.update(data); + kdf.update(publicKey.createUserKeyingMaterial(fingerprintCalculator)); + + final byte[] kek = kdf.digest(); + final Cipher c = Cipher.getInstance("AESWrap"); + + c.init(Cipher.UNWRAP_MODE, new SecretKeySpec(kek, 0, publicKey.getSecurityTokenSymmetricKeySize() / 8, "AES")); + + final Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY); + + Arrays.fill(kek, (byte) 0); + + return PGPPad.unpadSessionData(paddedSessionKey.getEncoded()); + } catch (NoSuchAlgorithmException e) { + throw new CardException("Unknown digest/encryption algorithm!"); + } catch (NoSuchPaddingException e) { + throw new CardException("Unknown padding algorithm!"); + } catch (PGPException e) { + throw new CardException(e.getMessage()); + } catch (InvalidKeyException e) { + throw new CardException("Invalid KEK!"); + } + + default: + throw new CardException("Unknown encryption key type!"); + } } /** @@ -300,6 +442,36 @@ public class SecurityTokenHelper { } } + + private void setKeyAttributes(final KeyType slot, final CanonicalizedSecretKey secretKey) + throws IOException { + + if (mOpenPgpCapabilities.isAttributesChangable()) { + int tag; + + if (slot == KeyType.SIGN) { + tag = 0xC1; + } else if (slot == KeyType.ENCRYPT) { + tag = 0xC2; + } else if (slot == KeyType.AUTH) { + tag = 0xC3; + } else { + throw new IOException("Unknown key for card."); + } + + try { + + putData(tag, SecurityTokenUtils.attributesFromSecretKey(slot, secretKey)); + + mOpenPgpCapabilities.updateWithData(getData(0x00, tag)); + + } catch (PgpGeneralException e) { + throw new IOException("Key algorithm not supported by the security token."); + } + + } + } + /** * Puts a key on the token in the given slot. * @@ -311,33 +483,58 @@ public class SecurityTokenHelper { private void putKey(KeyType slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { RSAPrivateCrtKey crtSecretKey; - try { - secretKey.unlock(passphrase); - crtSecretKey = secretKey.getCrtSecretKey(); - } catch (PgpGeneralException e) { - throw new IOException(e.getMessage()); - } - - // Shouldn't happen; the UI should block the user from getting an incompatible key this far. - if (crtSecretKey.getModulus().bitLength() > 2048) { - throw new IOException("Key too large to export to Security Token."); - } - - // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537. - if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) { - throw new IOException("Invalid public exponent for smart Security Token."); - } + ECPrivateKey ecSecretKey; + ECPublicKey ecPublicKey; if (!mPw3Validated) { verifyPin(0x83); // (Verify PW3 with mode 83) } - // Now we're ready to communicate with the token. - byte[] bytes = SecurityTokenUtils.createPrivKeyTemplate(crtSecretKey, slot, - mOpenPgpCapabilities.getFormatForKeyType(slot)); + byte[] keyBytes = null; - CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, bytes); + try { + secretKey.unlock(passphrase); + + setKeyAttributes(slot, secretKey); + + switch (mOpenPgpCapabilities.getFormatForKeyType(slot).keyFormatType()) { + case RSAKeyFormatType: + if (!secretKey.isRSA()) { + throw new IOException("Security Token not configured for RSA key."); + } + crtSecretKey = secretKey.getSecurityTokenRSASecretKey(); + + // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537. + if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) { + throw new IOException("Invalid public exponent for smart Security Token."); + } + + keyBytes = SecurityTokenUtils.createRSAPrivKeyTemplate(crtSecretKey, slot, + (RSAKeyFormat) (mOpenPgpCapabilities.getFormatForKeyType(slot))); + break; + + case ECKeyFormatType: + if (!secretKey.isEC()) { + throw new IOException("Security Token not configured for EC key."); + } + + secretKey.unlock(passphrase); + ecSecretKey = secretKey.getSecurityTokenECSecretKey(); + ecPublicKey = secretKey.getSecurityTokenECPublicKey(); + + keyBytes = SecurityTokenUtils.createECPrivKeyTemplate(ecSecretKey, ecPublicKey, slot, + (ECKeyFormat) (mOpenPgpCapabilities.getFormatForKeyType(slot))); + break; + + default: + throw new IOException("Key type unsupported by security token."); + } + } catch (PgpGeneralException e) { + throw new IOException(e.getMessage()); + } + + CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, keyBytes); ResponseAPDU response = communicate(apdu); if (response.getSW() != APDU_SW_SUCCESS) { @@ -423,10 +620,10 @@ public class SecurityTokenHelper { } dsi = Arrays.concatenate(Hex.decode( "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes - + "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes - + "0605" + "2B0E03021A" // OID of SHA1 - + "0500" // TLV coding of ZERO - + "0414"), hash); // 0x14 are 20 hash bytes + + "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes + + "0605" + "2B0E03021A" // OID of SHA1 + + "0500" // TLV coding of ZERO + + "0414"), hash); // 0x14 are 20 hash bytes break; case HashAlgorithmTags.RIPEMD160: if (hash.length != 20) { @@ -462,8 +659,21 @@ public class SecurityTokenHelper { throw new IOException("Not supported hash algo!"); } + byte[] data; + + switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.SIGN).keyFormatType()) { + case RSAKeyFormatType: + data = dsi; + break; + case ECKeyFormatType: + data = hash; + break; + default: + throw new IOException("Not supported key type!"); + } + // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37) - CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, dsi, MAX_APDU_NE_EXT); + CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, data, MAX_APDU_NE_EXT); ResponseAPDU response = communicate(command); if (response.getSW() != APDU_SW_SUCCESS) { @@ -477,9 +687,30 @@ public class SecurityTokenHelper { byte[] signature = response.getData(); // Make sure the signature we received is actually the expected number of bytes long! - if (signature.length != 128 && signature.length != 256 - && signature.length != 384 && signature.length != 512) { - throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length); + switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.SIGN).keyFormatType()) { + case RSAKeyFormatType: + if (signature.length != 128 && signature.length != 256 + && signature.length != 384 && signature.length != 512) { + throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length); + } + break; + + case ECKeyFormatType: + if (signature.length % 2 != 0) { + throw new IOException("Bad signature length!"); + } + final byte[] br = new byte[signature.length / 2]; + final byte[] bs = new byte[signature.length / 2]; + for (int i = 0; i < br.length; ++i) { + br[i] = signature[i]; + bs[i] = signature[br.length + i]; + } + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ASN1OutputStream out = new ASN1OutputStream(baos); + out.writeObject(new DERSequence(new ASN1Encodable[]{new ASN1Integer(br), new ASN1Integer(bs)})); + out.flush(); + signature = baos.toByteArray(); + break; } return signature; @@ -490,11 +721,21 @@ public class SecurityTokenHelper { * Transceives APDU * Splits extended APDU into short APDUs and chains them if necessary * Performs GET RESPONSE command(ISO/IEC 7816-4 par.7.6.1) on retrieving if necessary + * * @param apdu short or extended APDU to transceive * @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; @@ -541,7 +782,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() { @@ -549,6 +801,7 @@ public class SecurityTokenHelper { } public void setTransport(Transport mTransport) { + clearSecureMessaging(); this.mTransport = mTransport; } @@ -628,6 +881,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 @@ -666,13 +922,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/util/SecurityTokenUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtils.java similarity index 52% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SecurityTokenUtils.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtils.java index 2f959f912..7fe625434 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SecurityTokenUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtils.java @@ -15,21 +15,63 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.util; +package org.sufficientlysecure.keychain.securitytoken; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.securitytoken.KeyFormat; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.securitytoken.ECKeyFormat; +import org.sufficientlysecure.keychain.securitytoken.RSAKeyFormat; import org.sufficientlysecure.keychain.securitytoken.KeyType; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateCrtKey; public class SecurityTokenUtils { - public static byte[] createPrivKeyTemplate(RSAPrivateCrtKey secretKey, KeyType slot, - KeyFormat format) throws IOException { + public static byte[] attributesFromSecretKey(final KeyType slot, final CanonicalizedSecretKey secretKey) throws IOException, PgpGeneralException { + if (secretKey.isRSA()) { + final int mModulusLength = secretKey.getBitStrength(); + final int mExponentLength = secretKey.getSecurityTokenRSASecretKey().getPublicExponent().bitLength(); + final byte[] attrs = new byte[6]; + int i = 0; + + attrs[i++] = (byte) 0x01; + attrs[i++] = (byte) ((mModulusLength >> 8) & 0xff); + attrs[i++] = (byte) (mModulusLength & 0xff); + attrs[i++] = (byte) ((mExponentLength >> 8) & 0xff); + attrs[i++] = (byte) (mExponentLength & 0xff); + attrs[i++] = RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS.getValue(); + + return attrs; + } else if (secretKey.isEC()) { + final byte[] oid = new ASN1ObjectIdentifier(secretKey.getCurveOid()).getEncoded(); + final byte[] attrs = new byte[1 + (oid.length - 2) + 1]; + + if (slot.equals(KeyType.SIGN)) + attrs[0] = ECKeyFormat.ECAlgorithmFormat.ECDSA_WITH_PUBKEY.getValue(); + else { + attrs[0] = ECKeyFormat.ECAlgorithmFormat.ECDH_WITH_PUBKEY.getValue(); + } + + System.arraycopy(oid, 2, attrs, 1, (oid.length - 2)); + + attrs[attrs.length - 1] = (byte) 0xff; + + return attrs; + } else { + throw new IOException("Unsupported key type"); + } + } + + + public static byte[] createRSAPrivKeyTemplate(RSAPrivateCrtKey secretKey, KeyType slot, + RSAKeyFormat format) throws IOException { ByteArrayOutputStream stream = new ByteArrayOutputStream(), template = new ByteArrayOutputStream(), data = new ByteArrayOutputStream(), @@ -86,6 +128,51 @@ public class SecurityTokenUtils { stream.write(encodeLength(data.size())); stream.write(data.toByteArray()); + // Result tlv + res.write(Hex.decode("4D")); + res.write(encodeLength(stream.size())); + res.write(stream.toByteArray()); + + return res.toByteArray(); + } + + public static byte[] createECPrivKeyTemplate(ECPrivateKey secretKey, ECPublicKey publicKey, KeyType slot, + ECKeyFormat format) throws IOException { + ByteArrayOutputStream stream = new ByteArrayOutputStream(), + template = new ByteArrayOutputStream(), + data = new ByteArrayOutputStream(), + res = new ByteArrayOutputStream(); + + final int csize = (int) Math.ceil(publicKey.getParams().getCurve().getField().getFieldSize() / 8.0); + + writeBits(data, secretKey.getS(), csize); + template.write(Hex.decode("92")); + template.write(encodeLength(data.size())); + + if (format.getAlgorithmFormat().isWithPubkey()) { + data.write(Hex.decode("04")); + writeBits(data, publicKey.getW().getAffineX(), csize); + writeBits(data, publicKey.getW().getAffineY(), csize); + template.write(Hex.decode("99")); + template.write(encodeLength(1 + 2 * csize)); + } + + // Bundle up + + // Ext header list data + // Control Reference Template to indicate the private key + stream.write(slot.getSlot()); + stream.write(0); + + // Cardholder private key template + stream.write(Hex.decode("7F48")); + stream.write(encodeLength(template.size())); + stream.write(template.toByteArray()); + + // Concatenation of key data as defined in DO 7F48 + stream.write(Hex.decode("5F48")); + stream.write(encodeLength(data.size())); + stream.write(data.toByteArray()); // Result tlv res.write(Hex.decode("4D")); @@ -132,20 +219,21 @@ public class SecurityTokenUtils { throw new IllegalArgumentException("width <= 0"); } - byte[] prime = value.toByteArray(); - int stripIdx = 0; - while (prime[stripIdx] == 0 && stripIdx + 1 < prime.length) { - stripIdx++; - } + final byte[] prime = value.toByteArray(); + int skip = 0; - if (prime.length - stripIdx > width) { + while ((skip < prime.length) && (prime[skip] == 0)) ++skip; + + if ((prime.length - skip) > width) { throw new IllegalArgumentException("not enough width to fit value: " - + prime.length + "/" + width); + + (prime.length - skip) + "/" + width); } - byte[] res = new byte[width]; - int empty = width - (prime.length - stripIdx); - System.arraycopy(prime, stripIdx, res, Math.max(0, empty), Math.min(prime.length, width)); + byte[] res = new byte[width]; + + System.arraycopy(prime, skip, + res, width - (prime.length - skip), + prime.length - skip); stream.write(res, 0, width); Arrays.fill(res, (byte) 0); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbConnectionDispatcher.java similarity index 97% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbConnectionDispatcher.java index 7a8e65ae4..9db35c790 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbConnectionDispatcher.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.util; +package org.sufficientlysecure.keychain.securitytoken; import android.app.Activity; import android.content.BroadcastReceiver; @@ -27,6 +27,7 @@ import android.hardware.usb.UsbManager; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; +import org.sufficientlysecure.keychain.util.Log; public class UsbConnectionDispatcher { private Activity mActivity; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java index 99b3ef7a6..e72df4d7c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java @@ -37,14 +37,14 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.receiver.NetworkReceiver; +import org.sufficientlysecure.keychain.network.NetworkReceiver; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import java.util.ArrayList; import java.util.GregorianCalendar; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index b0e922c8a..19cbb7660 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.securitytoken.KeyFormat; import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Passphrase; @@ -64,6 +65,9 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity { boolean mCreateSecurityToken; Passphrase mSecurityTokenPin; Passphrase mSecurityTokenAdminPin; + KeyFormat mSecurityTokenSign; + KeyFormat mSecurityTokenDec; + KeyFormat mSecurityTokenAuth; Fragment mCurrentFragment; @@ -97,6 +101,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity { mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE); mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME); mCreateSecurityToken = savedInstanceState.getBoolean(EXTRA_CREATE_SECURITY_TOKEN); + mSecurityTokenAid = savedInstanceState.getByteArray(EXTRA_SECURITY_TOKEN_AID); mSecurityTokenPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_PIN); mSecurityTokenAdminPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN); @@ -122,7 +127,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity { setTitle(R.string.title_import_keys); } else { - Fragment frag = CreateSecurityTokenBlankFragment.newInstance(); + Fragment frag = CreateSecurityTokenBlankFragment.newInstance(nfcAid); loadFragment(frag, FragAction.START); setTitle(R.string.title_manage_my_keys); } @@ -192,7 +197,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity { loadFragment(frag, FragAction.TO_RIGHT); } } else { - Fragment frag = CreateSecurityTokenBlankFragment.newInstance(); + Fragment frag = CreateSecurityTokenBlankFragment.newInstance(mSecurityTokenAid); loadFragment(frag, FragAction.TO_RIGHT); } } @@ -221,6 +226,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity { outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase); outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime); outState.putBoolean(EXTRA_CREATE_SECURITY_TOKEN, mCreateSecurityToken); + outState.putByteArray(EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); outState.putParcelable(EXTRA_SECURITY_TOKEN_PIN, mSecurityTokenPin); outState.putParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN, mSecurityTokenAdminPin); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index 0e94a1168..924a97df2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -41,6 +41,7 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.UploadResult; import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; @@ -283,22 +284,19 @@ public class CreateKeyFinalFragment extends Fragment { SaveKeyringParcel saveKeyringParcel = new SaveKeyringParcel(); if (createKeyActivity.mCreateSecurityToken) { - saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 2048, null, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER, 0L)); - saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 2048, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); - saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 2048, null, KeyFlags.AUTHENTICATION, 0L)); + if (createKeyActivity.mSecurityTokenSign == null) { + createKeyActivity.mSecurityTokenSign = Constants.SECURITY_TOKEN_V2_SIGN; + createKeyActivity.mSecurityTokenDec = Constants.SECURITY_TOKEN_V2_DEC; + createKeyActivity.mSecurityTokenAuth = Constants.SECURITY_TOKEN_V2_AUTH; + } + createKeyActivity.mSecurityTokenSign.addToSaveKeyringParcel(saveKeyringParcel, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER); + createKeyActivity.mSecurityTokenDec.addToSaveKeyringParcel(saveKeyringParcel, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE); + createKeyActivity.mSecurityTokenAuth.addToSaveKeyringParcel(saveKeyringParcel, KeyFlags.AUTHENTICATION); // use empty passphrase saveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(new Passphrase())); } else { - saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 3072, null, KeyFlags.CERTIFY_OTHER, 0L)); - saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 3072, null, KeyFlags.SIGN_DATA, 0L)); - saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 3072, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); + Constants.addDefaultSubkeys(saveKeyringParcel); if (createKeyActivity.mPassphrase != null) { saveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(createKeyActivity.mPassphrase)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java new file mode 100644 index 000000000..1079229b4 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2016 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; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.securitytoken.KeyFormat; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.util.Choice; + +import java.util.ArrayList; +import java.util.List; + +public class CreateSecurityTokenAlgorithmFragment extends Fragment { + + public enum SupportedKeyType { + RSA_2048, RSA_3072, RSA_4096, ECC_P256, ECC_P384, ECC_P521 + } + + private CreateKeyActivity mCreateKeyActivity; + + private View mBackButton; + private View mNextButton; + + private Spinner mSignKeySpinner; + private Spinner mDecKeySpinner; + private Spinner mAuthKeySpinner; + + /** + * Creates new instance of this fragment + */ + public static CreateSecurityTokenAlgorithmFragment newInstance() { + CreateSecurityTokenAlgorithmFragment frag = new CreateSecurityTokenAlgorithmFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final FragmentActivity context = getActivity(); + View view = inflater.inflate(R.layout.create_yubi_key_algorithm_fragment, container, false); + + mBackButton = view.findViewById(R.id.create_key_back_button); + mNextButton = view.findViewById(R.id.create_key_next_button); + + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + back(); + } + }); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nextClicked(); + } + }); + + mSignKeySpinner = (Spinner) view.findViewById(R.id.create_key_yubi_key_algorithm_sign); + mDecKeySpinner = (Spinner) view.findViewById(R.id.create_key_yubi_key_algorithm_dec); + mAuthKeySpinner = (Spinner) view.findViewById(R.id.create_key_yubi_key_algorithm_auth); + + ArrayList> choices = new ArrayList<>(); + + choices.add(new Choice<>(SupportedKeyType.RSA_2048, getResources().getString( + R.string.rsa_2048), getResources().getString(R.string.rsa_2048_description_html))); + choices.add(new Choice<>(SupportedKeyType.RSA_3072, getResources().getString( + R.string.rsa_3072), getResources().getString(R.string.rsa_3072_description_html))); + choices.add(new Choice<>(SupportedKeyType.RSA_4096, getResources().getString( + R.string.rsa_4096), getResources().getString(R.string.rsa_4096_description_html))); + + final double version = SecurityTokenHelper.parseOpenPgpVersion(mCreateKeyActivity.mSecurityTokenAid); + + if (version >= 3.0) { + choices.add(new Choice<>(SupportedKeyType.ECC_P256, getResources().getString( + R.string.ecc_p256), getResources().getString(R.string.ecc_p256_description_html))); + choices.add(new Choice<>(SupportedKeyType.ECC_P384, getResources().getString( + R.string.ecc_p384), getResources().getString(R.string.ecc_p384_description_html))); + choices.add(new Choice<>(SupportedKeyType.ECC_P521, getResources().getString( + R.string.ecc_p521), getResources().getString(R.string.ecc_p521_description_html))); + } + + TwoLineArrayAdapter adapter = new TwoLineArrayAdapter(context, + android.R.layout.simple_spinner_item, choices); + mSignKeySpinner.setAdapter(adapter); + mDecKeySpinner.setAdapter(adapter); + mAuthKeySpinner.setAdapter(adapter); + + // make ECC nist256 the default for v3.x + for (int i = 0; i < choices.size(); ++i) { + if (version >= 3.0) { + if (choices.get(i).getId() == SupportedKeyType.ECC_P256) { + mSignKeySpinner.setSelection(i); + mDecKeySpinner.setSelection(i); + mAuthKeySpinner.setSelection(i); + break; + } + } else { + if (choices.get(i).getId() == SupportedKeyType.RSA_2048) { + mSignKeySpinner.setSelection(i); + mDecKeySpinner.setSelection(i); + mAuthKeySpinner.setSelection(i); + break; + } + } + } + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + private void back() { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + + private void nextClicked() { + mCreateKeyActivity.mSecurityTokenSign = KeyFormat.fromCreationKeyType(((Choice) mSignKeySpinner.getSelectedItem()).getId(), false); + mCreateKeyActivity.mSecurityTokenDec = KeyFormat.fromCreationKeyType(((Choice) mDecKeySpinner.getSelectedItem()).getId(), true); + mCreateKeyActivity.mSecurityTokenAuth = KeyFormat.fromCreationKeyType(((Choice) mAuthKeySpinner.getSelectedItem()).getId(), false); + + CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + + + private class TwoLineArrayAdapter extends ArrayAdapter> { + TwoLineArrayAdapter(Context context, int resource, List> objects) { + super(context, resource, objects); + } + + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + // inflate view if not given one + if (convertView == null) { + convertView = getActivity().getLayoutInflater() + .inflate(R.layout.two_line_spinner_dropdown_item, parent, false); + } + + Choice c = this.getItem(position); + + TextView text1 = (TextView) convertView.findViewById(android.R.id.text1); + TextView text2 = (TextView) convertView.findViewById(android.R.id.text2); + + text1.setText(c.getName()); + text2.setText(Html.fromHtml(c.getDescription())); + + return convertView; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java index 729cb7dea..61a069e9d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java @@ -34,14 +34,17 @@ public class CreateSecurityTokenBlankFragment extends Fragment { View mBackButton; View mNextButton; + private byte[] mAid; + /** * Creates new instance of this fragment */ - public static CreateSecurityTokenBlankFragment newInstance() { + public static CreateSecurityTokenBlankFragment newInstance(byte[] aid) { CreateSecurityTokenBlankFragment frag = new CreateSecurityTokenBlankFragment(); Bundle args = new Bundle(); + frag.mAid = aid; frag.setArguments(args); return frag; @@ -83,6 +86,7 @@ public class CreateSecurityTokenBlankFragment extends Fragment { private void nextClicked() { mCreateKeyActivity.mCreateSecurityToken = true; + mCreateKeyActivity.mSecurityTokenAid = mAid; CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index f2eba1386..2ce1c266e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; @@ -66,6 +67,7 @@ public class CreateSecurityTokenImportResetFragment private byte[] mTokenFingerprints; private byte[] mTokenAid; + private double mTokenVersion; private String mTokenUserId; private String mTokenFingerprint; private TextView vSerNo; @@ -254,6 +256,7 @@ public class CreateSecurityTokenImportResetFragment mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints(); mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid(); + mTokenVersion = SecurityTokenHelper.parseOpenPgpVersion(mTokenAid); mTokenUserId = mCreateKeyActivity.getSecurityTokenHelper().getUserId(); byte[] fp = new byte[20]; @@ -290,6 +293,7 @@ public class CreateSecurityTokenImportResetFragment viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0])); viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result); viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mTokenAid); + viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mTokenVersion); viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mTokenUserId); viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mTokenFingerprints); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java index 1033d373e..f100ee8bc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java @@ -30,6 +30,7 @@ import android.widget.EditText; import android.widget.TextView; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.util.Passphrase; @@ -200,7 +201,14 @@ public class CreateSecurityTokenPinFragment extends Fragment { mCreateKeyActivity.mSecurityTokenPin = new Passphrase(mPin.getText().toString()); - CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance(); + final double version = SecurityTokenHelper.parseOpenPgpVersion(mCreateKeyActivity.mSecurityTokenAid); + + Fragment frag; + if (version >= 3.0) { + frag = CreateSecurityTokenAlgorithmFragment.newInstance(); + } else { + frag = CreateKeyFinalFragment.newInstance(); + } hideKeyboard(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 9181c6f79..6bf7cae15 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -35,6 +35,8 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -449,19 +451,31 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment() { @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..edcd196ed 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -56,14 +56,17 @@ import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import org.sufficientlysecure.keychain.network.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/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 96f620a4c..a91ade9ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -111,6 +111,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements public static final String EXTRA_SECURITY_TOKEN_USER_ID = "security_token_user_id"; public static final String EXTRA_SECURITY_TOKEN_AID = "security_token_aid"; + public static final String EXTRA_SECURITY_TOKEN_VERSION = "security_token_version"; public static final String EXTRA_SECURITY_TOKEN_FINGERPRINTS = "security_token_fingerprints"; @Retention(RetentionPolicy.SOURCE) @@ -178,6 +179,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private byte[] mSecurityTokenFingerprints; private String mSecurityTokenUserId; private byte[] mSecurityTokenAid; + private double mSecurityTokenVersion; @SuppressLint("InflateParams") @Override @@ -671,7 +673,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements // if the master key of that key matches this one, just show the token dialog if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) { - showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid); + showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid, mSecurityTokenVersion); return; } @@ -685,6 +687,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements ViewKeyActivity.this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mSecurityTokenVersion); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); @@ -700,6 +703,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements Intent intent = new Intent( ViewKeyActivity.this, CreateKeyActivity.class); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mSecurityTokenVersion); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); @@ -710,13 +714,13 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements } public void showSecurityTokenFragment( - final byte[] tokenFingerprints, final String tokenUserId, final byte[] tokenAid) { + final byte[] tokenFingerprints, final String tokenUserId, final byte[] tokenAid, final double tokenVersion) { new Handler().post(new Runnable() { @Override public void run() { ViewKeySecurityTokenFragment frag = ViewKeySecurityTokenFragment.newInstance( - mMasterKeyId, tokenFingerprints, tokenUserId, tokenAid); + mMasterKeyId, tokenFingerprints, tokenUserId, tokenAid, tokenVersion); FragmentManager manager = getSupportFragmentManager(); @@ -899,7 +903,8 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements byte[] tokenFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_FINGERPRINTS); String tokenUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID); byte[] tokenAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID); - showSecurityTokenFragment(tokenFingerprints, tokenUserId, tokenAid); + double tokenVersion = intent.getDoubleExtra(EXTRA_SECURITY_TOKEN_VERSION, 2.0); + showSecurityTokenFragment(tokenFingerprints, tokenUserId, tokenAid, tokenVersion); } mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java index e2a534f41..d900356a7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java @@ -38,6 +38,8 @@ import android.widget.AdapterView; import android.widget.ListView; import android.widget.ViewAnimator; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -356,19 +358,31 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements break; } - int algorithm = mSubkeysAdapter.getAlgorithm(position); - if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL - && algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT - && algorithm != PublicKeyAlgorithmTags.RSA_SIGN) { - Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR) - .show(); - break; - } + switch (mSubkeysAdapter.getAlgorithm(position)) { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + case PublicKeyAlgorithmTags.RSA_SIGN: + if (mSubkeysAdapter.getKeySize(position) < 2048) { + Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR) + .show(); + } + break; - if (mSubkeysAdapter.getKeySize(position) != 2048) { - Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR) - .show(); - break; + case PublicKeyAlgorithmTags.ECDH: + case PublicKeyAlgorithmTags.ECDSA: + final ASN1ObjectIdentifier curve = NISTNamedCurves.getOID(mSubkeysAdapter.getCurveOid(position)); + if (!curve.equals(NISTNamedCurves.getOID("P-256")) && + !curve.equals(NISTNamedCurves.getOID("P-384")) && + !curve.equals(NISTNamedCurves.getOID("P-521"))) { + Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_curve, Notify.Style.ERROR) + .show(); + } + break; + + default: + Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR) + .show(); + break; } SubkeyChange change; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java index 81fbedfd1..eb748b257 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java @@ -53,10 +53,10 @@ import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.LoaderFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.OkHttpKeybaseClient; +import org.sufficientlysecure.keychain.network.OkHttpKeybaseClient; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import java.util.ArrayList; import java.util.Hashtable; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java index 2a31191db..4a7f4861d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java @@ -50,10 +50,12 @@ public class ViewKeySecurityTokenFragment public static final String ARG_FINGERPRINT = "fingerprint"; public static final String ARG_USER_ID = "user_id"; public static final String ARG_CARD_AID = "aid"; + public static final String ARG_CARD_VERSION = "version"; private byte[][] mFingerprints; private String mUserId; private byte[] mCardAid; + private double mCardVersion; private long mMasterKeyId; private long[] mSubKeyIds; @@ -61,7 +63,7 @@ public class ViewKeySecurityTokenFragment private TextView vStatus; public static ViewKeySecurityTokenFragment newInstance(long masterKeyId, - byte[] fingerprints, String userId, byte[] aid) { + byte[] fingerprints, String userId, byte[] aid, double version) { ViewKeySecurityTokenFragment frag = new ViewKeySecurityTokenFragment(); Bundle args = new Bundle(); @@ -69,6 +71,7 @@ public class ViewKeySecurityTokenFragment args.putByteArray(ARG_FINGERPRINT, fingerprints); args.putString(ARG_USER_ID, userId); args.putByteArray(ARG_CARD_AID, aid); + args.putDouble(ARG_CARD_VERSION, version); frag.setArguments(args); return frag; @@ -91,6 +94,7 @@ public class ViewKeySecurityTokenFragment } mUserId = args.getString(ARG_USER_ID); mCardAid = args.getByteArray(ARG_CARD_AID); + mCardVersion = args.getDouble(ARG_CARD_VERSION); mMasterKeyId = args.getLong(ARG_MASTER_KEY_ID); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index b5ef1d5e4..ba288ea2f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -121,6 +121,11 @@ public class SubkeysAdapter extends CursorAdapter { return mCursor.getInt(INDEX_KEY_SIZE); } + public String getCurveOid(int position) { + mCursor.moveToPosition(position); + return mCursor.getString(INDEX_KEY_CURVE_OID); + } + public SecretKeyType getSecretKeyType(int position) { mCursor.moveToPosition(position); return SecretKeyType.fromNum(mCursor.getInt(INDEX_HAS_SECRET)); 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 72216bd52..7d9a07d9f 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 @@ -46,7 +46,7 @@ import org.sufficientlysecure.keychain.securitytoken.CardException; import org.sufficientlysecure.keychain.securitytoken.NfcTransport; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; import org.sufficientlysecure.keychain.securitytoken.Transport; -import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher; +import org.sufficientlysecure.keychain.securitytoken.UsbConnectionDispatcher; import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; @@ -91,9 +91,9 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity * Override to implement SecurityToken operations (background thread) */ protected void doSecurityTokenInBackground() throws IOException { + mSecurityTokenAid = mSecurityTokenHelper.getAid(); mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); - mSecurityTokenAid = mSecurityTokenHelper.getAid(); } /** @@ -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/AddEditKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java index 8b04c2d78..932a436da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java @@ -48,11 +48,11 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.OkHttpClientFactory; +import org.sufficientlysecure.keychain.network.OkHttpClientFactory; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.TlsHelper; -import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import org.sufficientlysecure.keychain.network.TlsCertificatePinning; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import java.io.IOException; import java.net.MalformedURLException; @@ -367,7 +367,7 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On // check TLS pinning only for non-Tor keyservers if (onlyTrustedKeyserver - && TlsHelper.getPinnedSslSocketFactory(keyserverUriHttp.toURL()) == null) { + && TlsCertificatePinning.getPinnedSslSocketFactory(keyserverUriHttp.toURL()) == null) { Log.w(Constants.TAG, "No pinned certificate for this host in OpenKeychain's assets."); reason = VerifyReturn.NO_PINNED_CERTIFICATE; return reason; @@ -385,7 +385,7 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On keyserverUriOnion.toURL(), proxy.getProxy()); clientTor.newCall(new Request.Builder().url(keyserverUriOnion.toURL()).build()).execute(); } - } catch (TlsHelper.TlsHelperException e) { + } catch (TlsCertificatePinning.TlsCertificatePinningException e) { reason = VerifyReturn.CONNECTION_FAILED; } catch (MalformedURLException | URISyntaxException e) { Log.w(Constants.TAG, "Invalid keyserver URL entered by user."); 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/ui/dialog/OrbotStartDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java index b06e05c30..800ae0d00 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java @@ -36,7 +36,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; /** * displays a dialog asking the user to enable Tor diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java index af4e0d4f8..7c2d1aa3f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java @@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.Log; import java.lang.ref.WeakReference; 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 @@ + + + + + + + + + +