diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org')
6 files changed, 918 insertions, 7 deletions
| 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 <email>" +     * @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)); +    } +} | 
