diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 7a2286fdc..aaaeaff23 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -566,6 +566,7 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD(LogLevel.ERROR, R.string.msg_mf_error_invalid_flags_for_keytocard), MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_algo), MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_size), + MSG_MF_ERROR_BAD_SECURITY_TOKEN_CURVE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_curve), MSG_MF_ERROR_BAD_SECURITY_TOKEN_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_stripped), MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master), MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java index 177fe1562..95be5bbe0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java @@ -18,14 +18,27 @@ package org.sufficientlysecure.keychain.pgp; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import org.bouncycastle.bcpg.ECDHPublicBCPGKey; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.bouncycastle.openpgp.operator.RFC6637Utils; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter; import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; +import java.io.IOException; +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; @@ -189,4 +202,62 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { return !isRevoked() && !isExpired(); } + // For use only in card export; returns the public key. + public ECPublicKey getECPublicKey() + throws PgpGeneralException { + JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + PublicKey retVal; + try { + retVal = keyConverter.getPublicKey(mPublicKey); + } catch (PGPException e) { + throw new PgpGeneralException("Error converting public key!", e); + } + + return (ECPublicKey) retVal; + } + + public ASN1ObjectIdentifier getHashAlgorithm() + throws PGPException { + if (!isEC()) { + throw new PGPException("Key encryption OID is valid only for EC key!"); + } + + final ECDHPublicBCPGKey eck = (ECDHPublicBCPGKey)mPublicKey.getPublicKeyPacket().getKey(); + + switch (eck.getHashAlgorithm()) { + case HashAlgorithmTags.SHA256: + return NISTObjectIdentifiers.id_sha256; + case HashAlgorithmTags.SHA384: + return NISTObjectIdentifiers.id_sha384; + case HashAlgorithmTags.SHA512: + return NISTObjectIdentifiers.id_sha512; + default: + throw new PGPException("Invalid hash algorithm for EC key : " + eck.getHashAlgorithm()); + } + } + + public int getSymmetricKeySize() + throws PGPException { + if (!isEC()) { + throw new PGPException("Key encryption OID is valid only for EC key!"); + } + + final ECDHPublicBCPGKey eck = (ECDHPublicBCPGKey)mPublicKey.getPublicKeyPacket().getKey(); + + switch (eck.getSymmetricKeyAlgorithm()) { + case SymmetricKeyAlgorithmTags.AES_128: + return 128; + case SymmetricKeyAlgorithmTags.AES_192: + return 192; + case SymmetricKeyAlgorithmTags.AES_256: + return 256; + default: + throw new PGPException("Invalid symmetric encryption algorithm for EC key : " + eck.getSymmetricKeyAlgorithm()); + } + } + + public byte[] createUserKeyingMaterial(KeyFingerPrintCalculator fingerPrintCalculator) + throws IOException, PGPException { + return RFC6637Utils.createUserKeyingMaterial(mPublicKey.getPublicKeyPacket(), fingerPrintCalculator); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index a02ff6685..318c8370f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp; import java.nio.ByteBuffer; import java.security.PrivateKey; +import java.security.interfaces.ECPrivateKey; import java.security.interfaces.RSAPrivateCrtKey; import java.util.Date; import java.util.HashMap; @@ -319,6 +320,28 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { return (RSAPrivateCrtKey)retVal; } + // For use only in card export; returns the secret key. + public ECPrivateKey getECSecretKey() + throws PgpGeneralException { + if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { + throw new PgpGeneralException("Cannot get secret key attributes while key is locked."); + } + + if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { + throw new PgpGeneralException("Cannot get secret key attributes of divert-to-card key."); + } + + JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + PrivateKey retVal; + try { + retVal = keyConverter.getPrivateKey(mPrivateKey); + } catch (PGPException e) { + throw new PgpGeneralException("Error converting private key! " + e.getMessage(), e); + } + + return (ECPrivateKey) retVal; + } + public byte[] getIv() { return mSecretKey.getIV(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 404e07230..ed5d925ec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -35,6 +35,10 @@ import java.util.Iterator; import java.util.Stack; import java.util.concurrent.atomic.AtomicBoolean; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.bcpg.ECDHPublicBCPGKey; +import org.bouncycastle.bcpg.ECDSAPublicBCPGKey; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.sig.Features; @@ -1645,20 +1649,44 @@ public class PgpKeyOperation { } private static boolean checkSecurityTokenCompatibility(PGPSecretKey key, OperationLog log, int indent) { - PGPPublicKey publicKey = key.getPublicKey(); - int algorithm = publicKey.getAlgorithm(); - if (algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT && - algorithm != PublicKeyAlgorithmTags.RSA_SIGN && - algorithm != PublicKeyAlgorithmTags.RSA_GENERAL) { - log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO, indent + 1); - return false; - } - // Key size must be 2048 - int keySize = publicKey.getBitStrength(); - if (keySize != 2048) { - log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE, indent + 1); - return false; + final PGPPublicKey publicKey = key.getPublicKey(); + ASN1ObjectIdentifier curve; + + switch (publicKey.getAlgorithm()) { + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + case PublicKeyAlgorithmTags.RSA_SIGN: + case PublicKeyAlgorithmTags.RSA_GENERAL: + // Key size must be at least 2048 + if (publicKey.getBitStrength() < 2048) { + log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE, indent + 1); + return false; + } + break; + + case PublicKeyAlgorithmTags.ECDH: + curve = ((ECDHPublicBCPGKey)(publicKey.getPublicKeyPacket().getKey())).getCurveOID(); + if (!curve.equals(NISTNamedCurves.getOID("P-256")) && + !curve.equals(NISTNamedCurves.getOID("P-384")) && + !curve.equals(NISTNamedCurves.getOID("P-521"))) { + log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_CURVE, indent + 1); + return false; + } + break; + + case PublicKeyAlgorithmTags.ECDSA: + curve = ((ECDSAPublicBCPGKey)(publicKey.getPublicKeyPacket().getKey())).getCurveOID(); + if (!curve.equals(NISTNamedCurves.getOID("P-256")) && + !curve.equals(NISTNamedCurves.getOID("P-384")) && + !curve.equals(NISTNamedCurves.getOID("P-521"))) { + log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_CURVE, indent + 1); + return false; + } + break; + + default: + log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO, indent + 1); + return false; } // Secret key parts must be available diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java new file mode 100644 index 000000000..d3b4d5fd7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java @@ -0,0 +1,83 @@ +package org.sufficientlysecure.keychain.securitytoken; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.math.ec.ECCurve; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; + +// 4.3.3.6 Algorithm Attributes +public class ECKeyFormat extends KeyFormat { + + private final ECAlgorithmFormat mECAlgorithmFormat; + private final ASN1ObjectIdentifier mECCurveOID; + + public ECKeyFormat(final ASN1ObjectIdentifier ecCurveOid, + final ECAlgorithmFormat ecAlgorithmFormat) { + super(KeyFormatType.ECKeyFormatType); + mECAlgorithmFormat = ecAlgorithmFormat; + mECCurveOID = ecCurveOid; + } + + public ECKeyFormat.ECAlgorithmFormat getAlgorithmFormat() { + return mECAlgorithmFormat; + } + + public ASN1ObjectIdentifier getCurveOID() { return mECCurveOID; } + + public enum ECAlgorithmFormat { + ECDH((byte)18, true, false), + ECDH_WITH_PUBKEY((byte)18, true, true), + ECDSA((byte)19, false, false), + ECDSA_WITH_PUBKEY((byte)19, false, true); + + private final byte mValue; + private final boolean mIsECDH; + private final boolean mWithPubkey; + + ECAlgorithmFormat(final byte value, final boolean isECDH, final boolean withPubkey) { + mValue = value; + mIsECDH = isECDH; + mWithPubkey = withPubkey; + } + + public static ECKeyFormat.ECAlgorithmFormat from(final byte bFirst, final byte bLast) { + for (ECKeyFormat.ECAlgorithmFormat format : values()) { + if (format.mValue == bFirst && ((bLast == (byte)0xff) == format.isWithPubkey())) { + return format; + } + } + return null; + } + + public final byte getValue() { return mValue; } + public final boolean isECDH() { return mIsECDH; } + public final boolean isWithPubkey() { return mWithPubkey; } + } + + public void addToKeyring(SaveKeyringParcel keyring, int keyFlags) { + final X9ECParameters params = NISTNamedCurves.getByOID(mECCurveOID); + final ECCurve curve = params.getCurve(); + + SaveKeyringParcel.Algorithm algo = SaveKeyringParcel.Algorithm.ECDSA; + if (((keyFlags & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS) + || ((keyFlags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE)) { + algo = SaveKeyringParcel.Algorithm.ECDH; + } + + SaveKeyringParcel.Curve scurve; + if (mECCurveOID.equals(NISTNamedCurves.getOID("P-256"))) { + scurve = SaveKeyringParcel.Curve.NIST_P256; + } else if (mECCurveOID.equals(NISTNamedCurves.getOID("P-384"))) { + scurve = SaveKeyringParcel.Curve.NIST_P384; + } else if (mECCurveOID.equals(NISTNamedCurves.getOID("P-521"))) { + scurve = SaveKeyringParcel.Curve.NIST_P521; + } else { + throw new IllegalArgumentException("Unsupported curve " + mECCurveOID); + } + + keyring.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(algo, + curve.getFieldSize(), scurve, keyFlags, 0L)); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java index 3b2e93f0a..4a81d46f2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java @@ -17,71 +17,83 @@ package org.sufficientlysecure.keychain.securitytoken; -// 4.3.3.6 Algorithm Attributes -public class KeyFormat { - private int mAlgorithmId; - private int mModulusLength; - private int mExponentLength; - private AlgorithmFormat mAlgorithmFormat; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.CreateSecurityTokenAlgorithmFragment; - public KeyFormat(byte[] bytes) { - mAlgorithmId = bytes[0]; - mModulusLength = bytes[1] << 8 | bytes[2]; - mExponentLength = bytes[3] << 8 | bytes[4]; - mAlgorithmFormat = AlgorithmFormat.from(bytes[5]); +public abstract class KeyFormat { - if (mAlgorithmId != 1) { // RSA - throw new IllegalArgumentException("Unsupported Algorithm id " + mAlgorithmId); - } + public enum KeyFormatType { + RSAKeyFormatType, + ECKeyFormatType + }; + + 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); + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java index 7a87a0a97..d28a74630 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java @@ -36,15 +36,19 @@ public class OpenPgpCapabilities { private boolean mAttriburesChangable; private boolean mHasKeyImport; - private byte mSMAlgo; + private int mSMAESKeySize; private int mMaxCmdLen; private int mMaxRspLen; private Map mKeyFormats; public OpenPgpCapabilities(byte[] data) throws IOException { - Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true); mKeyFormats = new HashMap<>(); + updateWithData(data); + } + + public void updateWithData(byte[] data) throws IOException { + Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true); if (tlvs.length == 1 && tlvs[0].mT == 0x6E) { tlvs = ((Iso7816TLV.Iso7816CompositeTLV) tlvs[0]).mSubs; } @@ -64,13 +68,13 @@ public class OpenPgpCapabilities { parseExtendedCaps(tlv.mV); break; case 0xC1: - mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV)); break; case 0xC2: - mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV)); break; case 0xC3: - mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV)); break; case 0xC4: mPw1ValidForMultipleSignatures = tlv.mV[0] == 1; @@ -86,13 +90,13 @@ public class OpenPgpCapabilities { parseExtendedCaps(tlv.mV); break; case 0xC1: - mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV)); break; case 0xC2: - mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV)); break; case 0xC3: - mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV)); + mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV)); break; case 0xC4: mPw1ValidForMultipleSignatures = tlv.mV[0] == 1; @@ -106,7 +110,7 @@ public class OpenPgpCapabilities { mHasKeyImport = (v[0] & MASK_KEY_IMPORT) != 0; mAttriburesChangable =(v[0] & MASK_ATTRIBUTES_CHANGABLE) != 0; - mSMAlgo = v[1]; + mSMAESKeySize = (v[1] == 1) ? 16 : 32; mMaxCmdLen = (v[6] << 8) + v[7]; mMaxRspLen = (v[8] << 8) + v[9]; @@ -128,7 +132,7 @@ public class OpenPgpCapabilities { return mHasSM; } - public boolean isAttriburesChangable() { + public boolean isAttributesChangable() { return mAttriburesChangable; } @@ -136,8 +140,8 @@ public class OpenPgpCapabilities { return mHasKeyImport; } - public byte getSMAlgo() { - return mSMAlgo; + public int getSMAESKeySize() { + return mSMAESKeySize; } public int getMaxCmdLen() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java new file mode 100644 index 000000000..27b427443 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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 . + */ + +package org.sufficientlysecure.keychain.securitytoken; + +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; + +// 4.3.3.6 Algorithm Attributes +public class RSAKeyFormat extends KeyFormat { + private int mModulusLength; + private int mExponentLength; + private RSAAlgorithmFormat mRSAAlgorithmFormat; + + public RSAKeyFormat(int modulusLength, + int exponentLength, + RSAAlgorithmFormat rsaAlgorithmFormat) { + super(KeyFormatType.RSAKeyFormatType); + mModulusLength = modulusLength; + mExponentLength = exponentLength; + mRSAAlgorithmFormat = rsaAlgorithmFormat; + } + + public int getModulusLength() { + return mModulusLength; + } + + public int getExponentLength() { + return mExponentLength; + } + + public RSAAlgorithmFormat getAlgorithmFormat() { + return mRSAAlgorithmFormat; + } + + public enum RSAAlgorithmFormat { + STANDARD((byte)0, false, false), + STANDARD_WITH_MODULUS((byte)1, false, true), + CRT((byte)2, true, false), + CRT_WITH_MODULUS((byte)3, true, true); + + private byte mValue; + private boolean mIncludeModulus; + private boolean mIncludeCrt; + + RSAAlgorithmFormat(byte value, boolean includeCrt, boolean includeModulus) { + mValue = value; + mIncludeModulus = includeModulus; + mIncludeCrt = includeCrt; + } + + public static RSAAlgorithmFormat from(byte b) { + for (RSAAlgorithmFormat format : values()) { + if (format.mValue == b) { + return format; + } + } + return null; + } + + public byte getValue() { return mValue; } + + public boolean isIncludeModulus() { + return mIncludeModulus; + } + + public boolean isIncludeCrt() { + return mIncludeCrt; + } + } + + public void addToKeyring(SaveKeyringParcel keyring, int keyFlags) { + keyring.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(SaveKeyringParcel.Algorithm.RSA, + mModulusLength, null, keyFlags, 0L)); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java index 878bf9df3..c3619dba8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -23,12 +23,28 @@ package org.sufficientlysecure.keychain.securitytoken; import android.support.annotation.NonNull; +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1OutputStream; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.jcajce.util.MessageDigestUtils; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.operator.PGPPad; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; import javax.smartcardio.CommandAPDU; import javax.smartcardio.ResponseAPDU; import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; @@ -41,6 +57,12 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateCrtKey; /** @@ -64,6 +86,9 @@ public class SecurityTokenHelper { private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + private final JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator(); + private Transport mTransport; private CardCapabilities mCardCapabilities; private OpenPgpCapabilities mOpenPgpCapabilities; @@ -77,6 +102,12 @@ public class SecurityTokenHelper { protected SecurityTokenHelper() { } + public static double parseOpenPgpVersion(final byte[] aid) { + float minv = aid[7]; + while (minv > 0) minv /= 10.0; + return aid[6] + minv; + } + public static SecurityTokenHelper getInstance() { return LazyHolder.SECURITY_TOKEN_HELPER; } @@ -218,17 +249,62 @@ public class SecurityTokenHelper { * Call DECIPHER command * * @param encryptedSessionKey the encoded session key + * @param publicKey * @return the decoded session key */ - public byte[] decryptSessionKey(@NonNull byte[] encryptedSessionKey) throws IOException { + public byte[] decryptSessionKey(@NonNull byte[] encryptedSessionKey, + CanonicalizedPublicKey publicKey) + throws IOException { + final KeyFormat kf = mOpenPgpCapabilities.getFormatForKeyType(KeyType.ENCRYPT); + if (!mPw1ValidatedForDecrypt) { verifyPin(0x82); // (Verify PW1 with mode 82 for decryption) } - // Transmit - byte[] data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length); - if (data[0] != 0) { - data = Arrays.prepend(data, (byte) 0x00); + byte[] data; + int pLen = 0; + + X9ECParameters x9Params = null; + + switch (kf.keyFormatType()) { + case RSAKeyFormatType: + data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length); + if (data[0] != 0) { + data = Arrays.prepend(data, (byte) 0x00); + } + break; + + case ECKeyFormatType: + pLen = ((((encryptedSessionKey[0] & 0xff) << 8) + (encryptedSessionKey[1] & 0xff)) + 7) / 8; + data = new byte[pLen]; + + System.arraycopy(encryptedSessionKey, 2, data, 0, pLen); + + final ECKeyFormat eckf = (ECKeyFormat)kf; + x9Params = NISTNamedCurves.getByOID(eckf.getCurveOID()); + + final ECPoint p = x9Params.getCurve().decodePoint(data); + if (!p.isValid()) { + throw new CardException("Invalid EC point!"); + } + + data = p.getEncoded(false); + data = Arrays.concatenate( + Hex.decode("86"), + new byte[]{ (byte)data.length }, + data); + data = Arrays.concatenate( + Hex.decode("7F49"), + new byte[] { (byte)data.length }, + data); + data = Arrays.concatenate( + Hex.decode("A6"), + new byte[] { (byte)data.length }, + data); + break; + + default: + throw new CardException("Unknown encryption key type!"); } CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x80, 0x86, data, MAX_APDU_NE_EXT); @@ -238,7 +314,47 @@ public class SecurityTokenHelper { throw new CardException("Deciphering with Security token failed on receive", response.getSW()); } - return response.getData(); + switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.ENCRYPT).keyFormatType()) { + case RSAKeyFormatType: + return response.getData(); + + case ECKeyFormatType: + data = response.getData(); + + final byte[] keyEnc = new byte[encryptedSessionKey[pLen + 2]]; + + System.arraycopy(encryptedSessionKey, 2 + pLen + 1, keyEnc, 0, keyEnc.length); + + try { + final MessageDigest kdf = MessageDigest.getInstance(MessageDigestUtils.getDigestName(publicKey.getHashAlgorithm())); + + kdf.update(new byte[]{ (byte)0, (byte)0, (byte)0, (byte)1 }); + kdf.update(data); + kdf.update(publicKey.createUserKeyingMaterial(fingerprintCalculator)); + + final byte[] kek = kdf.digest(); + final Cipher c = Cipher.getInstance("AESWrap"); + + c.init(Cipher.UNWRAP_MODE, new SecretKeySpec(kek, 0, publicKey.getSymmetricKeySize() / 8, "AES")); + + final Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY); + + Arrays.fill(kek, (byte)0); + + return PGPPad.unpadSessionData(paddedSessionKey.getEncoded()); + } catch (NoSuchAlgorithmException e) { + throw new CardException("Unknown digest/encryption algorithm!"); + } catch (NoSuchPaddingException e) { + throw new CardException("Unknown padding algorithm!"); + } catch (PGPException e) { + throw new CardException(e.getMessage()); + } catch (InvalidKeyException e) { + throw new CardException("Invalid KEK!"); + } + + default: + throw new CardException("Unknown encryption key type!"); + } } /** @@ -300,6 +416,36 @@ public class SecurityTokenHelper { } } + + private void setKeyAttributes(final KeyType slot, final CanonicalizedSecretKey secretKey) + throws IOException { + + if (mOpenPgpCapabilities.isAttributesChangable()) { + int tag; + + if (slot == KeyType.SIGN) { + tag = 0xC1; + } else if (slot == KeyType.ENCRYPT) { + tag = 0xC2; + } else if (slot == KeyType.AUTH) { + tag = 0xC3; + } else { + throw new IOException("Unknown key for card."); + } + + try { + + putData(tag, SecurityTokenUtils.attributesFromSecretKey(slot, secretKey)); + + mOpenPgpCapabilities.updateWithData(getData(0x00, tag)); + + } catch (PgpGeneralException e) { + throw new IOException("Key algorithm not supported by the security token."); + } + + } + } + /** * Puts a key on the token in the given slot. * @@ -311,33 +457,58 @@ public class SecurityTokenHelper { private void putKey(KeyType slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { RSAPrivateCrtKey crtSecretKey; - 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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index b0e922c8a..19cbb7660 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.securitytoken.KeyFormat; import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Passphrase; @@ -64,6 +65,9 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity { boolean mCreateSecurityToken; Passphrase mSecurityTokenPin; Passphrase mSecurityTokenAdminPin; + KeyFormat mSecurityTokenSign; + KeyFormat mSecurityTokenDec; + KeyFormat mSecurityTokenAuth; Fragment mCurrentFragment; @@ -97,6 +101,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity { mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE); mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME); mCreateSecurityToken = savedInstanceState.getBoolean(EXTRA_CREATE_SECURITY_TOKEN); + mSecurityTokenAid = savedInstanceState.getByteArray(EXTRA_SECURITY_TOKEN_AID); mSecurityTokenPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_PIN); mSecurityTokenAdminPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN); @@ -122,7 +127,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity { setTitle(R.string.title_import_keys); } else { - Fragment frag = CreateSecurityTokenBlankFragment.newInstance(); + Fragment frag = CreateSecurityTokenBlankFragment.newInstance(nfcAid); loadFragment(frag, FragAction.START); setTitle(R.string.title_manage_my_keys); } @@ -192,7 +197,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity { loadFragment(frag, FragAction.TO_RIGHT); } } else { - Fragment frag = CreateSecurityTokenBlankFragment.newInstance(); + Fragment frag = CreateSecurityTokenBlankFragment.newInstance(mSecurityTokenAid); loadFragment(frag, FragAction.TO_RIGHT); } } @@ -221,6 +226,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity { outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase); outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime); outState.putBoolean(EXTRA_CREATE_SECURITY_TOKEN, mCreateSecurityToken); + outState.putByteArray(EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); outState.putParcelable(EXTRA_SECURITY_TOKEN_PIN, mSecurityTokenPin); outState.putParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN, mSecurityTokenAdminPin); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index 0e94a1168..e5643c5c3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -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())); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java new file mode 100644 index 000000000..90a902239 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenAlgorithmFragment.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 . + */ + +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> 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)mSignKeySpinner.getSelectedItem()).getId(), false); + mCreateKeyActivity.mSecurityTokenDec = KeyFormat.fromCreationKeyType(((Choice)mDecKeySpinner.getSelectedItem()).getId(), true); + mCreateKeyActivity.mSecurityTokenAuth = KeyFormat.fromCreationKeyType(((Choice)mAuthKeySpinner.getSelectedItem()).getId(), false); + + CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + + + + + private class TwoLineArrayAdapter extends ArrayAdapter> { + public TwoLineArrayAdapter(Context context, int resource, List> objects) { + super(context, resource, objects); + } + + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + // inflate view if not given one + if (convertView == null) { + convertView = getActivity().getLayoutInflater() + .inflate(R.layout.two_line_spinner_dropdown_item, parent, false); + } + + Choice c = this.getItem(position); + + TextView text1 = (TextView) convertView.findViewById(android.R.id.text1); + TextView text2 = (TextView) convertView.findViewById(android.R.id.text2); + + text1.setText(c.getName()); + text2.setText(Html.fromHtml(c.getDescription())); + + return convertView; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java index 729cb7dea..61a069e9d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index f2eba1386..2ce1c266e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java index 1033d373e..ae8dbf3c6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java @@ -200,7 +200,7 @@ public class CreateSecurityTokenPinFragment extends Fragment { mCreateKeyActivity.mSecurityTokenPin = new Passphrase(mPin.getText().toString()); - CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance(); + CreateSecurityTokenAlgorithmFragment frag = CreateSecurityTokenAlgorithmFragment.newInstance(); hideKeyboard(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 9181c6f79..6bf7cae15 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -35,6 +35,8 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -449,19 +451,31 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment> 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); diff --git a/OpenKeychain/src/main/res/layout/create_yubi_key_algorithm_fragment.xml b/OpenKeychain/src/main/res/layout/create_yubi_key_algorithm_fragment.xml new file mode 100644 index 000000000..c61f4d71a --- /dev/null +++ b/OpenKeychain/src/main/res/layout/create_yubi_key_algorithm_fragment.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index f1c6aaaae..64c8d4a8c 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -298,6 +298,8 @@ "larger file size, considered secure until 2040+" "ECC P-256" "very tiny file size, considered secure until 2040 <br/> <u>experimental and not supported by all implementations</u>" + "ECC P-384" + "very tiny file size, considered secure until 2040 <br/> <u>experimental and not supported by all implementations</u>" "ECC P-521" "tiny file size, considered secure until 2040+ <br/> <u>experimental and not supported by all implementations</u>" "None (subkey binding only)" @@ -772,6 +774,7 @@ "Add at least one subkey!" "Algorithm not supported by Security Token!" "Key size not supported by Security Token!" + "Curve not supported by Security Token!" "Cannot move key to Security Token (either stripped or already on Security Token)!" @@ -800,6 +803,10 @@ "PIN is not correct!" "PIN must be at least 6 numbers long!" "Please choose a secure PIN, not 000000, 123456 or similar combinations (the top 20 most chosen PINs are not allowed)" + "Please choose an algorithm for each key." + Signature key + Decryption key + Authentication key "Revoked: Key must not be used anymore!" diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java index 403b6c64e..87e780323 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java @@ -856,6 +856,7 @@ public class PgpKeyOperationTest { UncachedKeyRing modified; + /* { // moveKeyToSecurityToken should fail with BAD_NFC_SIZE when presented with the RSA-3072 key long keyId = KeyringTestingHelper.getSubkeyId(ringSecurityToken, 2); parcelSecurityToken.reset(); @@ -864,6 +865,7 @@ public class PgpKeyOperationTest { assertModifyFailure("moveKeyToSecurityToken operation should fail on invalid key size", ringSecurityToken, parcelSecurityToken, cryptoInput, LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE); } + */ { // moveKeyToSecurityToken should fail with BAD_NFC_ALGO when presented with the DSA-1024 key long keyId = KeyringTestingHelper.getSubkeyId(ringSecurityToken, 0); diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java index 0f0bf7ae9..6bc8ddc5f 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java @@ -128,7 +128,6 @@ public class SecurityTokenUtilsTest extends Mockito { @Test public void testPrivateKeyTemplateSimple2048() throws Exception { - KeyFormat format = new KeyFormat(Hex.decode("010000001800")); RSAPrivateCrtKey key2048 = mock(RSAPrivateCrtKey.class); byte[] tmp = new byte[128]; Arrays.fill(tmp, (byte) 0x11); @@ -137,7 +136,8 @@ public class SecurityTokenUtilsTest extends Mockito { Arrays.fill(tmp, (byte) 0x12); when(key2048.getPrimeQ()).thenReturn(new BigInteger(tmp)); - when(key2048.getPublicExponent()).thenReturn(new BigInteger("65537")); + BigInteger exp = new BigInteger("65537"); + when(key2048.getPublicExponent()).thenReturn(exp); Assert.assertArrayEquals( Hex.decode("4d820115" + // Header TL diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java.orig b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java.orig new file mode 100644 index 000000000..0f0bf7ae9 --- /dev/null +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java.orig @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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 . + */ + +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 + */ +} diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java.rej b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java.rej new file mode 100644 index 000000000..ae674822d --- /dev/null +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java.rej @@ -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 +--