Java tutorial
/******************************************************************************* * Implementation of the protocols PACE, Terminal Authentication and Chip * Authentication (client side) with respect to the according BSI standards. * * Copyright (C) 2013 Fraunhofer-Gesellschaft * * 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 de.fraunhofer.fokus.openeid.pace; import java.io.IOException; import java.math.BigInteger; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DERApplicationSpecific; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERTaggedObject; import org.bouncycastle.asn1.DERTags; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.Arrays; import de.fraunhofer.fokus.openeid.commands.GeneralAuthenticate; import de.fraunhofer.fokus.openeid.commands.GeneralAuthenticateShortApdu; import de.fraunhofer.fokus.openeid.cryptography.CryptoException; import de.fraunhofer.fokus.openeid.cryptography.ECKeyPair; import de.fraunhofer.fokus.openeid.cryptography.EllipticCurve; import de.fraunhofer.fokus.openeid.cryptography.Key; import de.fraunhofer.fokus.openeid.cryptography.KeyDerivation; import de.fraunhofer.fokus.openeid.cryptography.mac.MAC; import de.fraunhofer.fokus.openeid.device.NfcHandler; import de.fraunhofer.fokus.openeid.iso7816_4.CommandManager; import de.fraunhofer.fokus.openeid.iso7816_4.InvalidDataObjectException; import de.fraunhofer.fokus.openeid.iso7816_4.InvalidInterindustryClassException; import de.fraunhofer.fokus.openeid.iso7816_4.NotTrustedResponseAPDU; import de.fraunhofer.fokus.openeid.iso7816_4.ResponseAPDU; import de.fraunhofer.fokus.openeid.iso7816_4.ResponseStatusException; import de.fraunhofer.fokus.openeid.iso7816_4.Utils; import de.fraunhofer.fokus.openeid.iso7816_4.Command.PASSWORD_TYPE; import de.fraunhofer.fokus.openeid.pace.auth.AuthenticationToken; import de.fraunhofer.fokus.openeid.structure.DynamicAuthenticationData; /** * PACE implementation using elliptic curve cryptography and Diffie-Hellman key * agreement. * @author Simon Hohberg and Mateusz Khalil * */ public class PaceECDH extends Pace { private ECKeyPair ephemeralKeyPair; private ECPoint ephemeralKeyPICC; public PaceECDH(NfcHandler device, PASSWORD_TYPE passwordType, byte[] chat, CommandManager manager) { super(device, passwordType, chat, manager); } public PaceECDH(NfcHandler device, PASSWORD_TYPE passwordType, byte[] chat, Pace parent, CommandManager manager) { super(device, passwordType, chat, parent, manager); } @Override public void performKeyAgreement(byte[] password) throws CryptoException, InvalidAuthenticationException, IOException, PaceProtocolException, NotTrustedResponseAPDU, InvalidDataObjectException { try { //see BSI TR-03110 4.2.1 //derive initial key K_pi Key Kpi = KeyDerivation.deriveKey(password, 3, protocolParameters.getKeyType()); //1. Get nonce byte[] z = requestNonce(); byte[] s = Kpi.decrypt(z); logger.info("Nonce decrypted: " + new BigInteger(1, s)); //static domain parameters X9ECParameters curveParams = domainParameters.getDomainParameter().getCurveParams(); EllipticCurve curve = new EllipticCurve(curveParams); //compute ephemeral domain parameters //which is actually the mapped generator for the specified curve ECKeyPair mappingKeys; ECPoint mappingPICC; logger.info("Mapping nonce.."); int i = 0; do { logger.info(" Trying No. {}", ++i); //choose random ephemeral key pair mappingKeys = curve.generateRandomKeyPair(); mappingPICC = mapNonce(mappingKeys.getPublicKey()); } while (mappingKeys.isEqualPublicKey(mappingPICC)); ECPoint ephemerealSecret = calculateSharedSecret(mappingKeys, mappingPICC); ECPoint mappedGenerator = ((ECDHMapping) protocolParameters.getMapping()).map(s, ephemerealSecret); logger.info("..done"); //perform Diffie-Hellman key agreement using ephemeral domain parameters (mappedGenerator) logger.info("Performing key agreement.."); i = 0; do { logger.info(" Trying No. {}", ++i); //choose random ephemeral key pair ephemeralKeyPair = curve.generateRandomKeyPair(mappedGenerator); ephemeralKeyPICC = exchangeEphemeralKeys(ephemeralKeyPair.getPublicKey()); } while (ephemeralKeyPair.isEqualPublicKey(ephemeralKeyPICC)); ID_PICC = Utils.trimLeadingZeros(ephemeralKeyPICC.getX().toBigInteger().toByteArray()); //calculate shared secret ECPoint secretPoint = calculateSharedSecret(ephemeralKeyPair, ephemeralKeyPICC); byte[] secret = calculateEffectiveSharedSecret(secretPoint); logger.info("..done"); //derive session keys keyENC = KeyDerivation.deriveKeyENC(secret, protocolParameters.getKeyType()); keyMAC = KeyDerivation.deriveKeyMAC(secret, protocolParameters.getKeyType()); //generate authentication token logger.info("Creating auth token.."); MAC mac = protocolParameters.getMACAlgorithm(); byte[] computedMac = AuthenticationToken.computeMAC(mac, keyMAC, protocolParameters, ephemeralKeyPICC); DERTaggedObject macObj = new DERTaggedObject(false, 5, new DEROctetString(computedMac)); GeneralAuthenticate authCommand = new GeneralAuthenticateShortApdu(manager, Utils.convert(macObj.getDEREncoded()), 0x0); executeCommand(authCommand); ResponseAPDU authPICCresponse = authCommand.getResponse(); logger.info(Utils.toString(Utils.convert(authPICCresponse.getBytes()))); //verify authentication tokens if (!authPICCresponse.normalProcessing()) { logger.warn("PACE (sent) authentication token issues.\nResponse was " + Utils.byteArrayToHexString(authPICCresponse.getBytes())); throw new InvalidAuthenticationException(); } if (authPICCresponse.getSW1() == 0x63) { byte[] macBytes = AuthenticationToken.computeMAC(mac, keyMAC, protocolParameters, ephemeralKeyPICC); logger.warn("WARNING: Remaining password tries: " + Utils.signedByteToUnsigned((byte) (authPICCresponse.getSW2() - 0xC0))); logger.info("\nkeyMac: " + Utils.byteArrayToHexString(keyMAC.getKey()) + "\nkeyEnc: " + Utils.byteArrayToHexString(keyENC.getKey()) + "\nMAC: " + Utils.byteArrayToHexString(macBytes) + "\nauthCommmand: " + authCommand.toString()); throw new InvalidAuthenticationException(); } logger.info("PACE (sent) authentication token ok"); CAR = new DynamicAuthenticationData(authPICCresponse).getCertificationAuthorityReference(); if (verifyAuthToken(mac, authPICCresponse)) { logger.info("PACE (received) authentication token ok"); logger.info("..done. PACE successful!"); isSecureMessagingEnabled = true; } else { logger.warn("PACE (received) authentication token issues"); isSecureMessagingEnabled = false; throw new InvalidAuthenticationException(); } } catch (ResponseStatusException e) { logger.error(e.toString()); throw new PaceProtocolException(e); } catch (InvalidInterindustryClassException e) { logger.error("invalid industry class (in apdu command)"); throw new PaceProtocolException(); } } public static byte[] calculateEffectiveSharedSecret(ECPoint secretPoint) { BigInteger secretBigInt = secretPoint.getX().toBigInteger(); byte[] secret = secretBigInt.toByteArray(); logger.info("secret: " + Utils.byteArrayToHexString(secret) + "(" + secretBigInt.signum() + ")"); secret = Utils.trimLeadingZeros(secret); return secret; } private boolean verifyAuthToken(MAC mac, ResponseAPDU response) throws IOException { DynamicAuthenticationData dynamicAuthenticationData = new DynamicAuthenticationData(response); byte[] receivedMac = dynamicAuthenticationData.getAuthenticationToken(); return verifyAuthToken(mac, receivedMac); } private boolean verifyAuthToken(MAC mac, byte[] authToken) { byte[] expectedMac = AuthenticationToken.computeMAC(mac, keyMAC, protocolParameters, ephemeralKeyPair.getPublicKey()); return Arrays.areEqual(expectedMac, authToken); } private ECPoint exchangeEphemeralKeys(ECPoint publicKey) throws IOException, InvalidInterindustryClassException, ResponseStatusException, CryptoException, NotTrustedResponseAPDU, InvalidDataObjectException { Byte[] point = createSendPointDataObject(publicKey, 0x03); GeneralAuthenticate exchangeCommand = new GeneralAuthenticateShortApdu(manager, point, 0x0); exchangeCommand.setNotLastCommandChainingCommand(); executeCommand(exchangeCommand); byte[] response = exchangeCommand.getResponseData(); logger.info("ephmKeyResponse: " + Utils.byteArrayToHexString(response)); byte[] ephemPiccPublic = parseGetPointResponse(response); logger.info("ephemPiccPublic: " + Utils.byteArrayToHexString(ephemPiccPublic)); X9ECParameters curveParams = domainParameters.getDomainParameter().getCurveParams(); EllipticCurve curve = new EllipticCurve(curveParams); return curve.decodePoint(ephemPiccPublic); } private ECPoint mapNonce(ECPoint pcdPublic) throws IOException, InvalidInterindustryClassException, ResponseStatusException, CryptoException, NotTrustedResponseAPDU, InvalidDataObjectException { Byte[] point = createSendPointDataObject(pcdPublic, 0x01); GeneralAuthenticate sendPointCommand = new GeneralAuthenticateShortApdu(manager, point, 0x0); sendPointCommand.setNotLastCommandChainingCommand(); executeCommand(sendPointCommand); byte[] mappingPiccPublic = parseGetPointResponse(sendPointCommand.getResponseData()); X9ECParameters curveParams = domainParameters.getDomainParameter().getCurveParams(); EllipticCurve curve = new EllipticCurve(curveParams); return curve.decodePoint(mappingPiccPublic); } public static ECPoint calculateSharedSecret(ECKeyPair keys, ECPoint publicPICC) { return publicPICC.multiply(keys.getPrivateKey()); } public Byte[] createSendPointDataObject(ECPoint pcdPublic, int tagNo) { DERTaggedObject pcdPoint = new DERTaggedObject(false, tagNo, new DEROctetString(pcdPublic.getEncoded())); return Utils.convert(pcdPoint.getDEREncoded()); } public byte[] parseGetPointResponse(byte[] response) throws IOException { DERApplicationSpecific dataObject = (DERApplicationSpecific) DERApplicationSpecific.fromByteArray(response); ASN1Sequence sequence = ASN1Sequence.getInstance(dataObject.getObject(DERTags.SEQUENCE)); DERTaggedObject tagged = (DERTaggedObject) sequence.getObjectAt(0); DEROctetString octetString = (DEROctetString) tagged.getObjectParser(DERTags.OCTET_STRING, false); return octetString.getOctets(); } }