/**************************************************************************** * 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.crypto; import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.GeneralSecurityException; import org.openecard.bouncycastle.crypto.engines.AESEngine; import org.openecard.bouncycastle.crypto.macs.CMac; import org.openecard.bouncycastle.crypto.params.KeyParameter; import org.openecard.common.tlv.TLV; import org.openecard.common.tlv.TagClass; import org.openecard.common.util.ByteUtils; import org.openecard.crypto.common.asn1.eac.PACESecurityInfos; import org.openecard.crypto.common.asn1.utils.ObjectIdentifierUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * See BSI-TR-03110, version 2.10, part 3, section B.1. * * @author Moritz Horsch <horsch@cdc.informatik.tu-darmstadt.de> */ public final class AuthenticationToken { private static final Logger logger = LoggerFactory.getLogger(AuthenticationToken.class.getName()); // Byte encoded token private byte[] token = new byte[8]; // Certificate Authority Reference (CAR) private byte[] currentCAR, previousCAR; private PACESecurityInfos psi; /** * Creates a new AuthenticationToken. * * @param psi PACESecurityInfos */ public AuthenticationToken(PACESecurityInfos psi) { this.psi = psi; } /** * Generate an authentication token. * * @param keyMac Key for message authentication * @param key Key * @throws GeneralSecurityException */ public void generateToken(byte[] keyMac, byte[] key) throws GeneralSecurityException { byte[] tmp = new byte[16]; byte[] macData = getMACObject(key); CMac cMAC = new CMac(new AESEngine()); cMAC.init(new KeyParameter(keyMac)); cMAC.update(macData, 0, macData.length); cMAC.doFinal(tmp, 0); System.arraycopy(tmp, 0, token, 0, 8); } /** * Verify the authentication token by the PICC and extract Certificate Authority Reference (CAR). * * @param token Token * @param specifiedCHAT true if PACE is used with a CHAT * @return true if token is equal to my token * @throws GeneralSecurityException */ public boolean verifyToken(AuthenticationToken token, boolean specifiedCHAT) throws GeneralSecurityException { return verifyToken(token.toByteArray(), specifiedCHAT); } /** * Verify the authentication token by the PICC and extract Certificate Authority Reference (CAR). * * @param T_PICC Token from the PICC * @param specifiedCHAT true if PACE is used with a CHAT * @return true if T_PICC is equal to my T_PICC * @throws GeneralSecurityException */ public boolean verifyToken(byte[] T_PICC, boolean specifiedCHAT) throws GeneralSecurityException { final ByteArrayInputStream bais = new ByteArrayInputStream(T_PICC); byte tag = (byte) bais.read(); byte size = (byte) bais.read(); // Verify tag and size if (tag == (byte) 0x7C && (size & 0xFF) == bais.available()) { tag = (byte) bais.read(); size = (byte) bais.read(); } else { throw new GeneralSecurityException("Malformed authentication token"); } // Verify authentication token T_PICC if (tag == (byte) 0x86 && (size & 0xFF) == 8) { byte[] buf = new byte[8]; bais.read(buf, 0, 8); if (!ByteUtils.compare(buf, token)) { throw new GeneralSecurityException("Cannot verify authentication token"); } // Read next bytes tag = (byte) bais.read(); size = (byte) bais.read(); } else { throw new GeneralSecurityException("Malformed authentication token"); } // if PACE is used with a CHAT if (specifiedCHAT) { // Read current CAR if (tag == (byte) 0x87 && size == (byte) 0x0E) { currentCAR = new byte[size]; bais.read(currentCAR, 0, size); // Read next bytes tag = (byte) bais.read(); size = (byte) bais.read(); } else { throw new GeneralSecurityException("Malformed authentication token"); } // Read optional previous CAR if (bais.available() > 0) { if (tag == (byte) 0x88 && size == (byte) 0x0E) { previousCAR = new byte[size]; bais.read(previousCAR, 0, size); } else { throw new GeneralSecurityException("Malformed authentication token"); } } } // ensure bais is empty if (bais.available() != 0) { throw new GeneralSecurityException("Malformed authentication token"); } try { bais.close(); } catch (IOException ignore) { } return true; } /** * * @return */ public byte[] toByteArray() { return token; } /** * Returns the current Certificate Authority Reference (CAR). See TR-03110 2.05 Appendix B.1 * * @return current CAR */ public byte[] getCurrentCAR() { return currentCAR; } /** * Returns the previous Certificate Authority Reference (CAR). See TR-03110 2.05 Appendix B.1 * * @return previous CAR */ public byte[] getPreviousCAR() { return previousCAR; } /** * Calculates the data for the authentication token. * * @param key Key * @return data object for token */ private byte[] getMACObject(byte[] key) throws GeneralSecurityException { byte[] ret = null; try { TLV keyObject = new TLV(); keyObject.setTagNumWithClass((byte) 0x86); keyObject.setValue(ByteUtils.cutLeadingNullBytes(key)); TLV oidObject = new TLV(); oidObject.setTagNumWithClass((byte) 0x06); oidObject.setValue(ObjectIdentifierUtils.getValue(psi.getPACEInfo().getProtocol())); oidObject.addToEnd(keyObject); TLV macObject = new TLV(); macObject.setTagNum((byte) 0x49); macObject.setTagClass(TagClass.APPLICATION); macObject.setChild(oidObject); ret = macObject.toBER(true); } catch (Throwable e) { logger.error(e.getMessage(), e); throw new GeneralSecurityException(e); } return ret; } }