/**************************************************************************** * Copyright (C) 2012 ecsec GmbH. * All rights reserved. * Contact: ecsec GmbH (info@ecsec.de) * * This file is part of the Open eCard App. * * GNU General Public License Usage * This file may be used under the terms of the GNU General Public * License version 3.0 as published by the Free Software Foundation * and appearing in the file LICENSE.GPL included in the packaging of * this file. Please review the following information to ensure the * GNU General Public License version 3.0 requirements will be met: * http://www.gnu.org/copyleft/gpl.html. * * Other Usage * Alternatively, this file may be used in accordance with the terms * and conditions contained in a signed written agreement between * you and ecsec GmbH. * ***************************************************************************/ package org.openecard.ifd.protocol.pace; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.Key; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.openecard.bouncycastle.crypto.engines.AESFastEngine; import org.openecard.bouncycastle.crypto.macs.CMac; import org.openecard.bouncycastle.crypto.params.KeyParameter; import org.openecard.common.apdu.common.CardCommandAPDU; import org.openecard.common.tlv.TLV; import org.openecard.common.util.ByteUtils; /** * Implements Secure Messaging according to ISO/IEC 7816-4. * * @author Moritz Horsch <horsch@cdc.informatik.tu-darmstadt.de> */ public class SecureMessaging { private static final byte[] NULL = new byte[]{0x00}; // ISO/IEC 7816-4 padding tag private static final byte PAD = (byte) 0x80; // Send Sequence Counter. See BSI-TR-03110 section F.3. private byte[] secureMessagingSSC; // Keys for encryption and message authentication. private byte[] keyMAC, keyENC; /** * Instantiates a new secure messaging. * * @param keyMAC Key for message authentication * @param keyENC Key for encryption */ public SecureMessaging(byte[] keyMAC, byte[] keyENC) { this.keyENC = keyENC; this.keyMAC = keyMAC; secureMessagingSSC = new byte[16]; } /** * Encrypt the APDU. * * @param apdu APDU * @return Encrypted APDU * @throws Exception */ public byte[] encrypt(byte[] apdu) throws Exception { incrementSSC(secureMessagingSSC); byte[] commandAPDU = encrypt(apdu, secureMessagingSSC); incrementSSC(secureMessagingSSC); return commandAPDU; } /** * Encrypt the APDU. * * @param apdu APDU * @param secureMessagingSSC Secure Messaging Send Sequence Counter * @return Encrypted APDU * @throws Exception */ private byte[] encrypt(byte[] apdu, byte[] secureMessagingSSC) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); CardCommandAPDU cAPDU = new CardCommandAPDU(apdu); if (cAPDU.isSecureMessaging()) { throw new IllegalArgumentException("Malformed APDU."); } byte[] data = cAPDU.getData(); byte[] header = cAPDU.getHeader(); int lc = cAPDU.getLC(); int le = cAPDU.getLE(); if (data != null) { data = pad(data, 16); // Encrypt data Cipher c = getCipher(secureMessagingSSC, Cipher.ENCRYPT_MODE); byte[] dataEncrypted = c.doFinal(data); // Add padding indicator 0x01 dataEncrypted = ByteUtils.concatenate((byte) 0x01, dataEncrypted); TLV dataObject = new TLV(); dataObject.setTagNumWithClass((byte) 0x87); dataObject.setValue(dataEncrypted); baos.write(dataObject.toBER()); } // Write protected LE if (le >= 0) { TLV leObject = new TLV(); leObject.setTagNumWithClass((byte) 0x97); if (le == 0x100) { leObject.setValue(NULL); } else if (le > 0x100) { leObject.setValue(new byte[]{(byte) ((le >> 8) & 0xFF), (byte) (le & 0xFF)}); } else { leObject.setValue(new byte[]{(byte) le}); } baos.write(leObject.toBER()); } // Indicate Secure Messaging // note: must be done before mac calculation header[0] |= 0x0C; /* * Calculate MAC */ byte[] mac = new byte[16]; CMac cmac = getCMAC(secureMessagingSSC); byte[] paddedHeader = pad(header, 16); cmac.update(paddedHeader, 0, paddedHeader.length); if (baos.size() > 0) { byte[] paddedData = pad(baos.toByteArray(), 16); cmac.update(paddedData, 0, paddedData.length); lc = baos.size(); } cmac.doFinal(mac, 0); mac = ByteUtils.copy(mac, 0, 8); // // Build APDU ByteArrayOutputStream out = new ByteArrayOutputStream(); // Write header out.write(header); // Add MAC length to LC lc += 10; // Write LC field if ((lc > 0xFF) || (le > 0x100)) { out.write(NULL); out.write((lc >> 8) & 0xFF); out.write(lc & 0xFF); } else { out.write(lc & 0xFF); } // Write data if present if (baos.size() > 0) { out.write(baos.toByteArray()); } // Write SM tag out.write(new byte[]{(byte) 0x8E, (byte) 0x08}); // Write SM MAC out.write(mac); out.write(NULL); if ((lc > 0xFF) || (le > 0x100)) { out.write(NULL); } return out.toByteArray(); } /** * Decrypt the APDU. * * @param response the response * @return the byte[] * @throws Exception the exception */ public byte[] decrypt(byte[] response) throws Exception { if (response.length < 12) { throw new IllegalArgumentException("Malformed Secure Messaging APDU"); } return decrypt(response, secureMessagingSSC); } /** * Decrypt the APDU. * * @param response the response * @param secureMessagingSSC the secure messaging ssc * @return the byte[] * @throws Exception the exception */ private byte[] decrypt(byte[] response, byte[] secureMessagingSSC) throws Exception { ByteArrayInputStream bais = new ByteArrayInputStream(response); ByteArrayOutputStream baos = new ByteArrayOutputStream(response.length - 10); // Status bytes of the response APDU. MUST be 2 bytes. byte[] statusBytes = new byte[2]; // Padding-content indicator followed by cryptogram 0x87. byte[] dataObject = null; // Cryptographic checksum 0x8E. MUST be 8 bytes. byte[] macObject = new byte[8]; /* * Read APDU structure * Case 1: DO99|DO8E|SW1SW2 * Case 2: DO87|DO99|DO8E|SW1SW2 * Case 3: DO99|DO8E|SW1SW2 * Case 4: DO87|DO99|DO8E|SW1SW2 */ byte tag = (byte) bais.read(); // Read data object (OPTIONAL) if (tag == (byte) 0x87) { int size = bais.read(); if (size > 0x80) { byte[] sizeBytes = new byte[size & 0x0F]; bais.read(sizeBytes, 0, sizeBytes.length); size = new BigInteger(1, sizeBytes).intValue(); } bais.skip(1); // Skip encryption header dataObject = new byte[size - 1]; bais.read(dataObject, 0, dataObject.length); tag = (byte) bais.read(); } // Read processing status (REQUIRED) if (tag == (byte) 0x99) { if (bais.read() == (byte) 0x02) { bais.read(statusBytes, 0, 2); tag = (byte) bais.read(); } } else { throw new IOException("Malformed Secure Messaging APDU"); } // Read MAC (REQUIRED) if (tag == (byte) 0x8E) { if (bais.read() == (byte) 0x08) { bais.read(macObject, 0, 8); } } else { throw new IOException("Malformed Secure Messaging APDU"); } // Only 2 bytes status should remain if (bais.available() != 2) { throw new IOException("Malformed Secure Messaging APDU"); } // Calculate MAC for verification CMac cmac = getCMAC(secureMessagingSSC); byte[] mac = new byte[16]; synchronized (cmac) { ByteArrayOutputStream macData = new ByteArrayOutputStream(); // Write padding-content if (dataObject != null) { TLV paddedDataObject = new TLV(); paddedDataObject.setTagNumWithClass((byte) 0x87); paddedDataObject.setValue(ByteUtils.concatenate((byte) 0x01, dataObject)); macData.write(paddedDataObject.toBER()); } // Write status bytes TLV statusBytesObject = new TLV(); statusBytesObject.setTagNumWithClass((byte) 0x99); statusBytesObject.setValue(statusBytes); macData.write(statusBytesObject.toBER()); byte[] paddedData = pad(macData.toByteArray(), 16); cmac.update(paddedData, 0, paddedData.length); cmac.doFinal(mac, 0); mac = ByteUtils.copy(mac, 0, 8); } // Verify MAC if (!ByteUtils.compare(mac, macObject)) { throw new GeneralSecurityException("Secure Messaging MAC verification failed"); } // Decrypt data if (dataObject != null) { Cipher c = getCipher(secureMessagingSSC, Cipher.DECRYPT_MODE); byte[] data_decrypted = c.doFinal(dataObject); baos.write(unpad(data_decrypted)); } // Add status code baos.write(statusBytes); return baos.toByteArray(); } /** * Increment the Send Sequence Counter (SSC). * * @param ssc the Send Sequence Counter (SSC) */ public static void incrementSSC(byte[] ssc) { for (int i = ssc.length - 1; i >= 0; i--) { ssc[i]++; if (ssc[i] != 0) { break; } } } /* * Cipher functions */ /** * Gets the cipher for de/encryption. * * @param smssc the Secure Messaging Send Sequence Counter * @param mode the mode indicating de/encryption * @return the cipher * @throws Exception the exception */ private Cipher getCipher(byte[] smssc, int mode) throws Exception { Cipher c = Cipher.getInstance("AES/CBC/NoPadding"); Key key = new SecretKeySpec(keyENC, "AES"); byte[] iv = getCipherIV(smssc); AlgorithmParameterSpec algoPara = new IvParameterSpec(iv); c.init(mode, key, algoPara); return c; } /** * Gets the Initialization Vector (IV) for the cipher. * * @param smssc Secure Messaging Send Sequence Counter * @return Initialization Vector * @throws Exception */ private byte[] getCipherIV(byte[] smssc) throws Exception { Cipher c = Cipher.getInstance("AES/ECB/NoPadding"); Key key = new SecretKeySpec(keyENC, "AES"); c.init(Cipher.ENCRYPT_MODE, key); return c.doFinal(smssc); } /** * Gets the CMAC. * * @param smssc Secure Messaging Send Sequence Counter * @return CMAC */ private CMac getCMAC(byte[] smssc) { CMac cmac = new CMac(new AESFastEngine()); cmac.init(new KeyParameter(keyMAC)); cmac.update(smssc, 0, smssc.length); return cmac; } /* * ISO/IEC 7816-4 padding functions */ /** * Padding the data. * * @param data Unpadded data * @param blockSize Block size * @return Padded data */ private byte[] pad(byte[] data, int blockSize) { byte[] result = new byte[data.length + (blockSize - data.length % blockSize)]; System.arraycopy(data, 0, result, 0, data.length); result[data.length] = PAD; return result; } /** * Unpadding the data. * * @param data Padded data * @return Unpadded data */ private byte[] unpad(byte[] data) { for (int i = data.length - 1; i >= 0; i--) { if (data[i] == PAD) { return ByteUtils.copy(data, 0, i); } } return data; } }