Support of OpenPGP card v3

This commit is contained in:
Arnaud Fontaine 2016-10-25 16:37:43 +02:00
parent 7616c1c8b8
commit 05bfd6bc01
29 changed files with 1403 additions and 167 deletions

View file

@ -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),

View file

@ -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);
}
}

View file

@ -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();
}

View file

@ -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

View file

@ -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));
}
}

View file

@ -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);
}

View file

@ -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() {

View file

@ -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));
}
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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()));

View file

@ -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;
}
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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);
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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));

View file

@ -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();
}
/**

View file

@ -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);

View file

@ -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>

View file

@ -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 &lt;br/> &lt;u>experimental and not supported by all implementations&lt;/u>"</string>
<string name="ecc_p384">"ECC P-384"</string>
<string name="ecc_p384_description_html">"very tiny file size, considered secure until 2040 &lt;br/> &lt;u>experimental and not supported by all implementations&lt;/u>"</string>
<string name="ecc_p521">"ECC P-521"</string>
<string name="ecc_p521_description_html">"tiny file size, considered secure until 2040+ &lt;br/> &lt;u>experimental and not supported by all implementations&lt;/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>

View file

@ -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);

View file

@ -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

View file

@ -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
*/
}

View file

@ -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
--