712 lines
28 KiB
Java
712 lines
28 KiB
Java
/*
|
|
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
|
*
|
|
* 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 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.EllipticCurve;
|
|
import java.security.spec.InvalidKeySpecException;
|
|
import java.security.spec.InvalidParameterSpecException;
|
|
import java.util.ArrayList;
|
|
|
|
import android.content.Context;
|
|
import androidx.annotation.CheckResult;
|
|
import androidx.annotation.NonNull;
|
|
|
|
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 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;
|
|
|
|
|
|
class SCP11bSecureMessaging implements SecureMessaging {
|
|
|
|
private static final byte OPENPGP_SECURE_MESSAGING_CLA_MASK = (byte)0x04;
|
|
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 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.curveOid())));
|
|
|
|
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.curveOid());
|
|
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() + ")");
|
|
}
|
|
}
|
|
|
|
|
|
@CheckResult
|
|
static SecureMessaging establish(final SecurityTokenConnection t, final Context ctx,
|
|
OpenPgpCommandApduFactory commandFactory)
|
|
throws SecureMessagingException, IOException {
|
|
|
|
CommandApdu cmd;
|
|
ResponseApdu resp;
|
|
Iso7816TLV[] tlvs;
|
|
|
|
t.clearSecureMessaging();
|
|
|
|
// retrieving key algorithm
|
|
cmd = commandFactory.createGetDataCommand(0x00, OPENPGP_SECURE_MESSAGING_KEY_ATTRIBUTES_TAG);
|
|
resp = t.communicate(cmd);
|
|
if (!resp.isSuccess()) {
|
|
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 instanceof EcKeyFormat)) {
|
|
throw new SecureMessagingException("invalid format of secure messaging key");
|
|
}
|
|
|
|
final EcKeyFormat eckf = (EcKeyFormat)kf;
|
|
|
|
if (eckf.curveOid() == null) {
|
|
throw new SecureMessagingException("unsupported curve");
|
|
}
|
|
|
|
try {
|
|
ECPublicKey pkcard = null;
|
|
|
|
final Preferences prefs = Preferences.getPreferences(ctx);
|
|
|
|
if (prefs != null && prefs.getExperimentalSmartPGPAuthoritiesEnable()) {
|
|
// retrieving certificate
|
|
cmd = commandFactory.createSelectSecureMessagingCertificateCommand();
|
|
resp = t.communicate(cmd);
|
|
if (!resp.isSuccess()) {
|
|
throw new SecureMessagingException("failed to select secure messaging certificate");
|
|
}
|
|
cmd = commandFactory.createGetDataCardHolderCertCommand();
|
|
resp = t.communicate(cmd);
|
|
if (!resp.isSuccess()) {
|
|
throw new SecureMessagingException("failed to retrieve secure messaging certificate");
|
|
}
|
|
|
|
pkcard = verifyCertificate(ctx, eckf, resp.getData());
|
|
|
|
} else {
|
|
cmd = commandFactory.createRetrieveSecureMessagingPublicKeyCommand();
|
|
resp = t.communicate(cmd);
|
|
if (!resp.isSuccess()) {
|
|
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 EllipticCurve curve = pkcard.getParams().getCurve();
|
|
final int fieldSize = curve.getField().getFieldSize();
|
|
int keySize;
|
|
|
|
if(fieldSize < 512) {
|
|
keySize = 16;
|
|
} else {
|
|
keySize = 32;
|
|
}
|
|
|
|
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;
|
|
|
|
cmd = commandFactory.createInternalAuthForSecureMessagingCommand(pkout.toByteArray());
|
|
resp = t.communicate(cmd);
|
|
if (!resp.isSuccess()) {
|
|
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);
|
|
|
|
return 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 = CommandApdu.fromBytes(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.isSuccess() &&
|
|
(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 = ResponseApdu.fromBytes(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());
|
|
}
|
|
}
|
|
|
|
}
|