All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jmrtd.PassportApduService Maven / Gradle / Ivy

There is a newer version: 0.7.42
Show newest version
/*
 *
 * Copyright (C) 2006 - 2017  The JMRTD team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * $Id: PassportApduService.java 1701 2017-09-04 18:17:07Z martijno $
 */

package org.jmrtd;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.Provider;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;

import net.sf.scuba.smartcards.APDUEvent;
import net.sf.scuba.smartcards.APDUListener;
import net.sf.scuba.smartcards.APDUWrapper;
import net.sf.scuba.smartcards.CardService;
import net.sf.scuba.smartcards.CardServiceException;
import net.sf.scuba.smartcards.CommandAPDU;
import net.sf.scuba.smartcards.ISO7816;
import net.sf.scuba.smartcards.ResponseAPDU;
import net.sf.scuba.tlv.TLVInputStream;
import net.sf.scuba.util.Hex;

/**
 * Low level card service for sending APDUs to the passport. This service is not
 * responsible for maintaining information about the state of the authentication
 * or secure messaging protocols. It merely offers the basic functionality for
 * sending passport specific APDUs to the passport.
 *
 * Based on ICAO-TR-PKI. Defines the following commands:
 * 
    *
  • GET CHALLENGE
  • *
  • EXTERNAL AUTHENTICATE
  • *
  • INTERNAL AUTHENTICATE (using secure messaging)
  • *
  • SELECT FILE (using secure messaging)
  • *
  • READ BINARY (using secure messaging)
  • *
* * @author The JMRTD team ([email protected]) * * @version $Revision: 1701 $ */ public class PassportApduService extends CardService { /** Shared secret type for PACE according to BSI TR-03110 v2.03 B.11.1. */ public static final byte MRZ_PACE_KEY_REFERENCE = 0x01, CAN_PACE_KEY_REFERENCE = 0x02, PIN_PACE_KEY_REFERENCE = 0x03, PUK_PACE_KEY_REFERENCE = 0x04; private static final long serialVersionUID = 2451509825132976178L; private static final Logger LOGGER = Logger.getLogger("org.jmrtd"); private static final Provider BC_PROVIDER = Util.getBouncyCastleProvider(); /** The applet we select when we start a session. */ protected static final byte[] APPLET_AID = { (byte)0xA0, 0x00, 0x00, 0x02, 0x47, 0x10, 0x01 }; /** Initialization vector used by the cipher below. */ private static final IvParameterSpec ZERO_IV_PARAM_SPEC = new IvParameterSpec(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); /** The general Authenticate command is used to perform the PACE protocol. See Section 3.2.2 of SAC-TR 1.01. */ private static final byte INS_PACE_GENERAL_AUTHENTICATE = (byte)0x86; /** The service we decorate. */ private CardService service; private byte[] atr; /** DESede encryption/decryption cipher. */ private transient Cipher cipher; /** ISO9797Alg3Mac. */ private transient Mac mac; private Collection plainTextAPDUListeners; private int plainAPDUCount; /** * Creates a new passport APDU sending service. * * @param service another service which will deal with sending the APDUs to the card * * @throws CardServiceException when the available JCE providers cannot provide the necessary * cryptographic primitives: *
    *
  • Cipher: "DESede/CBC/Nopadding"
  • *
  • Mac: "ISO9797Alg3Mac"
  • *
*/ public PassportApduService(CardService service) throws CardServiceException { this.service = service; plainTextAPDUListeners = new HashSet(); plainAPDUCount = 0; try { mac = Mac.getInstance("ISO9797Alg3Mac", BC_PROVIDER); cipher = Util.getCipher("DESede/CBC/NoPadding"); } catch (GeneralSecurityException gse) { throw new CardServiceException("Unexpected security exception during initialization", gse); } } /** * Opens a session by connecting to the card. Since version 0.5.1 this method no longer automatically * selects the MRTD applet, caller (for instance {@link PassportService}) is responsible to do this now. * * @throws CardServiceException on failure to open the service */ @Override public void open() throws CardServiceException { if (!service.isOpen()) { service.open(); } atr = service.getATR(); } /** * Whether this service is open. * * @return a boolean */ @Override public synchronized boolean isOpen() { return service.isOpen(); } /** * Tranceives an APDU. * * If the card responds with a status word other than {@code 0x9000} this method does * NOT throw a {@linkplain CardServiceException}, but it returns this as error code * as result. * @param capdu the command APDU * * @return the response APDU * * @throws CardServiceException on error */ @Override public synchronized ResponseAPDU transmit(CommandAPDU capdu) throws CardServiceException { return service.transmit(capdu); } /** * Gets the answer to reset bytes. * * @return the answer to reset bytes */ @Override public byte[] getATR() { return atr; } /** * Closes the service. */ @Override public void close() { if (service != null) { service.close(); } } /** * Sets the service. * * @param service the carrier service that is decorated by this service * * @param service the carrier service */ /* FIXME: why is this here? -- MO */ public void setService(CardService service) { this.service = service; } /** * Adds a listener. * * @param l a listener */ public void addAPDUListener(APDUListener l) { service.addAPDUListener(l); } /** * Removes a listener. * * @param l a listener */ public void removeAPDUListener(APDUListener l) { service.removeAPDUListener(l); } /** * Transmits an APDU. * * @param wrapper the secure messaging wrapper * @param capdu the APDU to send * * @return the APDU received from the PICC * * @throws CardServiceException if tranceiving failed */ private ResponseAPDU transmit(APDUWrapper wrapper, CommandAPDU capdu) throws CardServiceException { CommandAPDU plainCapdu = capdu; if (wrapper != null) { capdu = wrapper.wrap(capdu); } ResponseAPDU rapdu = transmit(capdu); short sw = (short)rapdu.getSW(); if (wrapper != null) { try { if (rapdu.getBytes().length == 2) { throw new CardServiceException("Exception during transmission of wrapped APDU" + "\nC=" + Hex.bytesToHexString(plainCapdu.getBytes()), sw); } else { rapdu = wrapper.unwrap(rapdu); } } catch (Exception e) { if (e instanceof CardServiceException) { throw (CardServiceException)e; } else { throw new CardServiceException("Exception during transmission of wrapped APDU" + "\nC=" + Hex.bytesToHexString(plainCapdu.getBytes()), e, sw); } } finally { notifyExchangedPlainTextAPDU(++plainAPDUCount, plainCapdu, rapdu); } } // if ((sw & ISO7816.SW_CORRECT_LENGTH_00) == ISO7816.SW_CORRECT_LENGTH_00) { // /* Re-transmit with corrected length if incorrect length. */ // int ne = (sw & 0xFF); // plainCapdu = new CommandAPDU(plainCapdu.getCLA(), plainCapdu.getINS(), plainCapdu.getP1(), plainCapdu.getP2(), plainCapdu.getData(), ne); // if (wrapper != null) { // capdu = wrapper.wrap(plainCapdu); // } // rapdu = transmit(capdu); // if (wrapper != null) { // rapdu = wrapper.unwrap(rapdu, rapdu.getBytes().length); // notifyExchangedPlainTextAPDU(++plainAPDUCount, plainCapdu, rapdu); // } // } return rapdu; } /** * Sends a SELECT APPLET command to the card. * * @param wrapper the secure messaging wrapper to use * @param aid the applet to select * * @throws CardServiceException on tranceive error */ public synchronized void sendSelectApplet(APDUWrapper wrapper, byte[] aid) throws CardServiceException { if (aid == null) { throw new IllegalArgumentException("AID cannot be null"); } CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816,ISO7816.INS_SELECT_FILE, (byte) 0x04, (byte) 0x0C, aid); ResponseAPDU rapdu = transmit(wrapper, capdu); checkStatusWordAfterFileOperation(capdu, rapdu); } /** * Selects a file. * * @param fid the file identifier * * @throws CardServiceException on error */ public synchronized void sendSelectFile(short fid) throws CardServiceException { sendSelectFile(null, fid); } /** * Sends a SELECT FILE command to the passport. Secure * messaging will be applied to the command and response apdu. * * @param wrapper the secure messaging wrapper to use * @param fid the file to select * * @throws CardServiceException on tranceive error */ public synchronized void sendSelectFile(APDUWrapper wrapper, short fid) throws CardServiceException { byte[] fiddle = { (byte) ((fid >> 8) & 0xFF), (byte) (fid & 0xFF) }; CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_SELECT_FILE, (byte) 0x02, (byte) 0x0c, fiddle, 0); ResponseAPDU rapdu = transmit(wrapper, capdu); if( rapdu == null ) { return; } checkStatusWordAfterFileOperation(capdu, rapdu); } /** * Sends a READ BINARY command to the passport. * * @param offset offset into the file * @param le the expected length of the file to read * @param longRead whether to use extended length APDUs * * @return a byte array of length le with (the specified part of) the contents of the currently selected file * * @throws CardServiceException if the command was not successful */ public synchronized byte[] sendReadBinary(short offset, int le, boolean longRead) throws CardServiceException { return sendReadBinary(null, offset, le, longRead); } /** * Sends a READ BINARY command to the passport. Secure * messaging will be applied to the command and response apdu. * * @param wrapper the secure messaging wrapper to use * @param offset offset into the file * @param le the expected length of the file to read * @param isExtendedLength whether it should be a long (INS=B1) read * * @return a byte array of length at most le with (the specified part of) the contents of the currently selected file * * @throws CardServiceException if the command was not successful */ public synchronized byte[] sendReadBinary(APDUWrapper wrapper, int offset, int le, boolean isExtendedLength) throws CardServiceException { // boolean retrySending = false; CommandAPDU capdu = null; ResponseAPDU rapdu = null; // do { // retrySending = false; // In case the data ended right on the block boundary if (le == 0) { return null; } // In the case of long read 2/3 less bytes of the actual data will be returned, // because a tag and length will be sent along, here we need to account for this if (isExtendedLength) { if (le < 128) { le += 2; } else if (le < 256) { le += 3; } if (le > 256) { le = 256; } } byte offsetHi = (byte)((offset & 0xFF00) >> 8); byte offsetLo = (byte)(offset & 0xFF); if (isExtendedLength) { byte[] data = new byte[] { 0x54, 0x02, offsetHi, offsetLo }; capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_READ_BINARY2, 0, 0, data, le); } else { capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_READ_BINARY, offsetHi, offsetLo, le); } short sw = ISO7816.SW_UNKNOWN; try { rapdu = transmit(wrapper, capdu); sw = (short)rapdu.getSW(); } catch (CardServiceException cse) { sw = (short)cse.getSW(); } /* There are 3 cases according to R2-p1_v2_sIII_0039... */ // if (sw == ISO7816.SW_NO_ERROR) { // /* sw == 0x9000, no need to try again. */ // retrySending = false; // } else if (sw == ISO7816.SW_END_OF_FILE) { // /* sw == 0x6282 means EOF, try again with shorter le. */ // le--; // retrySending = true; // } else if ((sw & ISO7816.SW_CORRECT_LENGTH_00) == ISO7816.SW_CORRECT_LENGTH_00) { // /* sw == 0x6Cxx means xx is correct length, try again with that le. */ // /* NOTE: the transmit method also does retransmission on 6Cxx. */ // le = sw & 0xFF; // retrySending = true; // } else { // /* All other cases. */ // if (sw == ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED) { // /* No access, fail READ BINARY, throw a CSE. */ // throw new CardServiceException("Security status not satisfied during READ BINARY", sw); // } else { // /* Unexpected sw, don't throw exception, but log. */ // LOGGER.warning("Unhandled case for status word in READ BINARY, sw == " + Integer.toHexString(sw)); // retrySending = false; // } // } // } while (retrySending); byte[] rapduBytes = rapdu == null ? null : rapdu.getData(); // short sw = (short)rapdu.getSW(); /* NOTE: Update the SW to the last resent APDU. */ if (isExtendedLength && sw == ISO7816.SW_NO_ERROR) { /* Strip the response off the tag 0x53 and the length field. */ byte[] data = rapduBytes; int index = 0; if(data[index++] != (byte)0x53) { throw new CardServiceException("Malformed read binary long response data", sw); } if((byte)(data[index] & 0x80) == (byte)0x80) { index += (data[index] & 0xF); } index ++; rapduBytes = new byte[data.length - index]; System.arraycopy(data, index, rapduBytes, 0, rapduBytes.length); } if (rapduBytes == null || rapduBytes.length == 0) { LOGGER.warning("DEBUG: rapduBytes = " + Arrays.toString(rapduBytes) + ", le = " + le + ", sw = " + Integer.toHexString(sw)); } else { checkStatusWordAfterFileOperation(capdu, rapdu); } return rapduBytes; } /** * Sends a GET CHALLENGE command to the passport. * * @return a byte array of length 8 containing the challenge * * @throws CardServiceException on tranceive error */ public synchronized byte[] sendGetChallenge() throws CardServiceException { return sendGetChallenge(null); } /** * Sends a GET CHALLENGE command to the passport. * * @param wrapper secure messaging wrapper * * @return a byte array of length 8 containing the challenge * * @throws CardServiceException on tranceive error */ public synchronized byte[] sendGetChallenge(APDUWrapper wrapper) throws CardServiceException { CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_GET_CHALLENGE, 0x00, 0x00, 8); ResponseAPDU rapdu = transmit(wrapper, capdu); return rapdu.getData(); } /** * Sends an INTERNAL AUTHENTICATE command to the passport. * This is part of AA. * * @param wrapper secure messaging wrapper * @param rndIFD the challenge to send * * @return the response from the passport (status word removed) * * @throws CardServiceException on tranceive error */ public synchronized byte[] sendInternalAuthenticate(APDUWrapper wrapper, byte[] rndIFD) throws CardServiceException { if (rndIFD == null || rndIFD.length != 8) { throw new IllegalArgumentException("rndIFD wrong length"); } CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_INTERNAL_AUTHENTICATE, 0x00, 0x00, rndIFD, 256); ResponseAPDU rapdu = transmit(wrapper, capdu); return rapdu.getData(); } /** * Sends an EXTERNAL AUTHENTICATE command to the passport. * This is part of BAC. * The resulting byte array has length 32 and contains rndICC * (first 8 bytes), rndIFD (next 8 bytes), their key material " * kICC" (last 16 bytes). * * @param rndIFD our challenge * @param rndICC their challenge * @param kIFD our key material * @param kEnc the static encryption key * @param kMac the static mac key * * @return a byte array of length 32 containing the response that was sent * by the passport, decrypted (using kEnc) and verified * (using kMac) * * @throws CardServiceException on tranceive error */ public synchronized byte[] sendMutualAuth(byte[] rndIFD, byte[] rndICC, byte[] kIFD, SecretKey kEnc, SecretKey kMac) throws CardServiceException { try { if (rndIFD == null || rndIFD.length != 8) { throw new IllegalArgumentException("rndIFD wrong length"); } if (rndICC == null || rndICC.length != 8) { rndICC = new byte[8]; } if (kIFD == null || kIFD.length != 16) { throw new IllegalArgumentException("kIFD wrong length"); } if (kEnc == null) { throw new IllegalArgumentException("kEnc == null"); } if (kMac == null) { throw new IllegalArgumentException("kMac == null"); } cipher.init(Cipher.ENCRYPT_MODE, kEnc, ZERO_IV_PARAM_SPEC); /* * cipher.update(rndIFD); cipher.update(rndICC); cipher.update(kIFD); // * This doesn't work, apparently we need to create plaintext array. // * Probably has something to do with ZERO_IV_PARAM_SPEC. */ byte[] plaintext = new byte[32]; System.arraycopy(rndIFD, 0, plaintext, 0, 8); System.arraycopy(rndICC, 0, plaintext, 8, 8); System.arraycopy(kIFD, 0, plaintext, 16, 16); byte[] ciphertext = cipher.doFinal(plaintext); if (ciphertext.length != 32) { throw new IllegalStateException("Cryptogram wrong length " + ciphertext.length); } mac.init(kMac); byte[] mactext = mac.doFinal(Util.pad(ciphertext, 8)); if (mactext.length != 8) { throw new IllegalStateException("MAC wrong length"); } byte p1 = (byte)0x00; byte p2 = (byte)0x00; byte[] data = new byte[32 + 8]; System.arraycopy(ciphertext, 0, data, 0, 32); System.arraycopy(mactext, 0, data, 32, 8); int le = 40; /* 40 means max ne is 40 (0x28). */ CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_EXTERNAL_AUTHENTICATE, p1, p2, data, le); ResponseAPDU rapdu = transmit(capdu); if (rapdu == null) { throw new CardServiceException("Mutual authentication failed, received null response APDU"); } byte[] rapduBytes = rapdu.getBytes(); short sw = (short)rapdu.getSW(); if (rapduBytes == null) { throw new CardServiceException("Mutual authentication failed, received empty data in response APDU", sw); } /* Some MRTDs apparently don't support 40 here, try again with 0. See R2-p1_v2_sIII_0035 (and other issues). */ if (sw != ISO7816.SW_NO_ERROR) { le = 0; /* 0 means ne is max 256 (0xFF). */ capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_EXTERNAL_AUTHENTICATE, p1, p2, data, le); rapdu = transmit(capdu); rapduBytes = rapdu.getBytes(); sw = (short)rapdu.getSW(); } if (rapduBytes.length != 42) { throw new CardServiceException("Mutual authentication failed: expected length: 40 + 2, actual length: " + rapduBytes.length, sw); } /* * byte[] eICC = new byte[32]; System.arraycopy(rapdu, 0, eICC, 0, 32); * * byte[] mICC = new byte[8]; System.arraycopy(rapdu, 32, mICC, 0, 8); */ /* Decrypt the response. */ cipher.init(Cipher.DECRYPT_MODE, kEnc, ZERO_IV_PARAM_SPEC); byte[] result = cipher.doFinal(rapduBytes, 0, rapduBytes.length - 8 - 2); if (result.length != 32) { throw new IllegalStateException("Cryptogram wrong length " + result.length); } return result; } catch (GeneralSecurityException gse) { throw new CardServiceException(gse.toString()); } } /** * Sends the EXTERNAL AUTHENTICATE command. * This is used in EAC-TA. * * @param wrapper secure messaging wrapper * @param signature terminal signature * * @throws CardServiceException if the resulting status word different from 9000 */ public synchronized void sendMutualAuthenticate(APDUWrapper wrapper, byte[] signature) throws CardServiceException { CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_EXTERNAL_AUTHENTICATE, 0, 0, signature); ResponseAPDU rapdu = transmit(wrapper, capdu); short sw = (short)rapdu.getSW(); if (sw != ISO7816.SW_NO_ERROR) { throw new CardServiceException("Sending External Authenticate failed.", sw); } } /** * The MSE KAT APDU, see EAC 1.11 spec, Section B.1 * * @param wrapper secure messaging wrapper * @param keyData key data object (tag 0x91) * @param idData key id data object (tag 0x84), can be null * * @throws CardServiceException on error */ public synchronized void sendMSEKAT(APDUWrapper wrapper, byte[] keyData, byte[] idData) throws CardServiceException { byte[] data = new byte[keyData.length + ((idData != null) ? idData.length : 0)]; System.arraycopy(keyData, 0, data, 0, keyData.length); if (idData != null) { System.arraycopy(idData, 0, data, keyData.length, idData.length); } CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_MSE, 0x41, 0xA6, data); ResponseAPDU rapdu = transmit(wrapper, capdu); short sw = (short)rapdu.getSW(); if (sw != ISO7816.SW_NO_ERROR) { throw new CardServiceException("Sending MSE KAT failed", sw); } } /** * The MSE DST APDU, see EAC 1.11 spec, Section B.2 * * @param wrapper secure messaging wrapper * @param data public key reference data object (tag 0x83) * * @throws CardServiceException on error */ public synchronized void sendMSESetDST(APDUWrapper wrapper, byte[] data) throws CardServiceException { CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_MSE, 0x81, 0xB6, data); ResponseAPDU rapdu = transmit(wrapper, capdu); short sw = (short)rapdu.getSW(); if (sw != ISO7816.SW_NO_ERROR) { throw new CardServiceException("Sending MSE Set DST failed", sw); } } /** * The MSE Set AT APDU for TA, see EAC 1.11 spec, Section B.2. * MANAGE SECURITY ENVIRONMENT command with SET Authentication Template function. * * Note that caller is responsible for prefixing the byte[] params with specified tags. * * @param wrapper secure messaging wrapper * @param data public key reference data object (should already be prefixed with tag 0x83) * * @throws CardServiceException on error */ public synchronized void sendMSESetATExtAuth(APDUWrapper wrapper, byte[] data) throws CardServiceException { CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_MSE, 0x81, 0xA4, data); ResponseAPDU rapdu = transmit(wrapper, capdu); short sw = (short)rapdu.getSW(); if (sw != ISO7816.SW_NO_ERROR) { throw new CardServiceException("Sending MSE AT failed", sw); } } /* * FIXME: Make prefixing 0x8x tags responsibilities consistent between ext auth and int auth and mutual auth * Now: above method makes caller responsible, below method callee is responsible. -- MO */ /* For Chip Authentication. We prefix 0x80 for OID and 0x84 for keyId. */ /** * The MSE Set AT for chip authentication. * * @param wrapper secure messaging wrapper * @param oid the OID * @param keyId the keyId or {@code null} * * @throws CardServiceException on error */ public synchronized void sendMSESetATIntAuth(APDUWrapper wrapper, String oid, BigInteger keyId) throws CardServiceException { int p1 = 0x41; int p2 = 0xA4; // int p2 = 0xA6; ResponseAPDU rapdu = null; if (keyId == null || keyId.compareTo(BigInteger.ZERO) < 0) { LOGGER.info("DEBUG: implicit case, keyId == " + keyId); CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_MSE, p1, p2, toOIDBytes(oid)); rapdu = transmit(wrapper, capdu); } else { LOGGER.info("DEBUG: explicit case, keyId == " + keyId); byte[] oidBytes = toOIDBytes(oid); byte[] keyIdBytes = Util.wrapDO((byte)0x84, Util.i2os(keyId)); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try { byteArrayOutputStream.write(oidBytes); byteArrayOutputStream.write(keyIdBytes); byteArrayOutputStream.close(); } catch (IOException ioe) { LOGGER.log(Level.WARNING, "Exception", ioe); } CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_MSE, p1, p2, byteArrayOutputStream.toByteArray()); rapdu = transmit(wrapper, capdu); } short sw = rapdu == null ? -1 : (short)rapdu.getSW(); if (sw != ISO7816.SW_NO_ERROR) { throw new CardServiceException("Sending MSE AT failed", sw); } } /** * The MSE AT APDU for PACE, see ICAO TR-SAC-1.01, Section 3.2.1, BSI TR 03110 v2.03 B11.1. * Note that (for now) caller is responsible for prefixing the byte[] params with specified tags. * * @param wrapper secure messaging wrapper * @param oid OID of the protocol to select (this method will prefix 0x80) * @param refPublicKeyOrSecretKey value specifying whether to use MRZ (0x01) or CAN (0x02) (this method will prefix 0x83) * @param refPrivateKeyOrForComputingSessionKey indicates a private key or reference for computing a session key (this method will prefix 0x84) * * @throws CardServiceException on error */ public synchronized void sendMSESetATMutualAuth(APDUWrapper wrapper, String oid, int refPublicKeyOrSecretKey, byte[] refPrivateKeyOrForComputingSessionKey) throws CardServiceException { if (oid == null) { throw new IllegalArgumentException("OID cannot be null"); } byte[] oidBytes = toOIDBytes(oid); /* * 0x83 Reference of a public key / secret key. * The password to be used is indicated as follows: 0x01: MRZ, 0x02: CAN. */ if (!(refPublicKeyOrSecretKey == MRZ_PACE_KEY_REFERENCE || refPublicKeyOrSecretKey == CAN_PACE_KEY_REFERENCE || refPublicKeyOrSecretKey == PIN_PACE_KEY_REFERENCE || refPublicKeyOrSecretKey == PUK_PACE_KEY_REFERENCE)) { throw new IllegalArgumentException("Unsupported key type reference (MRZ, CAN, etc), found " + refPublicKeyOrSecretKey); } byte[] refPublicKeyOrSecretKeyBytes = Util.wrapDO((byte)0x83, new byte[] { (byte)refPublicKeyOrSecretKey }); /* FIXME: define constant for 0x83 */ /* * 0x84 Reference of a private key / Reference for computing a * session key. * This data object is REQUIRED to indicate the identifier * of the domain parameters to be used if the domain * parameters are ambiguous, i.e. more than one set of * domain parameters is available for PACE. */ if (refPrivateKeyOrForComputingSessionKey != null) { refPrivateKeyOrForComputingSessionKey = Util.wrapDO((byte)0x84, refPrivateKeyOrForComputingSessionKey); } /* Construct data. */ ByteArrayOutputStream dataOutputStream = new ByteArrayOutputStream(); try { dataOutputStream.write(oidBytes); dataOutputStream.write(refPublicKeyOrSecretKeyBytes); if (refPrivateKeyOrForComputingSessionKey != null) { dataOutputStream.write(refPrivateKeyOrForComputingSessionKey); } } catch (IOException ioe) { /* NOTE: should never happen. */ LOGGER.log(Level.WARNING, "Error while copying data", ioe); throw new IllegalStateException("Error while copying data"); } byte[] data = dataOutputStream.toByteArray(); /* Tranceive APDU. */ CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_MSE, 0xC1, 0xA4, data); ResponseAPDU rapdu = transmit(wrapper, capdu); /* Handle error status word. */ short sw = (short)rapdu.getSW(); if (sw != ISO7816.SW_NO_ERROR) { throw new CardServiceException("Sending MSE AT failed", sw); } } /* * 0x80 Cryptographic mechanism reference * Object Identifier of the protocol to select (value only, tag 0x06 is omitted). */ private byte[] toOIDBytes(String oid) { byte[] oidBytes = null; try { TLVInputStream oidTLVIn = new TLVInputStream(new ByteArrayInputStream(new ASN1ObjectIdentifier(oid).getEncoded())); oidTLVIn.readTag(); /* Should be 0x06 */ oidTLVIn.readLength(); oidBytes = oidTLVIn.readValue(); oidTLVIn.close(); return Util.wrapDO((byte)0x80, oidBytes); /* FIXME: define constant for 0x80. */ } catch (IOException ioe) { throw new IllegalArgumentException("Illegal OID: \"" + oid, ioe); } } /** * Sends a General Authenticate command. * * @param wrapper secure messaging wrapper * @param data data to be sent, without the {@code 0x7C} prefix (this method will add it) * @param isLast indicates whether this is the last command in the chain * * @return dynamic authentication data without the {@code 0x7C} prefix (this method will remove it) * * @throws CardServiceException on error */ public synchronized byte[] sendGeneralAuthenticate(APDUWrapper wrapper, byte[] data, boolean isLast) throws CardServiceException { /* Tranceive APDU. */ byte[] commandData = Util.wrapDO((byte)0x7C, data); // FIXME: constant for 0x7C CommandAPDU capdu = new CommandAPDU(isLast ? ISO7816.CLA_ISO7816 : ISO7816.CLA_COMMAND_CHAINING, INS_PACE_GENERAL_AUTHENTICATE, 0x00, 0x00, commandData, 256); ResponseAPDU rapdu = transmit(wrapper, capdu); /* Handle error status word. */ short sw = (short)rapdu.getSW(); if (sw != ISO7816.SW_NO_ERROR) { throw new CardServiceException("Sending general authenticate failed", sw); } byte[] responseData = rapdu.getData(); responseData = Util.unwrapDO((byte)0x7C, responseData); return responseData; } /** * Sends a perform security operation command in extended length mode. * * @param wrapper secure messaging wrapper * @param certBodyData the certificate body * @param certSignatureData signature data * * @throws CardServiceException on error communicating over the service */ public synchronized void sendPSOExtendedLengthMode(APDUWrapper wrapper, byte[] certBodyData, byte[] certSignatureData) throws CardServiceException { byte[] certData = new byte[certBodyData.length + certSignatureData.length]; System.arraycopy(certBodyData, 0, certData, 0, certBodyData.length); System.arraycopy(certSignatureData, 0, certData, certBodyData.length, certSignatureData.length); CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_PSO, 0, 0xBE, certData); ResponseAPDU rapdu = transmit(wrapper, capdu); short sw = (short)rapdu.getSW(); if (sw != ISO7816.SW_NO_ERROR) { throw new CardServiceException("Sending PSO failed", sw); } } /** * Sends a perform security operation command in chain mode. * * @param wrapper secure messaging wrapper * @param certBodyData the certificate body * @param certSignatureData signature data * * @throws CardServiceException on error communicating over the service */ public synchronized void sendPSOChainMode(APDUWrapper wrapper, byte[] certBodyData, byte[] certSignatureData) throws CardServiceException { byte[] certData = new byte[certBodyData.length + certSignatureData.length]; System.arraycopy(certBodyData, 0, certData, 0, certBodyData.length); System.arraycopy(certSignatureData, 0, certData, certBodyData.length, certSignatureData.length); int maxBlock = 223; int blockSize = 223; int offset = 0; int length = certData.length; if (certData.length > maxBlock) { int numBlock = certData.length / blockSize; if (numBlock * blockSize < certData.length) numBlock++; int i = 0; while (i < numBlock - 1) { CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816 | 0x10, ISO7816.INS_PSO, 0x00, 0xBE, certData, offset, length); ResponseAPDU rapdu = transmit(wrapper, capdu); short sw = (short)rapdu.getSW(); if (sw != ISO7816.SW_NO_ERROR) { throw new CardServiceException("Sending PSO failed", sw); } length -= blockSize; offset += blockSize; i++; } } CommandAPDU capdu = new CommandAPDU(ISO7816.CLA_ISO7816 | 0x00, ISO7816.INS_PSO, 0x00, 0xBE, certData, offset, length); ResponseAPDU rapdu = transmit(wrapper, capdu); short sw = (short)rapdu.getSW(); if (sw != ISO7816.SW_NO_ERROR) { throw new CardServiceException("Sending PSO failed", sw); } } /** * Adds a plain text listener. * * @param l a listener */ public void addPlainTextAPDUListener(APDUListener l) { if (plainTextAPDUListeners != null) { plainTextAPDUListeners.add(l); } } /** * Removes a plain text listener. * * @param l a listener */ public void removePlainTextAPDUListener(APDUListener l) { if (plainTextAPDUListeners != null) { plainTextAPDUListeners.remove(l); } } /** * Notifies listeners about APDU event. * * @param count count * @param capdu command APDU * @param rapdu response APDU */ protected void notifyExchangedPlainTextAPDU(int count, CommandAPDU capdu, ResponseAPDU rapdu) { if (plainTextAPDUListeners == null || plainTextAPDUListeners.size() < 1) { return; } APDUEvent event = new APDUEvent(this, "PLAINTEXT", count, capdu, rapdu); for (APDUListener listener: plainTextAPDUListeners) { listener.exchangedAPDU(event); } } private static void checkStatusWordAfterFileOperation(CommandAPDU capdu, ResponseAPDU rapdu) throws CardServiceException { short sw = (short)rapdu.getSW(); String commandResponseMessage = "CAPDU = " + Hex.bytesToHexString(capdu.getBytes()) + ", RAPDU = " + Hex.bytesToHexString(rapdu.getBytes()); switch(sw) { case ISO7816.SW_NO_ERROR: return; case ISO7816.SW_FILE_NOT_FOUND: throw new CardServiceException("File not found, " + commandResponseMessage, sw); case ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED: /* NOTE: fall through. */ case ISO7816.SW_CONDITIONS_NOT_SATISFIED: /* NOTE: fall through. */ case ISO7816.SW_COMMAND_NOT_ALLOWED: throw new CardServiceException("Access to file denied, " + commandResponseMessage, sw); } throw new CardServiceException("Error occured, " + commandResponseMessage, sw); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy