diff options
| author | Vincent <valodim@mugenguild.com> | 2015-05-15 12:42:42 +0200 | 
|---|---|---|
| committer | Vincent <valodim@mugenguild.com> | 2015-05-15 12:42:42 +0200 | 
| commit | 5f6421e82b3875386818c4e7da89f79004695f4c (patch) | |
| tree | a97b7a5d71134e46ed735ebe9c5338067f93746c /OpenKeychain/src | |
| parent | 5c44f8400970de5943fd6c5e804dfa394f3deb01 (diff) | |
| parent | 2d3f745c36280fcd0e5c73820cc3e72f41feae2d (diff) | |
| download | open-keychain-5f6421e82b3875386818c4e7da89f79004695f4c.tar.gz open-keychain-5f6421e82b3875386818c4e7da89f79004695f4c.tar.bz2 open-keychain-5f6421e82b3875386818c4e7da89f79004695f4c.zip | |
Merge pull request #1278 from josecastillo/nfckeytocard
Support for moving RSA keys to Yubikey or NFC smart card
Diffstat (limited to 'OpenKeychain/src')
28 files changed, 683 insertions, 72 deletions
| diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index 4072d91c5..e8e888c7a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -83,7 +83,7 @@ public class EditKeyOperation extends BaseOperation {                      CanonicalizedSecretKeyRing secRing =                              mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId); -                    modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel); +                    modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel, log, 2);                      if (modifyResult.isPending()) {                          return modifyResult;                      } 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 094afd4a5..7f36aeb08 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 @@ -494,6 +494,12 @@ public abstract class OperationResult implements Parcelable {          MSG_MF_ERROR_REVOKED_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_revoked_primary),          MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig),          MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing), +        MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS(LogLevel.ERROR, R.string.msg_mf_error_conflicting_nfc_commands), +        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_NFC_ALGO(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_algo), +        MSG_MF_ERROR_BAD_NFC_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_size), +        MSG_MF_ERROR_BAD_NFC_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_stripped),          MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master),          MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin),          MSG_MF_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty), @@ -511,6 +517,8 @@ public abstract class OperationResult implements Parcelable {          MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new),          MSG_MF_SUBKEY_REVOKE (LogLevel.INFO, R.string.msg_mf_subkey_revoke),          MSG_MF_SUBKEY_STRIP (LogLevel.INFO, R.string.msg_mf_subkey_strip), +        MSG_MF_KEYTOCARD_START (LogLevel.INFO, R.string.msg_mf_keytocard_start), +        MSG_MF_KEYTOCARD_FINISH (LogLevel.OK, R.string.msg_mf_keytocard_finish),          MSG_MF_SUCCESS (LogLevel.OK, R.string.msg_mf_success),          MSG_MF_UID_ADD (LogLevel.INFO, R.string.msg_mf_uid_add),          MSG_MF_UID_PRIMARY (LogLevel.INFO, R.string.msg_mf_uid_primary), 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 39d0a2f1d..fd023576b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -33,6 +33,7 @@ import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;  import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;  import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;  import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;  import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;  import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;  import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; @@ -45,6 +46,8 @@ import org.sufficientlysecure.keychain.util.Log;  import org.sufficientlysecure.keychain.util.Passphrase;  import java.nio.ByteBuffer; +import java.security.PrivateKey; +import java.security.interfaces.RSAPrivateCrtKey;  import java.util.ArrayList;  import java.util.Date;  import java.util.HashMap; @@ -281,6 +284,27 @@ 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 { +        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); +        } + +        return (RSAPrivateCrtKey)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 89db378a9..62809ca6b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -18,6 +18,7 @@  package org.sufficientlysecure.keychain.pgp; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags;  import org.spongycastle.bcpg.S2K;  import org.spongycastle.bcpg.sig.Features;  import org.spongycastle.bcpg.sig.KeyFlags; @@ -45,8 +46,10 @@ import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;  import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;  import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;  import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded; +import org.spongycastle.util.encoders.Hex;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult;  import org.sufficientlysecure.keychain.operations.results.OperationResult;  import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;  import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -59,6 +62,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;  import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;  import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;  import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcKeyToCardOperationsBuilder;  import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;  import org.sufficientlysecure.keychain.util.IterableIterator;  import org.sufficientlysecure.keychain.util.Log; @@ -68,6 +72,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;  import java.io.IOException;  import java.math.BigInteger; +import java.nio.ByteBuffer;  import java.security.InvalidAlgorithmParameterException;  import java.security.KeyPairGenerator;  import java.security.NoSuchAlgorithmException; @@ -324,7 +329,7 @@ public class PgpKeyOperation {              subProgressPush(50, 100);              CryptoInputParcel cryptoInput = new CryptoInputParcel(new Date(), new Passphrase("")); -            return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, cryptoInput, saveParcel, log); +            return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, cryptoInput, saveParcel, log, indent);          } catch (PGPException e) {              log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); @@ -356,11 +361,16 @@ public class PgpKeyOperation {       *       */      public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, -            CryptoInputParcel cryptoInput, -            SaveKeyringParcel saveParcel) { +                                                CryptoInputParcel cryptoInput, +                                                SaveKeyringParcel saveParcel) { +        return modifySecretKeyRing(wsKR, cryptoInput, saveParcel, new OperationLog(), 0); +    } -        OperationLog log = new OperationLog(); -        int indent = 0; +    public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, +            CryptoInputParcel cryptoInput, +            SaveKeyringParcel saveParcel, +            OperationLog log, +            int indent) {          /*           * 1. Unlock private key @@ -400,9 +410,61 @@ public class PgpKeyOperation {              return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);          } +        // Ensure we don't have multiple keys for the same slot. +        boolean hasSign = false; +        boolean hasEncrypt = false; +        boolean hasAuth = false; +        for(SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) { +            if (change.mMoveKeyToCard) { +                // If this is a keytocard operation, see if it was completed: look for a hash +                // matching the given subkey ID in cryptoData. +                byte[] subKeyId = new byte[8]; +                ByteBuffer buf = ByteBuffer.wrap(subKeyId); +                buf.putLong(change.mKeyId).rewind(); + +                byte[] serialNumber = cryptoInput.getCryptoData().get(buf); +                if (serialNumber != null) { +                    change.mMoveKeyToCard = false; +                    change.mDummyDivert = serialNumber; +                } +            } + +            if (change.mMoveKeyToCard) { +                // Pending keytocard operation. Need to make sure that we don't have multiple +                // subkeys pending for the same slot. +                CanonicalizedSecretKey wsK = wsKR.getSecretKey(change.mKeyId); + +                if ((wsK.canSign() || wsK.canCertify())) { +                    if (hasSign) { +                        log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1); +                        return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); +                    } else { +                        hasSign = true; +                    } +                } else if ((wsK.canEncrypt())) { +                    if (hasEncrypt) { +                        log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1); +                        return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); +                    } else { +                        hasEncrypt = true; +                    } +                } else if ((wsK.canAuthenticate())) { +                    if (hasAuth) { +                        log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1); +                        return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); +                    } else { +                        hasAuth = true; +                    } +                } else { +                    log.add(LogType.MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD, indent + 1); +                    return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); +                } +            } +        } +          if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) {              log.add(LogType.MSG_MF_RESTRICTED_MODE, indent); -            return internalRestricted(sKR, saveParcel, log); +            return internalRestricted(sKR, saveParcel, log, indent + 1);          }          // Do we require a passphrase? If so, pass it along @@ -420,7 +482,7 @@ public class PgpKeyOperation {          Date expiryTime = wsKR.getPublicKey().getExpiryTime();          long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L; -        return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, cryptoInput, saveParcel, log); +        return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, cryptoInput, saveParcel, log, indent);      } @@ -428,13 +490,14 @@ public class PgpKeyOperation {                                       int masterKeyFlags, long masterKeyExpiry,                                       CryptoInputParcel cryptoInput,                                       SaveKeyringParcel saveParcel, -                                     OperationLog log) { - -        int indent = 1; +                                     OperationLog log, +                                     int indent) {          NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(                  cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),                  masterSecretKey.getKeyID()); +        NfcKeyToCardOperationsBuilder nfcKeyToCardOps = new NfcKeyToCardOperationsBuilder( +                masterSecretKey.getKeyID());          progress(R.string.progress_modify, 0); @@ -743,22 +806,36 @@ public class PgpKeyOperation {                      return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);                  } -                if (change.mDummyStrip || change.mDummyDivert != null) { +                if (change.mDummyStrip) {                      // IT'S DANGEROUS~                      // no really, it is. this operation irrevocably removes the private key data from the key -                    if (change.mDummyStrip) { -                        sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey()); +                    sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey()); +                    sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); +                } else if (change.mMoveKeyToCard) { +                    if (checkSmartCardCompatibility(sKey, log, indent + 1)) { +                        log.add(LogType.MSG_MF_KEYTOCARD_START, indent + 1, +                                KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); +                        nfcKeyToCardOps.addSubkey(change.mKeyId);                      } else { -                        // the serial number must be 16 bytes in length -                        if (change.mDummyDivert.length != 16) { -                            log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL, -                                    indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); -                            return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); -                        } +                        // Appropriate log message already set by checkSmartCardCompatibility +                        return new PgpEditKeyResult(EditKeyResult.RESULT_ERROR, log, null); +                    } +                } else if (change.mDummyDivert != null) { +                    // NOTE: Does this code get executed? Or always handled in internalRestricted? +                    if (change.mDummyDivert.length != 16) { +                        log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL, +                                indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); +                        return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);                      } +                    log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1, +                            KeyFormattingUtils.convertKeyIdToHex(change.mKeyId), +                            Hex.toHexString(change.mDummyDivert, 8, 6)); +                    sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);                      sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);                  } + +                  // This doesn't concern us any further                  if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) {                      continue; @@ -980,11 +1057,21 @@ public class PgpKeyOperation {          progress(R.string.progress_done, 100); +        if (!nfcSignOps.isEmpty() && !nfcKeyToCardOps.isEmpty()) { +            log.add(LogType.MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS, indent+1); +            return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); +        } +          if (!nfcSignOps.isEmpty()) {              log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);              return new PgpEditKeyResult(log, nfcSignOps.build());          } +        if (!nfcKeyToCardOps.isEmpty()) { +            log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent); +            return new PgpEditKeyResult(log, nfcKeyToCardOps.build()); +        } +          log.add(LogType.MSG_MF_SUCCESS, indent);          return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); @@ -995,9 +1082,7 @@ public class PgpKeyOperation {       * otherwise.       */      private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel, -                                                OperationLog log) { - -        int indent = 1; +                                                OperationLog log, int indent) {          progress(R.string.progress_modify, 0); @@ -1042,6 +1127,9 @@ public class PgpKeyOperation {                                  indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));                          return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);                      } +                    log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1, +                            KeyFormattingUtils.convertKeyIdToHex(change.mKeyId), +                            Hex.toHexString(change.mDummyDivert, 8, 6));                      sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);                  }                  sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); @@ -1481,4 +1569,29 @@ public class PgpKeyOperation {                  && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;      } +    private static boolean checkSmartCardCompatibility(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_NFC_ALGO, indent + 1); +            return false; +        } + +        // Key size must be 2048 +        int keySize = publicKey.getBitStrength(); +        if (keySize != 2048) { +            log.add(LogType.MSG_MF_ERROR_BAD_NFC_SIZE, indent + 1); +            return false; +        } + +        // Secret key parts must be available +        if (isDivertToCard(key) || isDummy(key)) { +            log.add(LogType.MSG_MF_ERROR_BAD_NFC_STRIPPED, indent + 1); +            return false; +        } + +        return true; +    }  } 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 4a8bf9332..1e2a72c3c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -167,6 +167,7 @@ public class OpenPgpService extends RemoteService {                                                                 Intent data, RequiredInputParcel requiredInput) {          switch (requiredInput.mType) { +            case NFC_KEYTOCARD:              case NFC_DECRYPT:              case NFC_SIGN: {                  // build PendingIntent for YubiKey NFC operations diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 2e0524141..e2c4dc542 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -95,7 +95,8 @@ public class SaveKeyringParcel implements Parcelable {          }          for (SubkeyChange change : mChangeSubKeys) { -            if (change.mRecertify || change.mFlags != null || change.mExpiry != null) { +            if (change.mRecertify || change.mFlags != null || change.mExpiry != null +                    || change.mMoveKeyToCard) {                  return false;              }          } @@ -142,6 +143,8 @@ public class SaveKeyringParcel implements Parcelable {          public boolean mRecertify;          // if this flag is true, the subkey should be changed to a stripped key          public boolean mDummyStrip; +        // if this flag is true, the subkey should be moved to a card +        public boolean mMoveKeyToCard;          // if this is non-null, the subkey will be changed to a divert-to-card          // key for the given serial number          public byte[] mDummyDivert; @@ -161,16 +164,16 @@ public class SaveKeyringParcel implements Parcelable {              mExpiry = expiry;          } -        public SubkeyChange(long keyId, boolean dummyStrip, byte[] dummyDivert) { +        public SubkeyChange(long keyId, boolean dummyStrip, boolean moveKeyToCard) {              this(keyId, null, null);              // these flags are mutually exclusive! -            if (dummyStrip && dummyDivert != null) { +            if (dummyStrip && moveKeyToCard) {                  throw new AssertionError( -                        "cannot set strip and divert flags at the same time - this is a bug!"); +                        "cannot set strip and keytocard flags at the same time - this is a bug!");              }              mDummyStrip = dummyStrip; -            mDummyDivert = dummyDivert; +            mMoveKeyToCard = moveKeyToCard;          }          @Override @@ -179,6 +182,7 @@ public class SaveKeyringParcel implements Parcelable {              out += "mFlags: " + mFlags + ", ";              out += "mExpiry: " + mExpiry + ", ";              out += "mDummyStrip: " + mDummyStrip + ", "; +            out += "mMoveKeyToCard: " + mMoveKeyToCard + ", ";              out += "mDummyDivert: [" + (mDummyDivert == null ? 0 : mDummyDivert.length) + " bytes]";              return out; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index 535c1e735..727f1638c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -1,5 +1,6 @@  package org.sufficientlysecure.keychain.service.input; +import java.nio.ByteBuffer;  import java.util.ArrayList;  import java.util.Collections;  import java.util.Date; @@ -7,13 +8,11 @@ import java.util.Date;  import android.os.Parcel;  import android.os.Parcelable; -import org.sufficientlysecure.keychain.Constants.key; -  public class RequiredInputParcel implements Parcelable {      public enum RequiredInputType { -        PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT +        PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_KEYTOCARD      }      public Date mSignatureTime; @@ -211,4 +210,46 @@ public class RequiredInputParcel implements Parcelable {      } +    public static class NfcKeyToCardOperationsBuilder { +        ArrayList<byte[]> mSubkeysToExport = new ArrayList<>(); +        Long mMasterKeyId; + +        public NfcKeyToCardOperationsBuilder(Long masterKeyId) { +            mMasterKeyId = masterKeyId; +        } + +        public RequiredInputParcel build() { +            byte[][] inputHashes = new byte[mSubkeysToExport.size()][]; +            mSubkeysToExport.toArray(inputHashes); +            ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0)); + +            // We need to pass in a subkey here... +            return new RequiredInputParcel(RequiredInputType.NFC_KEYTOCARD, +                    inputHashes, null, null, mMasterKeyId, buf.getLong()); +        } + +        public void addSubkey(long subkeyId) { +            byte[] subKeyId = new byte[8]; +            ByteBuffer buf = ByteBuffer.wrap(subKeyId); +            buf.putLong(subkeyId).rewind(); +            mSubkeysToExport.add(subKeyId); +        } + +        public void addAll(RequiredInputParcel input) { +            if (!mMasterKeyId.equals(input.mMasterKeyId)) { +                throw new AssertionError("Master keys must match, this is a programming error!"); +            } +            if (input.mType != RequiredInputType.NFC_KEYTOCARD) { +                throw new AssertionError("Operation types must match, this is a programming error!"); +            } + +            Collections.addAll(mSubkeysToExport, input.mInputHashes); +        } + +        public boolean isEmpty() { +            return mSubkeysToExport.isEmpty(); +        } + +    } +  } 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 e0b728bd4..68a809b69 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -17,6 +17,8 @@  package org.sufficientlysecure.keychain.ui; +import android.app.AlertDialog; +import android.content.DialogInterface;  import android.content.Intent;  import android.os.Bundle;  import android.support.v4.app.Fragment; @@ -41,6 +43,7 @@ public class CreateKeyActivity extends BaseNfcActivity {      public static final String EXTRA_FIRST_TIME = "first_time";      public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails";      public static final String EXTRA_PASSPHRASE = "passphrase"; +    public static final String EXTRA_USE_SMART_CARD_SETTINGS = "use_smart_card_settings";      public static final String EXTRA_NFC_USER_ID = "nfc_user_id";      public static final String EXTRA_NFC_AID = "nfc_aid"; @@ -53,6 +56,7 @@ public class CreateKeyActivity extends BaseNfcActivity {      ArrayList<String> mAdditionalEmails;      Passphrase mPassphrase;      boolean mFirstTime; +    boolean mUseSmartCardSettings;      Fragment mCurrentFragment; @@ -68,6 +72,7 @@ public class CreateKeyActivity extends BaseNfcActivity {              mAdditionalEmails = savedInstanceState.getStringArrayList(EXTRA_ADDITIONAL_EMAILS);              mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE);              mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME); +            mUseSmartCardSettings = savedInstanceState.getBoolean(EXTRA_USE_SMART_CARD_SETTINGS);              mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);          } else { @@ -77,6 +82,7 @@ public class CreateKeyActivity extends BaseNfcActivity {              mName = intent.getStringExtra(EXTRA_NAME);              mEmail = intent.getStringExtra(EXTRA_EMAIL);              mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false); +            mUseSmartCardSettings = intent.getBooleanExtra(EXTRA_USE_SMART_CARD_SETTINGS, false);              if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) {                  byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); @@ -116,23 +122,45 @@ public class CreateKeyActivity extends BaseNfcActivity {          byte[] nfcAid = nfcGetAid();          String userId = nfcGetUserId(); -        try { -            long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints); -            CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId); -            ring.getMasterKeyId(); - -            Intent intent = new Intent(this, ViewKeyActivity.class); -            intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); -            intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); -            intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId); -            intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints); -            startActivity(intent); -            finish(); - -        } catch (PgpKeyNotFoundException e) { -            Fragment frag = CreateKeyYubiKeyImportFragment.createInstance( -                    scannedFingerprints, nfcAid, userId); -            loadFragment(frag, FragAction.TO_RIGHT); +        // If all fingerprint bytes are 0, the card contains no keys. +        boolean cardContainsKeys = false; +        for (byte b : scannedFingerprints) { +            if (b != 0) { +                cardContainsKeys = true; +                break; +            } +        } + +        if (cardContainsKeys) { +            try { +                long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints); +                CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId); +                ring.getMasterKeyId(); + +                Intent intent = new Intent(this, ViewKeyActivity.class); +                intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); +                intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); +                intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId); +                intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints); +                startActivity(intent); +                finish(); + +            } catch (PgpKeyNotFoundException e) { +                Fragment frag = CreateKeyYubiKeyImportFragment.createInstance( +                        scannedFingerprints, nfcAid, userId); +                loadFragment(frag, FragAction.TO_RIGHT); +            } +        } else { +            AlertDialog.Builder builder = new AlertDialog.Builder(this); +            builder.setTitle(R.string.first_time_blank_smartcard_title) +                    .setMessage(R.string.first_time_blank_smartcard_message) +                    .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { +                        @Override +                        public void onClick(DialogInterface dialog, int button) { +                            CreateKeyActivity.this.mUseSmartCardSettings = true; +                        } +                    }) +                    .setNegativeButton(android.R.string.no, null).show();          }      } @@ -146,6 +174,7 @@ public class CreateKeyActivity extends BaseNfcActivity {          outState.putStringArrayList(EXTRA_ADDITIONAL_EMAILS, mAdditionalEmails);          outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase);          outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime); +        outState.putBoolean(EXTRA_USE_SMART_CARD_SETTINGS, mUseSmartCardSettings);      }      @Override 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 b0a13c897..84962915b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -163,12 +163,22 @@ public class CreateKeyFinalFragment extends Fragment {          if (mSaveKeyringParcel == null) {              mSaveKeyringParcel = new SaveKeyringParcel(); -            mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( -                    Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L)); -            mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( -                    Algorithm.RSA, 4096, null, KeyFlags.SIGN_DATA, 0L)); -            mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( -                    Algorithm.RSA, 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); +            if (mCreateKeyActivity.mUseSmartCardSettings) { +                mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, +                        2048, null, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER, 0L)); +                mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, +                        2048, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); +                mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, +                        2048, null, KeyFlags.AUTHENTICATION, 0L)); +                mEditText.setText(R.string.create_key_custom); +            } else { +                mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, +                        4096, null, KeyFlags.CERTIFY_OTHER, 0L)); +                mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, +                        4096, null, KeyFlags.SIGN_DATA, 0L)); +                mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, +                        4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); +            }              String userId = KeyRing.createUserId(                      new KeyRing.UserId(mCreateKeyActivity.mName, mCreateKeyActivity.mEmail, null)              ); 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 390efddce..1f7a0eb0d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -42,6 +42,7 @@ import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;  import org.sufficientlysecure.keychain.operations.results.OperationResult;  import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;  import org.sufficientlysecure.keychain.operations.results.SingletonResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;  import org.sufficientlysecure.keychain.pgp.KeyRing;  import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;  import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; @@ -65,6 +66,8 @@ import org.sufficientlysecure.keychain.ui.util.Notify;  import org.sufficientlysecure.keychain.util.Log;  import org.sufficientlysecure.keychain.util.Passphrase; +import java.nio.ByteBuffer; +  public class EditKeyFragment extends CryptoOperationFragment implements          LoaderManager.LoaderCallbacks<Cursor> { @@ -415,15 +418,65 @@ public class EditKeyFragment extends CryptoOperationFragment implements                              mSaveKeyringParcel.mRevokeSubKeys.add(keyId);                          }                          break; -                    case EditSubkeyDialogFragment.MESSAGE_STRIP: +                    case EditSubkeyDialogFragment.MESSAGE_STRIP: { +                        SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); +                        if (secretKeyType == SecretKeyType.GNU_DUMMY) { +                            // Key is already stripped; this is a no-op. +                            break; +                        } +                          SubkeyChange change = mSaveKeyringParcel.getSubkeyChange(keyId);                          if (change == null) { -                            mSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null)); +                            mSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, false));                              break;                          }                          // toggle                          change.mDummyStrip = !change.mDummyStrip; +                        if (change.mDummyStrip && change.mMoveKeyToCard) { +                            // User had chosen to divert key, but now wants to strip it instead. +                            change.mMoveKeyToCard = false; +                        }                          break; +                    } +                    case EditSubkeyDialogFragment.MESSAGE_KEYTOCARD: { +                        Activity activity = EditKeyFragment.this.getActivity(); +                        SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); +                        if (secretKeyType == SecretKeyType.DIVERT_TO_CARD || +                            secretKeyType == SecretKeyType.GNU_DUMMY) { +                            Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR) +                                    .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +                            break; +                        } +                        int algorithm = mSubkeysAdapter.getAlgorithm(position); +                        // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN +                        if (algorithm != 1 && algorithm != 2 && algorithm != 3) { +                            Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR) +                                    .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +                            break; +                        } +                        if (mSubkeysAdapter.getKeySize(position) != 2048) { +                            Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR) +                                    .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +                            break; +                        } + + +                        SubkeyChange change; +                        change = mSaveKeyringParcel.getSubkeyChange(keyId); +                        if (change == null) { +                            mSaveKeyringParcel.mChangeSubKeys.add( +                                    new SubkeyChange(keyId, false, true) +                            ); +                            break; +                        } +                        // toggle +                        change.mMoveKeyToCard = !change.mMoveKeyToCard; +                        if (change.mMoveKeyToCard && change.mDummyStrip) { +                            // User had chosen to strip key, but now wants to divert it. +                            change.mDummyStrip = false; +                        } +                        break; +                    }                  }                  getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();              } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java index aa66053fa..3d7058ae5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -10,17 +10,24 @@ import android.content.Intent;  import android.os.Bundle;  import android.view.WindowManager; +import org.spongycastle.util.Arrays;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;  import org.sufficientlysecure.keychain.service.PassphraseCacheService;  import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;  import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;  import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;  import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase;  import org.sufficientlysecure.keychain.util.Preferences;  import java.io.IOException; +import java.nio.ByteBuffer;  /**   * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant @@ -53,8 +60,9 @@ public class NfcOperationActivity extends BaseNfcActivity {          mRequiredInput = data.getParcelable(EXTRA_REQUIRED_INPUT);          mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT); -        // obtain passphrase for this subkey -        obtainYubiKeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput)); +        if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_KEYTOCARD) { +            obtainYubiKeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput)); +        }      }      @Override @@ -85,6 +93,56 @@ public class NfcOperationActivity extends BaseNfcActivity {                  }                  break;              } +            case NFC_KEYTOCARD: { +                ProviderHelper providerHelper = new ProviderHelper(this); +                CanonicalizedSecretKeyRing secretKeyRing; +                try { +                    secretKeyRing = providerHelper.getCanonicalizedSecretKeyRing( +                            KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mRequiredInput.getMasterKeyId()) +                    ); +                } catch (ProviderHelper.NotFoundException e) { +                    throw new IOException("Couldn't find subkey for key to card operation."); +                } + +                for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) { +                    byte[] subkeyBytes = mRequiredInput.mInputHashes[i]; +                    ByteBuffer buf = ByteBuffer.wrap(subkeyBytes); +                    long subkeyId = buf.getLong(); + +                    CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId); + +                    long keyGenerationTimestampMillis = key.getCreationTime().getTime(); +                    long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000; +                    byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); +                    byte[] cardSerialNumber = Arrays.copyOf(nfcGetAid(), 16); + +                    Passphrase passphrase; +                    try { +                        passphrase = PassphraseCacheService.getCachedPassphrase(this, +                                mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); +                    } catch (PassphraseCacheService.KeyNotFoundException e) { +                        throw new IOException("Unable to get cached passphrase!"); +                    } + +                    if (key.canSign() || key.canCertify()) { +                        nfcPutKey(0xB6, key, passphrase); +                        nfcPutData(0xCE, timestampBytes); +                        nfcPutData(0xC7, key.getFingerprint()); +                    } else if (key.canEncrypt()) { +                        nfcPutKey(0xB8, key, passphrase); +                        nfcPutData(0xCF, timestampBytes); +                        nfcPutData(0xC8, key.getFingerprint()); +                    } else if (key.canAuthenticate()) { +                        nfcPutKey(0xA4, key, passphrase); +                        nfcPutData(0xD0, timestampBytes); +                        nfcPutData(0xC9, key.getFingerprint()); +                    } else { +                        throw new IOException("Inappropriate key flags for smart card key."); +                    } + +                    inputParcel.addCryptoData(subkeyBytes, cardSerialNumber); +                } +            }          }          if (mServiceIntent != null) { 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 096dea51f..87539ea05 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 @@ -116,6 +116,21 @@ public class SubkeysAdapter extends CursorAdapter {          }      } +    public int getAlgorithm(int position) { +        mCursor.moveToPosition(position); +        return mCursor.getInt(INDEX_ALGORITHM); +    } + +    public int getKeySize(int position) { +        mCursor.moveToPosition(position); +        return mCursor.getInt(INDEX_KEY_SIZE); +    } + +    public SecretKeyType getSecretKeyType(int position) { +        mCursor.moveToPosition(position); +        return SecretKeyType.fromNum(mCursor.getInt(INDEX_HAS_SECRET)); +    } +      @Override      public Cursor swapCursor(Cursor newCursor) {          hasAnySecret = false; @@ -164,13 +179,23 @@ public class SubkeysAdapter extends CursorAdapter {                  ? mSaveKeyringParcel.getSubkeyChange(keyId)                  : null; -        if (change != null && change.mDummyStrip) { -            algorithmStr.append(", "); -            final SpannableString boldStripped = new SpannableString( -                    context.getString(R.string.key_stripped) -            ); -            boldStripped.setSpan(new StyleSpan(Typeface.BOLD), 0, boldStripped.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); -            algorithmStr.append(boldStripped); +        if (change != null && (change.mDummyStrip || change.mMoveKeyToCard)) { +            if (change.mDummyStrip) { +                algorithmStr.append(", "); +                final SpannableString boldStripped = new SpannableString( +                        context.getString(R.string.key_stripped) +                ); +                boldStripped.setSpan(new StyleSpan(Typeface.BOLD), 0, boldStripped.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); +                algorithmStr.append(boldStripped); +            } +            if (change.mMoveKeyToCard) { +                algorithmStr.append(", "); +                final SpannableString boldDivert = new SpannableString( +                        context.getString(R.string.key_divert) +                ); +                boldDivert.setSpan(new StyleSpan(Typeface.BOLD), 0, boldDivert.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); +                algorithmStr.append(boldDivert); +            }          } else {              switch (SecretKeyType.fromNum(cursor.getInt(INDEX_HAS_SECRET))) {                  case GNU_DUMMY: diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java index 1d09b281f..686c86b3a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java @@ -19,7 +19,9 @@  package org.sufficientlysecure.keychain.ui.base;  import java.io.IOException; +import java.math.BigInteger;  import java.nio.ByteBuffer; +import java.security.interfaces.RSAPrivateCrtKey;  import android.app.Activity;  import android.app.PendingIntent; @@ -32,9 +34,12 @@ import android.os.Bundle;  import android.widget.Toast;  import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.util.Arrays;  import org.spongycastle.util.encoders.Hex;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;  import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;  import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;  import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -54,12 +59,14 @@ import org.sufficientlysecure.keychain.util.Preferences;  public abstract class BaseNfcActivity extends BaseActivity { -    public static final int REQUEST_CODE_PASSPHRASE = 1; +    public static final int REQUEST_CODE_PIN = 1;      protected Passphrase mPin; +    protected Passphrase mAdminPin;      protected boolean mPw1ValidForMultipleSignatures;      protected boolean mPw1ValidatedForSignature;      protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? +    protected boolean mPw3Validated;      private NfcAdapter mNfcAdapter;      private IsoDep mIsoDep; @@ -138,7 +145,7 @@ public abstract class BaseNfcActivity extends BaseActivity {          Intent intent = new Intent(this, PassphraseDialogActivity.class);          intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,                  RequiredInputParcel.createRequiredPassphrase(requiredInput)); -        startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); +        startActivityForResult(intent, REQUEST_CODE_PIN);      } @@ -149,7 +156,7 @@ public abstract class BaseNfcActivity extends BaseActivity {      @Override      protected void onActivityResult(int requestCode, int resultCode, Intent data) {          switch (requestCode) { -            case REQUEST_CODE_PASSPHRASE: +            case REQUEST_CODE_PIN: {                  if (resultCode != Activity.RESULT_OK) {                      setResult(resultCode);                      finish(); @@ -158,6 +165,7 @@ public abstract class BaseNfcActivity extends BaseActivity {                  CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);                  mPin = input.getPassphrase();                  break; +            }              default:                  super.onActivityResult(requestCode, resultCode, data); @@ -208,6 +216,10 @@ public abstract class BaseNfcActivity extends BaseActivity {          mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1);          mPw1ValidatedForSignature = false;          mPw1ValidatedForDecrypt = false; +        mPw3Validated = false; + +        // TODO: Handle non-default Admin PIN +        mAdminPin = new Passphrase("12345678");          onNfcPerform(); @@ -460,13 +472,21 @@ public abstract class BaseNfcActivity extends BaseActivity {          return Hex.decode(decryptedSessionKey);      } -    /** Verifies the user's PW1 with the appropriate mode. +    /** Verifies the user's PW1 or PW3 with the appropriate mode.       * -     * @param mode This is 0x81 for signing, 0x82 for everything else +     * @param mode For PW1, this is 0x81 for signing, 0x82 for everything else. +     *             For PW3 (Admin PIN), mode is 0x83.       */      public void nfcVerifyPIN(int mode) throws IOException { -        if (mPin != null) { -            byte[] pin = new String(mPin.getCharArray()).getBytes(); +        if (mPin != null || mode == 0x83) { +            byte[] pin; + +            if (mode == 0x83) { +                pin = new String(mAdminPin.getCharArray()).getBytes(); +            } else { +                pin = new String(mPin.getCharArray()).getBytes(); +            } +              // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.              // See specification, page 51              String accepted = "9000"; @@ -488,8 +508,205 @@ public abstract class BaseNfcActivity extends BaseActivity {                  mPw1ValidatedForSignature = true;              } else if (mode == 0x82) {                  mPw1ValidatedForDecrypt = true; +            } else if (mode == 0x83) { +                mPw3Validated = true; +            } +        } +    } + +    /** Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for +     *  conformance to the card's requirements for key length. +     * +     * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. +     * @param newPinString The new PW1 or PW3. +     */ +    public void nfcModifyPIN(int pw, String newPinString) throws IOException { +        final int MAX_PW1_LENGTH_INDEX = 1; +        final int MAX_PW3_LENGTH_INDEX = 3; + +        byte[] pwStatusBytes = nfcGetPwStatusBytes(); +        byte[] newPin = newPinString.getBytes(); + +        if (pw == 0x81) { +            if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { +                throw new IOException("Invalid PIN length"); +            } +        } else if (pw == 0x83) { +            if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) { +                throw new IOException("Invalid PIN length");              } +        } else { +            throw new IOException("Invalid PW index for modify PIN operation"); +        } + +        byte[] pin; + +        if (pw == 0x83) { +            pin = new String(mAdminPin.getCharArray()).getBytes(); +        } else { +            pin = new String(mPin.getCharArray()).getBytes(); +        } + +        // Command APDU for CHANGE REFERENCE DATA command (page 32) +        String changeReferenceDataApdu = "00" // CLA +                + "24" // INS +                + "00" // P1 +                + String.format("%02x", pw) // P2 +                + String.format("%02x", pin.length + newPin.length) // Lc +                + getHex(pin) +                + getHex(newPin); +        if (!nfcCommunicate(changeReferenceDataApdu).equals("9000")) { // Change reference data +            handlePinError(); +            throw new IOException("Failed to change PIN"); +        } +    } + +    /** +     * Stores a data object on the card. Automatically validates the proper PIN for the operation. +     * Supported for all data objects < 255 bytes in length. Only the cardholder certificate +     * (0x7F21) can exceed this length. +     * +     * @param dataObject The data object to be stored. +     * @param data The data to store in the object +     */ +    public void nfcPutData(int dataObject, byte[] data) throws IOException { +        if (data.length > 254) { +            throw new IOException("Cannot PUT DATA with length > 254");          } +        if (dataObject == 0x0101 || dataObject == 0x0103) { +            if (!mPw1ValidatedForDecrypt) { +                nfcVerifyPIN(0x82); // (Verify PW1 for non-signing operations) +            } +        } else if (!mPw3Validated) { +            nfcVerifyPIN(0x83); // (Verify PW3) +        } + +        String putDataApdu = "00" // CLA +                + "DA" // INS +                + String.format("%02x", (dataObject & 0xFF00) >> 8) // P1 +                + String.format("%02x", dataObject & 0xFF) // P2 +                + String.format("%02x", data.length) // Lc +                + getHex(data); + +        String response = nfcCommunicate(putDataApdu); +        if (!response.equals("9000")) { +            throw new IOException("Failed to put data for tag " +                    + String.format("%02x", (dataObject & 0xFF00) >> 8) +                    + String.format("%02x", dataObject & 0xFF) +                    + ": " + response); +        } +    } + +    /** +     * Puts a key on the card in the given slot. +     * +     * @param slot The slot on the card where the key should be stored: +     *             0xB6: Signature Key +     *             0xB8: Decipherment Key +     *             0xA4: Authentication Key +     */ +    public void nfcPutKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) +            throws IOException { +        if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { +            throw new IOException("Invalid key slot"); +        } + +        RSAPrivateCrtKey crtSecretKey = null; +        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 smart card."); +        } + +        // 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 card key."); +        } + +        if (!mPw3Validated) { +            nfcVerifyPIN(0x83); // (Verify PW1 with mode 83) +        } + +        byte[] header= Hex.decode( +                "4D82" + "03A2"      // Extended header list 4D82, length of 930 bytes. (page 23) +                + String.format("%02x", slot) + "00" // CRT to indicate targeted key, no length +                + "7F48" + "15"      // Private key template 0x7F48, length 21 (decimal, 0x15 hex) +                + "9103"             // Public modulus, length 3 +                + "928180"           // Prime P, length 128 +                + "938180"           // Prime Q, length 128 +                + "948180"           // Coefficient (1/q mod p), length 128 +                + "958180"           // Prime exponent P (d mod (p - 1)), length 128 +                + "968180"           // Prime exponent Q (d mod (1 - 1)), length 128 +                + "97820100"         // Modulus, length 256, last item in private key template +                + "5F48" + "820383");// DO 5F48; 899 bytes of concatenated key data will follow +        byte[] dataToSend = new byte[934]; +        byte[] currentKeyObject; +        int offset = 0; + +        System.arraycopy(header, 0, dataToSend, offset, header.length); +        offset += header.length; +        currentKeyObject = crtSecretKey.getPublicExponent().toByteArray(); +        System.arraycopy(currentKeyObject, 0, dataToSend, offset, 3); +        offset += 3; +        // NOTE: For a 2048-bit key, these lengths are fixed. However, bigint includes a leading 0 +        // in the array to represent sign, so we take care to set the offset to 1 if necessary. +        currentKeyObject = crtSecretKey.getPrimeP().toByteArray(); +        System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); +        Arrays.fill(currentKeyObject, (byte)0); +        offset += 128; +        currentKeyObject = crtSecretKey.getPrimeQ().toByteArray(); +        System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); +        Arrays.fill(currentKeyObject, (byte)0); +        offset += 128; +        currentKeyObject = crtSecretKey.getCrtCoefficient().toByteArray(); +        System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); +        Arrays.fill(currentKeyObject, (byte)0); +        offset += 128; +        currentKeyObject = crtSecretKey.getPrimeExponentP().toByteArray(); +        System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); +        Arrays.fill(currentKeyObject, (byte)0); +        offset += 128; +        currentKeyObject = crtSecretKey.getPrimeExponentQ().toByteArray(); +        System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); +        Arrays.fill(currentKeyObject, (byte)0); +        offset += 128; +        currentKeyObject = crtSecretKey.getModulus().toByteArray(); +        System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256); + +        String putKeyCommand = "10DB3FFF"; +        String lastPutKeyCommand = "00DB3FFF"; + +        // Now we're ready to communicate with the card. +        offset = 0; +        String response; +        while(offset < dataToSend.length) { +            int dataRemaining = dataToSend.length - offset; +            if (dataRemaining > 254) { +                response = nfcCommunicate( +                        putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254) +                ); +                offset += 254; +            } else { +                int length = dataToSend.length - offset; +                response = nfcCommunicate( +                        lastPutKeyCommand + String.format("%02x", length) +                        + Hex.toHexString(dataToSend, offset, length)); +                offset += length; +            } + +            if (!response.endsWith("9000")) { +                throw new IOException("Key export to card failed"); +            } +        } + +        // Clear array with secret data before we return. +        Arrays.fill(dataToSend, (byte) 0);      }      /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java index 232a39f86..3b92f7208 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java @@ -43,6 +43,7 @@ public abstract class CryptoOperationFragment extends Fragment {      private void initiateInputActivity(RequiredInputParcel requiredInput) {          switch (requiredInput.mType) { +            case NFC_KEYTOCARD:              case NFC_DECRYPT:              case NFC_SIGN: {                  Intent intent = new Intent(getActivity(), NfcOperationActivity.class); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java index 9568312f5..eafa129f0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java @@ -35,6 +35,7 @@ public class EditSubkeyDialogFragment extends DialogFragment {      public static final int MESSAGE_CHANGE_EXPIRY = 1;      public static final int MESSAGE_REVOKE = 2;      public static final int MESSAGE_STRIP = 3; +    public static final int MESSAGE_KEYTOCARD = 4;      private Messenger mMessenger; @@ -76,6 +77,9 @@ public class EditSubkeyDialogFragment extends DialogFragment {                      case 2:                          sendMessageToHandler(MESSAGE_STRIP, null);                          break; +                    case 3: +                        sendMessageToHandler(MESSAGE_KEYTOCARD, null); +                        break;                      default:                          break;                  } diff --git a/OpenKeychain/src/main/res/values-cs/strings.xml b/OpenKeychain/src/main/res/values-cs/strings.xml index f99b32a2d..56ba11f29 100644 --- a/OpenKeychain/src/main/res/values-cs/strings.xml +++ b/OpenKeychain/src/main/res/values-cs/strings.xml @@ -424,6 +424,7 @@      <item>Změnit expiraci</item>      <item>Zneplatnit podklíč</item>      <item>Strip podklíč</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">nový podklíč</string>    <string name="edit_key_select_flag">Prosím vyberte alespoň jeden příznak!</string> diff --git a/OpenKeychain/src/main/res/values-de/strings.xml b/OpenKeychain/src/main/res/values-de/strings.xml index 4fd310837..8b27425ae 100644 --- a/OpenKeychain/src/main/res/values-de/strings.xml +++ b/OpenKeychain/src/main/res/values-de/strings.xml @@ -555,6 +555,7 @@      <item>Ablaufdatum ändern</item>      <item>Unterschlüssel widerrufen</item>      <item>Unterschlüssel kürzen</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">neuer Unterschlüssel</string>    <string name="edit_key_select_flag">Mindestens ein Attribut wählen!</string> diff --git a/OpenKeychain/src/main/res/values-es/strings.xml b/OpenKeychain/src/main/res/values-es/strings.xml index 2d94eb6a2..03f1c6265 100644 --- a/OpenKeychain/src/main/res/values-es/strings.xml +++ b/OpenKeychain/src/main/res/values-es/strings.xml @@ -555,6 +555,7 @@      <item>Cambiar expiración</item>      <item>Revocar subclave</item>      <item>Desnudar subclave</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">nueva subclave</string>    <string name="edit_key_select_flag">¡Por favor, seleccione al menos un indicador!</string> diff --git a/OpenKeychain/src/main/res/values-fr/strings.xml b/OpenKeychain/src/main/res/values-fr/strings.xml index f34dd8e46..c440cc242 100644 --- a/OpenKeychain/src/main/res/values-fr/strings.xml +++ b/OpenKeychain/src/main/res/values-fr/strings.xml @@ -555,6 +555,7 @@      <item>Changer l\'expiration</item>      <item>Révoquer la sous-clef</item>      <item>Dépouiller la sous-clef</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">nouvelle sous-clef</string>    <string name="edit_key_select_flag">Veuillez sélectionner au moins un drapeau !</string> diff --git a/OpenKeychain/src/main/res/values-it/strings.xml b/OpenKeychain/src/main/res/values-it/strings.xml index 6db044b49..2e598a0c2 100644 --- a/OpenKeychain/src/main/res/values-it/strings.xml +++ b/OpenKeychain/src/main/res/values-it/strings.xml @@ -409,6 +409,7 @@ Permetti accesso?\n\nATTENZIONE: Se non sai perche\' questo schermata e\' appars      <item>Cambia Scadenza</item>      <item>Revoca Sottochiave</item>      <item>Pulisci Sottochiave</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">nuova sottochiave</string>    <string name="edit_key_select_flag">Per favore seleziona almeno una spunta!</string> diff --git a/OpenKeychain/src/main/res/values-ja/strings.xml b/OpenKeychain/src/main/res/values-ja/strings.xml index 13647a1bb..18af0b482 100644 --- a/OpenKeychain/src/main/res/values-ja/strings.xml +++ b/OpenKeychain/src/main/res/values-ja/strings.xml @@ -547,6 +547,7 @@      <item>期限の変更</item>      <item>副鍵の破棄</item>      <item>副鍵のストリップ</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">新しい副鍵</string>    <string name="edit_key_select_flag">最低1つフラグを選択してください!</string> diff --git a/OpenKeychain/src/main/res/values-nl/strings.xml b/OpenKeychain/src/main/res/values-nl/strings.xml index 74e2c7117..e4d9b2c1e 100644 --- a/OpenKeychain/src/main/res/values-nl/strings.xml +++ b/OpenKeychain/src/main/res/values-nl/strings.xml @@ -555,6 +555,7 @@      <item>Vervaldatum veranderen</item>      <item>Subsleutel intrekken</item>      <item>Subsleutel strippen</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">nieuwe subsleutel</string>    <string name="edit_key_select_flag">Gelieve minstens een vlag te selecteren!</string> diff --git a/OpenKeychain/src/main/res/values-pl/strings.xml b/OpenKeychain/src/main/res/values-pl/strings.xml index b0cf1abaa..c7b461edf 100644 --- a/OpenKeychain/src/main/res/values-pl/strings.xml +++ b/OpenKeychain/src/main/res/values-pl/strings.xml @@ -472,6 +472,7 @@ OSTRZEŻENIE: Jeżeli nie wiesz, czemu wyświetlił się ten komunikat, nie zezw      <item>Zmień wygaśnięcie</item>      <item>Unieważnij Pod-klucz</item>      <item>Usuń Pod-klucz</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">nowy pod-klucz</string>    <string name="edit_key_select_flag">Prosimy o wybranie przynajmniej jeden flagi!</string> diff --git a/OpenKeychain/src/main/res/values-ru/strings.xml b/OpenKeychain/src/main/res/values-ru/strings.xml index ce0911af5..f4b60fcc1 100644 --- a/OpenKeychain/src/main/res/values-ru/strings.xml +++ b/OpenKeychain/src/main/res/values-ru/strings.xml @@ -456,6 +456,7 @@      <item>Изменить срок годности</item>      <item>Отозвать доп. ключ</item>      <item>Отделить доп. ключ</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">новый доп. ключ</string>    <string name="edit_key_select_flag">Пожалуйста, выберите хотя бы один флаг!</string> diff --git a/OpenKeychain/src/main/res/values-sl/strings.xml b/OpenKeychain/src/main/res/values-sl/strings.xml index 4fee0250d..02b268d6b 100644 --- a/OpenKeychain/src/main/res/values-sl/strings.xml +++ b/OpenKeychain/src/main/res/values-sl/strings.xml @@ -587,6 +587,7 @@      <item>Spremeni datum poteka</item>      <item>Prekliči podključ</item>      <item>Razstavi podključ</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">nov podključ</string>    <string name="edit_key_select_flag">Izberite vsaj eno oznako!</string> diff --git a/OpenKeychain/src/main/res/values-sr/strings.xml b/OpenKeychain/src/main/res/values-sr/strings.xml index c4c92dce5..ffcd55cb1 100644 --- a/OpenKeychain/src/main/res/values-sr/strings.xml +++ b/OpenKeychain/src/main/res/values-sr/strings.xml @@ -542,6 +542,7 @@      <item>Измени истицање</item>      <item>Опозови поткључ</item>      <item>Оголи поткључ</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">нови поткључ</string>    <string name="edit_key_select_flag">Изаберите бар једну заставицу!</string> diff --git a/OpenKeychain/src/main/res/values-zh-rTW/strings.xml b/OpenKeychain/src/main/res/values-zh-rTW/strings.xml index 95fa8a28c..f4b28d703 100644 --- a/OpenKeychain/src/main/res/values-zh-rTW/strings.xml +++ b/OpenKeychain/src/main/res/values-zh-rTW/strings.xml @@ -465,6 +465,7 @@      <item>變更到期日</item>      <item>撤銷子金鑰</item>      <item>Strip Subkey</item> +    <item>"Move Subkey to Yubikey / Smart Card"</item>    </string-array>    <string name="edit_key_new_subkey">新增子金鑰</string>    <string name="edit_key_error_add_identity">新增至少一組身分識別!</string> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 45b1dc26b..f5f58d01e 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -621,11 +621,15 @@          <item>"Change Expiry"</item>          <item>"Revoke Subkey"</item>          <item>"Strip Subkey"</item> +        <item>"Move Subkey to Yubikey / Smart Card"</item>      </string-array>      <string name="edit_key_new_subkey">"new subkey"</string>      <string name="edit_key_select_flag">"Please select at least one flag!"</string>      <string name="edit_key_error_add_identity">"Add at least one identity!"</string>      <string name="edit_key_error_add_subkey">"Add at least one subkey!"</string> +    <string name="edit_key_error_bad_nfc_algo">"Algorithm not supported by smart card!"</string> +    <string name="edit_key_error_bad_nfc_size">"Key size not supported by smart card!"</string> +    <string name="edit_key_error_bad_nfc_stripped">"Cannot move stripped / diverted key to smart card!"</string>      <!-- Create key -->      <string name="create_key_upload">"Synchronize with the cloud"</string> @@ -924,6 +928,9 @@      <string name="msg_mf_error_pgp">"Internal OpenPGP error!"</string>      <string name="msg_mf_error_sig">"Signature exception!"</string>      <string name="msg_mf_error_subkey_missing">"Tried to operate on missing subkey %s!"</string> +    <string name="msg_mf_error_conflicting_nfc_commands">"Cannot move key to card in same operation that creates an on-card signature."</string> +    <string name="msg_mf_error_duplicate_keytocard_for_slot">"Smart card supports only one slot per key type."</string> +    <string name="msg_mf_error_invalid_flags_for_keytocard">"Inappropriate key flags for smart card key."</string>      <string name="msg_mf_master">"Modifying master certifications"</string>      <string name="msg_mf_notation_empty">"Adding empty notation packet"</string>      <string name="msg_mf_notation_pin">"Adding PIN notation packet"</string> @@ -942,6 +949,8 @@      <string name="msg_mf_error_past_expiry">"Expiry date cannot be in the past!"</string>      <string name="msg_mf_subkey_revoke">"Revoking subkey %s"</string>      <string name="msg_mf_subkey_strip">"Stripping subkey %s"</string> +    <string name="msg_mf_keytocard_start">"Moving subkey %s to smart card"</string> +    <string name="msg_mf_keytocard_finish">"Moved %1$s to card %2$s"</string>      <string name="msg_mf_success">"Keyring successfully modified"</string>      <string name="msg_mf_uid_add">"Adding user ID %s"</string>      <string name="msg_mf_uid_primary">"Changing primary user ID to %s"</string> @@ -1213,6 +1222,8 @@      <string name="first_time_import_key">"Import key from file"</string>      <string name="first_time_yubikey">"Use YubiKey NEO"</string>      <string name="first_time_skip">"Skip Setup"</string> +    <string name="first_time_blank_smartcard_title">"Blank Smart Card / Yubikey Detected"</string> +    <string name="first_time_blank_smartcard_message">"Would you like to generate a smart card compatible key?"</string>      <!-- unsorted -->      <string name="section_certifier_id">"Certifier"</string> | 
