Support of OpenPGP card v3

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

View File

@@ -0,0 +1,83 @@
package org.sufficientlysecure.keychain.securitytoken;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.math.ec.ECCurve;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
// 4.3.3.6 Algorithm Attributes
public class ECKeyFormat extends KeyFormat {
private final ECAlgorithmFormat mECAlgorithmFormat;
private final ASN1ObjectIdentifier mECCurveOID;
public ECKeyFormat(final ASN1ObjectIdentifier ecCurveOid,
final ECAlgorithmFormat ecAlgorithmFormat) {
super(KeyFormatType.ECKeyFormatType);
mECAlgorithmFormat = ecAlgorithmFormat;
mECCurveOID = ecCurveOid;
}
public ECKeyFormat.ECAlgorithmFormat getAlgorithmFormat() {
return mECAlgorithmFormat;
}
public ASN1ObjectIdentifier getCurveOID() { return mECCurveOID; }
public enum ECAlgorithmFormat {
ECDH((byte)18, true, false),
ECDH_WITH_PUBKEY((byte)18, true, true),
ECDSA((byte)19, false, false),
ECDSA_WITH_PUBKEY((byte)19, false, true);
private final byte mValue;
private final boolean mIsECDH;
private final boolean mWithPubkey;
ECAlgorithmFormat(final byte value, final boolean isECDH, final boolean withPubkey) {
mValue = value;
mIsECDH = isECDH;
mWithPubkey = withPubkey;
}
public static ECKeyFormat.ECAlgorithmFormat from(final byte bFirst, final byte bLast) {
for (ECKeyFormat.ECAlgorithmFormat format : values()) {
if (format.mValue == bFirst && ((bLast == (byte)0xff) == format.isWithPubkey())) {
return format;
}
}
return null;
}
public final byte getValue() { return mValue; }
public final boolean isECDH() { return mIsECDH; }
public final boolean isWithPubkey() { return mWithPubkey; }
}
public void addToKeyring(SaveKeyringParcel keyring, int keyFlags) {
final X9ECParameters params = NISTNamedCurves.getByOID(mECCurveOID);
final ECCurve curve = params.getCurve();
SaveKeyringParcel.Algorithm algo = SaveKeyringParcel.Algorithm.ECDSA;
if (((keyFlags & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS)
|| ((keyFlags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE)) {
algo = SaveKeyringParcel.Algorithm.ECDH;
}
SaveKeyringParcel.Curve scurve;
if (mECCurveOID.equals(NISTNamedCurves.getOID("P-256"))) {
scurve = SaveKeyringParcel.Curve.NIST_P256;
} else if (mECCurveOID.equals(NISTNamedCurves.getOID("P-384"))) {
scurve = SaveKeyringParcel.Curve.NIST_P384;
} else if (mECCurveOID.equals(NISTNamedCurves.getOID("P-521"))) {
scurve = SaveKeyringParcel.Curve.NIST_P521;
} else {
throw new IllegalArgumentException("Unsupported curve " + mECCurveOID);
}
keyring.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(algo,
curve.getFieldSize(), scurve, keyFlags, 0L));
}
}

View File

@@ -17,71 +17,83 @@
package org.sufficientlysecure.keychain.securitytoken;
// 4.3.3.6 Algorithm Attributes
public class KeyFormat {
private int mAlgorithmId;
private int mModulusLength;
private int mExponentLength;
private AlgorithmFormat mAlgorithmFormat;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.CreateSecurityTokenAlgorithmFragment;
public KeyFormat(byte[] bytes) {
mAlgorithmId = bytes[0];
mModulusLength = bytes[1] << 8 | bytes[2];
mExponentLength = bytes[3] << 8 | bytes[4];
mAlgorithmFormat = AlgorithmFormat.from(bytes[5]);
public abstract class KeyFormat {
if (mAlgorithmId != 1) { // RSA
throw new IllegalArgumentException("Unsupported Algorithm id " + mAlgorithmId);
}
public enum KeyFormatType {
RSAKeyFormatType,
ECKeyFormatType
};
private final KeyFormatType mKeyFormatType;
public KeyFormat(final KeyFormatType keyFormatType) {
mKeyFormatType = keyFormatType;
}
public int getAlgorithmId() {
return mAlgorithmId;
public final KeyFormatType keyFormatType() {
return mKeyFormatType;
}
public int getModulusLength() {
return mModulusLength;
}
public int getExponentLength() {
return mExponentLength;
}
public AlgorithmFormat getAlgorithmFormat() {
return mAlgorithmFormat;
}
public enum AlgorithmFormat {
STANDARD(0, false, false),
STANDARD_WITH_MODULUS(1, false, true),
CRT(2, true, false),
CRT_WITH_MODULUS(3, true, true);
private int mValue;
private boolean mIncludeModulus;
private boolean mIncludeCrt;
AlgorithmFormat(int value, boolean includeCrt, boolean includeModulus) {
mValue = value;
mIncludeModulus = includeModulus;
mIncludeCrt = includeCrt;
}
public static AlgorithmFormat from(byte b) {
for (AlgorithmFormat format : values()) {
if (format.mValue == b) {
return format;
public static KeyFormat fromBytes(byte[] bytes) {
switch (bytes[0]) {
case PublicKeyAlgorithmTags.RSA_GENERAL:
if (bytes.length < 6) {
throw new IllegalArgumentException("Bad length for RSA attributes");
}
}
return null;
}
return new RSAKeyFormat(bytes[1] << 8 | bytes[2],
bytes[3] << 8 | bytes[4],
RSAKeyFormat.RSAAlgorithmFormat.from(bytes[5]));
public boolean isIncludeModulus() {
return mIncludeModulus;
}
case PublicKeyAlgorithmTags.ECDH:
case PublicKeyAlgorithmTags.ECDSA:
if (bytes.length < 2) {
throw new IllegalArgumentException("Bad length for RSA attributes");
}
int len = bytes.length - 1;
if (bytes[bytes.length - 1] == (byte)0xff) {
len -= 1;
}
final byte[] boid = new byte[2 + len];
boid[0] = (byte)0x06;
boid[1] = (byte)len;
System.arraycopy(bytes, 1, boid, 2, len);
final ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(boid);
return new ECKeyFormat(oid, ECKeyFormat.ECAlgorithmFormat.from(bytes[0], bytes[bytes.length - 1]));
public boolean isIncludeCrt() {
return mIncludeCrt;
default:
throw new IllegalArgumentException("Unsupported Algorithm id " + bytes[0]);
}
}
public static KeyFormat fromCreationKeyType(CreateSecurityTokenAlgorithmFragment.SupportedKeyType t, boolean forEncryption) {
final int elen = 17; //65537
final ECKeyFormat.ECAlgorithmFormat kf =
forEncryption ? ECKeyFormat.ECAlgorithmFormat.ECDH_WITH_PUBKEY : ECKeyFormat.ECAlgorithmFormat.ECDSA_WITH_PUBKEY;
switch (t) {
case RSA_2048:
return new RSAKeyFormat(2048, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
case RSA_3072:
return new RSAKeyFormat(3072, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
case RSA_4096:
return new RSAKeyFormat(4096, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
case ECC_P256:
return new ECKeyFormat(NISTNamedCurves.getOID("P-256"), kf);
case ECC_P384:
return new ECKeyFormat(NISTNamedCurves.getOID("P-384"), kf);
case ECC_P521:
return new ECKeyFormat(NISTNamedCurves.getOID("P-521"), kf);
}
throw new IllegalArgumentException("Unsupported Algorithm id " + t);
}
public abstract void addToKeyring(SaveKeyringParcel keyring, int keyFlags);
}

View File

@@ -36,15 +36,19 @@ public class OpenPgpCapabilities {
private boolean mAttriburesChangable;
private boolean mHasKeyImport;
private byte mSMAlgo;
private int mSMAESKeySize;
private int mMaxCmdLen;
private int mMaxRspLen;
private Map<KeyType, KeyFormat> mKeyFormats;
public OpenPgpCapabilities(byte[] data) throws IOException {
Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true);
mKeyFormats = new HashMap<>();
updateWithData(data);
}
public void updateWithData(byte[] data) throws IOException {
Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true);
if (tlvs.length == 1 && tlvs[0].mT == 0x6E) {
tlvs = ((Iso7816TLV.Iso7816CompositeTLV) tlvs[0]).mSubs;
}
@@ -64,13 +68,13 @@ public class OpenPgpCapabilities {
parseExtendedCaps(tlv.mV);
break;
case 0xC1:
mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV));
mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC2:
mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV));
mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC3:
mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV));
mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC4:
mPw1ValidForMultipleSignatures = tlv.mV[0] == 1;
@@ -86,13 +90,13 @@ public class OpenPgpCapabilities {
parseExtendedCaps(tlv.mV);
break;
case 0xC1:
mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV));
mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC2:
mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV));
mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC3:
mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV));
mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC4:
mPw1ValidForMultipleSignatures = tlv.mV[0] == 1;
@@ -106,7 +110,7 @@ public class OpenPgpCapabilities {
mHasKeyImport = (v[0] & MASK_KEY_IMPORT) != 0;
mAttriburesChangable =(v[0] & MASK_ATTRIBUTES_CHANGABLE) != 0;
mSMAlgo = v[1];
mSMAESKeySize = (v[1] == 1) ? 16 : 32;
mMaxCmdLen = (v[6] << 8) + v[7];
mMaxRspLen = (v[8] << 8) + v[9];
@@ -128,7 +132,7 @@ public class OpenPgpCapabilities {
return mHasSM;
}
public boolean isAttriburesChangable() {
public boolean isAttributesChangable() {
return mAttriburesChangable;
}
@@ -136,8 +140,8 @@ public class OpenPgpCapabilities {
return mHasKeyImport;
}
public byte getSMAlgo() {
return mSMAlgo;
public int getSMAESKeySize() {
return mSMAESKeySize;
}
public int getMaxCmdLen() {

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.securitytoken;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
// 4.3.3.6 Algorithm Attributes
public class RSAKeyFormat extends KeyFormat {
private int mModulusLength;
private int mExponentLength;
private RSAAlgorithmFormat mRSAAlgorithmFormat;
public RSAKeyFormat(int modulusLength,
int exponentLength,
RSAAlgorithmFormat rsaAlgorithmFormat) {
super(KeyFormatType.RSAKeyFormatType);
mModulusLength = modulusLength;
mExponentLength = exponentLength;
mRSAAlgorithmFormat = rsaAlgorithmFormat;
}
public int getModulusLength() {
return mModulusLength;
}
public int getExponentLength() {
return mExponentLength;
}
public RSAAlgorithmFormat getAlgorithmFormat() {
return mRSAAlgorithmFormat;
}
public enum RSAAlgorithmFormat {
STANDARD((byte)0, false, false),
STANDARD_WITH_MODULUS((byte)1, false, true),
CRT((byte)2, true, false),
CRT_WITH_MODULUS((byte)3, true, true);
private byte mValue;
private boolean mIncludeModulus;
private boolean mIncludeCrt;
RSAAlgorithmFormat(byte value, boolean includeCrt, boolean includeModulus) {
mValue = value;
mIncludeModulus = includeModulus;
mIncludeCrt = includeCrt;
}
public static RSAAlgorithmFormat from(byte b) {
for (RSAAlgorithmFormat format : values()) {
if (format.mValue == b) {
return format;
}
}
return null;
}
public byte getValue() { return mValue; }
public boolean isIncludeModulus() {
return mIncludeModulus;
}
public boolean isIncludeCrt() {
return mIncludeCrt;
}
}
public void addToKeyring(SaveKeyringParcel keyring, int keyFlags) {
keyring.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(SaveKeyringParcel.Algorithm.RSA,
mModulusLength, null, keyFlags, 0L));
}
}

View File

@@ -23,12 +23,28 @@ package org.sufficientlysecure.keychain.securitytoken;
import android.support.annotation.NonNull;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.jcajce.util.MessageDigestUtils;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.operator.PGPPad;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException;
@@ -41,6 +57,12 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
/**
@@ -64,6 +86,9 @@ public class SecurityTokenHelper {
private static final String FIDESMO_APPS_AID_PREFIX = "A000000617";
private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
private final JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator();
private Transport mTransport;
private CardCapabilities mCardCapabilities;
private OpenPgpCapabilities mOpenPgpCapabilities;
@@ -77,6 +102,12 @@ public class SecurityTokenHelper {
protected SecurityTokenHelper() {
}
public static double parseOpenPgpVersion(final byte[] aid) {
float minv = aid[7];
while (minv > 0) minv /= 10.0;
return aid[6] + minv;
}
public static SecurityTokenHelper getInstance() {
return LazyHolder.SECURITY_TOKEN_HELPER;
}
@@ -218,17 +249,62 @@ public class SecurityTokenHelper {
* Call DECIPHER command
*
* @param encryptedSessionKey the encoded session key
* @param publicKey
* @return the decoded session key
*/
public byte[] decryptSessionKey(@NonNull byte[] encryptedSessionKey) throws IOException {
public byte[] decryptSessionKey(@NonNull byte[] encryptedSessionKey,
CanonicalizedPublicKey publicKey)
throws IOException {
final KeyFormat kf = mOpenPgpCapabilities.getFormatForKeyType(KeyType.ENCRYPT);
if (!mPw1ValidatedForDecrypt) {
verifyPin(0x82); // (Verify PW1 with mode 82 for decryption)
}
// Transmit
byte[] data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length);
if (data[0] != 0) {
data = Arrays.prepend(data, (byte) 0x00);
byte[] data;
int pLen = 0;
X9ECParameters x9Params = null;
switch (kf.keyFormatType()) {
case RSAKeyFormatType:
data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length);
if (data[0] != 0) {
data = Arrays.prepend(data, (byte) 0x00);
}
break;
case ECKeyFormatType:
pLen = ((((encryptedSessionKey[0] & 0xff) << 8) + (encryptedSessionKey[1] & 0xff)) + 7) / 8;
data = new byte[pLen];
System.arraycopy(encryptedSessionKey, 2, data, 0, pLen);
final ECKeyFormat eckf = (ECKeyFormat)kf;
x9Params = NISTNamedCurves.getByOID(eckf.getCurveOID());
final ECPoint p = x9Params.getCurve().decodePoint(data);
if (!p.isValid()) {
throw new CardException("Invalid EC point!");
}
data = p.getEncoded(false);
data = Arrays.concatenate(
Hex.decode("86"),
new byte[]{ (byte)data.length },
data);
data = Arrays.concatenate(
Hex.decode("7F49"),
new byte[] { (byte)data.length },
data);
data = Arrays.concatenate(
Hex.decode("A6"),
new byte[] { (byte)data.length },
data);
break;
default:
throw new CardException("Unknown encryption key type!");
}
CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x80, 0x86, data, MAX_APDU_NE_EXT);
@@ -238,7 +314,47 @@ public class SecurityTokenHelper {
throw new CardException("Deciphering with Security token failed on receive", response.getSW());
}
return response.getData();
switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.ENCRYPT).keyFormatType()) {
case RSAKeyFormatType:
return response.getData();
case ECKeyFormatType:
data = response.getData();
final byte[] keyEnc = new byte[encryptedSessionKey[pLen + 2]];
System.arraycopy(encryptedSessionKey, 2 + pLen + 1, keyEnc, 0, keyEnc.length);
try {
final MessageDigest kdf = MessageDigest.getInstance(MessageDigestUtils.getDigestName(publicKey.getHashAlgorithm()));
kdf.update(new byte[]{ (byte)0, (byte)0, (byte)0, (byte)1 });
kdf.update(data);
kdf.update(publicKey.createUserKeyingMaterial(fingerprintCalculator));
final byte[] kek = kdf.digest();
final Cipher c = Cipher.getInstance("AESWrap");
c.init(Cipher.UNWRAP_MODE, new SecretKeySpec(kek, 0, publicKey.getSymmetricKeySize() / 8, "AES"));
final Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY);
Arrays.fill(kek, (byte)0);
return PGPPad.unpadSessionData(paddedSessionKey.getEncoded());
} catch (NoSuchAlgorithmException e) {
throw new CardException("Unknown digest/encryption algorithm!");
} catch (NoSuchPaddingException e) {
throw new CardException("Unknown padding algorithm!");
} catch (PGPException e) {
throw new CardException(e.getMessage());
} catch (InvalidKeyException e) {
throw new CardException("Invalid KEK!");
}
default:
throw new CardException("Unknown encryption key type!");
}
}
/**
@@ -300,6 +416,36 @@ public class SecurityTokenHelper {
}
}
private void setKeyAttributes(final KeyType slot, final CanonicalizedSecretKey secretKey)
throws IOException {
if (mOpenPgpCapabilities.isAttributesChangable()) {
int tag;
if (slot == KeyType.SIGN) {
tag = 0xC1;
} else if (slot == KeyType.ENCRYPT) {
tag = 0xC2;
} else if (slot == KeyType.AUTH) {
tag = 0xC3;
} else {
throw new IOException("Unknown key for card.");
}
try {
putData(tag, SecurityTokenUtils.attributesFromSecretKey(slot, secretKey));
mOpenPgpCapabilities.updateWithData(getData(0x00, tag));
} catch (PgpGeneralException e) {
throw new IOException("Key algorithm not supported by the security token.");
}
}
}
/**
* Puts a key on the token in the given slot.
*
@@ -311,33 +457,58 @@ public class SecurityTokenHelper {
private void putKey(KeyType slot, CanonicalizedSecretKey secretKey, Passphrase passphrase)
throws IOException {
RSAPrivateCrtKey crtSecretKey;
try {
secretKey.unlock(passphrase);
crtSecretKey = secretKey.getCrtSecretKey();
} catch (PgpGeneralException e) {
throw new IOException(e.getMessage());
}
// Shouldn't happen; the UI should block the user from getting an incompatible key this far.
if (crtSecretKey.getModulus().bitLength() > 2048) {
throw new IOException("Key too large to export to Security Token.");
}
// Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
throw new IOException("Invalid public exponent for smart Security Token.");
}
ECPrivateKey ecSecretKey;
ECPublicKey ecPublicKey;
if (!mPw3Validated) {
verifyPin(0x83); // (Verify PW3 with mode 83)
}
// Now we're ready to communicate with the token.
byte[] bytes = SecurityTokenUtils.createPrivKeyTemplate(crtSecretKey, slot,
mOpenPgpCapabilities.getFormatForKeyType(slot));
byte[] keyBytes = null;
CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, bytes);
try {
secretKey.unlock(passphrase);
setKeyAttributes(slot, secretKey);
switch (mOpenPgpCapabilities.getFormatForKeyType(slot).keyFormatType()) {
case RSAKeyFormatType:
if (!secretKey.isRSA()) {
throw new IOException("Security Token not configured for RSA key.");
}
crtSecretKey = secretKey.getCrtSecretKey();
// Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
throw new IOException("Invalid public exponent for smart Security Token.");
}
keyBytes = SecurityTokenUtils.createRSAPrivKeyTemplate(crtSecretKey, slot,
(RSAKeyFormat) (mOpenPgpCapabilities.getFormatForKeyType(slot)));
break;
case ECKeyFormatType:
if (!secretKey.isEC()) {
throw new IOException("Security Token not configured for EC key.");
}
secretKey.unlock(passphrase);
ecSecretKey = secretKey.getECSecretKey();
ecPublicKey = secretKey.getECPublicKey();
keyBytes = SecurityTokenUtils.createECPrivKeyTemplate(ecSecretKey, ecPublicKey, slot,
(ECKeyFormat) (mOpenPgpCapabilities.getFormatForKeyType(slot)));
break;
default:
throw new IOException("Key type unsupported by security token.");
}
} catch (PgpGeneralException e) {
throw new IOException(e.getMessage());
}
CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, keyBytes);
ResponseAPDU response = communicate(apdu);
if (response.getSW() != APDU_SW_SUCCESS) {
@@ -462,8 +633,21 @@ public class SecurityTokenHelper {
throw new IOException("Not supported hash algo!");
}
byte[] data;
switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.SIGN).keyFormatType()) {
case RSAKeyFormatType:
data = dsi;
break;
case ECKeyFormatType:
data = hash;
break;
default:
throw new IOException("Not supported key type!");
}
// Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, dsi, MAX_APDU_NE_EXT);
CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, data, MAX_APDU_NE_EXT);
ResponseAPDU response = communicate(command);
if (response.getSW() != APDU_SW_SUCCESS) {
@@ -477,9 +661,30 @@ public class SecurityTokenHelper {
byte[] signature = response.getData();
// Make sure the signature we received is actually the expected number of bytes long!
if (signature.length != 128 && signature.length != 256
&& signature.length != 384 && signature.length != 512) {
throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length);
switch (mOpenPgpCapabilities.getFormatForKeyType(KeyType.SIGN).keyFormatType()) {
case RSAKeyFormatType:
if (signature.length != 128 && signature.length != 256
&& signature.length != 384 && signature.length != 512) {
throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length);
}
break;
case ECKeyFormatType:
if (signature.length % 2 != 0) {
throw new IOException("Bad signature length!");
}
final byte[] br = new byte[signature.length / 2];
final byte[] bs = new byte[signature.length / 2];
for(int i = 0; i < br.length; ++i) {
br[i] = signature[i];
bs[i] = signature[br.length + i];
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
ASN1OutputStream out = new ASN1OutputStream(baos);
out.writeObject(new DERSequence(new ASN1Encodable[] { new ASN1Integer(br), new ASN1Integer(bs) }));
out.flush();
signature = baos.toByteArray();
break;
}
return signature;