2043 lines
97 KiB
Diff
2043 lines
97 KiB
Diff
From d9952df66529b9dda611c4433ba09dfff3509633 Mon Sep 17 00:00:00 2001
|
|
From: Arnaud Fontaine <arnaud.fontaine@ssi.gouv.fr>
|
|
Date: Tue, 25 Oct 2016 15:23:30 +0200
|
|
Subject: [PATCH 1/3] Support of OpenPGP card v3
|
|
|
|
---
|
|
.../operations/results/OperationResult.java | 1 +
|
|
.../keychain/pgp/CanonicalizedPublicKey.java | 71 ++++++
|
|
.../keychain/pgp/CanonicalizedSecretKey.java | 23 ++
|
|
.../keychain/pgp/PgpKeyOperation.java | 54 ++++-
|
|
.../keychain/securitytoken/ECKeyFormat.java | 83 +++++++
|
|
.../keychain/securitytoken/KeyFormat.java | 114 +++++----
|
|
.../securitytoken/OpenPgpCapabilities.java | 28 ++-
|
|
.../keychain/securitytoken/RSAKeyFormat.java | 89 +++++++
|
|
.../securitytoken/SecurityTokenHelper.java | 263 ++++++++++++++++++---
|
|
.../keychain/ui/CreateKeyActivity.java | 10 +-
|
|
.../keychain/ui/CreateKeyFinalFragment.java | 9 +-
|
|
.../ui/CreateSecurityTokenAlgorithmFragment.java | 193 +++++++++++++++
|
|
.../ui/CreateSecurityTokenBlankFragment.java | 6 +-
|
|
.../ui/CreateSecurityTokenImportResetFragment.java | 4 +
|
|
.../ui/CreateSecurityTokenPinFragment.java | 2 +-
|
|
.../keychain/ui/EditKeyFragment.java | 40 +++-
|
|
.../ui/SecurityTokenOperationActivity.java | 12 +-
|
|
.../keychain/ui/ViewKeyActivity.java | 13 +-
|
|
.../keychain/ui/ViewKeyAdvSubkeysFragment.java | 40 +++-
|
|
.../keychain/ui/ViewKeySecurityTokenFragment.java | 6 +-
|
|
.../keychain/ui/adapter/SubkeysAdapter.java | 5 +
|
|
.../ui/base/BaseSecurityTokenActivity.java | 2 +-
|
|
.../keychain/util/SecurityTokenUtils.java | 112 ++++++++-
|
|
.../layout/create_yubi_key_algorithm_fragment.xml | 121 ++++++++++
|
|
OpenKeychain/src/main/res/values/strings.xml | 7 +
|
|
.../keychain/pgp/PgpKeyOperationTest.java | 2 +
|
|
.../securitytoken/SecurityTokenUtilsTest.java | 6 +-
|
|
27 files changed, 1153 insertions(+), 163 deletions(-)
|
|
create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java
|
|
create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java
|
|
create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java
|
|
create mode 100644 OpenKeychain/src/main/res/layout/create_yubi_key_algorithm_fragment.xml
|
|
|
|
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 be736d7..5cf6415 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
|
|
@@ -566,6 +566,7 @@ public abstract class OperationResult implements Parcelable {
|
|
MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD(LogLevel.ERROR, R.string.msg_mf_error_invalid_flags_for_keytocard),
|
|
MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_algo),
|
|
MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_size),
|
|
+ MSG_MF_ERROR_BAD_SECURITY_TOKEN_CURVE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_curve),
|
|
MSG_MF_ERROR_BAD_SECURITY_TOKEN_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_stripped),
|
|
MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master),
|
|
MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase),
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java
|
|
index 177fe15..95be5bb 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java
|
|
@@ -18,14 +18,27 @@
|
|
|
|
package org.sufficientlysecure.keychain.pgp;
|
|
|
|
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
|
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
|
|
+import org.bouncycastle.bcpg.ECDHPublicBCPGKey;
|
|
+import org.bouncycastle.bcpg.HashAlgorithmTags;
|
|
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
|
|
import org.bouncycastle.bcpg.sig.KeyFlags;
|
|
+import org.bouncycastle.openpgp.PGPException;
|
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
|
import org.bouncycastle.openpgp.PGPSignature;
|
|
+import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
|
+import org.bouncycastle.openpgp.operator.RFC6637Utils;
|
|
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
|
|
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
|
|
import org.sufficientlysecure.keychain.Constants;
|
|
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
|
import org.sufficientlysecure.keychain.util.Log;
|
|
|
|
+import java.io.IOException;
|
|
+import java.security.PublicKey;
|
|
+import java.security.interfaces.ECPublicKey;
|
|
import java.util.Calendar;
|
|
import java.util.Date;
|
|
import java.util.GregorianCalendar;
|
|
@@ -189,4 +202,62 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
|
|
return !isRevoked() && !isExpired();
|
|
}
|
|
|
|
+ // For use only in card export; returns the public key.
|
|
+ public ECPublicKey getECPublicKey()
|
|
+ throws PgpGeneralException {
|
|
+ JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
|
|
+ PublicKey retVal;
|
|
+ try {
|
|
+ retVal = keyConverter.getPublicKey(mPublicKey);
|
|
+ } catch (PGPException e) {
|
|
+ throw new PgpGeneralException("Error converting public key!", e);
|
|
+ }
|
|
+
|
|
+ return (ECPublicKey) retVal;
|
|
+ }
|
|
+
|
|
+ public ASN1ObjectIdentifier getHashAlgorithm()
|
|
+ throws PGPException {
|
|
+ if (!isEC()) {
|
|
+ throw new PGPException("Key encryption OID is valid only for EC key!");
|
|
+ }
|
|
+
|
|
+ final ECDHPublicBCPGKey eck = (ECDHPublicBCPGKey)mPublicKey.getPublicKeyPacket().getKey();
|
|
+
|
|
+ switch (eck.getHashAlgorithm()) {
|
|
+ case HashAlgorithmTags.SHA256:
|
|
+ return NISTObjectIdentifiers.id_sha256;
|
|
+ case HashAlgorithmTags.SHA384:
|
|
+ return NISTObjectIdentifiers.id_sha384;
|
|
+ case HashAlgorithmTags.SHA512:
|
|
+ return NISTObjectIdentifiers.id_sha512;
|
|
+ default:
|
|
+ throw new PGPException("Invalid hash algorithm for EC key : " + eck.getHashAlgorithm());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public int getSymmetricKeySize()
|
|
+ throws PGPException {
|
|
+ if (!isEC()) {
|
|
+ throw new PGPException("Key encryption OID is valid only for EC key!");
|
|
+ }
|
|
+
|
|
+ final ECDHPublicBCPGKey eck = (ECDHPublicBCPGKey)mPublicKey.getPublicKeyPacket().getKey();
|
|
+
|
|
+ switch (eck.getSymmetricKeyAlgorithm()) {
|
|
+ case SymmetricKeyAlgorithmTags.AES_128:
|
|
+ return 128;
|
|
+ case SymmetricKeyAlgorithmTags.AES_192:
|
|
+ return 192;
|
|
+ case SymmetricKeyAlgorithmTags.AES_256:
|
|
+ return 256;
|
|
+ default:
|
|
+ throw new PGPException("Invalid symmetric encryption algorithm for EC key : " + eck.getSymmetricKeyAlgorithm());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public byte[] createUserKeyingMaterial(KeyFingerPrintCalculator fingerPrintCalculator)
|
|
+ throws IOException, PGPException {
|
|
+ return RFC6637Utils.createUserKeyingMaterial(mPublicKey.getPublicKeyPacket(), fingerPrintCalculator);
|
|
+ }
|
|
}
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
|
|
index a02ff66..318c837 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
|
|
@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.security.PrivateKey;
|
|
+import java.security.interfaces.ECPrivateKey;
|
|
import java.security.interfaces.RSAPrivateCrtKey;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
@@ -319,6 +320,28 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
|
return (RSAPrivateCrtKey)retVal;
|
|
}
|
|
|
|
+ // For use only in card export; returns the secret key.
|
|
+ public ECPrivateKey getECSecretKey()
|
|
+ throws PgpGeneralException {
|
|
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
|
+ throw new PgpGeneralException("Cannot get secret key attributes while key is locked.");
|
|
+ }
|
|
+
|
|
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
|
|
+ throw new PgpGeneralException("Cannot get secret key attributes of divert-to-card key.");
|
|
+ }
|
|
+
|
|
+ JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
|
|
+ PrivateKey retVal;
|
|
+ try {
|
|
+ retVal = keyConverter.getPrivateKey(mPrivateKey);
|
|
+ } catch (PGPException e) {
|
|
+ throw new PgpGeneralException("Error converting private key! " + e.getMessage(), e);
|
|
+ }
|
|
+
|
|
+ return (ECPrivateKey) retVal;
|
|
+ }
|
|
+
|
|
public byte[] getIv() {
|
|
return mSecretKey.getIV();
|
|
}
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
|
|
index 404e072..ed5d925 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
|
|
@@ -35,6 +35,10 @@ import java.util.Iterator;
|
|
import java.util.Stack;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
|
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
|
+import org.bouncycastle.bcpg.ECDHPublicBCPGKey;
|
|
+import org.bouncycastle.bcpg.ECDSAPublicBCPGKey;
|
|
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
|
import org.bouncycastle.bcpg.S2K;
|
|
import org.bouncycastle.bcpg.sig.Features;
|
|
@@ -1645,20 +1649,44 @@ public class PgpKeyOperation {
|
|
}
|
|
|
|
private static boolean checkSecurityTokenCompatibility(PGPSecretKey key, OperationLog log, int indent) {
|
|
- PGPPublicKey publicKey = key.getPublicKey();
|
|
- int algorithm = publicKey.getAlgorithm();
|
|
- if (algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT &&
|
|
- algorithm != PublicKeyAlgorithmTags.RSA_SIGN &&
|
|
- algorithm != PublicKeyAlgorithmTags.RSA_GENERAL) {
|
|
- log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO, indent + 1);
|
|
- return false;
|
|
- }
|
|
|
|
- // Key size must be 2048
|
|
- int keySize = publicKey.getBitStrength();
|
|
- if (keySize != 2048) {
|
|
- log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE, indent + 1);
|
|
- return false;
|
|
+ final PGPPublicKey publicKey = key.getPublicKey();
|
|
+ ASN1ObjectIdentifier curve;
|
|
+
|
|
+ switch (publicKey.getAlgorithm()) {
|
|
+ case PublicKeyAlgorithmTags.RSA_ENCRYPT:
|
|
+ case PublicKeyAlgorithmTags.RSA_SIGN:
|
|
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
|
|
+ // Key size must be at least 2048
|
|
+ if (publicKey.getBitStrength() < 2048) {
|
|
+ log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE, indent + 1);
|
|
+ return false;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case PublicKeyAlgorithmTags.ECDH:
|
|
+ curve = ((ECDHPublicBCPGKey)(publicKey.getPublicKeyPacket().getKey())).getCurveOID();
|
|
+ if (!curve.equals(NISTNamedCurves.getOID("P-256")) &&
|
|
+ !curve.equals(NISTNamedCurves.getOID("P-384")) &&
|
|
+ !curve.equals(NISTNamedCurves.getOID("P-521"))) {
|
|
+ log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_CURVE, indent + 1);
|
|
+ return false;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case PublicKeyAlgorithmTags.ECDSA:
|
|
+ curve = ((ECDSAPublicBCPGKey)(publicKey.getPublicKeyPacket().getKey())).getCurveOID();
|
|
+ if (!curve.equals(NISTNamedCurves.getOID("P-256")) &&
|
|
+ !curve.equals(NISTNamedCurves.getOID("P-384")) &&
|
|
+ !curve.equals(NISTNamedCurves.getOID("P-521"))) {
|
|
+ log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_CURVE, indent + 1);
|
|
+ return false;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO, indent + 1);
|
|
+ return false;
|
|
}
|
|
|
|
// Secret key parts must be available
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java
|
|
new file mode 100644
|
|
index 0000000..d3b4d5f
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java
|
|
@@ -0,0 +1,83 @@
|
|
+package org.sufficientlysecure.keychain.securitytoken;
|
|
+
|
|
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
|
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
|
+import org.bouncycastle.asn1.x9.X9ECParameters;
|
|
+import org.bouncycastle.bcpg.sig.KeyFlags;
|
|
+import org.bouncycastle.math.ec.ECCurve;
|
|
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
|
+
|
|
+// 4.3.3.6 Algorithm Attributes
|
|
+public class ECKeyFormat extends KeyFormat {
|
|
+
|
|
+ private final ECAlgorithmFormat mECAlgorithmFormat;
|
|
+ private final ASN1ObjectIdentifier mECCurveOID;
|
|
+
|
|
+ public ECKeyFormat(final ASN1ObjectIdentifier ecCurveOid,
|
|
+ final ECAlgorithmFormat ecAlgorithmFormat) {
|
|
+ super(KeyFormatType.ECKeyFormatType);
|
|
+ mECAlgorithmFormat = ecAlgorithmFormat;
|
|
+ mECCurveOID = ecCurveOid;
|
|
+ }
|
|
+
|
|
+ public ECKeyFormat.ECAlgorithmFormat getAlgorithmFormat() {
|
|
+ return mECAlgorithmFormat;
|
|
+ }
|
|
+
|
|
+ public ASN1ObjectIdentifier getCurveOID() { return mECCurveOID; }
|
|
+
|
|
+ public enum ECAlgorithmFormat {
|
|
+ ECDH((byte)18, true, false),
|
|
+ ECDH_WITH_PUBKEY((byte)18, true, true),
|
|
+ ECDSA((byte)19, false, false),
|
|
+ ECDSA_WITH_PUBKEY((byte)19, false, true);
|
|
+
|
|
+ private final byte mValue;
|
|
+ private final boolean mIsECDH;
|
|
+ private final boolean mWithPubkey;
|
|
+
|
|
+ ECAlgorithmFormat(final byte value, final boolean isECDH, final boolean withPubkey) {
|
|
+ mValue = value;
|
|
+ mIsECDH = isECDH;
|
|
+ mWithPubkey = withPubkey;
|
|
+ }
|
|
+
|
|
+ public static ECKeyFormat.ECAlgorithmFormat from(final byte bFirst, final byte bLast) {
|
|
+ for (ECKeyFormat.ECAlgorithmFormat format : values()) {
|
|
+ if (format.mValue == bFirst && ((bLast == (byte)0xff) == format.isWithPubkey())) {
|
|
+ return format;
|
|
+ }
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ public final byte getValue() { return mValue; }
|
|
+ public final boolean isECDH() { return mIsECDH; }
|
|
+ public final boolean isWithPubkey() { return mWithPubkey; }
|
|
+ }
|
|
+
|
|
+ public void addToKeyring(SaveKeyringParcel keyring, int keyFlags) {
|
|
+ final X9ECParameters params = NISTNamedCurves.getByOID(mECCurveOID);
|
|
+ final ECCurve curve = params.getCurve();
|
|
+
|
|
+ SaveKeyringParcel.Algorithm algo = SaveKeyringParcel.Algorithm.ECDSA;
|
|
+ if (((keyFlags & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS)
|
|
+ || ((keyFlags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE)) {
|
|
+ algo = SaveKeyringParcel.Algorithm.ECDH;
|
|
+ }
|
|
+
|
|
+ SaveKeyringParcel.Curve scurve;
|
|
+ if (mECCurveOID.equals(NISTNamedCurves.getOID("P-256"))) {
|
|
+ scurve = SaveKeyringParcel.Curve.NIST_P256;
|
|
+ } else if (mECCurveOID.equals(NISTNamedCurves.getOID("P-384"))) {
|
|
+ scurve = SaveKeyringParcel.Curve.NIST_P384;
|
|
+ } else if (mECCurveOID.equals(NISTNamedCurves.getOID("P-521"))) {
|
|
+ scurve = SaveKeyringParcel.Curve.NIST_P521;
|
|
+ } else {
|
|
+ throw new IllegalArgumentException("Unsupported curve " + mECCurveOID);
|
|
+ }
|
|
+
|
|
+ keyring.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(algo,
|
|
+ curve.getFieldSize(), scurve, keyFlags, 0L));
|
|
+ }
|
|
+}
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java
|
|
index 3b2e93f..4a81d46 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java
|
|
@@ -17,71 +17,83 @@
|
|
|
|
package org.sufficientlysecure.keychain.securitytoken;
|
|
|
|
-// 4.3.3.6 Algorithm Attributes
|
|
-public class KeyFormat {
|
|
- private int mAlgorithmId;
|
|
- private int mModulusLength;
|
|
- private int mExponentLength;
|
|
- private AlgorithmFormat mAlgorithmFormat;
|
|
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
|
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
|
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
|
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
|
+import org.sufficientlysecure.keychain.ui.CreateSecurityTokenAlgorithmFragment;
|
|
|
|
- public KeyFormat(byte[] bytes) {
|
|
- mAlgorithmId = bytes[0];
|
|
- mModulusLength = bytes[1] << 8 | bytes[2];
|
|
- mExponentLength = bytes[3] << 8 | bytes[4];
|
|
- mAlgorithmFormat = AlgorithmFormat.from(bytes[5]);
|
|
+public abstract class KeyFormat {
|
|
|
|
- if (mAlgorithmId != 1) { // RSA
|
|
- throw new IllegalArgumentException("Unsupported Algorithm id " + mAlgorithmId);
|
|
- }
|
|
- }
|
|
+ public enum KeyFormatType {
|
|
+ RSAKeyFormatType,
|
|
+ ECKeyFormatType
|
|
+ };
|
|
|
|
- public int getAlgorithmId() {
|
|
- return mAlgorithmId;
|
|
- }
|
|
+ private final KeyFormatType mKeyFormatType;
|
|
|
|
- public int getModulusLength() {
|
|
- return mModulusLength;
|
|
+ public KeyFormat(final KeyFormatType keyFormatType) {
|
|
+ mKeyFormatType = keyFormatType;
|
|
}
|
|
|
|
- public int getExponentLength() {
|
|
- return mExponentLength;
|
|
+ public final KeyFormatType keyFormatType() {
|
|
+ return mKeyFormatType;
|
|
}
|
|
|
|
- public AlgorithmFormat getAlgorithmFormat() {
|
|
- return mAlgorithmFormat;
|
|
- }
|
|
-
|
|
- public enum AlgorithmFormat {
|
|
- STANDARD(0, false, false),
|
|
- STANDARD_WITH_MODULUS(1, false, true),
|
|
- CRT(2, true, false),
|
|
- CRT_WITH_MODULUS(3, true, true);
|
|
+ public static KeyFormat fromBytes(byte[] bytes) {
|
|
+ switch (bytes[0]) {
|
|
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
|
|
+ if (bytes.length < 6) {
|
|
+ throw new IllegalArgumentException("Bad length for RSA attributes");
|
|
+ }
|
|
+ return new RSAKeyFormat(bytes[1] << 8 | bytes[2],
|
|
+ bytes[3] << 8 | bytes[4],
|
|
+ RSAKeyFormat.RSAAlgorithmFormat.from(bytes[5]));
|
|
|
|
- private int mValue;
|
|
- private boolean mIncludeModulus;
|
|
- private boolean mIncludeCrt;
|
|
+ case PublicKeyAlgorithmTags.ECDH:
|
|
+ case PublicKeyAlgorithmTags.ECDSA:
|
|
+ if (bytes.length < 2) {
|
|
+ throw new IllegalArgumentException("Bad length for RSA attributes");
|
|
+ }
|
|
+ int len = bytes.length - 1;
|
|
+ if (bytes[bytes.length - 1] == (byte)0xff) {
|
|
+ len -= 1;
|
|
+ }
|
|
+ final byte[] boid = new byte[2 + len];
|
|
+ boid[0] = (byte)0x06;
|
|
+ boid[1] = (byte)len;
|
|
+ System.arraycopy(bytes, 1, boid, 2, len);
|
|
+ final ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(boid);
|
|
+ return new ECKeyFormat(oid, ECKeyFormat.ECAlgorithmFormat.from(bytes[0], bytes[bytes.length - 1]));
|
|
|
|
- AlgorithmFormat(int value, boolean includeCrt, boolean includeModulus) {
|
|
- mValue = value;
|
|
- mIncludeModulus = includeModulus;
|
|
- mIncludeCrt = includeCrt;
|
|
+ default:
|
|
+ throw new IllegalArgumentException("Unsupported Algorithm id " + bytes[0]);
|
|
}
|
|
+ }
|
|
|
|
- public static AlgorithmFormat from(byte b) {
|
|
- for (AlgorithmFormat format : values()) {
|
|
- if (format.mValue == b) {
|
|
- return format;
|
|
- }
|
|
- }
|
|
- return null;
|
|
- }
|
|
+ public static KeyFormat fromCreationKeyType(CreateSecurityTokenAlgorithmFragment.SupportedKeyType t, boolean forEncryption) {
|
|
+ final int elen = 17; //65537
|
|
+ final ECKeyFormat.ECAlgorithmFormat kf =
|
|
+ forEncryption ? ECKeyFormat.ECAlgorithmFormat.ECDH_WITH_PUBKEY : ECKeyFormat.ECAlgorithmFormat.ECDSA_WITH_PUBKEY;
|
|
|
|
- public boolean isIncludeModulus() {
|
|
- return mIncludeModulus;
|
|
+ switch (t) {
|
|
+ case RSA_2048:
|
|
+ return new RSAKeyFormat(2048, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
|
|
+ case RSA_3072:
|
|
+ return new RSAKeyFormat(3072, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
|
|
+ case RSA_4096:
|
|
+ return new RSAKeyFormat(4096, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
|
|
+ case ECC_P256:
|
|
+ return new ECKeyFormat(NISTNamedCurves.getOID("P-256"), kf);
|
|
+ case ECC_P384:
|
|
+ return new ECKeyFormat(NISTNamedCurves.getOID("P-384"), kf);
|
|
+ case ECC_P521:
|
|
+ return new ECKeyFormat(NISTNamedCurves.getOID("P-521"), kf);
|
|
}
|
|
|
|
- public boolean isIncludeCrt() {
|
|
- return mIncludeCrt;
|
|
- }
|
|
+ throw new IllegalArgumentException("Unsupported Algorithm id " + t);
|
|
}
|
|
+
|
|
+ public abstract void addToKeyring(SaveKeyringParcel keyring, int keyFlags);
|
|
+
|
|
}
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java
|
|
index 7a87a0a..d28a746 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java
|
|
@@ -36,15 +36,19 @@ public class OpenPgpCapabilities {
|
|
private boolean mAttriburesChangable;
|
|
private boolean mHasKeyImport;
|
|
|
|
- private byte mSMAlgo;
|
|
+ private int mSMAESKeySize;
|
|
private int mMaxCmdLen;
|
|
private int mMaxRspLen;
|
|
|
|
private Map<KeyType, KeyFormat> mKeyFormats;
|
|
|
|
public OpenPgpCapabilities(byte[] data) throws IOException {
|
|
- Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true);
|
|
mKeyFormats = new HashMap<>();
|
|
+ updateWithData(data);
|
|
+ }
|
|
+
|
|
+ public void updateWithData(byte[] data) throws IOException {
|
|
+ Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true);
|
|
if (tlvs.length == 1 && tlvs[0].mT == 0x6E) {
|
|
tlvs = ((Iso7816TLV.Iso7816CompositeTLV) tlvs[0]).mSubs;
|
|
}
|
|
@@ -64,13 +68,13 @@ public class OpenPgpCapabilities {
|
|
parseExtendedCaps(tlv.mV);
|
|
break;
|
|
case 0xC1:
|
|
- mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV));
|
|
+ mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV));
|
|
break;
|
|
case 0xC2:
|
|
- mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV));
|
|
+ mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV));
|
|
break;
|
|
case 0xC3:
|
|
- mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV));
|
|
+ mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV));
|
|
break;
|
|
case 0xC4:
|
|
mPw1ValidForMultipleSignatures = tlv.mV[0] == 1;
|
|
@@ -86,13 +90,13 @@ public class OpenPgpCapabilities {
|
|
parseExtendedCaps(tlv.mV);
|
|
break;
|
|
case 0xC1:
|
|
- mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV));
|
|
+ mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV));
|
|
break;
|
|
case 0xC2:
|
|
- mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV));
|
|
+ mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV));
|
|
break;
|
|
case 0xC3:
|
|
- mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV));
|
|
+ mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV));
|
|
break;
|
|
case 0xC4:
|
|
mPw1ValidForMultipleSignatures = tlv.mV[0] == 1;
|
|
@@ -106,7 +110,7 @@ public class OpenPgpCapabilities {
|
|
mHasKeyImport = (v[0] & MASK_KEY_IMPORT) != 0;
|
|
mAttriburesChangable =(v[0] & MASK_ATTRIBUTES_CHANGABLE) != 0;
|
|
|
|
- mSMAlgo = v[1];
|
|
+ mSMAESKeySize = (v[1] == 1) ? 16 : 32;
|
|
|
|
mMaxCmdLen = (v[6] << 8) + v[7];
|
|
mMaxRspLen = (v[8] << 8) + v[9];
|
|
@@ -128,7 +132,7 @@ public class OpenPgpCapabilities {
|
|
return mHasSM;
|
|
}
|
|
|
|
- public boolean isAttriburesChangable() {
|
|
+ public boolean isAttributesChangable() {
|
|
return mAttriburesChangable;
|
|
}
|
|
|
|
@@ -136,8 +140,8 @@ public class OpenPgpCapabilities {
|
|
return mHasKeyImport;
|
|
}
|
|
|
|
- public byte getSMAlgo() {
|
|
- return mSMAlgo;
|
|
+ public int getSMAESKeySize() {
|
|
+ return mSMAESKeySize;
|
|
}
|
|
|
|
public int getMaxCmdLen() {
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java
|
|
new file mode 100644
|
|
index 0000000..27b4274
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java
|
|
@@ -0,0 +1,89 @@
|
|
+/*
|
|
+ * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, either version 3 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
+ */
|
|
+
|
|
+package org.sufficientlysecure.keychain.securitytoken;
|
|
+
|
|
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
|
+
|
|
+// 4.3.3.6 Algorithm Attributes
|
|
+public class RSAKeyFormat extends KeyFormat {
|
|
+ private int mModulusLength;
|
|
+ private int mExponentLength;
|
|
+ private RSAAlgorithmFormat mRSAAlgorithmFormat;
|
|
+
|
|
+ public RSAKeyFormat(int modulusLength,
|
|
+ int exponentLength,
|
|
+ RSAAlgorithmFormat rsaAlgorithmFormat) {
|
|
+ super(KeyFormatType.RSAKeyFormatType);
|
|
+ mModulusLength = modulusLength;
|
|
+ mExponentLength = exponentLength;
|
|
+ mRSAAlgorithmFormat = rsaAlgorithmFormat;
|
|
+ }
|
|
+
|
|
+ public int getModulusLength() {
|
|
+ return mModulusLength;
|
|
+ }
|
|
+
|
|
+ public int getExponentLength() {
|
|
+ return mExponentLength;
|
|
+ }
|
|
+
|
|
+ public RSAAlgorithmFormat getAlgorithmFormat() {
|
|
+ return mRSAAlgorithmFormat;
|
|
+ }
|
|
+
|
|
+ public enum RSAAlgorithmFormat {
|
|
+ STANDARD((byte)0, false, false),
|
|
+ STANDARD_WITH_MODULUS((byte)1, false, true),
|
|
+ CRT((byte)2, true, false),
|
|
+ CRT_WITH_MODULUS((byte)3, true, true);
|
|
+
|
|
+ private byte mValue;
|
|
+ private boolean mIncludeModulus;
|
|
+ private boolean mIncludeCrt;
|
|
+
|
|
+ RSAAlgorithmFormat(byte value, boolean includeCrt, boolean includeModulus) {
|
|
+ mValue = value;
|
|
+ mIncludeModulus = includeModulus;
|
|
+ mIncludeCrt = includeCrt;
|
|
+ }
|
|
+
|
|
+ public static RSAAlgorithmFormat from(byte b) {
|
|
+ for (RSAAlgorithmFormat format : values()) {
|
|
+ if (format.mValue == b) {
|
|
+ return format;
|
|
+ }
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ public byte getValue() { return mValue; }
|
|
+
|
|
+ public boolean isIncludeModulus() {
|
|
+ return mIncludeModulus;
|
|
+ }
|
|
+
|
|
+ public boolean isIncludeCrt() {
|
|
+ return mIncludeCrt;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void addToKeyring(SaveKeyringParcel keyring, int keyFlags) {
|
|
+ keyring.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(SaveKeyringParcel.Algorithm.RSA,
|
|
+ mModulusLength, null, keyFlags, 0L));
|
|
+ }
|
|
+}
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java
|
|
index 878bf9d..c3619db 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java
|
|
@@ -23,12 +23,28 @@ package org.sufficientlysecure.keychain.securitytoken;
|
|
|
|
import android.support.annotation.NonNull;
|
|
|
|
+import org.bouncycastle.asn1.ASN1Encodable;
|
|
+import org.bouncycastle.asn1.ASN1Integer;
|
|
+import org.bouncycastle.asn1.ASN1OutputStream;
|
|
+import org.bouncycastle.asn1.DERSequence;
|
|
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
|
+import org.bouncycastle.asn1.x9.X9ECParameters;
|
|
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
|
+import org.bouncycastle.jcajce.util.MessageDigestUtils;
|
|
+import org.bouncycastle.math.ec.ECPoint;
|
|
+import org.bouncycastle.openpgp.PGPException;
|
|
+import org.bouncycastle.openpgp.operator.PGPPad;
|
|
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
|
import org.bouncycastle.util.Arrays;
|
|
import org.bouncycastle.util.encoders.Hex;
|
|
import org.sufficientlysecure.keychain.Constants;
|
|
+import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
|
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
|
+
|
|
+import javax.crypto.Cipher;
|
|
+import javax.crypto.NoSuchPaddingException;
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
import javax.smartcardio.CommandAPDU;
|
|
import javax.smartcardio.ResponseAPDU;
|
|
import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException;
|
|
@@ -41,6 +57,12 @@ import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.math.BigInteger;
|
|
import java.nio.ByteBuffer;
|
|
+import java.security.InvalidKeyException;
|
|
+import java.security.Key;
|
|
+import java.security.MessageDigest;
|
|
+import java.security.NoSuchAlgorithmException;
|
|
+import java.security.interfaces.ECPrivateKey;
|
|
+import java.security.interfaces.ECPublicKey;
|
|
import java.security.interfaces.RSAPrivateCrtKey;
|
|
|
|
/**
|
|
@@ -64,6 +86,9 @@ public class SecurityTokenHelper {
|
|
private static final String FIDESMO_APPS_AID_PREFIX = "A000000617";
|
|
|
|
private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
+
|
|
+ private final JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator();
|
|
+
|
|
private Transport mTransport;
|
|
private CardCapabilities mCardCapabilities;
|
|
private OpenPgpCapabilities mOpenPgpCapabilities;
|
|
@@ -77,6 +102,12 @@ public class SecurityTokenHelper {
|
|
protected SecurityTokenHelper() {
|
|
}
|
|
|
|
+ public static double parseOpenPgpVersion(final byte[] aid) {
|
|
+ float minv = aid[7];
|
|
+ while (minv > 0) minv /= 10.0;
|
|
+ return aid[6] + minv;
|
|
+ }
|
|
+
|
|
public static SecurityTokenHelper getInstance() {
|
|
return LazyHolder.SECURITY_TOKEN_HELPER;
|
|
}
|
|
@@ -218,17 +249,62 @@ public class SecurityTokenHelper {
|
|
* Call DECIPHER command
|
|
*
|
|
* @param encryptedSessionKey the encoded session key
|
|
+ * @param publicKey
|
|
* @return the decoded session key
|
|
*/
|
|
- public byte[] decryptSessionKey(@NonNull byte[] encryptedSessionKey) throws IOException {
|
|
+ public byte[] decryptSessionKey(@NonNull byte[] encryptedSessionKey,
|
|
+ CanonicalizedPublicKey publicKey)
|
|
+ throws IOException {
|
|
+ final KeyFormat kf = mOpenPgpCapabilities.getFormatForKeyType(KeyType.ENCRYPT);
|
|
+
|
|
if (!mPw1ValidatedForDecrypt) {
|
|
verifyPin(0x82); // (Verify PW1 with mode 82 for decryption)
|
|
}
|
|
|
|
- // Transmit
|
|
- byte[] data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length);
|
|
- if (data[0] != 0) {
|
|
- data = Arrays.prepend(data, (byte) 0x00);
|
|
+ byte[] data;
|
|
+ int pLen = 0;
|
|
+
|
|
+ X9ECParameters x9Params = null;
|
|
+
|
|
+ switch (kf.keyFormatType()) {
|
|
+ case RSAKeyFormatType:
|
|
+ data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length);
|
|
+ if (data[0] != 0) {
|
|
+ data = Arrays.prepend(data, (byte) 0x00);
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case ECKeyFormatType:
|
|
+ pLen = ((((encryptedSessionKey[0] & 0xff) << 8) + (encryptedSessionKey[1] & 0xff)) + 7) / 8;
|
|
+ data = new byte[pLen];
|
|
+
|
|
+ System.arraycopy(encryptedSessionKey, 2, data, 0, pLen);
|
|
+
|
|
+ final ECKeyFormat eckf = (ECKeyFormat)kf;
|
|
+ x9Params = NISTNamedCurves.getByOID(eckf.getCurveOID());
|
|
+
|
|
+ final ECPoint p = x9Params.getCurve().decodePoint(data);
|
|
+ if (!p.isValid()) {
|
|
+ throw new CardException("Invalid EC point!");
|
|
+ }
|
|
+
|
|
+ data = p.getEncoded(false);
|
|
+ data = Arrays.concatenate(
|
|
+ Hex.decode("86"),
|
|
+ new byte[]{ (byte)data.length },
|
|
+ data);
|
|
+ data = Arrays.concatenate(
|
|
+ Hex.decode("7F49"),
|
|
+ new byte[] { (byte)data.length },
|
|
+ data);
|
|
+ data = Arrays.concatenate(
|
|
+ Hex.decode("A6"),
|
|
+ new byte[] { (byte)data.length },
|
|
+ data);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ throw new CardException("Unknown encryption key type!");
|
|
}
|
|
|
|
CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x80, 0x86, data, MAX_APDU_NE_EXT);
|
|
@@ -238,7 +314,47 @@ public class SecurityTokenHelper {
|
|
throw new CardException("Deciphering with Security token failed on receive", response.getSW());
|
|
}
|
|
|
|
- return response.getData();
|
|
+ switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.ENCRYPT).keyFormatType()) {
|
|
+ case RSAKeyFormatType:
|
|
+ return response.getData();
|
|
+
|
|
+ case ECKeyFormatType:
|
|
+ data = response.getData();
|
|
+
|
|
+ final byte[] keyEnc = new byte[encryptedSessionKey[pLen + 2]];
|
|
+
|
|
+ System.arraycopy(encryptedSessionKey, 2 + pLen + 1, keyEnc, 0, keyEnc.length);
|
|
+
|
|
+ try {
|
|
+ final MessageDigest kdf = MessageDigest.getInstance(MessageDigestUtils.getDigestName(publicKey.getHashAlgorithm()));
|
|
+
|
|
+ kdf.update(new byte[]{ (byte)0, (byte)0, (byte)0, (byte)1 });
|
|
+ kdf.update(data);
|
|
+ kdf.update(publicKey.createUserKeyingMaterial(fingerprintCalculator));
|
|
+
|
|
+ final byte[] kek = kdf.digest();
|
|
+ final Cipher c = Cipher.getInstance("AESWrap");
|
|
+
|
|
+ c.init(Cipher.UNWRAP_MODE, new SecretKeySpec(kek, 0, publicKey.getSymmetricKeySize() / 8, "AES"));
|
|
+
|
|
+ final Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY);
|
|
+
|
|
+ Arrays.fill(kek, (byte)0);
|
|
+
|
|
+ return PGPPad.unpadSessionData(paddedSessionKey.getEncoded());
|
|
+ } catch (NoSuchAlgorithmException e) {
|
|
+ throw new CardException("Unknown digest/encryption algorithm!");
|
|
+ } catch (NoSuchPaddingException e) {
|
|
+ throw new CardException("Unknown padding algorithm!");
|
|
+ } catch (PGPException e) {
|
|
+ throw new CardException(e.getMessage());
|
|
+ } catch (InvalidKeyException e) {
|
|
+ throw new CardException("Invalid KEK!");
|
|
+ }
|
|
+
|
|
+ default:
|
|
+ throw new CardException("Unknown encryption key type!");
|
|
+ }
|
|
}
|
|
|
|
/**
|
|
@@ -300,6 +416,36 @@ public class SecurityTokenHelper {
|
|
}
|
|
}
|
|
|
|
+
|
|
+ private void setKeyAttributes(final KeyType slot, final CanonicalizedSecretKey secretKey)
|
|
+ throws IOException {
|
|
+
|
|
+ if (mOpenPgpCapabilities.isAttributesChangable()) {
|
|
+ int tag;
|
|
+
|
|
+ if (slot == KeyType.SIGN) {
|
|
+ tag = 0xC1;
|
|
+ } else if (slot == KeyType.ENCRYPT) {
|
|
+ tag = 0xC2;
|
|
+ } else if (slot == KeyType.AUTH) {
|
|
+ tag = 0xC3;
|
|
+ } else {
|
|
+ throw new IOException("Unknown key for card.");
|
|
+ }
|
|
+
|
|
+ try {
|
|
+
|
|
+ putData(tag, SecurityTokenUtils.attributesFromSecretKey(slot, secretKey));
|
|
+
|
|
+ mOpenPgpCapabilities.updateWithData(getData(0x00, tag));
|
|
+
|
|
+ } catch (PgpGeneralException e) {
|
|
+ throw new IOException("Key algorithm not supported by the security token.");
|
|
+ }
|
|
+
|
|
+ }
|
|
+ }
|
|
+
|
|
/**
|
|
* Puts a key on the token in the given slot.
|
|
*
|
|
@@ -311,33 +457,58 @@ public class SecurityTokenHelper {
|
|
private void putKey(KeyType slot, CanonicalizedSecretKey secretKey, Passphrase passphrase)
|
|
throws IOException {
|
|
RSAPrivateCrtKey crtSecretKey;
|
|
+ ECPrivateKey ecSecretKey;
|
|
+ ECPublicKey ecPublicKey;
|
|
+
|
|
+ if (!mPw3Validated) {
|
|
+ verifyPin(0x83); // (Verify PW3 with mode 83)
|
|
+ }
|
|
+
|
|
+ // Now we're ready to communicate with the token.
|
|
+ byte[] keyBytes = 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 Security Token.");
|
|
- }
|
|
+ setKeyAttributes(slot, secretKey);
|
|
|
|
- // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
|
|
- if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
|
|
- throw new IOException("Invalid public exponent for smart Security Token.");
|
|
- }
|
|
+ switch (mOpenPgpCapabilities.getFormatForKeyType(slot).keyFormatType()) {
|
|
+ case RSAKeyFormatType:
|
|
+ if (!secretKey.isRSA()) {
|
|
+ throw new IOException("Security Token not configured for RSA key.");
|
|
+ }
|
|
+ crtSecretKey = secretKey.getCrtSecretKey();
|
|
|
|
- if (!mPw3Validated) {
|
|
- verifyPin(0x83); // (Verify PW3 with mode 83)
|
|
- }
|
|
+ // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
|
|
+ if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
|
|
+ throw new IOException("Invalid public exponent for smart Security Token.");
|
|
+ }
|
|
|
|
+ keyBytes = SecurityTokenUtils.createRSAPrivKeyTemplate(crtSecretKey, slot,
|
|
+ (RSAKeyFormat) (mOpenPgpCapabilities.getFormatForKeyType(slot)));
|
|
+ break;
|
|
|
|
- // Now we're ready to communicate with the token.
|
|
- byte[] bytes = SecurityTokenUtils.createPrivKeyTemplate(crtSecretKey, slot,
|
|
- mOpenPgpCapabilities.getFormatForKeyType(slot));
|
|
+ case ECKeyFormatType:
|
|
+ if (!secretKey.isEC()) {
|
|
+ throw new IOException("Security Token not configured for EC key.");
|
|
+ }
|
|
+
|
|
+ secretKey.unlock(passphrase);
|
|
+ ecSecretKey = secretKey.getECSecretKey();
|
|
+ ecPublicKey = secretKey.getECPublicKey();
|
|
|
|
- CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, bytes);
|
|
+ keyBytes = SecurityTokenUtils.createECPrivKeyTemplate(ecSecretKey, ecPublicKey, slot,
|
|
+ (ECKeyFormat) (mOpenPgpCapabilities.getFormatForKeyType(slot)));
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ throw new IOException("Key type unsupported by security token.");
|
|
+ }
|
|
+ } catch (PgpGeneralException e) {
|
|
+ throw new IOException(e.getMessage());
|
|
+ }
|
|
+
|
|
+ CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, keyBytes);
|
|
ResponseAPDU response = communicate(apdu);
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) {
|
|
@@ -462,8 +633,21 @@ public class SecurityTokenHelper {
|
|
throw new IOException("Not supported hash algo!");
|
|
}
|
|
|
|
+ byte[] data;
|
|
+
|
|
+ switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.SIGN).keyFormatType()) {
|
|
+ case RSAKeyFormatType:
|
|
+ data = dsi;
|
|
+ break;
|
|
+ case ECKeyFormatType:
|
|
+ data = hash;
|
|
+ break;
|
|
+ default:
|
|
+ throw new IOException("Not supported key type!");
|
|
+ }
|
|
+
|
|
// Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
|
|
- CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, dsi, MAX_APDU_NE_EXT);
|
|
+ CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, data, MAX_APDU_NE_EXT);
|
|
ResponseAPDU response = communicate(command);
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) {
|
|
@@ -477,9 +661,30 @@ public class SecurityTokenHelper {
|
|
byte[] signature = response.getData();
|
|
|
|
// Make sure the signature we received is actually the expected number of bytes long!
|
|
- if (signature.length != 128 && signature.length != 256
|
|
- && signature.length != 384 && signature.length != 512) {
|
|
- throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length);
|
|
+ switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.SIGN).keyFormatType()) {
|
|
+ case RSAKeyFormatType:
|
|
+ if (signature.length != 128 && signature.length != 256
|
|
+ && signature.length != 384 && signature.length != 512) {
|
|
+ throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length);
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case ECKeyFormatType:
|
|
+ if (signature.length % 2 != 0) {
|
|
+ throw new IOException("Bad signature length!");
|
|
+ }
|
|
+ final byte[] br = new byte[signature.length / 2];
|
|
+ final byte[] bs = new byte[signature.length / 2];
|
|
+ for(int i = 0; i < br.length; ++i) {
|
|
+ br[i] = signature[i];
|
|
+ bs[i] = signature[br.length + i];
|
|
+ }
|
|
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
+ ASN1OutputStream out = new ASN1OutputStream(baos);
|
|
+ out.writeObject(new DERSequence(new ASN1Encodable[] { new ASN1Integer(br), new ASN1Integer(bs) }));
|
|
+ out.flush();
|
|
+ signature = baos.toByteArray();
|
|
+ break;
|
|
}
|
|
|
|
return signature;
|
|
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 b0e922c..19cbb76 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
|
|
@@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
|
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|
+import org.sufficientlysecure.keychain.securitytoken.KeyFormat;
|
|
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
|
@@ -64,6 +65,9 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
|
boolean mCreateSecurityToken;
|
|
Passphrase mSecurityTokenPin;
|
|
Passphrase mSecurityTokenAdminPin;
|
|
+ KeyFormat mSecurityTokenSign;
|
|
+ KeyFormat mSecurityTokenDec;
|
|
+ KeyFormat mSecurityTokenAuth;
|
|
|
|
Fragment mCurrentFragment;
|
|
|
|
@@ -97,6 +101,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
|
mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE);
|
|
mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME);
|
|
mCreateSecurityToken = savedInstanceState.getBoolean(EXTRA_CREATE_SECURITY_TOKEN);
|
|
+ mSecurityTokenAid = savedInstanceState.getByteArray(EXTRA_SECURITY_TOKEN_AID);
|
|
mSecurityTokenPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_PIN);
|
|
mSecurityTokenAdminPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN);
|
|
|
|
@@ -122,7 +127,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
|
|
|
setTitle(R.string.title_import_keys);
|
|
} else {
|
|
- Fragment frag = CreateSecurityTokenBlankFragment.newInstance();
|
|
+ Fragment frag = CreateSecurityTokenBlankFragment.newInstance(nfcAid);
|
|
loadFragment(frag, FragAction.START);
|
|
setTitle(R.string.title_manage_my_keys);
|
|
}
|
|
@@ -192,7 +197,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
|
loadFragment(frag, FragAction.TO_RIGHT);
|
|
}
|
|
} else {
|
|
- Fragment frag = CreateSecurityTokenBlankFragment.newInstance();
|
|
+ Fragment frag = CreateSecurityTokenBlankFragment.newInstance(mSecurityTokenAid);
|
|
loadFragment(frag, FragAction.TO_RIGHT);
|
|
}
|
|
}
|
|
@@ -221,6 +226,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
|
outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase);
|
|
outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime);
|
|
outState.putBoolean(EXTRA_CREATE_SECURITY_TOKEN, mCreateSecurityToken);
|
|
+ outState.putByteArray(EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
|
outState.putParcelable(EXTRA_SECURITY_TOKEN_PIN, mSecurityTokenPin);
|
|
outState.putParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN, mSecurityTokenAdminPin);
|
|
}
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
|
|
index 99a4b02..1d5473d 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
|
|
@@ -282,12 +282,9 @@ public class CreateKeyFinalFragment extends Fragment {
|
|
SaveKeyringParcel saveKeyringParcel = new SaveKeyringParcel();
|
|
|
|
if (createKeyActivity.mCreateSecurityToken) {
|
|
- saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
|
- 2048, null, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER, 0L));
|
|
- saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
|
- 2048, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
|
|
- saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
|
- 2048, null, KeyFlags.AUTHENTICATION, 0L));
|
|
+ createKeyActivity.mSecurityTokenSign.addToKeyring(saveKeyringParcel, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER);
|
|
+ createKeyActivity.mSecurityTokenDec.addToKeyring(saveKeyringParcel, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
|
|
+ createKeyActivity.mSecurityTokenAuth.addToKeyring(saveKeyringParcel, KeyFlags.AUTHENTICATION);
|
|
|
|
// use empty passphrase
|
|
saveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(new Passphrase()));
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java
|
|
new file mode 100644
|
|
index 0000000..90a9022
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java
|
|
@@ -0,0 +1,193 @@
|
|
+/*
|
|
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, either version 3 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
+ */
|
|
+
|
|
+package org.sufficientlysecure.keychain.ui;
|
|
+
|
|
+import android.app.Activity;
|
|
+import android.content.Context;
|
|
+import android.os.Bundle;
|
|
+import android.support.v4.app.Fragment;
|
|
+import android.support.v4.app.FragmentActivity;
|
|
+import android.text.Html;
|
|
+import android.view.LayoutInflater;
|
|
+import android.view.View;
|
|
+import android.view.ViewGroup;
|
|
+import android.widget.ArrayAdapter;
|
|
+import android.widget.Spinner;
|
|
+import android.widget.TextView;
|
|
+
|
|
+import org.sufficientlysecure.keychain.R;
|
|
+import org.sufficientlysecure.keychain.securitytoken.KeyFormat;
|
|
+import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper;
|
|
+import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
|
+import org.sufficientlysecure.keychain.util.Choice;
|
|
+import org.sufficientlysecure.keychain.util.Log;
|
|
+import org.sufficientlysecure.keychain.util.SecurityTokenUtils;
|
|
+
|
|
+import java.util.ArrayList;
|
|
+import java.util.List;
|
|
+
|
|
+public class CreateSecurityTokenAlgorithmFragment extends Fragment {
|
|
+
|
|
+
|
|
+ public enum SupportedKeyType {
|
|
+ RSA_2048, RSA_3072, RSA_4096, ECC_P256, ECC_P384, ECC_P521
|
|
+ }
|
|
+
|
|
+ private CreateKeyActivity mCreateKeyActivity;
|
|
+
|
|
+ private View mBackButton;
|
|
+ private View mNextButton;
|
|
+
|
|
+ private Spinner mSignKeySpinner;
|
|
+ private Spinner mDecKeySpinner;
|
|
+ private Spinner mAuthKeySpinner;
|
|
+
|
|
+
|
|
+ /**
|
|
+ * Creates new instance of this fragment
|
|
+ */
|
|
+ public static CreateSecurityTokenAlgorithmFragment newInstance() {
|
|
+ CreateSecurityTokenAlgorithmFragment frag = new CreateSecurityTokenAlgorithmFragment();
|
|
+
|
|
+ Bundle args = new Bundle();
|
|
+ frag.setArguments(args);
|
|
+
|
|
+ return frag;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
+ final FragmentActivity context = getActivity();
|
|
+ View view = inflater.inflate(R.layout.create_yubi_key_algorithm_fragment, container, false);
|
|
+
|
|
+ mBackButton = (TextView) view.findViewById(R.id.create_key_back_button);
|
|
+ mNextButton = (TextView) view.findViewById(R.id.create_key_next_button);
|
|
+
|
|
+ mBackButton.setOnClickListener(new View.OnClickListener() {
|
|
+ @Override
|
|
+ public void onClick(View v) {
|
|
+ back();
|
|
+ }
|
|
+ });
|
|
+ mNextButton.setOnClickListener(new View.OnClickListener() {
|
|
+ @Override
|
|
+ public void onClick(View v) {
|
|
+ nextClicked();
|
|
+ }
|
|
+ });
|
|
+
|
|
+ mSignKeySpinner = (Spinner) view.findViewById(R.id.create_key_yubi_key_algorithm_sign);
|
|
+ mDecKeySpinner = (Spinner) view.findViewById(R.id.create_key_yubi_key_algorithm_dec);
|
|
+ mAuthKeySpinner = (Spinner) view.findViewById(R.id.create_key_yubi_key_algorithm_auth);
|
|
+
|
|
+ ArrayList<Choice<SupportedKeyType>> choices = new ArrayList<>();
|
|
+
|
|
+ choices.add(new Choice<>(SupportedKeyType.RSA_2048, getResources().getString(
|
|
+ R.string.rsa_2048), getResources().getString(R.string.rsa_2048_description_html)));
|
|
+ choices.add(new Choice<>(SupportedKeyType.RSA_3072, getResources().getString(
|
|
+ R.string.rsa_3072), getResources().getString(R.string.rsa_3072_description_html)));
|
|
+ choices.add(new Choice<>(SupportedKeyType.RSA_4096, getResources().getString(
|
|
+ R.string.rsa_4096), getResources().getString(R.string.rsa_4096_description_html)));
|
|
+
|
|
+ final double version = SecurityTokenHelper.parseOpenPgpVersion(mCreateKeyActivity.mSecurityTokenAid);
|
|
+
|
|
+ if (version >= 3.0) {
|
|
+ choices.add(new Choice<>(SupportedKeyType.ECC_P256, getResources().getString(
|
|
+ R.string.ecc_p256), getResources().getString(R.string.ecc_p256_description_html)));
|
|
+ choices.add(new Choice<>(SupportedKeyType.ECC_P384, getResources().getString(
|
|
+ R.string.ecc_p384), getResources().getString(R.string.ecc_p384_description_html)));
|
|
+ choices.add(new Choice<>(SupportedKeyType.ECC_P521, getResources().getString(
|
|
+ R.string.ecc_p521), getResources().getString(R.string.ecc_p521_description_html)));
|
|
+ }
|
|
+
|
|
+ TwoLineArrayAdapter adapter = new TwoLineArrayAdapter(context,
|
|
+ android.R.layout.simple_spinner_item, choices);
|
|
+ mSignKeySpinner.setAdapter(adapter);
|
|
+ mDecKeySpinner.setAdapter(adapter);
|
|
+ mAuthKeySpinner.setAdapter(adapter);
|
|
+
|
|
+ // make ECC nist256 the default for v3.x
|
|
+ for (int i = 0; i < choices.size(); ++i) {
|
|
+ if (version >= 3.0) {
|
|
+ if (choices.get(i).getId() == SupportedKeyType.ECC_P256) {
|
|
+ mSignKeySpinner.setSelection(i);
|
|
+ mDecKeySpinner.setSelection(i);
|
|
+ mAuthKeySpinner.setSelection(i);
|
|
+ break;
|
|
+ }
|
|
+ } else {
|
|
+ if (choices.get(i).getId() == SupportedKeyType.RSA_2048) {
|
|
+ mSignKeySpinner.setSelection(i);
|
|
+ mDecKeySpinner.setSelection(i);
|
|
+ mAuthKeySpinner.setSelection(i);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return view;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onAttach(Activity activity) {
|
|
+ super.onAttach(activity);
|
|
+ mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
|
+ }
|
|
+
|
|
+ private void back() {
|
|
+ mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
|
|
+ }
|
|
+
|
|
+ private void nextClicked() {
|
|
+ mCreateKeyActivity.mSecurityTokenSign = KeyFormat.fromCreationKeyType(((Choice<SupportedKeyType>)mSignKeySpinner.getSelectedItem()).getId(), false);
|
|
+ mCreateKeyActivity.mSecurityTokenDec = KeyFormat.fromCreationKeyType(((Choice<SupportedKeyType>)mDecKeySpinner.getSelectedItem()).getId(), true);
|
|
+ mCreateKeyActivity.mSecurityTokenAuth = KeyFormat.fromCreationKeyType(((Choice<SupportedKeyType>)mAuthKeySpinner.getSelectedItem()).getId(), false);
|
|
+
|
|
+ CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance();
|
|
+ mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
|
|
+ }
|
|
+
|
|
+
|
|
+
|
|
+
|
|
+ private class TwoLineArrayAdapter extends ArrayAdapter<Choice<SupportedKeyType>> {
|
|
+ public TwoLineArrayAdapter(Context context, int resource, List<Choice<SupportedKeyType>> objects) {
|
|
+ super(context, resource, objects);
|
|
+ }
|
|
+
|
|
+
|
|
+ @Override
|
|
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
|
|
+ // inflate view if not given one
|
|
+ if (convertView == null) {
|
|
+ convertView = getActivity().getLayoutInflater()
|
|
+ .inflate(R.layout.two_line_spinner_dropdown_item, parent, false);
|
|
+ }
|
|
+
|
|
+ Choice c = this.getItem(position);
|
|
+
|
|
+ TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
|
|
+ TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
|
|
+
|
|
+ text1.setText(c.getName());
|
|
+ text2.setText(Html.fromHtml(c.getDescription()));
|
|
+
|
|
+ return convertView;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java
|
|
index 08441c1..3f52e09 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java
|
|
@@ -33,14 +33,17 @@ public class CreateSecurityTokenBlankFragment extends Fragment {
|
|
View mBackButton;
|
|
View mNextButton;
|
|
|
|
+ private byte[] mAid;
|
|
+
|
|
/**
|
|
* Creates new instance of this fragment
|
|
*/
|
|
- public static CreateSecurityTokenBlankFragment newInstance() {
|
|
+ public static CreateSecurityTokenBlankFragment newInstance(byte[] aid) {
|
|
CreateSecurityTokenBlankFragment frag = new CreateSecurityTokenBlankFragment();
|
|
|
|
Bundle args = new Bundle();
|
|
|
|
+ frag.mAid = aid;
|
|
frag.setArguments(args);
|
|
|
|
return frag;
|
|
@@ -82,6 +85,7 @@ public class CreateSecurityTokenBlankFragment extends Fragment {
|
|
|
|
private void nextClicked() {
|
|
mCreateKeyActivity.mCreateSecurityToken = true;
|
|
+ mCreateKeyActivity.mSecurityTokenAid = mAid;
|
|
|
|
CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance();
|
|
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java
|
|
index 795d27b..480e0fa 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java
|
|
@@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.R;
|
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
|
+import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper;
|
|
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
|
@@ -62,6 +63,7 @@ public class CreateSecurityTokenImportResetFragment
|
|
|
|
private byte[] mTokenFingerprints;
|
|
private byte[] mTokenAid;
|
|
+ private double mTokenVersion;
|
|
private String mTokenUserId;
|
|
private String mTokenFingerprint;
|
|
private ImportKeysListFragment mListFragment;
|
|
@@ -251,6 +253,7 @@ public class CreateSecurityTokenImportResetFragment
|
|
|
|
mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints();
|
|
mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid();
|
|
+ mTokenVersion = SecurityTokenHelper.parseOpenPgpVersion(mTokenAid);
|
|
mTokenUserId = mCreateKeyActivity.getSecurityTokenHelper().getUserId();
|
|
|
|
byte[] fp = new byte[20];
|
|
@@ -287,6 +290,7 @@ public class CreateSecurityTokenImportResetFragment
|
|
viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0]));
|
|
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result);
|
|
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mTokenAid);
|
|
+ viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mTokenVersion);
|
|
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mTokenUserId);
|
|
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mTokenFingerprints);
|
|
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java
|
|
index a6ecef4..9fe35fc 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java
|
|
@@ -200,7 +200,7 @@ public class CreateSecurityTokenPinFragment extends Fragment {
|
|
|
|
mCreateKeyActivity.mSecurityTokenPin = new Passphrase(mPin.getText().toString());
|
|
|
|
- CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance();
|
|
+ CreateSecurityTokenAlgorithmFragment frag = CreateSecurityTokenAlgorithmFragment.newInstance();
|
|
hideKeyboard();
|
|
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
|
|
}
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
|
|
index 9181c6f..6bf7cae 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
|
|
@@ -35,6 +35,8 @@ import android.view.ViewGroup;
|
|
import android.widget.AdapterView;
|
|
import android.widget.ListView;
|
|
|
|
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
|
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
|
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
|
import org.sufficientlysecure.keychain.Constants;
|
|
import org.sufficientlysecure.keychain.R;
|
|
@@ -449,19 +451,31 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
|
|
break;
|
|
}
|
|
|
|
- int algorithm = mSubkeysAdapter.getAlgorithm(position);
|
|
- if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL
|
|
- && algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT
|
|
- && algorithm != PublicKeyAlgorithmTags.RSA_SIGN) {
|
|
- Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR)
|
|
- .show();
|
|
- break;
|
|
- }
|
|
-
|
|
- if (mSubkeysAdapter.getKeySize(position) != 2048) {
|
|
- Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
|
|
- .show();
|
|
- break;
|
|
+ switch (mSubkeysAdapter.getAlgorithm(position)) {
|
|
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
|
|
+ case PublicKeyAlgorithmTags.RSA_ENCRYPT:
|
|
+ case PublicKeyAlgorithmTags.RSA_SIGN:
|
|
+ if (mSubkeysAdapter.getKeySize(position) < 2048) {
|
|
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
|
|
+ .show();
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case PublicKeyAlgorithmTags.ECDH:
|
|
+ case PublicKeyAlgorithmTags.ECDSA:
|
|
+ final ASN1ObjectIdentifier curve = NISTNamedCurves.getOID(mSubkeysAdapter.getCurveOid(position));
|
|
+ if (!curve.equals(NISTNamedCurves.getByName("P-256")) &&
|
|
+ !curve.equals(NISTNamedCurves.getByName("P-384")) &&
|
|
+ !curve.equals(NISTNamedCurves.getByName("P-521"))) {
|
|
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_curve, Notify.Style.ERROR)
|
|
+ .show();
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR)
|
|
+ .show();
|
|
+ break;
|
|
}
|
|
|
|
SubkeyChange change;
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java
|
|
index 4d07025..651780f 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java
|
|
@@ -32,6 +32,7 @@ import android.widget.ViewAnimator;
|
|
|
|
import org.sufficientlysecure.keychain.Constants;
|
|
import org.sufficientlysecure.keychain.R;
|
|
+import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
|
@@ -192,9 +193,18 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
|
|
throw new IOException(getString(R.string.error_wrong_security_token));
|
|
}
|
|
|
|
+ ProviderHelper providerHelper = new ProviderHelper(this);
|
|
+ CanonicalizedPublicKeyRing publicKeyRing;
|
|
+ try {
|
|
+ publicKeyRing = providerHelper.getCanonicalizedPublicKeyRing(
|
|
+ KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mRequiredInput.getMasterKeyId()));
|
|
+ } catch (ProviderHelper.NotFoundException e) {
|
|
+ throw new IOException("Couldn't find subkey for key to token operation.");
|
|
+ }
|
|
+
|
|
for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
|
|
byte[] encryptedSessionKey = mRequiredInput.mInputData[i];
|
|
- byte[] decryptedSessionKey = mSecurityTokenHelper.decryptSessionKey(encryptedSessionKey);
|
|
+ byte[] decryptedSessionKey = mSecurityTokenHelper.decryptSessionKey(encryptedSessionKey, publicKeyRing.getPublicKey(tokenKeyId));
|
|
mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey);
|
|
}
|
|
break;
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
|
|
index 4dceb94..feceb18 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
|
|
@@ -110,6 +110,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|
|
|
public static final String EXTRA_SECURITY_TOKEN_USER_ID = "security_token_user_id";
|
|
public static final String EXTRA_SECURITY_TOKEN_AID = "security_token_aid";
|
|
+ public static final String EXTRA_SECURITY_TOKEN_VERSION = "security_token_version";
|
|
public static final String EXTRA_SECURITY_TOKEN_FINGERPRINTS = "security_token_fingerprints";
|
|
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
@@ -175,6 +176,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|
private byte[] mSecurityTokenFingerprints;
|
|
private String mSecurityTokenUserId;
|
|
private byte[] mSecurityTokenAid;
|
|
+ private double mSecurityTokenVersion;
|
|
|
|
@SuppressLint("InflateParams")
|
|
@Override
|
|
@@ -668,7 +670,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|
|
|
// if the master key of that key matches this one, just show the token dialog
|
|
if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) {
|
|
- showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid);
|
|
+ showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid, mSecurityTokenVersion);
|
|
return;
|
|
}
|
|
|
|
@@ -682,6 +684,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|
ViewKeyActivity.this, ViewKeyActivity.class);
|
|
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
|
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mSecurityTokenVersion);
|
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
|
|
startActivity(intent);
|
|
@@ -697,6 +700,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|
Intent intent = new Intent(
|
|
ViewKeyActivity.this, CreateKeyActivity.class);
|
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
|
+ intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mSecurityTokenVersion);
|
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
|
|
startActivity(intent);
|
|
@@ -707,13 +711,13 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|
}
|
|
|
|
public void showSecurityTokenFragment(
|
|
- final byte[] tokenFingerprints, final String tokenUserId, final byte[] tokenAid) {
|
|
+ final byte[] tokenFingerprints, final String tokenUserId, final byte[] tokenAid, final double tokenVersion) {
|
|
|
|
new Handler().post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
ViewKeySecurityTokenFragment frag = ViewKeySecurityTokenFragment.newInstance(
|
|
- mMasterKeyId, tokenFingerprints, tokenUserId, tokenAid);
|
|
+ mMasterKeyId, tokenFingerprints, tokenUserId, tokenAid, tokenVersion);
|
|
|
|
FragmentManager manager = getSupportFragmentManager();
|
|
|
|
@@ -896,7 +900,8 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|
byte[] tokenFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_FINGERPRINTS);
|
|
String tokenUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID);
|
|
byte[] tokenAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID);
|
|
- showSecurityTokenFragment(tokenFingerprints, tokenUserId, tokenAid);
|
|
+ double tokenVersion = intent.getDoubleExtra(EXTRA_SECURITY_TOKEN_VERSION, 2.0);
|
|
+ showSecurityTokenFragment(tokenFingerprints, tokenUserId, tokenAid, tokenVersion);
|
|
}
|
|
|
|
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java
|
|
index 93b38af..e70ac5a 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java
|
|
@@ -39,6 +39,8 @@ import android.widget.AdapterView;
|
|
import android.widget.ListView;
|
|
import android.widget.ViewAnimator;
|
|
|
|
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
|
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
|
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
|
import org.sufficientlysecure.keychain.Constants;
|
|
import org.sufficientlysecure.keychain.R;
|
|
@@ -356,19 +358,31 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
|
|
break;
|
|
}
|
|
|
|
- int algorithm = mSubkeysAdapter.getAlgorithm(position);
|
|
- if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL
|
|
- && algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT
|
|
- && algorithm != PublicKeyAlgorithmTags.RSA_SIGN) {
|
|
- Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR)
|
|
- .show();
|
|
- break;
|
|
- }
|
|
-
|
|
- if (mSubkeysAdapter.getKeySize(position) != 2048) {
|
|
- Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
|
|
- .show();
|
|
- break;
|
|
+ switch (mSubkeysAdapter.getAlgorithm(position)) {
|
|
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
|
|
+ case PublicKeyAlgorithmTags.RSA_ENCRYPT:
|
|
+ case PublicKeyAlgorithmTags.RSA_SIGN:
|
|
+ if (mSubkeysAdapter.getKeySize(position) < 2048) {
|
|
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
|
|
+ .show();
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case PublicKeyAlgorithmTags.ECDH:
|
|
+ case PublicKeyAlgorithmTags.ECDSA:
|
|
+ final ASN1ObjectIdentifier curve = NISTNamedCurves.getOID(mSubkeysAdapter.getCurveOid(position));
|
|
+ if (!curve.equals(NISTNamedCurves.getOID("P-256")) &&
|
|
+ !curve.equals(NISTNamedCurves.getOID("P-384")) &&
|
|
+ !curve.equals(NISTNamedCurves.getOID("P-521"))) {
|
|
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_curve, Notify.Style.ERROR)
|
|
+ .show();
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR)
|
|
+ .show();
|
|
+ break;
|
|
}
|
|
|
|
SubkeyChange change;
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java
|
|
index 8b77341..e142656 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java
|
|
@@ -52,10 +52,12 @@ public class ViewKeySecurityTokenFragment
|
|
public static final String ARG_FINGERPRINT = "fingerprint";
|
|
public static final String ARG_USER_ID = "user_id";
|
|
public static final String ARG_CARD_AID = "aid";
|
|
+ public static final String ARG_CARD_VERSION = "version";
|
|
|
|
private byte[][] mFingerprints;
|
|
private String mUserId;
|
|
private byte[] mCardAid;
|
|
+ private double mCardVersion;
|
|
private long mMasterKeyId;
|
|
private long[] mSubKeyIds;
|
|
|
|
@@ -63,7 +65,7 @@ public class ViewKeySecurityTokenFragment
|
|
private TextView vStatus;
|
|
|
|
public static ViewKeySecurityTokenFragment newInstance(long masterKeyId,
|
|
- byte[] fingerprints, String userId, byte[] aid) {
|
|
+ byte[] fingerprints, String userId, byte[] aid, double version) {
|
|
ViewKeySecurityTokenFragment frag = new ViewKeySecurityTokenFragment();
|
|
|
|
Bundle args = new Bundle();
|
|
@@ -71,6 +73,7 @@ public class ViewKeySecurityTokenFragment
|
|
args.putByteArray(ARG_FINGERPRINT, fingerprints);
|
|
args.putString(ARG_USER_ID, userId);
|
|
args.putByteArray(ARG_CARD_AID, aid);
|
|
+ args.putDouble(ARG_CARD_VERSION, version);
|
|
frag.setArguments(args);
|
|
|
|
return frag;
|
|
@@ -93,6 +96,7 @@ public class ViewKeySecurityTokenFragment
|
|
}
|
|
mUserId = args.getString(ARG_USER_ID);
|
|
mCardAid = args.getByteArray(ARG_CARD_AID);
|
|
+ mCardVersion = args.getDouble(ARG_CARD_VERSION);
|
|
|
|
mMasterKeyId = args.getLong(ARG_MASTER_KEY_ID);
|
|
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
|
|
index b5ef1d5..ba288ea 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
|
|
@@ -121,6 +121,11 @@ public class SubkeysAdapter extends CursorAdapter {
|
|
return mCursor.getInt(INDEX_KEY_SIZE);
|
|
}
|
|
|
|
+ public String getCurveOid(int position) {
|
|
+ mCursor.moveToPosition(position);
|
|
+ return mCursor.getString(INDEX_KEY_CURVE_OID);
|
|
+ }
|
|
+
|
|
public SecretKeyType getSecretKeyType(int position) {
|
|
mCursor.moveToPosition(position);
|
|
return SecretKeyType.fromNum(mCursor.getInt(INDEX_HAS_SECRET));
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java
|
|
index 72216bd..fca1666 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java
|
|
@@ -91,9 +91,9 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
|
|
* Override to implement SecurityToken operations (background thread)
|
|
*/
|
|
protected void doSecurityTokenInBackground() throws IOException {
|
|
+ mSecurityTokenAid = mSecurityTokenHelper.getAid();
|
|
mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints();
|
|
mSecurityTokenUserId = mSecurityTokenHelper.getUserId();
|
|
- mSecurityTokenAid = mSecurityTokenHelper.getAid();
|
|
}
|
|
|
|
/**
|
|
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SecurityTokenUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SecurityTokenUtils.java
|
|
index 2f959f9..4066aed 100644
|
|
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SecurityTokenUtils.java
|
|
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SecurityTokenUtils.java
|
|
@@ -17,19 +17,61 @@
|
|
|
|
package org.sufficientlysecure.keychain.util;
|
|
|
|
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
|
import org.bouncycastle.util.Arrays;
|
|
import org.bouncycastle.util.encoders.Hex;
|
|
-import org.sufficientlysecure.keychain.securitytoken.KeyFormat;
|
|
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
|
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
|
+import org.sufficientlysecure.keychain.securitytoken.ECKeyFormat;
|
|
+import org.sufficientlysecure.keychain.securitytoken.RSAKeyFormat;
|
|
import org.sufficientlysecure.keychain.securitytoken.KeyType;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.math.BigInteger;
|
|
+import java.security.interfaces.ECPrivateKey;
|
|
+import java.security.interfaces.ECPublicKey;
|
|
import java.security.interfaces.RSAPrivateCrtKey;
|
|
|
|
public class SecurityTokenUtils {
|
|
- public static byte[] createPrivKeyTemplate(RSAPrivateCrtKey secretKey, KeyType slot,
|
|
- KeyFormat format) throws IOException {
|
|
+ public static byte[] attributesFromSecretKey(final KeyType slot, final CanonicalizedSecretKey secretKey) throws IOException, PgpGeneralException {
|
|
+ if (secretKey.isRSA()) {
|
|
+ final int mModulusLength = secretKey.getBitStrength();
|
|
+ final int mExponentLength = secretKey.getCrtSecretKey().getPublicExponent().bitLength();
|
|
+ final byte[] attrs = new byte[6];
|
|
+ int i = 0;
|
|
+
|
|
+ attrs[i++] = (byte)0x01;
|
|
+ attrs[i++] = (byte)((mModulusLength >> 8) & 0xff);
|
|
+ attrs[i++] = (byte)(mModulusLength & 0xff);
|
|
+ attrs[i++] = (byte)((mExponentLength >> 8) & 0xff);
|
|
+ attrs[i++] = (byte)(mExponentLength & 0xff);
|
|
+ attrs[i++] = RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS.getValue();
|
|
+
|
|
+ return attrs;
|
|
+ } else if (secretKey.isEC()) {
|
|
+ final byte[] oid = new ASN1ObjectIdentifier(secretKey.getCurveOid()).getEncoded();
|
|
+ final byte[] attrs = new byte[1 + (oid.length - 2) + 1];
|
|
+
|
|
+ if (slot.equals(KeyType.SIGN))
|
|
+ attrs[0] = ECKeyFormat.ECAlgorithmFormat.ECDSA_WITH_PUBKEY.getValue();
|
|
+ else {
|
|
+ attrs[0] = ECKeyFormat.ECAlgorithmFormat.ECDH_WITH_PUBKEY.getValue();
|
|
+ }
|
|
+
|
|
+ System.arraycopy(oid, 2, attrs, 1, (oid.length - 2));
|
|
+
|
|
+ attrs[attrs.length - 1] = (byte)0xff;
|
|
+
|
|
+ return attrs;
|
|
+ } else {
|
|
+ throw new IOException("Unsupported key type");
|
|
+ }
|
|
+ }
|
|
+
|
|
+
|
|
+ public static byte[] createRSAPrivKeyTemplate(RSAPrivateCrtKey secretKey, KeyType slot,
|
|
+ RSAKeyFormat format) throws IOException {
|
|
ByteArrayOutputStream stream = new ByteArrayOutputStream(),
|
|
template = new ByteArrayOutputStream(),
|
|
data = new ByteArrayOutputStream(),
|
|
@@ -86,6 +128,51 @@ public class SecurityTokenUtils {
|
|
stream.write(encodeLength(data.size()));
|
|
stream.write(data.toByteArray());
|
|
|
|
+ // Result tlv
|
|
+ res.write(Hex.decode("4D"));
|
|
+ res.write(encodeLength(stream.size()));
|
|
+ res.write(stream.toByteArray());
|
|
+
|
|
+ return res.toByteArray();
|
|
+ }
|
|
+
|
|
+ public static byte[] createECPrivKeyTemplate(ECPrivateKey secretKey, ECPublicKey publicKey, KeyType slot,
|
|
+ ECKeyFormat format) throws IOException {
|
|
+ ByteArrayOutputStream stream = new ByteArrayOutputStream(),
|
|
+ template = new ByteArrayOutputStream(),
|
|
+ data = new ByteArrayOutputStream(),
|
|
+ res = new ByteArrayOutputStream();
|
|
+
|
|
+ final int csize = (int)Math.ceil(publicKey.getParams().getCurve().getField().getFieldSize() / 8.0);
|
|
+
|
|
+ writeBits(data, secretKey.getS(), csize);
|
|
+ template.write(Hex.decode("92"));
|
|
+ template.write(encodeLength(data.size()));
|
|
+
|
|
+ if (format.getAlgorithmFormat().isWithPubkey()) {
|
|
+ data.write(Hex.decode("04"));
|
|
+ writeBits(data, publicKey.getW().getAffineX(), csize);
|
|
+ writeBits(data, publicKey.getW().getAffineY(), csize);
|
|
+ template.write(Hex.decode("99"));
|
|
+ template.write(encodeLength(1 + 2 * csize));
|
|
+ }
|
|
+
|
|
+ // Bundle up
|
|
+
|
|
+ // Ext header list data
|
|
+ // Control Reference Template to indicate the private key
|
|
+ stream.write(slot.getSlot());
|
|
+ stream.write(0);
|
|
+
|
|
+ // Cardholder private key template
|
|
+ stream.write(Hex.decode("7F48"));
|
|
+ stream.write(encodeLength(template.size()));
|
|
+ stream.write(template.toByteArray());
|
|
+
|
|
+ // Concatenation of key data as defined in DO 7F48
|
|
+ stream.write(Hex.decode("5F48"));
|
|
+ stream.write(encodeLength(data.size()));
|
|
+ stream.write(data.toByteArray());
|
|
|
|
// Result tlv
|
|
res.write(Hex.decode("4D"));
|
|
@@ -132,20 +219,21 @@ public class SecurityTokenUtils {
|
|
throw new IllegalArgumentException("width <= 0");
|
|
}
|
|
|
|
- byte[] prime = value.toByteArray();
|
|
- int stripIdx = 0;
|
|
- while (prime[stripIdx] == 0 && stripIdx + 1 < prime.length) {
|
|
- stripIdx++;
|
|
- }
|
|
+ final byte[] prime = value.toByteArray();
|
|
+ int skip = 0;
|
|
|
|
- if (prime.length - stripIdx > width) {
|
|
+ while((skip < prime.length) && (prime[skip] == 0)) ++skip;
|
|
+
|
|
+ if ((prime.length - skip) > width) {
|
|
throw new IllegalArgumentException("not enough width to fit value: "
|
|
- + prime.length + "/" + width);
|
|
+ + (prime.length - skip) + "/" + width);
|
|
}
|
|
+
|
|
byte[] res = new byte[width];
|
|
- int empty = width - (prime.length - stripIdx);
|
|
|
|
- System.arraycopy(prime, stripIdx, res, Math.max(0, empty), Math.min(prime.length, width));
|
|
+ System.arraycopy(prime, skip,
|
|
+ res, width - (prime.length - skip),
|
|
+ prime.length - skip);
|
|
|
|
stream.write(res, 0, width);
|
|
Arrays.fill(res, (byte) 0);
|
|
diff --git a/OpenKeychain/src/main/res/layout/create_yubi_key_algorithm_fragment.xml b/OpenKeychain/src/main/res/layout/create_yubi_key_algorithm_fragment.xml
|
|
new file mode 100644
|
|
index 0000000..c61f4d7
|
|
--- /dev/null
|
|
+++ b/OpenKeychain/src/main/res/layout/create_yubi_key_algorithm_fragment.xml
|
|
@@ -0,0 +1,121 @@
|
|
+<?xml version="1.0" encoding="UTF-8"?>
|
|
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
+ xmlns:tools="http://schemas.android.com/tools"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="match_parent">
|
|
+
|
|
+ <ScrollView
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="match_parent"
|
|
+ android:layout_above="@+id/create_key_buttons"
|
|
+ android:fillViewport="true">
|
|
+
|
|
+ <LinearLayout
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:orientation="vertical"
|
|
+ android:paddingLeft="16dp"
|
|
+ android:paddingRight="16dp">
|
|
+
|
|
+ <TextView
|
|
+ android:layout_width="wrap_content"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:layout_marginLeft="8dp"
|
|
+ android:layout_marginTop="16dp"
|
|
+ android:text="@string/create_key_yubi_key_algorithm_text"
|
|
+ android:textAppearance="?android:attr/textAppearanceMedium"
|
|
+ android:layout_marginBottom="16dp" />
|
|
+
|
|
+ <TextView
|
|
+ android:layout_width="wrap_content"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:layout_marginLeft="8dp"
|
|
+ android:layout_marginTop="16dp"
|
|
+ android:text="@string/create_key_yubi_key_algorithm_sign_text"
|
|
+ android:textAppearance="?android:attr/textAppearanceMedium" />
|
|
+
|
|
+ <org.sufficientlysecure.keychain.ui.util.spinner.FocusFirstItemSpinner
|
|
+ android:id="@+id/create_key_yubi_key_algorithm_sign"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:dropDownWidth="wrap_content"
|
|
+ android:layout_marginBottom="16dp"/>
|
|
+
|
|
+ <TextView
|
|
+ android:layout_width="wrap_content"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:layout_marginLeft="8dp"
|
|
+ android:layout_marginTop="16dp"
|
|
+ android:text="@string/create_key_yubi_key_algorithm_dec_text"
|
|
+ android:textAppearance="?android:attr/textAppearanceMedium" />
|
|
+
|
|
+ <org.sufficientlysecure.keychain.ui.util.spinner.FocusFirstItemSpinner
|
|
+ android:id="@+id/create_key_yubi_key_algorithm_dec"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:dropDownWidth="wrap_content"
|
|
+ android:layout_marginBottom="16dp"/>
|
|
+
|
|
+ <TextView
|
|
+ android:layout_width="wrap_content"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:layout_marginLeft="8dp"
|
|
+ android:layout_marginTop="16dp"
|
|
+ android:text="@string/create_key_yubi_key_algorithm_auth_text"
|
|
+ android:textAppearance="?android:attr/textAppearanceMedium" />
|
|
+
|
|
+ <org.sufficientlysecure.keychain.ui.util.spinner.FocusFirstItemSpinner
|
|
+ android:id="@+id/create_key_yubi_key_algorithm_auth"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:dropDownWidth="wrap_content"/>
|
|
+ </LinearLayout>
|
|
+ </ScrollView>
|
|
+
|
|
+ <LinearLayout
|
|
+ android:id="@+id/create_key_buttons"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:layout_alignParentBottom="true"
|
|
+ android:layout_alignParentLeft="true"
|
|
+ android:layout_alignParentStart="true"
|
|
+ android:background="?attr/colorButtonRow"
|
|
+ android:orientation="horizontal">
|
|
+
|
|
+ <TextView
|
|
+ android:id="@+id/create_key_back_button"
|
|
+ style="?android:attr/borderlessButtonStyle"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:layout_gravity="center_vertical"
|
|
+ android:layout_weight="1"
|
|
+ android:clickable="true"
|
|
+ android:drawableLeft="@drawable/ic_chevron_left_grey_24dp"
|
|
+ android:drawablePadding="8dp"
|
|
+ android:gravity="left|center_vertical"
|
|
+ android:minHeight="?android:attr/listPreferredItemHeight"
|
|
+ android:paddingLeft="16dp"
|
|
+ android:paddingRight="16dp"
|
|
+ android:text="@string/btn_back"
|
|
+ android:textAllCaps="true"
|
|
+ android:textAppearance="?android:attr/textAppearanceMedium" />
|
|
+
|
|
+ <TextView
|
|
+ android:id="@+id/create_key_next_button"
|
|
+ style="?android:attr/borderlessButtonStyle"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:layout_gravity="center_vertical"
|
|
+ android:layout_weight="1"
|
|
+ android:clickable="true"
|
|
+ android:drawablePadding="8dp"
|
|
+ android:drawableRight="@drawable/ic_chevron_right_grey_24dp"
|
|
+ android:gravity="right|center_vertical"
|
|
+ android:minHeight="?android:attr/listPreferredItemHeight"
|
|
+ android:paddingLeft="16dp"
|
|
+ android:paddingRight="16dp"
|
|
+ android:text="@string/btn_next"
|
|
+ android:textAllCaps="true"
|
|
+ android:textAppearance="?android:attr/textAppearanceMedium" />
|
|
+ </LinearLayout>
|
|
+</RelativeLayout>
|
|
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
|
|
index 21b6c09..770d97c 100644
|
|
--- a/OpenKeychain/src/main/res/values/strings.xml
|
|
+++ b/OpenKeychain/src/main/res/values/strings.xml
|
|
@@ -293,6 +293,8 @@
|
|
<string name="rsa_4096_description_html">"larger file size, considered secure until 2040+"</string>
|
|
<string name="ecc_p256">"ECC P-256"</string>
|
|
<string name="ecc_p256_description_html">"very tiny file size, considered secure until 2040 <br/> <u>experimental and not supported by all implementations</u>"</string>
|
|
+ <string name="ecc_p384">"ECC P-384"</string>
|
|
+ <string name="ecc_p384_description_html">"very tiny file size, considered secure until 2040 <br/> <u>experimental and not supported by all implementations</u>"</string>
|
|
<string name="ecc_p521">"ECC P-521"</string>
|
|
<string name="ecc_p521_description_html">"tiny file size, considered secure until 2040+ <br/> <u>experimental and not supported by all implementations</u>"</string>
|
|
<string name="usage_none">"None (subkey binding only)"</string>
|
|
@@ -764,6 +766,7 @@
|
|
<string name="edit_key_error_add_subkey">"Add at least one subkey!"</string>
|
|
<string name="edit_key_error_bad_security_token_algo">"Algorithm not supported by Security Token!"</string>
|
|
<string name="edit_key_error_bad_security_token_size">"Key size not supported by Security Token!"</string>
|
|
+ <string name="edit_key_error_bad_security_token_curve">"Curve not supported by Security Token!"</string>
|
|
<string name="edit_key_error_bad_security_token_stripped">"Cannot move key to Security Token (either stripped or already on Security Token)!"</string>
|
|
|
|
<!-- Create key -->
|
|
@@ -792,6 +795,10 @@
|
|
<string name="create_key_yubi_key_pin_not_correct">"PIN is not correct!"</string>
|
|
<string name="create_key_yubi_key_pin_too_short">"PIN must be at least 6 numbers long!"</string>
|
|
<string name="create_key_yubi_key_pin_insecure">"Please choose a secure PIN, not 000000, 123456 or similar combinations (the top 20 most chosen PINs are not allowed)"</string>
|
|
+ <string name="create_key_yubi_key_algorithm_text">"Please choose an algorithm for each key."</string>
|
|
+ <string name="create_key_yubi_key_algorithm_sign_text">Signature key</string>
|
|
+ <string name="create_key_yubi_key_algorithm_dec_text">Decryption key</string>
|
|
+ <string name="create_key_yubi_key_algorithm_auth_text">Authentication key</string>
|
|
|
|
<!-- View key -->
|
|
<string name="view_key_revoked">"Revoked: Key must not be used anymore!"</string>
|
|
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java
|
|
index 403b6c6..87e7803 100644
|
|
--- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java
|
|
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java
|
|
@@ -856,6 +856,7 @@ public class PgpKeyOperationTest {
|
|
|
|
UncachedKeyRing modified;
|
|
|
|
+ /*
|
|
{ // moveKeyToSecurityToken should fail with BAD_NFC_SIZE when presented with the RSA-3072 key
|
|
long keyId = KeyringTestingHelper.getSubkeyId(ringSecurityToken, 2);
|
|
parcelSecurityToken.reset();
|
|
@@ -864,6 +865,7 @@ public class PgpKeyOperationTest {
|
|
assertModifyFailure("moveKeyToSecurityToken operation should fail on invalid key size", ringSecurityToken,
|
|
parcelSecurityToken, cryptoInput, LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE);
|
|
}
|
|
+ */
|
|
|
|
{ // moveKeyToSecurityToken should fail with BAD_NFC_ALGO when presented with the DSA-1024 key
|
|
long keyId = KeyringTestingHelper.getSubkeyId(ringSecurityToken, 0);
|
|
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java
|
|
index 0f0bf7a..241a21a 100644
|
|
--- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java
|
|
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java
|
|
@@ -128,7 +128,6 @@ public class SecurityTokenUtilsTest extends Mockito {
|
|
|
|
@Test
|
|
public void testPrivateKeyTemplateSimple2048() throws Exception {
|
|
- KeyFormat format = new KeyFormat(Hex.decode("010000001800"));
|
|
RSAPrivateCrtKey key2048 = mock(RSAPrivateCrtKey.class);
|
|
byte[] tmp = new byte[128];
|
|
Arrays.fill(tmp, (byte) 0x11);
|
|
@@ -137,7 +136,8 @@ public class SecurityTokenUtilsTest extends Mockito {
|
|
Arrays.fill(tmp, (byte) 0x12);
|
|
when(key2048.getPrimeQ()).thenReturn(new BigInteger(tmp));
|
|
|
|
- when(key2048.getPublicExponent()).thenReturn(new BigInteger("65537"));
|
|
+ BigInteger exp = new BigInteger("65537");
|
|
+ when(key2048.getPublicExponent()).thenReturn(exp);
|
|
|
|
Assert.assertArrayEquals(
|
|
Hex.decode("4d820115" + // Header TL
|
|
@@ -160,8 +160,7 @@ public class SecurityTokenUtilsTest extends Mockito {
|
|
"1212121212121212121212121212121212121212121212121212121212121212" +
|
|
"1212121212121212121212121212121212121212121212121212121212121212"
|
|
),
|
|
- SecurityTokenUtils.createPrivKeyTemplate(key2048, KeyType.AUTH, format));
|
|
+ SecurityTokenUtils.createRSAPrivKeyTemplate(key2048, KeyType.AUTH, new RSAKeyFormat(2048, exp.bitLength(), RSAKeyFormat.RSAAlgorithmFormat.STANDARD)));
|
|
}
|
|
|
|
@Test
|
|
--
|
|
2.9.3
|
|
|