Support of OpenPGP card v3
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package org.sufficientlysecure.keychain.securitytoken;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||
import org.bouncycastle.math.ec.ECCurve;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
|
||||
// 4.3.3.6 Algorithm Attributes
|
||||
public class ECKeyFormat extends KeyFormat {
|
||||
|
||||
private final ECAlgorithmFormat mECAlgorithmFormat;
|
||||
private final ASN1ObjectIdentifier mECCurveOID;
|
||||
|
||||
public ECKeyFormat(final ASN1ObjectIdentifier ecCurveOid,
|
||||
final ECAlgorithmFormat ecAlgorithmFormat) {
|
||||
super(KeyFormatType.ECKeyFormatType);
|
||||
mECAlgorithmFormat = ecAlgorithmFormat;
|
||||
mECCurveOID = ecCurveOid;
|
||||
}
|
||||
|
||||
public ECKeyFormat.ECAlgorithmFormat getAlgorithmFormat() {
|
||||
return mECAlgorithmFormat;
|
||||
}
|
||||
|
||||
public ASN1ObjectIdentifier getCurveOID() { return mECCurveOID; }
|
||||
|
||||
public enum ECAlgorithmFormat {
|
||||
ECDH((byte)18, true, false),
|
||||
ECDH_WITH_PUBKEY((byte)18, true, true),
|
||||
ECDSA((byte)19, false, false),
|
||||
ECDSA_WITH_PUBKEY((byte)19, false, true);
|
||||
|
||||
private final byte mValue;
|
||||
private final boolean mIsECDH;
|
||||
private final boolean mWithPubkey;
|
||||
|
||||
ECAlgorithmFormat(final byte value, final boolean isECDH, final boolean withPubkey) {
|
||||
mValue = value;
|
||||
mIsECDH = isECDH;
|
||||
mWithPubkey = withPubkey;
|
||||
}
|
||||
|
||||
public static ECKeyFormat.ECAlgorithmFormat from(final byte bFirst, final byte bLast) {
|
||||
for (ECKeyFormat.ECAlgorithmFormat format : values()) {
|
||||
if (format.mValue == bFirst && ((bLast == (byte)0xff) == format.isWithPubkey())) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public final byte getValue() { return mValue; }
|
||||
public final boolean isECDH() { return mIsECDH; }
|
||||
public final boolean isWithPubkey() { return mWithPubkey; }
|
||||
}
|
||||
|
||||
public void addToKeyring(SaveKeyringParcel keyring, int keyFlags) {
|
||||
final X9ECParameters params = NISTNamedCurves.getByOID(mECCurveOID);
|
||||
final ECCurve curve = params.getCurve();
|
||||
|
||||
SaveKeyringParcel.Algorithm algo = SaveKeyringParcel.Algorithm.ECDSA;
|
||||
if (((keyFlags & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS)
|
||||
|| ((keyFlags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE)) {
|
||||
algo = SaveKeyringParcel.Algorithm.ECDH;
|
||||
}
|
||||
|
||||
SaveKeyringParcel.Curve scurve;
|
||||
if (mECCurveOID.equals(NISTNamedCurves.getOID("P-256"))) {
|
||||
scurve = SaveKeyringParcel.Curve.NIST_P256;
|
||||
} else if (mECCurveOID.equals(NISTNamedCurves.getOID("P-384"))) {
|
||||
scurve = SaveKeyringParcel.Curve.NIST_P384;
|
||||
} else if (mECCurveOID.equals(NISTNamedCurves.getOID("P-521"))) {
|
||||
scurve = SaveKeyringParcel.Curve.NIST_P521;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported curve " + mECCurveOID);
|
||||
}
|
||||
|
||||
keyring.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(algo,
|
||||
curve.getFieldSize(), scurve, keyFlags, 0L));
|
||||
}
|
||||
}
|
||||
@@ -17,71 +17,83 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.securitytoken;
|
||||
|
||||
// 4.3.3.6 Algorithm Attributes
|
||||
public class KeyFormat {
|
||||
private int mAlgorithmId;
|
||||
private int mModulusLength;
|
||||
private int mExponentLength;
|
||||
private AlgorithmFormat mAlgorithmFormat;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.CreateSecurityTokenAlgorithmFragment;
|
||||
|
||||
public KeyFormat(byte[] bytes) {
|
||||
mAlgorithmId = bytes[0];
|
||||
mModulusLength = bytes[1] << 8 | bytes[2];
|
||||
mExponentLength = bytes[3] << 8 | bytes[4];
|
||||
mAlgorithmFormat = AlgorithmFormat.from(bytes[5]);
|
||||
public abstract class KeyFormat {
|
||||
|
||||
if (mAlgorithmId != 1) { // RSA
|
||||
throw new IllegalArgumentException("Unsupported Algorithm id " + mAlgorithmId);
|
||||
}
|
||||
public enum KeyFormatType {
|
||||
RSAKeyFormatType,
|
||||
ECKeyFormatType
|
||||
};
|
||||
|
||||
private final KeyFormatType mKeyFormatType;
|
||||
|
||||
public KeyFormat(final KeyFormatType keyFormatType) {
|
||||
mKeyFormatType = keyFormatType;
|
||||
}
|
||||
|
||||
public int getAlgorithmId() {
|
||||
return mAlgorithmId;
|
||||
public final KeyFormatType keyFormatType() {
|
||||
return mKeyFormatType;
|
||||
}
|
||||
|
||||
public int getModulusLength() {
|
||||
return mModulusLength;
|
||||
}
|
||||
|
||||
public int getExponentLength() {
|
||||
return mExponentLength;
|
||||
}
|
||||
|
||||
public AlgorithmFormat getAlgorithmFormat() {
|
||||
return mAlgorithmFormat;
|
||||
}
|
||||
|
||||
public enum AlgorithmFormat {
|
||||
STANDARD(0, false, false),
|
||||
STANDARD_WITH_MODULUS(1, false, true),
|
||||
CRT(2, true, false),
|
||||
CRT_WITH_MODULUS(3, true, true);
|
||||
|
||||
private int mValue;
|
||||
private boolean mIncludeModulus;
|
||||
private boolean mIncludeCrt;
|
||||
|
||||
AlgorithmFormat(int value, boolean includeCrt, boolean includeModulus) {
|
||||
mValue = value;
|
||||
mIncludeModulus = includeModulus;
|
||||
mIncludeCrt = includeCrt;
|
||||
}
|
||||
|
||||
public static AlgorithmFormat from(byte b) {
|
||||
for (AlgorithmFormat format : values()) {
|
||||
if (format.mValue == b) {
|
||||
return format;
|
||||
public static KeyFormat fromBytes(byte[] bytes) {
|
||||
switch (bytes[0]) {
|
||||
case PublicKeyAlgorithmTags.RSA_GENERAL:
|
||||
if (bytes.length < 6) {
|
||||
throw new IllegalArgumentException("Bad length for RSA attributes");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return new RSAKeyFormat(bytes[1] << 8 | bytes[2],
|
||||
bytes[3] << 8 | bytes[4],
|
||||
RSAKeyFormat.RSAAlgorithmFormat.from(bytes[5]));
|
||||
|
||||
public boolean isIncludeModulus() {
|
||||
return mIncludeModulus;
|
||||
}
|
||||
case PublicKeyAlgorithmTags.ECDH:
|
||||
case PublicKeyAlgorithmTags.ECDSA:
|
||||
if (bytes.length < 2) {
|
||||
throw new IllegalArgumentException("Bad length for RSA attributes");
|
||||
}
|
||||
int len = bytes.length - 1;
|
||||
if (bytes[bytes.length - 1] == (byte)0xff) {
|
||||
len -= 1;
|
||||
}
|
||||
final byte[] boid = new byte[2 + len];
|
||||
boid[0] = (byte)0x06;
|
||||
boid[1] = (byte)len;
|
||||
System.arraycopy(bytes, 1, boid, 2, len);
|
||||
final ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(boid);
|
||||
return new ECKeyFormat(oid, ECKeyFormat.ECAlgorithmFormat.from(bytes[0], bytes[bytes.length - 1]));
|
||||
|
||||
public boolean isIncludeCrt() {
|
||||
return mIncludeCrt;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported Algorithm id " + bytes[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public static KeyFormat fromCreationKeyType(CreateSecurityTokenAlgorithmFragment.SupportedKeyType t, boolean forEncryption) {
|
||||
final int elen = 17; //65537
|
||||
final ECKeyFormat.ECAlgorithmFormat kf =
|
||||
forEncryption ? ECKeyFormat.ECAlgorithmFormat.ECDH_WITH_PUBKEY : ECKeyFormat.ECAlgorithmFormat.ECDSA_WITH_PUBKEY;
|
||||
|
||||
switch (t) {
|
||||
case RSA_2048:
|
||||
return new RSAKeyFormat(2048, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
|
||||
case RSA_3072:
|
||||
return new RSAKeyFormat(3072, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
|
||||
case RSA_4096:
|
||||
return new RSAKeyFormat(4096, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
|
||||
case ECC_P256:
|
||||
return new ECKeyFormat(NISTNamedCurves.getOID("P-256"), kf);
|
||||
case ECC_P384:
|
||||
return new ECKeyFormat(NISTNamedCurves.getOID("P-384"), kf);
|
||||
case ECC_P521:
|
||||
return new ECKeyFormat(NISTNamedCurves.getOID("P-521"), kf);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unsupported Algorithm id " + t);
|
||||
}
|
||||
|
||||
public abstract void addToKeyring(SaveKeyringParcel keyring, int keyFlags);
|
||||
|
||||
}
|
||||
|
||||
@@ -36,15 +36,19 @@ public class OpenPgpCapabilities {
|
||||
private boolean mAttriburesChangable;
|
||||
private boolean mHasKeyImport;
|
||||
|
||||
private byte mSMAlgo;
|
||||
private int mSMAESKeySize;
|
||||
private int mMaxCmdLen;
|
||||
private int mMaxRspLen;
|
||||
|
||||
private Map<KeyType, KeyFormat> mKeyFormats;
|
||||
|
||||
public OpenPgpCapabilities(byte[] data) throws IOException {
|
||||
Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true);
|
||||
mKeyFormats = new HashMap<>();
|
||||
updateWithData(data);
|
||||
}
|
||||
|
||||
public void updateWithData(byte[] data) throws IOException {
|
||||
Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true);
|
||||
if (tlvs.length == 1 && tlvs[0].mT == 0x6E) {
|
||||
tlvs = ((Iso7816TLV.Iso7816CompositeTLV) tlvs[0]).mSubs;
|
||||
}
|
||||
@@ -64,13 +68,13 @@ public class OpenPgpCapabilities {
|
||||
parseExtendedCaps(tlv.mV);
|
||||
break;
|
||||
case 0xC1:
|
||||
mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV));
|
||||
mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV));
|
||||
break;
|
||||
case 0xC2:
|
||||
mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV));
|
||||
mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV));
|
||||
break;
|
||||
case 0xC3:
|
||||
mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV));
|
||||
mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV));
|
||||
break;
|
||||
case 0xC4:
|
||||
mPw1ValidForMultipleSignatures = tlv.mV[0] == 1;
|
||||
@@ -86,13 +90,13 @@ public class OpenPgpCapabilities {
|
||||
parseExtendedCaps(tlv.mV);
|
||||
break;
|
||||
case 0xC1:
|
||||
mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV));
|
||||
mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV));
|
||||
break;
|
||||
case 0xC2:
|
||||
mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV));
|
||||
mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV));
|
||||
break;
|
||||
case 0xC3:
|
||||
mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV));
|
||||
mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV));
|
||||
break;
|
||||
case 0xC4:
|
||||
mPw1ValidForMultipleSignatures = tlv.mV[0] == 1;
|
||||
@@ -106,7 +110,7 @@ public class OpenPgpCapabilities {
|
||||
mHasKeyImport = (v[0] & MASK_KEY_IMPORT) != 0;
|
||||
mAttriburesChangable =(v[0] & MASK_ATTRIBUTES_CHANGABLE) != 0;
|
||||
|
||||
mSMAlgo = v[1];
|
||||
mSMAESKeySize = (v[1] == 1) ? 16 : 32;
|
||||
|
||||
mMaxCmdLen = (v[6] << 8) + v[7];
|
||||
mMaxRspLen = (v[8] << 8) + v[9];
|
||||
@@ -128,7 +132,7 @@ public class OpenPgpCapabilities {
|
||||
return mHasSM;
|
||||
}
|
||||
|
||||
public boolean isAttriburesChangable() {
|
||||
public boolean isAttributesChangable() {
|
||||
return mAttriburesChangable;
|
||||
}
|
||||
|
||||
@@ -136,8 +140,8 @@ public class OpenPgpCapabilities {
|
||||
return mHasKeyImport;
|
||||
}
|
||||
|
||||
public byte getSMAlgo() {
|
||||
return mSMAlgo;
|
||||
public int getSMAESKeySize() {
|
||||
return mSMAESKeySize;
|
||||
}
|
||||
|
||||
public int getMaxCmdLen() {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.securitytoken;
|
||||
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
|
||||
// 4.3.3.6 Algorithm Attributes
|
||||
public class RSAKeyFormat extends KeyFormat {
|
||||
private int mModulusLength;
|
||||
private int mExponentLength;
|
||||
private RSAAlgorithmFormat mRSAAlgorithmFormat;
|
||||
|
||||
public RSAKeyFormat(int modulusLength,
|
||||
int exponentLength,
|
||||
RSAAlgorithmFormat rsaAlgorithmFormat) {
|
||||
super(KeyFormatType.RSAKeyFormatType);
|
||||
mModulusLength = modulusLength;
|
||||
mExponentLength = exponentLength;
|
||||
mRSAAlgorithmFormat = rsaAlgorithmFormat;
|
||||
}
|
||||
|
||||
public int getModulusLength() {
|
||||
return mModulusLength;
|
||||
}
|
||||
|
||||
public int getExponentLength() {
|
||||
return mExponentLength;
|
||||
}
|
||||
|
||||
public RSAAlgorithmFormat getAlgorithmFormat() {
|
||||
return mRSAAlgorithmFormat;
|
||||
}
|
||||
|
||||
public enum RSAAlgorithmFormat {
|
||||
STANDARD((byte)0, false, false),
|
||||
STANDARD_WITH_MODULUS((byte)1, false, true),
|
||||
CRT((byte)2, true, false),
|
||||
CRT_WITH_MODULUS((byte)3, true, true);
|
||||
|
||||
private byte mValue;
|
||||
private boolean mIncludeModulus;
|
||||
private boolean mIncludeCrt;
|
||||
|
||||
RSAAlgorithmFormat(byte value, boolean includeCrt, boolean includeModulus) {
|
||||
mValue = value;
|
||||
mIncludeModulus = includeModulus;
|
||||
mIncludeCrt = includeCrt;
|
||||
}
|
||||
|
||||
public static RSAAlgorithmFormat from(byte b) {
|
||||
for (RSAAlgorithmFormat format : values()) {
|
||||
if (format.mValue == b) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte getValue() { return mValue; }
|
||||
|
||||
public boolean isIncludeModulus() {
|
||||
return mIncludeModulus;
|
||||
}
|
||||
|
||||
public boolean isIncludeCrt() {
|
||||
return mIncludeCrt;
|
||||
}
|
||||
}
|
||||
|
||||
public void addToKeyring(SaveKeyringParcel keyring, int keyFlags) {
|
||||
keyring.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(SaveKeyringParcel.Algorithm.RSA,
|
||||
mModulusLength, null, keyFlags, 0L));
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,28 @@ package org.sufficientlysecure.keychain.securitytoken;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1OutputStream;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
||||
import org.bouncycastle.jcajce.util.MessageDigestUtils;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.operator.PGPPad;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||
import org.bouncycastle.util.Arrays;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.smartcardio.CommandAPDU;
|
||||
import javax.smartcardio.ResponseAPDU;
|
||||
import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException;
|
||||
@@ -41,6 +57,12 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.interfaces.RSAPrivateCrtKey;
|
||||
|
||||
/**
|
||||
@@ -64,6 +86,9 @@ public class SecurityTokenHelper {
|
||||
private static final String FIDESMO_APPS_AID_PREFIX = "A000000617";
|
||||
|
||||
private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
private final JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator();
|
||||
|
||||
private Transport mTransport;
|
||||
private CardCapabilities mCardCapabilities;
|
||||
private OpenPgpCapabilities mOpenPgpCapabilities;
|
||||
@@ -77,6 +102,12 @@ public class SecurityTokenHelper {
|
||||
protected SecurityTokenHelper() {
|
||||
}
|
||||
|
||||
public static double parseOpenPgpVersion(final byte[] aid) {
|
||||
float minv = aid[7];
|
||||
while (minv > 0) minv /= 10.0;
|
||||
return aid[6] + minv;
|
||||
}
|
||||
|
||||
public static SecurityTokenHelper getInstance() {
|
||||
return LazyHolder.SECURITY_TOKEN_HELPER;
|
||||
}
|
||||
@@ -218,17 +249,62 @@ public class SecurityTokenHelper {
|
||||
* Call DECIPHER command
|
||||
*
|
||||
* @param encryptedSessionKey the encoded session key
|
||||
* @param publicKey
|
||||
* @return the decoded session key
|
||||
*/
|
||||
public byte[] decryptSessionKey(@NonNull byte[] encryptedSessionKey) throws IOException {
|
||||
public byte[] decryptSessionKey(@NonNull byte[] encryptedSessionKey,
|
||||
CanonicalizedPublicKey publicKey)
|
||||
throws IOException {
|
||||
final KeyFormat kf = mOpenPgpCapabilities.getFormatForKeyType(KeyType.ENCRYPT);
|
||||
|
||||
if (!mPw1ValidatedForDecrypt) {
|
||||
verifyPin(0x82); // (Verify PW1 with mode 82 for decryption)
|
||||
}
|
||||
|
||||
// Transmit
|
||||
byte[] data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length);
|
||||
if (data[0] != 0) {
|
||||
data = Arrays.prepend(data, (byte) 0x00);
|
||||
byte[] data;
|
||||
int pLen = 0;
|
||||
|
||||
X9ECParameters x9Params = null;
|
||||
|
||||
switch (kf.keyFormatType()) {
|
||||
case RSAKeyFormatType:
|
||||
data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length);
|
||||
if (data[0] != 0) {
|
||||
data = Arrays.prepend(data, (byte) 0x00);
|
||||
}
|
||||
break;
|
||||
|
||||
case ECKeyFormatType:
|
||||
pLen = ((((encryptedSessionKey[0] & 0xff) << 8) + (encryptedSessionKey[1] & 0xff)) + 7) / 8;
|
||||
data = new byte[pLen];
|
||||
|
||||
System.arraycopy(encryptedSessionKey, 2, data, 0, pLen);
|
||||
|
||||
final ECKeyFormat eckf = (ECKeyFormat)kf;
|
||||
x9Params = NISTNamedCurves.getByOID(eckf.getCurveOID());
|
||||
|
||||
final ECPoint p = x9Params.getCurve().decodePoint(data);
|
||||
if (!p.isValid()) {
|
||||
throw new CardException("Invalid EC point!");
|
||||
}
|
||||
|
||||
data = p.getEncoded(false);
|
||||
data = Arrays.concatenate(
|
||||
Hex.decode("86"),
|
||||
new byte[]{ (byte)data.length },
|
||||
data);
|
||||
data = Arrays.concatenate(
|
||||
Hex.decode("7F49"),
|
||||
new byte[] { (byte)data.length },
|
||||
data);
|
||||
data = Arrays.concatenate(
|
||||
Hex.decode("A6"),
|
||||
new byte[] { (byte)data.length },
|
||||
data);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new CardException("Unknown encryption key type!");
|
||||
}
|
||||
|
||||
CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x80, 0x86, data, MAX_APDU_NE_EXT);
|
||||
@@ -238,7 +314,47 @@ public class SecurityTokenHelper {
|
||||
throw new CardException("Deciphering with Security token failed on receive", response.getSW());
|
||||
}
|
||||
|
||||
return response.getData();
|
||||
switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.ENCRYPT).keyFormatType()) {
|
||||
case RSAKeyFormatType:
|
||||
return response.getData();
|
||||
|
||||
case ECKeyFormatType:
|
||||
data = response.getData();
|
||||
|
||||
final byte[] keyEnc = new byte[encryptedSessionKey[pLen + 2]];
|
||||
|
||||
System.arraycopy(encryptedSessionKey, 2 + pLen + 1, keyEnc, 0, keyEnc.length);
|
||||
|
||||
try {
|
||||
final MessageDigest kdf = MessageDigest.getInstance(MessageDigestUtils.getDigestName(publicKey.getHashAlgorithm()));
|
||||
|
||||
kdf.update(new byte[]{ (byte)0, (byte)0, (byte)0, (byte)1 });
|
||||
kdf.update(data);
|
||||
kdf.update(publicKey.createUserKeyingMaterial(fingerprintCalculator));
|
||||
|
||||
final byte[] kek = kdf.digest();
|
||||
final Cipher c = Cipher.getInstance("AESWrap");
|
||||
|
||||
c.init(Cipher.UNWRAP_MODE, new SecretKeySpec(kek, 0, publicKey.getSymmetricKeySize() / 8, "AES"));
|
||||
|
||||
final Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY);
|
||||
|
||||
Arrays.fill(kek, (byte)0);
|
||||
|
||||
return PGPPad.unpadSessionData(paddedSessionKey.getEncoded());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new CardException("Unknown digest/encryption algorithm!");
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new CardException("Unknown padding algorithm!");
|
||||
} catch (PGPException e) {
|
||||
throw new CardException(e.getMessage());
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new CardException("Invalid KEK!");
|
||||
}
|
||||
|
||||
default:
|
||||
throw new CardException("Unknown encryption key type!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,6 +416,36 @@ public class SecurityTokenHelper {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setKeyAttributes(final KeyType slot, final CanonicalizedSecretKey secretKey)
|
||||
throws IOException {
|
||||
|
||||
if (mOpenPgpCapabilities.isAttributesChangable()) {
|
||||
int tag;
|
||||
|
||||
if (slot == KeyType.SIGN) {
|
||||
tag = 0xC1;
|
||||
} else if (slot == KeyType.ENCRYPT) {
|
||||
tag = 0xC2;
|
||||
} else if (slot == KeyType.AUTH) {
|
||||
tag = 0xC3;
|
||||
} else {
|
||||
throw new IOException("Unknown key for card.");
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
putData(tag, SecurityTokenUtils.attributesFromSecretKey(slot, secretKey));
|
||||
|
||||
mOpenPgpCapabilities.updateWithData(getData(0x00, tag));
|
||||
|
||||
} catch (PgpGeneralException e) {
|
||||
throw new IOException("Key algorithm not supported by the security token.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a key on the token in the given slot.
|
||||
*
|
||||
@@ -311,33 +457,58 @@ public class SecurityTokenHelper {
|
||||
private void putKey(KeyType slot, CanonicalizedSecretKey secretKey, Passphrase passphrase)
|
||||
throws IOException {
|
||||
RSAPrivateCrtKey crtSecretKey;
|
||||
try {
|
||||
secretKey.unlock(passphrase);
|
||||
crtSecretKey = secretKey.getCrtSecretKey();
|
||||
} catch (PgpGeneralException e) {
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
|
||||
// Shouldn't happen; the UI should block the user from getting an incompatible key this far.
|
||||
if (crtSecretKey.getModulus().bitLength() > 2048) {
|
||||
throw new IOException("Key too large to export to Security Token.");
|
||||
}
|
||||
|
||||
// Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
|
||||
if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
|
||||
throw new IOException("Invalid public exponent for smart Security Token.");
|
||||
}
|
||||
ECPrivateKey ecSecretKey;
|
||||
ECPublicKey ecPublicKey;
|
||||
|
||||
if (!mPw3Validated) {
|
||||
verifyPin(0x83); // (Verify PW3 with mode 83)
|
||||
}
|
||||
|
||||
|
||||
// Now we're ready to communicate with the token.
|
||||
byte[] bytes = SecurityTokenUtils.createPrivKeyTemplate(crtSecretKey, slot,
|
||||
mOpenPgpCapabilities.getFormatForKeyType(slot));
|
||||
byte[] keyBytes = null;
|
||||
|
||||
CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, bytes);
|
||||
try {
|
||||
secretKey.unlock(passphrase);
|
||||
|
||||
setKeyAttributes(slot, secretKey);
|
||||
|
||||
switch (mOpenPgpCapabilities.getFormatForKeyType(slot).keyFormatType()) {
|
||||
case RSAKeyFormatType:
|
||||
if (!secretKey.isRSA()) {
|
||||
throw new IOException("Security Token not configured for RSA key.");
|
||||
}
|
||||
crtSecretKey = secretKey.getCrtSecretKey();
|
||||
|
||||
// Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
|
||||
if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
|
||||
throw new IOException("Invalid public exponent for smart Security Token.");
|
||||
}
|
||||
|
||||
keyBytes = SecurityTokenUtils.createRSAPrivKeyTemplate(crtSecretKey, slot,
|
||||
(RSAKeyFormat) (mOpenPgpCapabilities.getFormatForKeyType(slot)));
|
||||
break;
|
||||
|
||||
case ECKeyFormatType:
|
||||
if (!secretKey.isEC()) {
|
||||
throw new IOException("Security Token not configured for EC key.");
|
||||
}
|
||||
|
||||
secretKey.unlock(passphrase);
|
||||
ecSecretKey = secretKey.getECSecretKey();
|
||||
ecPublicKey = secretKey.getECPublicKey();
|
||||
|
||||
keyBytes = SecurityTokenUtils.createECPrivKeyTemplate(ecSecretKey, ecPublicKey, slot,
|
||||
(ECKeyFormat) (mOpenPgpCapabilities.getFormatForKeyType(slot)));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IOException("Key type unsupported by security token.");
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
|
||||
CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, keyBytes);
|
||||
ResponseAPDU response = communicate(apdu);
|
||||
|
||||
if (response.getSW() != APDU_SW_SUCCESS) {
|
||||
@@ -462,8 +633,21 @@ public class SecurityTokenHelper {
|
||||
throw new IOException("Not supported hash algo!");
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
|
||||
switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.SIGN).keyFormatType()) {
|
||||
case RSAKeyFormatType:
|
||||
data = dsi;
|
||||
break;
|
||||
case ECKeyFormatType:
|
||||
data = hash;
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Not supported key type!");
|
||||
}
|
||||
|
||||
// Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
|
||||
CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, dsi, MAX_APDU_NE_EXT);
|
||||
CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, data, MAX_APDU_NE_EXT);
|
||||
ResponseAPDU response = communicate(command);
|
||||
|
||||
if (response.getSW() != APDU_SW_SUCCESS) {
|
||||
@@ -477,9 +661,30 @@ public class SecurityTokenHelper {
|
||||
byte[] signature = response.getData();
|
||||
|
||||
// Make sure the signature we received is actually the expected number of bytes long!
|
||||
if (signature.length != 128 && signature.length != 256
|
||||
&& signature.length != 384 && signature.length != 512) {
|
||||
throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length);
|
||||
switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.SIGN).keyFormatType()) {
|
||||
case RSAKeyFormatType:
|
||||
if (signature.length != 128 && signature.length != 256
|
||||
&& signature.length != 384 && signature.length != 512) {
|
||||
throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length);
|
||||
}
|
||||
break;
|
||||
|
||||
case ECKeyFormatType:
|
||||
if (signature.length % 2 != 0) {
|
||||
throw new IOException("Bad signature length!");
|
||||
}
|
||||
final byte[] br = new byte[signature.length / 2];
|
||||
final byte[] bs = new byte[signature.length / 2];
|
||||
for(int i = 0; i < br.length; ++i) {
|
||||
br[i] = signature[i];
|
||||
bs[i] = signature[br.length + i];
|
||||
}
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ASN1OutputStream out = new ASN1OutputStream(baos);
|
||||
out.writeObject(new DERSequence(new ASN1Encodable[] { new ASN1Integer(br), new ASN1Integer(bs) }));
|
||||
out.flush();
|
||||
signature = baos.toByteArray();
|
||||
break;
|
||||
}
|
||||
|
||||
return signature;
|
||||
|
||||
Reference in New Issue
Block a user