diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle
index ec6d2c35d..6b957b622 100644
--- a/OpenKeychain/build.gradle
+++ b/OpenKeychain/build.gradle
@@ -19,7 +19,6 @@ dependencies {
compile project(':extern:minidns')
compile project(':extern:KeybaseLib:Lib')
compile project(':extern:TokenAutoComplete:library')
- compile project(':extern:openpgp-card-nfc-lib:library')
compile project(':extern:safeslinger-exchange')
}
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index c78e80c19..a3aa0d2a4 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -619,6 +619,9 @@
+
diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java
new file mode 100644
index 000000000..e0286ec15
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
+ * Copyright (c) 2000-2013 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)
+ *
+ * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
+ */
+
+package org.spongycastle.openpgp.operator.jcajce;
+
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.operator.PGPContentSigner;
+import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+import java.io.OutputStream;
+import java.security.Provider;
+import java.util.Date;
+
+/**
+ * This class is based on JcaPGPContentSignerBuilder.
+ *
+ * Instead of using a Signature object based on a privateKey, this class only calculates the digest
+ * of the output stream and gives the result back using a RuntimeException.
+ */
+public class NfcSyncPGPContentSignerBuilder
+ implements PGPContentSignerBuilder
+{
+ private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
+ private int hashAlgorithm;
+ private int keyAlgorithm;
+ private long keyID;
+
+ private byte[] signedHash;
+ private Date creationTimestamp;
+
+ public static class NfcInteractionNeeded extends RuntimeException
+ {
+ public byte[] hashToSign;
+ public Date creationTimestamp;
+ public int hashAlgo;
+
+ public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo, Date creationTimestamp)
+ {
+ super("NFC interaction required!");
+ this.hashToSign = hashToSign;
+ this.hashAlgo = hashAlgo;
+ this.creationTimestamp = creationTimestamp;
+ }
+ }
+
+ public NfcSyncPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm, long keyID, byte[] signedHash, Date creationTimestamp)
+ {
+ this.keyAlgorithm = keyAlgorithm;
+ this.hashAlgorithm = hashAlgorithm;
+ this.keyID = keyID;
+ this.signedHash = signedHash;
+ this.creationTimestamp = creationTimestamp;
+ }
+
+ public NfcSyncPGPContentSignerBuilder setProvider(Provider provider)
+ {
+ digestCalculatorProviderBuilder.setProvider(provider);
+
+ return this;
+ }
+
+ public NfcSyncPGPContentSignerBuilder setProvider(String providerName)
+ {
+ digestCalculatorProviderBuilder.setProvider(providerName);
+
+ return this;
+ }
+
+ public NfcSyncPGPContentSignerBuilder setDigestProvider(Provider provider)
+ {
+ digestCalculatorProviderBuilder.setProvider(provider);
+
+ return this;
+ }
+
+ public NfcSyncPGPContentSignerBuilder setDigestProvider(String providerName)
+ {
+ digestCalculatorProviderBuilder.setProvider(providerName);
+
+ return this;
+ }
+
+ public PGPContentSigner build(final int signatureType, PGPPrivateKey privateKey)
+ throws PGPException {
+ // NOTE: privateKey is null in this case!
+ return build(signatureType, keyID);
+ }
+
+ public PGPContentSigner build(final int signatureType, final long keyID)
+ throws PGPException
+ {
+ final PGPDigestCalculator digestCalculator = digestCalculatorProviderBuilder.build().get(hashAlgorithm);
+
+ return new PGPContentSigner()
+ {
+ public int getType()
+ {
+ return signatureType;
+ }
+
+ public int getHashAlgorithm()
+ {
+ return hashAlgorithm;
+ }
+
+ public int getKeyAlgorithm()
+ {
+ return keyAlgorithm;
+ }
+
+ public long getKeyID()
+ {
+ return keyID;
+ }
+
+ public OutputStream getOutputStream()
+ {
+ return digestCalculator.getOutputStream();
+ }
+
+ public byte[] getSignature() {
+ if (signedHash != null) {
+ // we already have the signed hash from a previous execution, return this!
+ return signedHash;
+ } else {
+ // catch this when signatureGenerator.generate() is executed and divert digest to card,
+ // when doing the operation again reuse creationTimestamp (this will be hashed)
+ throw new NfcInteractionNeeded(digestCalculator.getDigest(), getHashAlgorithm(), creationTimestamp);
+ }
+ }
+
+ public byte[] getDigest()
+ {
+ return digestCalculator.getDigest();
+ }
+ };
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java
new file mode 100644
index 000000000..ffa154876
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPublicKeyDataDecryptorFactoryBuilder.java
@@ -0,0 +1,275 @@
+/**
+ * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
+ *
+ * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
+ */
+
+package org.spongycastle.openpgp.operator.jcajce;
+
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.operator.PGPDataDecryptor;
+import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+
+import java.security.Provider;
+
+/**
+ * This class is based on JcePublicKeyDataDecryptorFactoryBuilder
+ *
+ */
+public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private OperatorHelper contentHelper = new OperatorHelper(new DefaultJcaJceHelper());
+ private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
+// private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
+// private JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator();
+
+ public static class NfcInteractionNeeded extends RuntimeException
+ {
+ public byte[] encryptedSessionKey;
+
+ public NfcInteractionNeeded(byte[] encryptedSessionKey)
+ {
+ super("NFC interaction required!");
+ this.encryptedSessionKey = encryptedSessionKey;
+ }
+ }
+
+ public NfcSyncPublicKeyDataDecryptorFactoryBuilder()
+ {
+ }
+
+ /**
+ * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces.
+ *
+ * @param provider provider object for cryptographic primitives.
+ * @return the current builder.
+ */
+ public NfcSyncPublicKeyDataDecryptorFactoryBuilder setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+ keyConverter.setProvider(provider);
+ this.contentHelper = helper;
+
+ return this;
+ }
+
+ /**
+ * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces.
+ *
+ * @param providerName the name of the provider to reference for cryptographic primitives.
+ * @return the current builder.
+ */
+ public NfcSyncPublicKeyDataDecryptorFactoryBuilder setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+ keyConverter.setProvider(providerName);
+ this.contentHelper = helper;
+
+ return this;
+ }
+
+ public NfcSyncPublicKeyDataDecryptorFactoryBuilder setContentProvider(Provider provider)
+ {
+ this.contentHelper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ return this;
+ }
+
+ public NfcSyncPublicKeyDataDecryptorFactoryBuilder setContentProvider(String providerName)
+ {
+ this.contentHelper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ return this;
+ }
+
+ public PublicKeyDataDecryptorFactory build(final byte[] nfcDecrypted) {
+ return new PublicKeyDataDecryptorFactory()
+ {
+ public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
+ throws PGPException
+ {
+ if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH)
+ {
+ throw new PGPException("ECDH not supported!");
+ }
+
+ return decryptSessionData(keyAlgorithm, secKeyData, nfcDecrypted);
+ }
+
+ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+ throws PGPException
+ {
+ return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+ }
+ };
+ }
+
+// public PublicKeyDataDecryptorFactory build(final PrivateKey privKey)
+// {
+// return new PublicKeyDataDecryptorFactory()
+// {
+// public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
+// throws PGPException
+// {
+// if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH)
+// {
+// throw new PGPException("ECDH requires use of PGPPrivateKey for decryption");
+// }
+// return decryptSessionData(keyAlgorithm, privKey, secKeyData);
+// }
+//
+// public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+// throws PGPException
+// {
+// return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+// }
+// };
+// }
+
+// public PublicKeyDataDecryptorFactory build(final PGPPrivateKey privKey, final byte[] nfcDecrypted)
+// {
+// return new PublicKeyDataDecryptorFactory()
+// {
+// public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
+// throws PGPException
+// {
+// if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH)
+// {
+// return decryptSessionData(privKey.getPrivateKeyDataPacket(), privKey.getPublicKeyPacket(), secKeyData);
+// }
+//
+// return decryptSessionData(keyAlgorithm, keyConverter.getPrivateKey(privKey), secKeyData, nfcDecrypted);
+// }
+//
+// public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+// throws PGPException
+// {
+// return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+// }
+// };
+// }
+
+// private byte[] decryptSessionData(BCPGKey privateKeyPacket, PublicKeyPacket pubKeyData, byte[][] secKeyData)
+// throws PGPException
+// {
+// ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKeyData.getKey();
+// X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID());
+//
+// byte[] enc = secKeyData[0];
+//
+// int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8;
+// byte[] pEnc = new byte[pLen];
+//
+// System.arraycopy(enc, 2, pEnc, 0, pLen);
+//
+// byte[] keyEnc = new byte[enc[pLen + 2]];
+//
+// System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.length);
+//
+// Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm());
+//
+// ECPoint S = x9Params.getCurve().decodePoint(pEnc).multiply(((ECSecretBCPGKey)privateKeyPacket).getX()).normalize();
+//
+// RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm());
+// Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, fingerprintCalculator.calculateFingerprint(pubKeyData)), "AESWrap");
+//
+// try
+// {
+// c.init(Cipher.UNWRAP_MODE, key);
+//
+// Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY);
+//
+// return PGPPad.unpadSessionData(paddedSessionKey.getEncoded());
+// }
+// catch (InvalidKeyException e)
+// {
+// throw new PGPException("error setting asymmetric cipher", e);
+// }
+// catch (NoSuchAlgorithmException e)
+// {
+// throw new PGPException("error setting asymmetric cipher", e);
+// }
+// }
+
+ private byte[] decryptSessionData(int keyAlgorithm, byte[][] secKeyData, byte[] nfcDecrypted)
+ throws PGPException
+ {
+// Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm);
+//
+// try
+// {
+// c1.init(Cipher.DECRYPT_MODE, privKey);
+// }
+// catch (InvalidKeyException e)
+// {
+// throw new PGPException("error setting asymmetric cipher", e);
+// }
+
+ if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT
+ || keyAlgorithm == PGPPublicKey.RSA_GENERAL)
+ {
+ byte[] bi = secKeyData[0]; // encoded MPI
+
+ if (nfcDecrypted != null) {
+ // we already have the decrypted bytes from a previous execution, return this!
+ return nfcDecrypted;
+ } else {
+ // catch this when decryptSessionData() is executed and divert digest to card,
+ // when doing the operation again reuse nfcDecrypted
+ throw new NfcInteractionNeeded(bi);
+ }
+
+// c1.update(bi, 2, bi.length - 2);
+ }
+ else
+ {
+ throw new PGPException("ElGamal not supported!");
+
+// ElGamalKey k = (ElGamalKey)privKey;
+// int size = (k.getParameters().getP().bitLength() + 7) / 8;
+// byte[] tmp = new byte[size];
+//
+// byte[] bi = secKeyData[0]; // encoded MPI
+// if (bi.length - 2 > size) // leading Zero? Shouldn't happen but...
+// {
+// c1.update(bi, 3, bi.length - 3);
+// }
+// else
+// {
+// System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2);
+// c1.update(tmp);
+// }
+//
+// bi = secKeyData[1]; // encoded MPI
+// for (int i = 0; i != tmp.length; i++)
+// {
+// tmp[i] = 0;
+// }
+//
+// if (bi.length - 2 > size) // leading Zero? Shouldn't happen but...
+// {
+// c1.update(bi, 3, bi.length - 3);
+// }
+// else
+// {
+// System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2);
+// c1.update(tmp);
+// }
+ }
+
+// try
+// {
+// return c1.doFinal();
+// }
+// catch (Exception e)
+// {
+// throw new PGPException("exception decrypting session data", e);
+// }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
index 1b4ad1fc1..8a115b245 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
@@ -31,10 +31,9 @@ import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.spongycastle.util.encoders.Hex;
-import org.sufficientlysecure.keychain.nfc.NfcActivity;
+import org.sufficientlysecure.keychain.ui.NfcActivity;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface;
-import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface.NoSecretKeyException;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
index c9cfb9e0c..dd035c78f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
@@ -30,7 +30,6 @@ import android.widget.TextView;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.nfc.NfcActivity;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.service.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java
index 17d4865fd..c1c3409f8 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java
@@ -2,10 +2,6 @@ package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
-import org.sufficientlysecure.keychain.nfc.NfcActivity;
-
-import java.util.Date;
-
public class EncryptActivity extends DrawerActivity {
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java
new file mode 100644
index 000000000..f466a7f1d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java
@@ -0,0 +1,498 @@
+/**
+ * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
+ *
+ * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.nfc.NfcAdapter;
+import android.nfc.Tag;
+import android.nfc.tech.IsoDep;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.util.encoders.Hex;
+import org.sufficientlysecure.keychain.R;
+
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
+ * NFC devices.
+ *
+ * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
+ */
+@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
+public class NfcActivity extends ActionBarActivity {
+ private static final String TAG = "Keychain";
+
+ // actions
+ public static final String ACTION_SIGN_HASH = "sign_hash";
+ public static final String ACTION_DECRYPT_SESSION_KEY = "decrypt_session_key";
+
+ // always
+ public static final String EXTRA_PIN = "pin";
+ // special extra for OpenPgpService
+ public static final String EXTRA_DATA = "data";
+
+ // sign
+ public static final String EXTRA_NFC_HASH_TO_SIGN = "nfc_hash";
+ public static final String EXTRA_NFC_HASH_ALGO = "nfc_hash_algo";
+
+ // decrypt
+ public static final String EXTRA_NFC_ENC_SESSION_KEY = "encrypted_session_key";
+
+ private Intent mServiceIntent;
+
+ private static final int TIMEOUT = 100000;
+
+ private NfcAdapter mNfcAdapter;
+ private IsoDep mIsoDep;
+ private String mAction;
+
+ private String mPin;
+
+ // sign
+ private byte[] mHashToSign;
+ private int mHashAlgo;
+
+ // decrypt
+ private byte[] mEncryptedSessionKey;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d(TAG, "NfcActivity.onCreate");
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ setContentView(R.layout.nfc_activity);
+
+ Intent intent = getIntent();
+ Bundle data = intent.getExtras();
+ String action = intent.getAction();
+
+ if (ACTION_SIGN_HASH.equals(action)) {
+ mAction = action;
+ mPin = data.getString(EXTRA_PIN);
+ mHashToSign = data.getByteArray(EXTRA_NFC_HASH_TO_SIGN);
+ mHashAlgo = data.getInt(EXTRA_NFC_HASH_ALGO);
+ mServiceIntent = data.getParcelable(EXTRA_DATA);
+
+ Log.d(TAG, "NfcActivity mAction: " + mAction);
+ Log.d(TAG, "NfcActivity mPin: " + mPin);
+ Log.d(TAG, "NfcActivity mHashToSign as hex: " + getHex(mHashToSign));
+ Log.d(TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
+ } else if (ACTION_DECRYPT_SESSION_KEY.equals(action)) {
+ mAction = action;
+ mPin = data.getString(EXTRA_PIN);
+ mEncryptedSessionKey = data.getByteArray(EXTRA_NFC_ENC_SESSION_KEY);
+ mServiceIntent = data.getParcelable(EXTRA_DATA);
+
+ Log.d(TAG, "NfcActivity mAction: " + mAction);
+ Log.d(TAG, "NfcActivity mPin: " + mPin);
+ Log.d(TAG, "NfcActivity mEncryptedSessionKey as hex: " + getHex(mEncryptedSessionKey));
+ Log.d(TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
+ } else if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
+ Log.e(TAG, "This should not happen, but there is a bug in Android!");
+
+ toast("This should not happen, but there is a bug in Android! Clear all app tasks and start app from launcher again!");
+ finish();
+ } else {
+ Log.d(TAG, "Action not supported: " + action);
+ }
+ }
+
+ /**
+ * Called when the system is about to start resuming a previous activity,
+ * disables NFC Foreground Dispatch
+ */
+ public void onPause() {
+ super.onPause();
+ Log.d(TAG, "NfcActivity.onPause");
+
+ disableNfcForegroundDispatch();
+ }
+
+ /**
+ * Called when the activity will start interacting with the user,
+ * enables NFC Foreground Dispatch
+ */
+ public void onResume() {
+ super.onResume();
+ Log.d(TAG, "NfcActivity.onResume");
+
+ enableNfcForegroundDispatch();
+ }
+
+ /**
+ * This activity is started as a singleTop activity.
+ * All new NFC Intents which are delivered to this activity are handled here
+ */
+ public void onNewIntent(Intent intent) {
+ if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
+ try {
+ handleNdefDiscoveredIntent(intent);
+ } catch (IOException e) {
+ Log.e(TAG, "Connection error!", e);
+ toast("Connection Error: " + e.getMessage());
+ setResult(RESULT_CANCELED, mServiceIntent);
+ finish();
+ }
+ }
+ }
+
+ /** Handle NFC communication and return a result.
+ *
+ * This method is called by onNewIntent above upon discovery of an NFC tag.
+ * It handles initialization and login to the application, subsequently
+ * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then
+ * finishes the activity with an appropiate result.
+ *
+ * On general communication, see also
+ * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx
+ *
+ * References to pages are generally related to the OpenPGP Application
+ * on ISO SmartCard Systems specification.
+ *
+ */
+ private void handleNdefDiscoveredIntent(Intent intent) throws IOException {
+
+ Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
+
+ // Connect to the detected tag, setting a couple of settings
+ mIsoDep = IsoDep.get(detectedTag);
+ mIsoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation
+ mIsoDep.connect();
+
+ // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
+ // See specification, page 51
+ String accepted = "9000";
+
+ // Command APDU (page 51) for SELECT FILE command (page 29)
+ String opening =
+ "00" // CLA
+ + "A4" // INS
+ + "04" // P1
+ + "00" // P2
+ + "06" // Lc (number of bytes)
+ + "D27600012401" // Data (6 bytes)
+ + "00"; // Le
+ if ( ! card(opening).equals(accepted)) { // activate connection
+ toast("Opening Error!");
+ setResult(RESULT_CANCELED, mServiceIntent);
+ finish();
+ return;
+ }
+
+ // Command APDU for VERIFY command (page 32)
+ String login =
+ "00" // CLA
+ + "20" // INS
+ + "00" // P1
+ + "82" // P2 (PW1)
+ + "06" // Lc TODO should be length of PIN?
+ + Hex.toHexString(mPin.getBytes());
+ if ( ! card(login).equals(accepted)) { // login
+ toast("Wrong PIN!");
+ setResult(RESULT_CANCELED, mServiceIntent);
+ finish();
+ return;
+ }
+
+ if (ACTION_SIGN_HASH.equals(mAction)) {
+ // returns signed hash
+ byte[] signedHash = nfcCalculateSignature(mHashToSign, mHashAlgo);
+
+ if (signedHash == null) {
+ setResult(RESULT_CANCELED, mServiceIntent);
+ finish();
+ return;
+ }
+
+ Log.d(TAG, "NfcActivity signedHash as hex: " + getHex(signedHash));
+
+ // give data through for new service call
+ // OpenPgpApi.EXTRA_NFC_SIGNED_HASH
+ mServiceIntent.putExtra("nfc_signed_hash", signedHash);
+ setResult(RESULT_OK, mServiceIntent);
+ finish();
+
+ } else if (ACTION_DECRYPT_SESSION_KEY.equals(mAction)) {
+ byte[] decryptedSessionKey = nfcDecryptSessionKey(mEncryptedSessionKey);
+
+ // give data through for new service call
+ // OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY
+ mServiceIntent.putExtra("nfc_decrypted_session_key", decryptedSessionKey);
+ setResult(RESULT_OK, mServiceIntent);
+ finish();
+ }
+ }
+
+ /**
+ * Gets the user ID
+ *
+ * @return the user id as "name "
+ * @throws java.io.IOException
+ */
+ public String getUserId() throws IOException {
+ String info = "00CA006500";
+ String data = "00CA005E00";
+ return getName(card(info)) + " <" + (new String(Hex.decode(getDataField(card(data))))) + ">";
+ }
+
+ /**
+ * Gets the long key ID
+ *
+ * @return the long key id (last 16 bytes of the fingerprint)
+ * @throws java.io.IOException
+ */
+ public long getKeyId() throws IOException {
+ String keyId = getFingerprint().substring(24);
+ Log.d(TAG, "keyId: " + keyId);
+ return Long.parseLong(keyId, 16);
+ }
+
+ /**
+ * Gets the fingerprint of the signature key
+ *
+ * @return the fingerprint
+ * @throws java.io.IOException
+ */
+ public String getFingerprint() throws IOException {
+ String data = "00CA006E00";
+ String fingerprint = card(data);
+ fingerprint = fingerprint.toLowerCase(Locale.ENGLISH); // better enforce lower case
+ fingerprint = fingerprint.substring(fingerprint.indexOf("c5") + 4, fingerprint.indexOf("c5") + 44);
+
+ Log.d(TAG, "fingerprint: " + fingerprint);
+
+ return fingerprint;
+ }
+
+ /**
+ * Calls to calculate the signature and returns the MPI value
+ *
+ * @param hash the hash for signing
+ * @return a big integer representing the MPI for the given hash
+ * @throws java.io.IOException
+ */
+ public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException {
+
+ // dsi, including Lc
+ String dsi;
+
+ Log.i(TAG, "Hash: " + hashAlgo);
+ switch (hashAlgo) {
+ case HashAlgorithmTags.SHA1:
+ if (hash.length != 20) {
+ throw new RuntimeException("Bad hash length (" + hash.length + ", expected 10!");
+ }
+ dsi = "30" // Lc TODO 0x30 == 48. shouldn't this be 35, for 15 header bytes + 20 hash?
+ + "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" + getHex(hash); // 0x14 are 20 hash bytes
+ break;
+ case HashAlgorithmTags.RIPEMD160:
+ if (hash.length != 20) {
+ throw new RuntimeException("Bad hash length (" + hash.length + ", expected 20!");
+ }
+ dsi = "303021300906052B2403020105000414" + getHex(hash);
+ break;
+ case HashAlgorithmTags.SHA224:
+ if (hash.length != 28) {
+ throw new RuntimeException("Bad hash length (" + hash.length + ", expected 28!");
+ }
+ dsi = "34302D300D06096086480165030402040500041C" + getHex(hash);
+ break;
+ case HashAlgorithmTags.SHA256:
+ if (hash.length != 32) {
+ throw new RuntimeException("Bad hash length (" + hash.length + ", expected 32!");
+ }
+ dsi = "333031300D060960864801650304020105000420" + getHex(hash);
+ break;
+ case HashAlgorithmTags.SHA384:
+ if (hash.length != 48) {
+ throw new RuntimeException("Bad hash length (" + hash.length + ", expected 48!");
+ }
+ dsi = "343041300D060960864801650304020205000430" + getHex(hash);
+ break;
+ case HashAlgorithmTags.SHA512:
+ if (hash.length != 64) {
+ throw new RuntimeException("Bad hash length (" + hash.length + ", expected 64!");
+ }
+ dsi = "343051300D060960864801650304020305000440" + getHex(hash);
+ break;
+ default:
+ throw new RuntimeException("Not supported hash algo!");
+ }
+
+ // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
+ String apdu =
+ "002A9E9A" // CLA, INS, P1, P2
+ + dsi // digital signature input
+ + "00"; // Le
+
+ String response = card(apdu);
+
+ // split up response into signature and status
+ String status = response.substring(response.length()-4);
+ String signature = response.substring(0, response.length() - 4);
+
+ // while we are getting 0x61 status codes, retrieve more data
+ while (status.substring(0, 2).equals("61")) {
+ Log.d(TAG, "requesting more data, status " + status);
+ // Send GET RESPONSE command
+ response = card("00C00000" + status.substring(2));
+ status = response.substring(response.length()-4);
+ signature += response.substring(0, response.length()-4);
+ }
+
+ Log.d(TAG, "final response:" + status);
+
+ if ( ! status.equals("9000")) {
+ toast("Bad NFC response code: " + status);
+ return null;
+ }
+
+ // Make sure the signature we received is actually the expected number of bytes long!
+ if (signature.length() != 512) {
+ toast("Bad signature length! Expected 256 bytes, got " + signature.length() / 2);
+ return null;
+ }
+
+ return Hex.decode(signature);
+ }
+
+ /**
+ * Calls to calculate the signature and returns the MPI value
+ *
+ * @param encryptedSessionKey the encoded session key
+ * @return the decoded session key
+ * @throws java.io.IOException
+ */
+ public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException {
+ String firstApdu = "102a8086fe";
+ String secondApdu = "002a808603";
+ String le = "00";
+
+ byte[] one = new byte[254];
+ // leave out first byte:
+ System.arraycopy(encryptedSessionKey, 1, one, 0, one.length);
+
+ byte[] two = new byte[encryptedSessionKey.length - 1 - one.length];
+ for (int i = 0; i < two.length; i++) {
+ two[i] = encryptedSessionKey[i + one.length + 1];
+ }
+
+ String first = card(firstApdu + getHex(one));
+ String second = card(secondApdu + getHex(two) + le);
+
+ String decryptedSessionKey = getDataField(second);
+
+ Log.d(TAG, "decryptedSessionKey: " + decryptedSessionKey);
+
+ return Hex.decode(decryptedSessionKey);
+ }
+
+ /**
+ * Prints a message to the screen
+ *
+ * @param text the text which should be contained within the toast
+ */
+ private void toast(String text) {
+ Toast.makeText(this, text, Toast.LENGTH_LONG).show();
+ }
+
+ /**
+ * Receive new NFC Intents to this activity only by enabling foreground dispatch.
+ * This can only be done in onResume!
+ */
+ public void enableNfcForegroundDispatch() {
+ mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ Intent nfcI = new Intent(this, NfcActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT);
+ IntentFilter[] writeTagFilters = new IntentFilter[]{
+ new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
+ };
+
+ // https://code.google.com/p/android/issues/detail?id=62918
+ // maybe mNfcAdapter.enableReaderMode(); ?
+ try {
+ mNfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null);
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "NfcForegroundDispatch Error!", e);
+ }
+ Log.d(TAG, "NfcForegroundDispatch has been enabled!");
+ }
+
+ /**
+ * Disable foreground dispatch in onPause!
+ */
+ public void disableNfcForegroundDispatch() {
+ mNfcAdapter.disableForegroundDispatch(this);
+ Log.d(TAG, "NfcForegroundDispatch has been disabled!");
+ }
+
+ /**
+ * Gets the name of the user out of the raw card output regarding card holder related data
+ *
+ * @param name the raw card holder related data from the card
+ * @return the name given in this data
+ */
+ public String getName(String name) {
+ String slength;
+ int ilength;
+ name = name.substring(6);
+ slength = name.substring(0, 2);
+ ilength = Integer.parseInt(slength, 16) * 2;
+ name = name.substring(2, ilength + 2);
+ name = (new String(Hex.decode(name))).replace('<', ' ');
+ return (name);
+ }
+
+ /**
+ * Reduces the raw data from the card by four characters
+ *
+ * @param output the raw data from the card
+ * @return the data field of that data
+ */
+ private String getDataField(String output) {
+ return output.substring(0, output.length() - 4);
+ }
+
+ /**
+ * Communicates with the OpenPgpCard via the APDU
+ *
+ * @param hex the hexadecimal APDU
+ * @return The answer from the card
+ * @throws java.io.IOException throws an exception if something goes wrong
+ */
+ public String card(String hex) throws IOException {
+ return getHex(mIsoDep.transceive(Hex.decode(hex)));
+ }
+
+ /**
+ * Converts a byte array into an hex string
+ *
+ * @param raw the byte array representation
+ * @return the hexadecimal string representation
+ */
+ public static String getHex(byte[] raw) {
+ return new String(Hex.encode(raw));
+ }
+}
diff --git a/OpenKeychain/src/main/res/drawable/yubikey_neo.png b/OpenKeychain/src/main/res/drawable/yubikey_neo.png
new file mode 100644
index 000000000..d4156eb5e
Binary files /dev/null and b/OpenKeychain/src/main/res/drawable/yubikey_neo.png differ
diff --git a/OpenKeychain/src/main/res/layout/nfc_activity.xml b/OpenKeychain/src/main/res/layout/nfc_activity.xml
new file mode 100644
index 000000000..e78fa7c87
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/nfc_activity.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/extern/openpgp-card-nfc-lib b/extern/openpgp-card-nfc-lib
index 6bc1f1240..dc2701e41 160000
--- a/extern/openpgp-card-nfc-lib
+++ b/extern/openpgp-card-nfc-lib
@@ -1 +1 @@
-Subproject commit 6bc1f1240f9700e5bd378f02540360ef20dea629
+Subproject commit dc2701e415aa01c811b093f152409a231475e506
diff --git a/settings.gradle b/settings.gradle
index 038e9da23..b51bdc94d 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,5 +13,4 @@ include ':extern:SuperToasts:supertoasts'
include ':extern:minidns'
include ':extern:KeybaseLib:Lib'
include ':extern:TokenAutoComplete:library'
-include ':extern:openpgp-card-nfc-lib:library'
include ':extern:safeslinger-exchange'