/*
* Copyright (C) 2016-2017 Arnaud Fontaine
*
* 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 android.content.Context;
import android.support.annotation.NonNull;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;
import org.sufficientlysecure.keychain.ui.SettingsSmartPGPAuthoritiesActivity;
import org.sufficientlysecure.keychain.util.Preferences;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXCertPathBuilderResult;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.util.ArrayList;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyAgreement;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
class SCP11bSecureMessaging implements SecureMessaging {
private static final byte OPENPGP_SECURE_MESSAGING_CLA_MASK = (byte)0x04;
private static final byte[] OPENPGP_SECURE_MESSAGING_KEY_CRT = new byte[] { (byte)0xA6, (byte)0 };
private static final byte OPENPGP_SECURE_MESSAGING_KEY_ATTRIBUTES_TAG = (byte)0xD4;
private static final int AES_BLOCK_SIZE = 128 / 8;
private static final int SCP11_MAC_LENGTH = AES_BLOCK_SIZE / 2;
private static final String SCP11_SYMMETRIC_ALGO = "AES";
private static final String SCP11_CIPHER_ALGO = "AES/CBC/NoPadding";
private static final String SCP11_MAC_ALGO = "AESCMAC";
private static final String SCP11B_KEY_AGREEMENT_ALGO = "ECDH";
private static final String SCP11B_KEY_AGREEMENT_KEY_TYPE = "ECDH";
private static final String SCP11B_KEY_AGREEMENT_KEY_ALGO = "EC";
private static final String SCP11B_KEY_DERIVATION_ALGO = "SHA256";
private static final String CERTIFICATE_FORMAT = "X.509";
private static final String PROVIDER = "BC";
private static SecureRandom srand;
private static KeyFactory ecdhFactory;
private static CertificateFactory certFactory;
private SecretKey mSEnc;
private SecretKey mSMac;
private SecretKey mSRMac;
private short mEncryptionCounter;
private byte[] mMacChaining;
private SCP11bSecureMessaging() {
}
private void setKeys(@NonNull final byte[] sEnc,
@NonNull final byte[] sMac,
@NonNull final byte[] sRmac,
@NonNull final byte[] receipt)
throws SecureMessagingException {
if ((sEnc.length != sMac.length)
|| (sEnc.length != sRmac.length)
|| (receipt.length != AES_BLOCK_SIZE)) {
throw new SecureMessagingException("incoherent SCP11b key set");
}
mSEnc = new SecretKeySpec(sEnc, SCP11_SYMMETRIC_ALGO);
mSMac = new SecretKeySpec(sMac, SCP11_SYMMETRIC_ALGO);
mSRMac = new SecretKeySpec(sRmac, SCP11_SYMMETRIC_ALGO);
mEncryptionCounter = 0;
mMacChaining = receipt;
}
@Override
public void clearSession() {
mSEnc = null;
mSMac = null;
mSRMac = null;
mEncryptionCounter = 0;
mMacChaining = null;
}
@Override
public boolean isEstablished() {
return (mSEnc != null)
&& (mSMac != null)
&& (mSRMac != null)
&& (mMacChaining != null);
}
private static final ECParameterSpec getAlgorithmParameterSpec(final ECKeyFormat kf)
throws NoSuchProviderException, NoSuchAlgorithmException, InvalidParameterSpecException {
final AlgorithmParameters algoParams = AlgorithmParameters.getInstance(SCP11B_KEY_AGREEMENT_KEY_ALGO, PROVIDER);
algoParams.init(new ECGenParameterSpec(ECNamedCurveTable.getName(kf.getCurveOID())));
return algoParams.getParameterSpec(ECParameterSpec.class);
}
private static ECPublicKey newECDHPublicKey(final ECKeyFormat kf, byte[] data)
throws InvalidKeySpecException, NoSuchAlgorithmException,
InvalidParameterSpecException, NoSuchProviderException {
if (ecdhFactory == null) {
ecdhFactory = KeyFactory.getInstance(SCP11B_KEY_AGREEMENT_KEY_TYPE, PROVIDER);
}
final X9ECParameters params = NISTNamedCurves.getByOID(kf.getCurveOID());
if (params == null) {
throw new InvalidParameterSpecException("unsupported curve");
}
final ECCurve curve = params.getCurve();
final ECPoint p = curve.decodePoint(data);
if (!p.isValid()) {
throw new InvalidKeySpecException("invalid EC point");
}
final java.security.spec.ECPublicKeySpec pk = new java.security.spec.ECPublicKeySpec(
new java.security.spec.ECPoint(p.getAffineXCoord().toBigInteger(), p.getAffineYCoord().toBigInteger()),
getAlgorithmParameterSpec(kf));
return (ECPublicKey)(ecdhFactory.generatePublic(pk));
}
private static KeyPair generateECDHKeyPair(final ECKeyFormat kf)
throws NoSuchProviderException, NoSuchAlgorithmException,
InvalidParameterSpecException, InvalidAlgorithmParameterException {
final KeyPairGenerator gen = KeyPairGenerator.getInstance(SCP11B_KEY_AGREEMENT_KEY_ALGO, PROVIDER);
if (srand == null) {
srand = new SecureRandom();
}
gen.initialize(getAlgorithmParameterSpec(kf), srand);
return gen.generateKeyPair();
}
private static ECPublicKey verifyCertificate(final Context ctx,
final ECKeyFormat kf,
final byte[] data) throws IOException {
try {
if (certFactory == null) {
certFactory = CertificateFactory.getInstance(CERTIFICATE_FORMAT, PROVIDER);
}
final ECParameterSpec kfParams = getAlgorithmParameterSpec(kf);
final Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(data));
if (!(cert instanceof X509Certificate)) {
throw new IOException("invalid card certificate");
}
final X509Certificate cardCert = (X509Certificate) cert;
final PublicKey pk = cardCert.getPublicKey();
if (!(pk instanceof ECPublicKey)) {
throw new IOException("invalid card public key");
}
final ECPublicKey cardPk = (ECPublicKey) pk;
final ECParameterSpec cardPkParams = cardPk.getParams();
if (!kfParams.getCurve().equals(cardPkParams.getCurve())) {
throw new IOException("incoherent card certificate/public key format");
}
final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(ctx);
if (ks == null) {
throw new KeyStoreException("no keystore found");
}
final X509CertSelector targetConstraints = new X509CertSelector();
targetConstraints.setCertificate(cardCert);
final ArrayList al = new ArrayList();
al.add(cardCert);
final CollectionCertStoreParameters certStoreParams = new CollectionCertStoreParameters(al);
final CertStore certStore = CertStore.getInstance("Collection", certStoreParams, PROVIDER);
final PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(ks, targetConstraints);
pkixParams.setRevocationEnabled(false);
pkixParams.addCertStore(certStore);
final CertPathBuilder builder = CertPathBuilder.getInstance(CertPathBuilder.getDefaultType(), PROVIDER);
final PKIXCertPathBuilderResult result =
(PKIXCertPathBuilderResult) builder.build(pkixParams);
return cardPk;
} catch (CertificateException e) {
throw new IOException("invalid card certificate (" + e.getMessage() + ")");
} catch (NoSuchAlgorithmException e) {
throw new IOException("unknown algorithm (" + e.getMessage() + ")");
} catch (InvalidParameterSpecException e) {
throw new IOException("invalid card key parameters (" + e.getMessage() + ")");
} catch (IllegalArgumentException e) {
e.printStackTrace();
throw new IOException("illegal argument (" + e.getMessage() + ")");
} catch (NoSuchProviderException e) {
throw new IOException("unavailable crypto (" + e.getMessage() + ")");
} catch (KeyStoreException e) {
throw new IOException("failed to build keystore (" + e.getMessage() + ")");
} catch (InvalidAlgorithmParameterException e) {
throw new IOException("invalid algorithm parameter (" + e.getMessage() + ")");
} catch (CertPathBuilderException e) {
throw new IOException("invalid certificate path (" + e.getMessage() + ")");
}
}
public static void establish(final SecurityTokenHelper t, final Context ctx)
throws SecureMessagingException, IOException {
final int keySize = t.getOpenPgpCapabilities().getSMAESKeySize();
t.clearSecureMessaging();
if ((keySize != 16)
&& (keySize != 32)) {
throw new SecureMessagingException("invalid key size");
}
CommandAPDU cmd;
ResponseAPDU resp;
Iso7816TLV[] tlvs;
// retrieving key algorithm
cmd = new CommandAPDU(0, (byte)0xCA, (byte)0x00,
OPENPGP_SECURE_MESSAGING_KEY_ATTRIBUTES_TAG, SecurityTokenHelper.MAX_APDU_NE_EXT);
resp = t.communicate(cmd);
if (resp.getSW() != SecurityTokenHelper.APDU_SW_SUCCESS) {
throw new SecureMessagingException("failed to retrieve secure messaging key attributes");
}
tlvs = Iso7816TLV.readList(resp.getData(), true);
if ((tlvs == null)
|| (tlvs.length != 1)
|| ((byte)tlvs[0].mT != OPENPGP_SECURE_MESSAGING_KEY_ATTRIBUTES_TAG)) {
throw new SecureMessagingException("invalid format of secure messaging key attributes");
}
final KeyFormat kf = KeyFormat.fromBytes(tlvs[0].mV);
if (kf.keyFormatType() != KeyFormat.KeyFormatType.ECKeyFormatType) {
throw new SecureMessagingException("invalid format of secure messaging key");
}
final ECKeyFormat eckf = (ECKeyFormat)kf;
if (eckf.getCurveOID() == null) {
throw new SecureMessagingException("unsupported curve");
}
try {
ECPublicKey pkcard = null;
final Preferences prefs = Preferences.getPreferences(ctx);
if (prefs != null && prefs.getExperimentalSmartPGPAuthoritiesEnable()) {
// retrieving certificate
cmd = new CommandAPDU(0, (byte) 0xA5, (byte) 0x03, (byte) 0x04,
new byte[]{(byte) 0x60, (byte) 0x04, (byte) 0x5C, (byte) 0x02, (byte) 0x7F, (byte) 0x21});
resp = t.communicate(cmd);
if (resp.getSW() != SecurityTokenHelper.APDU_SW_SUCCESS) {
throw new SecureMessagingException("failed to select secure messaging certificate");
}
cmd = new CommandAPDU(0, (byte) 0xCA, (byte) 0x7F, (byte) 0x21, SecurityTokenHelper.MAX_APDU_NE_EXT);
resp = t.communicate(cmd);
if (resp.getSW() != SecurityTokenHelper.APDU_SW_SUCCESS) {
throw new SecureMessagingException("failed to retrieve secure messaging certificate");
}
pkcard = verifyCertificate(ctx, eckf, resp.getData());
} else {
// retrieving public key
cmd = new CommandAPDU(0, (byte) 0x47, (byte) 0x81, (byte) 0x00,
OPENPGP_SECURE_MESSAGING_KEY_CRT, SecurityTokenHelper.MAX_APDU_NE_EXT);
resp = t.communicate(cmd);
if (resp.getSW() != SecurityTokenHelper.APDU_SW_SUCCESS) {
throw new SecureMessagingException("failed to retrieve secure messaging public key");
}
tlvs = Iso7816TLV.readList(resp.getData(), true);
if ((tlvs == null)
|| (tlvs.length != 1)
|| ((short)tlvs[0].mT != (short)0x7f49)) {
throw new SecureMessagingException("invalid format of secure messaging key");
}
tlvs = Iso7816TLV.readList(tlvs[0].mV, true);
if ((tlvs == null)
|| (tlvs.length != 1)
|| ((byte)tlvs[0].mT != (byte)0x86)) {
throw new SecureMessagingException("invalid format of secure messaging key");
}
pkcard = newECDHPublicKey(eckf, tlvs[0].mV);
}
if (pkcard == null) {
throw new SecureMessagingException("No key in token for secure messaging");
}
final KeyPair ekoce = generateECDHKeyPair(eckf);
final ECPublicKey epkoce = (ECPublicKey)ekoce.getPublic();
final ECPrivateKey eskoce = (ECPrivateKey)ekoce.getPrivate();
final byte[] crt_template = new byte[] {
(byte)0xA6, (byte)0x0D,
(byte)0x90, (byte)0x02, (byte)0x11, (byte)0x00,
(byte)0x95, (byte)0x01, (byte)0x3C,
(byte)0x80, (byte)0x01, (byte)0x88,
(byte)0x81, (byte)0x01, (byte)keySize,
(byte)0x5F, (byte)0x49 };
int csize = (int)Math.ceil(epkoce.getParams().getCurve().getField().getFieldSize() / 8.0);
ByteArrayOutputStream pkout = new ByteArrayOutputStream(), bout = new ByteArrayOutputStream();
pkout.write((byte)0x04);
SecurityTokenUtils.writeBits(pkout, epkoce.getW().getAffineX(), csize);
SecurityTokenUtils.writeBits(pkout, epkoce.getW().getAffineY(), csize);
bout.write(crt_template);
bout.write(SecurityTokenUtils.encodeLength(pkout.size()));
pkout.writeTo(bout);
pkout = bout;
// internal authenticate
cmd = new CommandAPDU(0, (byte)0x88, (byte)0x01, (byte)0x0, pkout.toByteArray(),
SecurityTokenHelper.MAX_APDU_NE_EXT);
resp = t.communicate(cmd);
if (resp.getSW() != SecurityTokenHelper.APDU_SW_SUCCESS) {
throw new SecureMessagingException("failed to initiate internal authenticate");
}
tlvs = Iso7816TLV.readList(resp.getData(), true);
if ((tlvs == null)
|| (tlvs.length != 2)
|| (tlvs[0].mT == tlvs[1].mT)) {
throw new SecureMessagingException("invalid internal authenticate response");
}
byte[] receipt = null;
ECPublicKey epkcard = null;
for (int i = 0; i < tlvs.length; ++i) {
switch (tlvs[i].mT) {
case 0x86:
if (tlvs[i].mL != AES_BLOCK_SIZE) {
throw new SecureMessagingException("invalid size for receipt");
}
receipt = tlvs[i].mV;
break;
case 0x5F49:
epkcard = newECDHPublicKey(eckf, tlvs[i].mV);
break;
default:
throw new SecureMessagingException("unexpected data in internal authenticate response");
}
}
final KeyAgreement ecdhKa = KeyAgreement.getInstance(SCP11B_KEY_AGREEMENT_ALGO, PROVIDER);
bout = new ByteArrayOutputStream();
//compute ShSe
ecdhKa.init(eskoce);
ecdhKa.doPhase(epkcard, true);
bout.write(ecdhKa.generateSecret());
//compute ShSs
ecdhKa.init(eskoce);
ecdhKa.doPhase(pkcard, true);
bout.write(ecdhKa.generateSecret());
csize = bout.size() + 3;
bout.write(new byte[] {
(byte)0, (byte)0, (byte)0, (byte)0,
crt_template[8], crt_template[11],
(byte)keySize });
byte[] shs = bout.toByteArray();
//key derivation
final MessageDigest h = MessageDigest.getInstance(SCP11B_KEY_DERIVATION_ALGO, PROVIDER);
bout = new ByteArrayOutputStream();
while (bout.size() < 4 * keySize) {
++shs[csize];
bout.write(h.digest(shs));
}
shs = bout.toByteArray();
final byte[] rkey = Arrays.copyOfRange(shs, 0, keySize);
final byte[] sEnc = Arrays.copyOfRange(shs, keySize, 2 * keySize);
final byte[] sMac = Arrays.copyOfRange(shs, 2 * keySize, 3 * keySize);
final byte[] sRmac = Arrays.copyOfRange(shs, 3 * keySize, 4 * keySize);
//receipt computation
final Mac mac = Mac.getInstance(SCP11_MAC_ALGO, PROVIDER);
mac.init(new SecretKeySpec(rkey, SCP11_SYMMETRIC_ALGO));
shs = resp.getData();
mac.update(pkout.toByteArray());
mac.update(shs, 0, shs.length - 2 - AES_BLOCK_SIZE);
shs = mac.doFinal();
for(int i = 0; i < AES_BLOCK_SIZE; ++i) {
if (shs[i] != receipt[i]) {
throw new SecureMessagingException("corrupted receipt!");
}
}
final SCP11bSecureMessaging sm = new SCP11bSecureMessaging();
sm.setKeys(sEnc, sMac, sRmac, receipt);
t.setSecureMessaging(sm);
} catch (InvalidKeySpecException e) {
throw new SecureMessagingException("invalid key specification : " + e.getMessage());
} catch (NoSuchAlgorithmException e) {
throw new SecureMessagingException("unknown EC key algorithm : " + e.getMessage());
} catch (InvalidParameterSpecException e) {
throw new SecureMessagingException("invalid ECDH parameters : " + e.getMessage());
} catch (NoSuchProviderException e) {
throw new SecureMessagingException("unknown provider " + PROVIDER);
} catch (InvalidAlgorithmParameterException e) {
throw new SecureMessagingException("invalid algorithm parameters : " + e.getMessage());
} catch (InvalidKeyException e) {
throw new SecureMessagingException("invalid key : " + e.getMessage());
} catch (IllegalArgumentException e) {
throw new SecureMessagingException("illegal argument (" + e.getMessage() + ")");
}
}
@Override
public CommandAPDU encryptAndSign(CommandAPDU apdu)
throws SecureMessagingException {
if (!isEstablished()) {
throw new SecureMessagingException("not established");
}
++mEncryptionCounter;
if(mEncryptionCounter <= 0) {
throw new SecureMessagingException("exhausted encryption counter");
}
try {
byte[] data = apdu.getData();
if (data.length > 0) {
final Cipher cipher = Cipher.getInstance(SCP11_CIPHER_ALGO);
byte[] iv = new byte[AES_BLOCK_SIZE];
Arrays.fill(iv, (byte)0);
cipher.init(Cipher.ENCRYPT_MODE, mSEnc, new IvParameterSpec(iv));
iv[AES_BLOCK_SIZE - 2] = (byte)((mEncryptionCounter >> 8) & 0xff);
iv[AES_BLOCK_SIZE - 1] = (byte)(mEncryptionCounter & 0xff);
iv = cipher.doFinal(iv);
cipher.init(Cipher.ENCRYPT_MODE, mSEnc, new IvParameterSpec(iv));
final byte[] pdata = new byte[data.length + AES_BLOCK_SIZE - (data.length % AES_BLOCK_SIZE)];
System.arraycopy(data, 0, pdata, 0, data.length);
pdata[data.length] = (byte)0x80;
Arrays.fill(data, (byte)0);
data = cipher.doFinal(pdata);
Arrays.fill(pdata, (byte)0);
Arrays.fill(iv, (byte)0);
}
final int lcc = data.length + SCP11_MAC_LENGTH;
final byte[] odata = new byte[4 + 3 + lcc + 3];
int ooff = 0;
odata[ooff++] = (byte) (((byte) apdu.getCLA()) | OPENPGP_SECURE_MESSAGING_CLA_MASK);
odata[ooff++] = (byte) apdu.getINS();
odata[ooff++] = (byte) apdu.getP1();
odata[ooff++] = (byte) apdu.getP2();
if (lcc > 0xff) {
odata[ooff++] = (byte) 0;
odata[ooff++] = (byte) ((lcc >> 8) & 0xff);
}
odata[ooff++] = (byte) (lcc & 0xff);
System.arraycopy(data, 0, odata, ooff, data.length);
ooff += data.length;
Arrays.fill(data, (byte)0);
final Mac mac = Mac.getInstance(SCP11_MAC_ALGO, PROVIDER);
mac.init(mSMac);
mac.update(mMacChaining);
mac.update(odata, 0, ooff);
mMacChaining = mac.doFinal();
System.arraycopy(mMacChaining, 0, odata, ooff, SCP11_MAC_LENGTH);
ooff += SCP11_MAC_LENGTH;
if (lcc > 0xff) {
odata[ooff++] = (byte) 0;
}
odata[ooff++] = (byte) 0;
apdu = new CommandAPDU(odata, 0, ooff);
Arrays.fill(odata, (byte)0);
return apdu;
} catch (NoSuchAlgorithmException e) {
throw new SecureMessagingException("unavailable algorithm : " + e.getMessage());
} catch (NoSuchProviderException e) {
throw new SecureMessagingException("unavailable provider : " + e.getMessage());
} catch (NoSuchPaddingException e) {
throw new SecureMessagingException("unavailable padding algorithm : " + e.getMessage());
} catch (InvalidKeyException e) {
throw new SecureMessagingException("invalid key : " + e.getMessage());
} catch (InvalidAlgorithmParameterException e) {
throw new SecureMessagingException("invalid IV : " + e.getMessage());
} catch (BadPaddingException e) {
throw new SecureMessagingException("invalid IV : " + e.getMessage());
} catch (IllegalBlockSizeException e) {
throw new SecureMessagingException("invalid block size : " + e.getMessage());
}
}
@Override
public ResponseAPDU verifyAndDecrypt(ResponseAPDU apdu)
throws SecureMessagingException {
if (!isEstablished()) {
throw new SecureMessagingException("not established");
}
byte[] data = apdu.getData();
if ((data.length == 0) &&
(apdu.getSW() != 0x9000) &&
(apdu.getSW1() != 0x62) &&
(apdu.getSW1() != 0x63)) {
return apdu;
}
if (data.length < SCP11_MAC_LENGTH) {
throw new SecureMessagingException("missing or incomplete MAC in response");
}
try {
final Mac mac = Mac.getInstance(SCP11_MAC_ALGO, PROVIDER);
mac.init(mSRMac);
mac.update(mMacChaining);
if ((data.length - SCP11_MAC_LENGTH) > 0) {
mac.update(data, 0, data.length - SCP11_MAC_LENGTH);
}
mac.update((byte) apdu.getSW1());
mac.update((byte) apdu.getSW2());
final byte[] sig = mac.doFinal();
for (int i = 0; i < SCP11_MAC_LENGTH; ++i) {
if ((i >= sig.length)
|| (sig[i] != data[data.length - SCP11_MAC_LENGTH + i])) {
throw new SecureMessagingException("corrupted integrity");
}
}
if (((data.length - SCP11_MAC_LENGTH) % AES_BLOCK_SIZE) != 0) {
throw new SecureMessagingException("invalid encrypted data size");
}
if (data.length > SCP11_MAC_LENGTH) {
final Cipher cipher = Cipher.getInstance(SCP11_CIPHER_ALGO);
byte[] iv = new byte[AES_BLOCK_SIZE];
Arrays.fill(iv,(byte)0);
cipher.init(Cipher.ENCRYPT_MODE, mSEnc, new IvParameterSpec(iv));
iv[0] = (byte) 0x80;
iv[AES_BLOCK_SIZE - 2] = (byte) ((mEncryptionCounter >> 8) & 0xff);
iv[AES_BLOCK_SIZE - 1] = (byte) (mEncryptionCounter & 0xff);
iv = cipher.doFinal(iv);
cipher.init(Cipher.DECRYPT_MODE, mSEnc, new IvParameterSpec(iv));
data = cipher.doFinal(data, 0, data.length - SCP11_MAC_LENGTH);
int i = data.length - 1;
while ((0 < i) && (data[i] == (byte) 0)) --i;
if ((i <= 0) || (data[i] != (byte) 0x80)) {
throw new SecureMessagingException("invalid data padding after decryption");
}
final byte[] datasw = new byte[i + 2];
System.arraycopy(data, 0, datasw, 0, i);
datasw[datasw.length - 2] = (byte) apdu.getSW1();
datasw[datasw.length - 1] = (byte) apdu.getSW2();
Arrays.fill(data, (byte) 0);
data = datasw;
} else {
data = new byte[2];
data[0] = (byte) apdu.getSW1();
data[1] = (byte) apdu.getSW2();
}
apdu = new ResponseAPDU(data);
return apdu;
} catch (NoSuchAlgorithmException e) {
throw new SecureMessagingException("unavailable algorithm : " + e.getMessage());
} catch (NoSuchProviderException e) {
throw new SecureMessagingException("unknown provider : " + e.getMessage());
} catch (NoSuchPaddingException e) {
throw new SecureMessagingException("unavailable padding algorithm : " + e.getMessage());
} catch (InvalidKeyException e) {
throw new SecureMessagingException("invalid key : " + e.getMessage());
} catch (BadPaddingException e) {
throw new SecureMessagingException("invalid IV : " + e.getMessage());
} catch (InvalidAlgorithmParameterException e) {
throw new SecureMessagingException("invalid IV : " + e.getMessage());
} catch (IllegalBlockSizeException e) {
throw new SecureMessagingException("invalid block size : " + e.getMessage());
}
}
}