Support of OpenPGP card v3
This commit is contained in:
parent
7616c1c8b8
commit
05bfd6bc01
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -17,71 +17,83 @@
|
|||
|
||||
package org.sufficientlysecure.keychain.securitytoken;
|
||||
|
||||
// 4.3.3.6 Algorithm Attributes
|
||||
public class KeyFormat {
|
||||
private int mAlgorithmId;
|
||||
private int mModulusLength;
|
||||
private int mExponentLength;
|
||||
private AlgorithmFormat mAlgorithmFormat;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.CreateSecurityTokenAlgorithmFragment;
|
||||
|
||||
public KeyFormat(byte[] bytes) {
|
||||
mAlgorithmId = bytes[0];
|
||||
mModulusLength = bytes[1] << 8 | bytes[2];
|
||||
mExponentLength = bytes[3] << 8 | bytes[4];
|
||||
mAlgorithmFormat = AlgorithmFormat.from(bytes[5]);
|
||||
public abstract class KeyFormat {
|
||||
|
||||
if (mAlgorithmId != 1) { // RSA
|
||||
throw new IllegalArgumentException("Unsupported Algorithm id " + mAlgorithmId);
|
||||
}
|
||||
public enum KeyFormatType {
|
||||
RSAKeyFormatType,
|
||||
ECKeyFormatType
|
||||
};
|
||||
|
||||
private final KeyFormatType mKeyFormatType;
|
||||
|
||||
public KeyFormat(final KeyFormatType keyFormatType) {
|
||||
mKeyFormatType = keyFormatType;
|
||||
}
|
||||
|
||||
public int getAlgorithmId() {
|
||||
return mAlgorithmId;
|
||||
public final KeyFormatType keyFormatType() {
|
||||
return mKeyFormatType;
|
||||
}
|
||||
|
||||
public int getModulusLength() {
|
||||
return mModulusLength;
|
||||
}
|
||||
|
||||
public int getExponentLength() {
|
||||
return mExponentLength;
|
||||
}
|
||||
|
||||
public AlgorithmFormat getAlgorithmFormat() {
|
||||
return mAlgorithmFormat;
|
||||
}
|
||||
|
||||
public enum AlgorithmFormat {
|
||||
STANDARD(0, false, false),
|
||||
STANDARD_WITH_MODULUS(1, false, true),
|
||||
CRT(2, true, false),
|
||||
CRT_WITH_MODULUS(3, true, true);
|
||||
|
||||
private int mValue;
|
||||
private boolean mIncludeModulus;
|
||||
private boolean mIncludeCrt;
|
||||
|
||||
AlgorithmFormat(int value, boolean includeCrt, boolean includeModulus) {
|
||||
mValue = value;
|
||||
mIncludeModulus = includeModulus;
|
||||
mIncludeCrt = includeCrt;
|
||||
}
|
||||
|
||||
public static AlgorithmFormat from(byte b) {
|
||||
for (AlgorithmFormat format : values()) {
|
||||
if (format.mValue == b) {
|
||||
return format;
|
||||
public static KeyFormat fromBytes(byte[] bytes) {
|
||||
switch (bytes[0]) {
|
||||
case PublicKeyAlgorithmTags.RSA_GENERAL:
|
||||
if (bytes.length < 6) {
|
||||
throw new IllegalArgumentException("Bad length for RSA attributes");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return new RSAKeyFormat(bytes[1] << 8 | bytes[2],
|
||||
bytes[3] << 8 | bytes[4],
|
||||
RSAKeyFormat.RSAAlgorithmFormat.from(bytes[5]));
|
||||
|
||||
public boolean isIncludeModulus() {
|
||||
return mIncludeModulus;
|
||||
}
|
||||
case PublicKeyAlgorithmTags.ECDH:
|
||||
case PublicKeyAlgorithmTags.ECDSA:
|
||||
if (bytes.length < 2) {
|
||||
throw new IllegalArgumentException("Bad length for RSA attributes");
|
||||
}
|
||||
int len = bytes.length - 1;
|
||||
if (bytes[bytes.length - 1] == (byte)0xff) {
|
||||
len -= 1;
|
||||
}
|
||||
final byte[] boid = new byte[2 + len];
|
||||
boid[0] = (byte)0x06;
|
||||
boid[1] = (byte)len;
|
||||
System.arraycopy(bytes, 1, boid, 2, len);
|
||||
final ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(boid);
|
||||
return new ECKeyFormat(oid, ECKeyFormat.ECAlgorithmFormat.from(bytes[0], bytes[bytes.length - 1]));
|
||||
|
||||
public boolean isIncludeCrt() {
|
||||
return mIncludeCrt;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported Algorithm id " + bytes[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public static KeyFormat fromCreationKeyType(CreateSecurityTokenAlgorithmFragment.SupportedKeyType t, boolean forEncryption) {
|
||||
final int elen = 17; //65537
|
||||
final ECKeyFormat.ECAlgorithmFormat kf =
|
||||
forEncryption ? ECKeyFormat.ECAlgorithmFormat.ECDH_WITH_PUBKEY : ECKeyFormat.ECAlgorithmFormat.ECDSA_WITH_PUBKEY;
|
||||
|
||||
switch (t) {
|
||||
case RSA_2048:
|
||||
return new RSAKeyFormat(2048, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
|
||||
case RSA_3072:
|
||||
return new RSAKeyFormat(3072, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
|
||||
case RSA_4096:
|
||||
return new RSAKeyFormat(4096, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
|
||||
case ECC_P256:
|
||||
return new ECKeyFormat(NISTNamedCurves.getOID("P-256"), kf);
|
||||
case ECC_P384:
|
||||
return new ECKeyFormat(NISTNamedCurves.getOID("P-384"), kf);
|
||||
case ECC_P521:
|
||||
return new ECKeyFormat(NISTNamedCurves.getOID("P-521"), kf);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unsupported Algorithm id " + t);
|
||||
}
|
||||
|
||||
public abstract void addToKeyring(SaveKeyringParcel keyring, int keyFlags);
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
try {
|
||||
secretKey.unlock(passphrase);
|
||||
crtSecretKey = secretKey.getCrtSecretKey();
|
||||
} catch (PgpGeneralException e) {
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
|
||||
// Shouldn't happen; the UI should block the user from getting an incompatible key this far.
|
||||
if (crtSecretKey.getModulus().bitLength() > 2048) {
|
||||
throw new IOException("Key too large to export to Security Token.");
|
||||
}
|
||||
|
||||
// Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
|
||||
if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
|
||||
throw new IOException("Invalid public exponent for smart Security Token.");
|
||||
}
|
||||
ECPrivateKey ecSecretKey;
|
||||
ECPublicKey ecPublicKey;
|
||||
|
||||
if (!mPw3Validated) {
|
||||
verifyPin(0x83); // (Verify PW3 with mode 83)
|
||||
}
|
||||
|
||||
|
||||
// Now we're ready to communicate with the token.
|
||||
byte[] bytes = SecurityTokenUtils.createPrivKeyTemplate(crtSecretKey, slot,
|
||||
mOpenPgpCapabilities.getFormatForKeyType(slot));
|
||||
byte[] keyBytes = null;
|
||||
|
||||
CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, bytes);
|
||||
try {
|
||||
secretKey.unlock(passphrase);
|
||||
|
||||
setKeyAttributes(slot, secretKey);
|
||||
|
||||
switch (mOpenPgpCapabilities.getFormatForKeyType(slot).keyFormatType()) {
|
||||
case RSAKeyFormatType:
|
||||
if (!secretKey.isRSA()) {
|
||||
throw new IOException("Security Token not configured for RSA key.");
|
||||
}
|
||||
crtSecretKey = secretKey.getCrtSecretKey();
|
||||
|
||||
// Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
|
||||
if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
|
||||
throw new IOException("Invalid public exponent for smart Security Token.");
|
||||
}
|
||||
|
||||
keyBytes = SecurityTokenUtils.createRSAPrivKeyTemplate(crtSecretKey, slot,
|
||||
(RSAKeyFormat) (mOpenPgpCapabilities.getFormatForKeyType(slot)));
|
||||
break;
|
||||
|
||||
case ECKeyFormatType:
|
||||
if (!secretKey.isEC()) {
|
||||
throw new IOException("Security Token not configured for EC key.");
|
||||
}
|
||||
|
||||
secretKey.unlock(passphrase);
|
||||
ecSecretKey = secretKey.getECSecretKey();
|
||||
ecPublicKey = secretKey.getECPublicKey();
|
||||
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -283,12 +283,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()));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,14 +34,17 @@ public class CreateSecurityTokenBlankFragment extends Fragment {
|
|||
View mBackButton;
|
||||
View mNextButton;
|
||||
|
||||
private byte[] mAid;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static CreateSecurityTokenBlankFragment newInstance() {
|
||||
public static CreateSecurityTokenBlankFragment newInstance(byte[] aid) {
|
||||
CreateSecurityTokenBlankFragment frag = new CreateSecurityTokenBlankFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
|
||||
frag.mAid = aid;
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
|
@ -83,6 +86,7 @@ public class CreateSecurityTokenBlankFragment extends Fragment {
|
|||
|
||||
private void nextClicked() {
|
||||
mCreateKeyActivity.mCreateSecurityToken = true;
|
||||
mCreateKeyActivity.mSecurityTokenAid = mAid;
|
||||
|
||||
CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance();
|
||||
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
|||
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper;
|
||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
@ -66,6 +67,7 @@ public class CreateSecurityTokenImportResetFragment
|
|||
|
||||
private byte[] mTokenFingerprints;
|
||||
private byte[] mTokenAid;
|
||||
private double mTokenVersion;
|
||||
private String mTokenUserId;
|
||||
private String mTokenFingerprint;
|
||||
private TextView vSerNo;
|
||||
|
@ -254,6 +256,7 @@ public class CreateSecurityTokenImportResetFragment
|
|||
|
||||
mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints();
|
||||
mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid();
|
||||
mTokenVersion = SecurityTokenHelper.parseOpenPgpVersion(mTokenAid);
|
||||
mTokenUserId = mCreateKeyActivity.getSecurityTokenHelper().getUserId();
|
||||
|
||||
byte[] fp = new byte[20];
|
||||
|
@ -290,6 +293,7 @@ public class CreateSecurityTokenImportResetFragment
|
|||
viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0]));
|
||||
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result);
|
||||
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mTokenAid);
|
||||
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mTokenVersion);
|
||||
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mTokenUserId);
|
||||
viewKeyIntent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mTokenFingerprints);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
switch (mSubkeysAdapter.getAlgorithm(position)) {
|
||||
case PublicKeyAlgorithmTags.RSA_GENERAL:
|
||||
case PublicKeyAlgorithmTags.RSA_ENCRYPT:
|
||||
case PublicKeyAlgorithmTags.RSA_SIGN:
|
||||
if (mSubkeysAdapter.getKeySize(position) < 2048) {
|
||||
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
|
||||
if (mSubkeysAdapter.getKeySize(position) != 2048) {
|
||||
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
|
||||
.show();
|
||||
break;
|
||||
case PublicKeyAlgorithmTags.ECDH:
|
||||
case PublicKeyAlgorithmTags.ECDSA:
|
||||
final ASN1ObjectIdentifier curve = NISTNamedCurves.getOID(mSubkeysAdapter.getCurveOid(position));
|
||||
if (!curve.equals(NISTNamedCurves.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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -111,6 +111,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||
|
||||
public static final String EXTRA_SECURITY_TOKEN_USER_ID = "security_token_user_id";
|
||||
public static final String EXTRA_SECURITY_TOKEN_AID = "security_token_aid";
|
||||
public static final String EXTRA_SECURITY_TOKEN_VERSION = "security_token_version";
|
||||
public static final String EXTRA_SECURITY_TOKEN_FINGERPRINTS = "security_token_fingerprints";
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
|
@ -178,6 +179,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||
private byte[] mSecurityTokenFingerprints;
|
||||
private String mSecurityTokenUserId;
|
||||
private byte[] mSecurityTokenAid;
|
||||
private double mSecurityTokenVersion;
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@Override
|
||||
|
@ -671,7 +673,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||
|
||||
// if the master key of that key matches this one, just show the token dialog
|
||||
if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) {
|
||||
showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid);
|
||||
showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid, mSecurityTokenVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -685,6 +687,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||
ViewKeyActivity.this, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mSecurityTokenVersion);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
|
||||
startActivity(intent);
|
||||
|
@ -700,6 +703,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||
Intent intent = new Intent(
|
||||
ViewKeyActivity.this, CreateKeyActivity.class);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_VERSION, mSecurityTokenVersion);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
|
||||
startActivity(intent);
|
||||
|
@ -710,13 +714,13 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||
}
|
||||
|
||||
public void showSecurityTokenFragment(
|
||||
final byte[] tokenFingerprints, final String tokenUserId, final byte[] tokenAid) {
|
||||
final byte[] tokenFingerprints, final String tokenUserId, final byte[] tokenAid, final double tokenVersion) {
|
||||
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ViewKeySecurityTokenFragment frag = ViewKeySecurityTokenFragment.newInstance(
|
||||
mMasterKeyId, tokenFingerprints, tokenUserId, tokenAid);
|
||||
mMasterKeyId, tokenFingerprints, tokenUserId, tokenAid, tokenVersion);
|
||||
|
||||
FragmentManager manager = getSupportFragmentManager();
|
||||
|
||||
|
@ -899,7 +903,8 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||
byte[] tokenFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_FINGERPRINTS);
|
||||
String tokenUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID);
|
||||
byte[] tokenAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID);
|
||||
showSecurityTokenFragment(tokenFingerprints, tokenUserId, tokenAid);
|
||||
double tokenVersion = intent.getDoubleExtra(EXTRA_SECURITY_TOKEN_VERSION, 2.0);
|
||||
showSecurityTokenFragment(tokenFingerprints, tokenUserId, tokenAid, tokenVersion);
|
||||
}
|
||||
|
||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
|
|
|
@ -38,6 +38,8 @@ import android.widget.AdapterView;
|
|||
import android.widget.ListView;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
@ -356,19 +358,31 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
|
|||
break;
|
||||
}
|
||||
|
||||
int algorithm = mSubkeysAdapter.getAlgorithm(position);
|
||||
if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL
|
||||
&& algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT
|
||||
&& algorithm != PublicKeyAlgorithmTags.RSA_SIGN) {
|
||||
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
switch (mSubkeysAdapter.getAlgorithm(position)) {
|
||||
case PublicKeyAlgorithmTags.RSA_GENERAL:
|
||||
case PublicKeyAlgorithmTags.RSA_ENCRYPT:
|
||||
case PublicKeyAlgorithmTags.RSA_SIGN:
|
||||
if (mSubkeysAdapter.getKeySize(position) < 2048) {
|
||||
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
|
||||
if (mSubkeysAdapter.getKeySize(position) != 2048) {
|
||||
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
|
||||
.show();
|
||||
break;
|
||||
case PublicKeyAlgorithmTags.ECDH:
|
||||
case PublicKeyAlgorithmTags.ECDSA:
|
||||
final ASN1ObjectIdentifier curve = NISTNamedCurves.getOID(mSubkeysAdapter.getCurveOid(position));
|
||||
if (!curve.equals(NISTNamedCurves.getOID("P-256")) &&
|
||||
!curve.equals(NISTNamedCurves.getOID("P-384")) &&
|
||||
!curve.equals(NISTNamedCurves.getOID("P-521"))) {
|
||||
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_curve, Notify.Style.ERROR)
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
|
||||
SubkeyChange change;
|
||||
|
|
|
@ -50,10 +50,12 @@ public class ViewKeySecurityTokenFragment
|
|||
public static final String ARG_FINGERPRINT = "fingerprint";
|
||||
public static final String ARG_USER_ID = "user_id";
|
||||
public static final String ARG_CARD_AID = "aid";
|
||||
public static final String ARG_CARD_VERSION = "version";
|
||||
|
||||
private byte[][] mFingerprints;
|
||||
private String mUserId;
|
||||
private byte[] mCardAid;
|
||||
private double mCardVersion;
|
||||
private long mMasterKeyId;
|
||||
private long[] mSubKeyIds;
|
||||
|
||||
|
@ -61,7 +63,7 @@ public class ViewKeySecurityTokenFragment
|
|||
private TextView vStatus;
|
||||
|
||||
public static ViewKeySecurityTokenFragment newInstance(long masterKeyId,
|
||||
byte[] fingerprints, String userId, byte[] aid) {
|
||||
byte[] fingerprints, String userId, byte[] aid, double version) {
|
||||
ViewKeySecurityTokenFragment frag = new ViewKeySecurityTokenFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
|
@ -69,6 +71,7 @@ public class ViewKeySecurityTokenFragment
|
|||
args.putByteArray(ARG_FINGERPRINT, fingerprints);
|
||||
args.putString(ARG_USER_ID, userId);
|
||||
args.putByteArray(ARG_CARD_AID, aid);
|
||||
args.putDouble(ARG_CARD_VERSION, version);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
|
@ -91,6 +94,7 @@ public class ViewKeySecurityTokenFragment
|
|||
}
|
||||
mUserId = args.getString(ARG_USER_ID);
|
||||
mCardAid = args.getByteArray(ARG_CARD_AID);
|
||||
mCardVersion = args.getDouble(ARG_CARD_VERSION);
|
||||
|
||||
mMasterKeyId = args.getLong(ARG_MASTER_KEY_ID);
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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));
|
||||
byte[] res = new byte[width];
|
||||
|
||||
System.arraycopy(prime, skip,
|
||||
res, width - (prime.length - skip),
|
||||
prime.length - skip);
|
||||
|
||||
stream.write(res, 0, width);
|
||||
Arrays.fill(res, (byte) 0);
|
||||
|
|
|
@ -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>
|
|
@ -298,6 +298,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>
|
||||
|
@ -772,6 +774,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 -->
|
||||
|
@ -800,6 +803,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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* 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.bouncycastle.util.Arrays;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.robolectric.RobolectricGradleTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
import org.sufficientlysecure.keychain.WorkaroundBuildConfig;
|
||||
import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException;
|
||||
import org.sufficientlysecure.keychain.util.SecurityTokenUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.interfaces.RSAPrivateCrtKey;
|
||||
|
||||
|
||||
@RunWith(RobolectricGradleTestRunner.class)
|
||||
@Config(constants = WorkaroundBuildConfig.class, sdk = 23, manifest = "src/main/AndroidManifest.xml")
|
||||
public class SecurityTokenUtilsTest extends Mockito {
|
||||
@Before
|
||||
public void setUp() {
|
||||
ShadowLog.stream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeLength() throws Exception {
|
||||
// One byte
|
||||
Assert.assertArrayEquals(new byte[]{0x00}, SecurityTokenUtils.encodeLength(0));
|
||||
Assert.assertArrayEquals(new byte[]{0x01}, SecurityTokenUtils.encodeLength(1));
|
||||
Assert.assertArrayEquals(new byte[]{0x7f}, SecurityTokenUtils.encodeLength(127));
|
||||
|
||||
// Two bytes
|
||||
Assert.assertArrayEquals(new byte[]{(byte) 0x81, (byte) 0x80},
|
||||
SecurityTokenUtils.encodeLength(128));
|
||||
Assert.assertArrayEquals(new byte[]{(byte) 0x81, (byte) 0xFF},
|
||||
SecurityTokenUtils.encodeLength(255));
|
||||
|
||||
// Three bytes
|
||||
Assert.assertArrayEquals(new byte[]{(byte) 0x82, (byte) 0x01, 0x00},
|
||||
SecurityTokenUtils.encodeLength(256));
|
||||
Assert.assertArrayEquals(new byte[]{(byte) 0x82, (byte) 0xFF, (byte) 0xFF},
|
||||
SecurityTokenUtils.encodeLength(65535));
|
||||
|
||||
// Four bytes
|
||||
Assert.assertArrayEquals(new byte[]{(byte) 0x83, (byte) 0x01, (byte) 0x00, (byte) 0x00},
|
||||
SecurityTokenUtils.encodeLength(65536));
|
||||
Assert.assertArrayEquals(new byte[]{(byte) 0x83, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF},
|
||||
SecurityTokenUtils.encodeLength(16777215));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testEncodeLengthNegative() throws Exception {
|
||||
SecurityTokenUtils.encodeLength(-1);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testEncodeLengthTooBig() throws Exception {
|
||||
SecurityTokenUtils.encodeLength(256 * 256 * 256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteBits() {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
SecurityTokenUtils.writeBits(stream, new BigInteger("0"), 10);
|
||||
Assert.assertArrayEquals(new byte[10], stream.toByteArray());
|
||||
|
||||
stream.reset();
|
||||
SecurityTokenUtils.writeBits(stream, new BigInteger("0"), 1);
|
||||
Assert.assertArrayEquals(new byte[1], stream.toByteArray());
|
||||
|
||||
stream.reset();
|
||||
SecurityTokenUtils.writeBits(stream, new BigInteger("65537"), 3);
|
||||
Assert.assertArrayEquals(new byte[]{1, 0, 1}, stream.toByteArray());
|
||||
stream.reset();
|
||||
SecurityTokenUtils.writeBits(stream, new BigInteger("128"), 1);
|
||||
Assert.assertArrayEquals(new byte[]{(byte) 128}, stream.toByteArray());
|
||||
|
||||
stream.reset();
|
||||
SecurityTokenUtils.writeBits(stream, new BigInteger("65537"), 4);
|
||||
Assert.assertArrayEquals(new byte[]{0, 1, 0, 1}, stream.toByteArray());
|
||||
|
||||
stream.reset();
|
||||
SecurityTokenUtils.writeBits(stream, new BigInteger("65537"), 11);
|
||||
Assert.assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1}, stream.toByteArray());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWriteBitsInvalidValue() {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
SecurityTokenUtils.writeBits(stream, new BigInteger("-1"), 1);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWriteBitsInvalidBits() {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
SecurityTokenUtils.writeBits(stream, new BigInteger("1"), 0);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWriteBitsDoesntFit() {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
SecurityTokenUtils.writeBits(stream, new BigInteger("65537"), 2);
|
||||
}
|
||||
|
||||
@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);
|
||||
when(key2048.getPrimeP()).thenReturn(new BigInteger(tmp));
|
||||
|
||||
Arrays.fill(tmp, (byte) 0x12);
|
||||
when(key2048.getPrimeQ()).thenReturn(new BigInteger(tmp));
|
||||
|
||||
when(key2048.getPublicExponent()).thenReturn(new BigInteger("65537"));
|
||||
|
||||
Assert.assertArrayEquals(
|
||||
Hex.decode("4d820115" + // Header TL
|
||||
"a400" + // CRT
|
||||
"7f4808" + // 8 bytes
|
||||
"9103" + // e
|
||||
"928180" + // p
|
||||
"938180" + // q
|
||||
"5f48820103" +
|
||||
|
||||
"010001" +
|
||||
|
||||
"1111111111111111111111111111111111111111111111111111111111111111" +
|
||||
"1111111111111111111111111111111111111111111111111111111111111111" +
|
||||
"1111111111111111111111111111111111111111111111111111111111111111" +
|
||||
"1111111111111111111111111111111111111111111111111111111111111111" +
|
||||
|
||||
"1212121212121212121212121212121212121212121212121212121212121212" +
|
||||
"1212121212121212121212121212121212121212121212121212121212121212" +
|
||||
"1212121212121212121212121212121212121212121212121212121212121212" +
|
||||
"1212121212121212121212121212121212121212121212121212121212121212"
|
||||
),
|
||||
SecurityTokenUtils.createPrivKeyTemplate(key2048, KeyType.AUTH, format));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCardCapabilities() throws UsbTransportException {
|
||||
CardCapabilities capabilities;
|
||||
|
||||
// Yk neo
|
||||
capabilities = new CardCapabilities(Hex.decode("007300008000000000000000000000"));
|
||||
Assert.assertEquals(capabilities.hasChaining(), true);
|
||||
Assert.assertEquals(capabilities.hasExtended(), false);
|
||||
|
||||
// Yk 4
|
||||
capabilities = new CardCapabilities(Hex.decode("0073000080059000"));
|
||||
Assert.assertEquals(capabilities.hasChaining(), true);
|
||||
Assert.assertEquals(capabilities.hasExtended(), false);
|
||||
|
||||
// Nitrokey pro
|
||||
capabilities = new CardCapabilities(Hex.decode("0031c573c00140059000"));
|
||||
Assert.assertEquals(capabilities.hasChaining(), false);
|
||||
Assert.assertEquals(capabilities.hasExtended(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenPgpCapabilities() throws IOException {
|
||||
byte[] data;
|
||||
|
||||
// Yk-neo applet
|
||||
data = Hex.decode("6e81de4f10d27600012401020000000000000100005f520f0073000080000000" +
|
||||
"000000000000007381b7c00af00000ff04c000ff00ffc106010800001103c206" +
|
||||
"010800001103c306010800001103c407007f7f7f030303c53cce1d5a2158a4f1" +
|
||||
"8a7d853394e9e4c9efb468055fae77ab8ea3c68f053930e35f658fb62176e901" +
|
||||
"df03d249cac1e82f8289c7ffabe1a1af868620fa56c63c000000000000000000" +
|
||||
"0000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"00000000000000000000000000000000000000cd0c5741e8695741e8695741e8" +
|
||||
"69");
|
||||
|
||||
OpenPgpCapabilities caps = new OpenPgpCapabilities(data);
|
||||
|
||||
Assert.assertEquals(caps.isHasSM(), true);
|
||||
}
|
||||
/*
|
||||
|
||||
yk-neo
|
||||
|
||||
6e81de4f10d27600012401020000000000000100005f520f0073000080000000000000000000007381b7c00af00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f030303c53cce1d5a2158a4f18a7d853394e9e4c9efb468055fae77ab8ea3c68f053930e35f658fb62176e901df03d249cac1e82f8289c7ffabe1a1af868620fa56c63c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd0c5741e8695741e8695741e869
|
||||
|
||||
6e81de4f10d27600012401020000000000000100005f520f0073000080000000000000000000007381b7c00af00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f030303c53c1a2f6436e422cd4f37f9e95775195c4984609678fbc5dd767789f1b304c9fba6f68a68ac563f71ae0000000000000000000000000000000000000000c63c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd0c5744793c5744793c00000000
|
||||
|
||||
|
||||
007300008000000000000000000000
|
||||
|
||||
|
||||
yk neo ng
|
||||
6e81de4f10d27600012401020000060364703400005f520f0073000080000000000000000000007381b7c00af00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f030302c53c7fdb876b9ebc534d674e63b7400207a0f9c34a50413b851137ced1b45d1b66526b4e93a9d5c4eed3585c34e7d38ec07d50f26e0554baa1867a038ed2c63c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd0c5711219f5711219f5711219f
|
||||
|
||||
|
||||
|
||||
6e81de4f10d27600012401020000060364703400005f520f0073000080000000000000000000007381b7c00af00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f030302c53c7fdb876b9ebc534d674e63b7400207a0f9c34a50413b851137ced1b45d1b66526b4e93a9d5c4eed3585c34e7d38ec07d50f26e0554baa1867a038ed2c63c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd0c5711219f5711219f5711219f
|
||||
|
||||
6e81dd4f10d27600012401020000060301402200005f520f0073000080000000000000000000007300c00af00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f030303c53c98b23401029d2666e129e0b97e2f7e6d1026a77c198dd46ed68a107aada3a267a82e3157a024f7b4be59004e379abaf8fe7abf28deacdd05542a5acac63c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd0c5741f0165741f0165741f016
|
||||
|
||||
yk4
|
||||
6e81dd4f10d27600012401020100060417430400005f520800730000800590007f74038101207381b7c00a3c00000004c000ff00ffc106010800001100c206010800001100c306010800001100c407ff7f7f7f030003c53c3a9150768282a1ccf13ee80c4ef0217876ba06001ee6fc5c633b764583cc2d7144b76c0c83f3bdf6671d04d2b6ca89fbbd3940ea7203396c7b1ff1bac63c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd0c5741ef475741ef475741ef47
|
||||
0073000080059000
|
||||
|
||||
nitrokey pro
|
||||
|
||||
4f10d2760001240102010005000038a100005f520a0031c573c001400590007381b7c00a7c000800080008000800c106010800002000c206010800002000c306010800002000c40700202020030003c53c5dcf5fc947201ef20636c448131e71ffecf7cbf7d3ed4826257b1340c5aedd2b15530eecf173fa890859306d64641ab01be054c3d6795052cedc3c38c63c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd0c5741eec65741eec65741eec6
|
||||
0031c573c00140059000
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
--- OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java
|
||||
+++ OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java
|
||||
@@ -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
|
||||
--
|
Loading…
Reference in a new issue