de.fraunhofer.fokus.openeid.pace.PaceECDH.java Source code

Java tutorial

Introduction

Here is the source code for de.fraunhofer.fokus.openeid.pace.PaceECDH.java

Source

/*******************************************************************************
 * 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();
    }

}