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

org.jmrtd.cbeff.ISO781611Decoder Maven / Gradle / Ivy

There is a newer version: 0.7.42
Show newest version
/*
 * JMRTD - A Java API for accessing machine readable travel documents.
 *
 * Copyright (C) 2006 - 2018  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: ISO781611Decoder.java 1768 2018-02-22 11:23:51Z martijno $
 */

package org.jmrtd.cbeff;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.AccessControlException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import net.sf.scuba.tlv.TLVInputStream;
import net.sf.scuba.tlv.TLVUtil;

/**
 * ISO 7816-11 decoder for BIR.
 *
 * @author The JMRTD team ([email protected])
 *
 * @version $Revision: 1768 $
 *
 * @since 0.4.7
 */
public class ISO781611Decoder implements ISO781611 {

  private static final Logger LOGGER = Logger.getLogger("org.jmrtd");

  private BiometricDataBlockDecoder bdbDecoder;

  /**
   * Constructs an ISO7816-11 decoder that uses the given BDB decoder.
   *
   * @param bdbDecoder the BDB decoder to use
   */
  public ISO781611Decoder(BiometricDataBlockDecoder bdbDecoder) {
    this.bdbDecoder = bdbDecoder;
  }

  /**
   * Reads a BIT group from an input stream.
   *
   * @param inputStream the input stream to read from
   *
   * @return a complex CBEFF info representing the BIT group
   *
   * @throws IOException if reading fails
   */
  public ComplexCBEFFInfo decode(InputStream inputStream) throws IOException {
    return readBITGroup(inputStream);
  }

  /**
   * Reads a BIT group from an input stream.
   *
   * @param inputStream the input stream to read from
   *
   * @return a complex CBEFF info representing the BIT group
   *
   * @throws IOException if reading fails
   */
  private ComplexCBEFFInfo readBITGroup(InputStream inputStream) throws IOException {
    TLVInputStream tlvIn = inputStream instanceof TLVInputStream ? (TLVInputStream)inputStream : new TLVInputStream(inputStream);
    int tag = tlvIn.readTag();
    if (tag != BIOMETRIC_INFORMATION_GROUP_TEMPLATE_TAG) {
      throw new IllegalArgumentException("Expected tag " + Integer.toHexString(BIOMETRIC_INFORMATION_GROUP_TEMPLATE_TAG) + ", found " + Integer.toHexString(tag));
    }

    int length = tlvIn.readLength();
    return readBITGroup(tag, length, inputStream);
  }

  /**
   * Reads a BIT group value from an input stream.
   *
   * @param tag the tag that was already read, which should be a BIT group tag ({@code 7F61})
   * @param length the length that was already read
   * @param inputStream the input stream from which to read the value
   *
   * @return CBEFF info representing the BIT group that was read
   *
   * @throws IOException on error reading from the stream
   */
  private ComplexCBEFFInfo readBITGroup(int tag, int length, InputStream inputStream) throws IOException {
    TLVInputStream tlvIn = inputStream instanceof TLVInputStream ? (TLVInputStream)inputStream : new TLVInputStream(inputStream);
    ComplexCBEFFInfo result = new ComplexCBEFFInfo();
    if (tag != BIOMETRIC_INFORMATION_GROUP_TEMPLATE_TAG) { /* 7F61 */
      throw new IllegalArgumentException("Expected tag " + Integer.toHexString(BIOMETRIC_INFORMATION_GROUP_TEMPLATE_TAG) + ", found " + Integer.toHexString(tag));
    }
    int bitCountTag = tlvIn.readTag();
    if (bitCountTag != BIOMETRIC_INFO_COUNT_TAG) { /* 02 */
      throw new IllegalArgumentException("Expected tag BIOMETRIC_INFO_COUNT_TAG (" + Integer.toHexString(BIOMETRIC_INFO_COUNT_TAG) + ") in CBEFF structure, found " + Integer.toHexString(bitCountTag));
    }
    int bitCountLength = tlvIn.readLength();
    if (bitCountLength != 1) {
      throw new IllegalArgumentException("BIOMETRIC_INFO_COUNT should have length 1, found length " + bitCountLength);
    }
    int bitCount = (tlvIn.readValue()[0] & 0xFF);
    for (int i = 0; i < bitCount; i++) {
      result.add(readBIT(inputStream, i));
    }

    /* TODO: possibly more content, e.g. 0x53 tag with random as per ICAO 9303 Supplement R7-p1_v2_sIII_0057 */

    return result;
  }

  /**
   * Reads a single BIT from the input stream.
   *
   * @param inputStream the input stream to read from
   * @param index index of this BIT within the BIT group
   *
   * @return a CBEFF info representing the BIT
   *
   * @throws IOException if reading fails
   */
  private CBEFFInfo readBIT(InputStream inputStream, int index) throws IOException {
    TLVInputStream tlvIn = inputStream instanceof TLVInputStream ? (TLVInputStream)inputStream : new TLVInputStream(inputStream);
    int tag = tlvIn.readTag();
    int length = tlvIn.readLength();
    return readBIT(tag, length, inputStream, index);
  }

  /**
   * Reads a single BIT from the input stream.
   *
   * @param tag the tag that was already read
   * @param length the length that was already read
   * @param inputStream the stream to read the BIT value from
   * @param index the index of the BIT withing the BIT group
   *
   * @return a CBEFF info representing the BIT
   *
   * @throws IOException on error reading from the stream
   */
  private CBEFFInfo readBIT(int tag, int length, InputStream inputStream, int index) throws IOException {
    TLVInputStream tlvIn = inputStream instanceof TLVInputStream ? (TLVInputStream)inputStream : new TLVInputStream(inputStream);
    if (tag != BIOMETRIC_INFORMATION_TEMPLATE_TAG /* 7F60 */) {
      throw new IllegalArgumentException("Expected tag BIOMETRIC_INFORMATION_TEMPLATE_TAG (" + Integer.toHexString(BIOMETRIC_INFORMATION_TEMPLATE_TAG) + "), found " + Integer.toHexString(tag) + ", index is " + index);
    }

    int bhtTag = tlvIn.readTag();
    int bhtLength = tlvIn.readLength();

    if ((bhtTag == SMT_TAG)) {
      /* The BIT is protected... */
      readStaticallyProtectedBIT(inputStream, bhtTag, bhtLength, index);
    } else if ((bhtTag & 0xA0) == 0xA0) {
      StandardBiometricHeader sbh = readBHT(inputStream, bhtTag, bhtLength, index);
      BiometricDataBlock bdb = readBiometricDataBlock(inputStream, sbh, index);
      return new SimpleCBEFFInfo(bdb);
    } else {
      throw new IllegalArgumentException("Unsupported template tag: " + Integer.toHexString(bhtTag));
    }

    return null; // FIXME
  }

  /**
   * Reads the biometric header template from an input stream.
   *  A1, A2, ...
   *  Will contain DOs as described in ISO 7816-11 Annex C.
   *
   *  @param inputStream the stream to read from
   *  @param bhtTag the tag of the biometric header
   *  @param bhtLength the length of the header
   *  @param index the index
   *
   *  @return the standard biometric header
   *
   *  @throws IOException on error reading from the stream
   */
  private StandardBiometricHeader readBHT(InputStream inputStream, int bhtTag, int bhtLength, int index) throws IOException {
    TLVInputStream tlvIn = inputStream instanceof TLVInputStream ? (TLVInputStream)inputStream : new TLVInputStream(inputStream);
    int expectedBHTTag = (BIOMETRIC_HEADER_TEMPLATE_BASE_TAG /* + index */) & 0xFF;
    if (bhtTag != expectedBHTTag) {
      LOGGER.warning("Expected tag " + Integer.toHexString(expectedBHTTag) + ", found " + Integer.toHexString(bhtTag));
    }
    Map elements = new HashMap();
    int bytesRead = 0;
    while (bytesRead < bhtLength) {
      int tag = tlvIn.readTag();
      bytesRead += TLVUtil.getTagLength(tag);
      int length = tlvIn.readLength();
      bytesRead += TLVUtil.getLengthLength(length);
      byte[] value = tlvIn.readValue();
      bytesRead += value.length;
      elements.put(tag, value);
    }
    return new StandardBiometricHeader(elements);
  }

  /**
   * Reads a biometric information template protected with secure messaging.
   * Described in ISO7816-11 Annex D.
   *
   * @param inputStream source to read from
   * @param tag should be {@code 0x7D}
   * @param length the length of the BIT
   * @param index index of the template
   *
   * @throws IOException on failure
   */
  private void readStaticallyProtectedBIT(InputStream inputStream, int tag, int length, int index) throws IOException {
    TLVInputStream tlvBHTIn = new TLVInputStream(new ByteArrayInputStream(decodeSMTValue(inputStream)));
    try {
      int headerTemplateTag = tlvBHTIn.readTag();
      int headerTemplateLength = tlvBHTIn.readLength();
      StandardBiometricHeader sbh = readBHT(tlvBHTIn, headerTemplateTag, headerTemplateLength, index);
      InputStream biometricDataBlockIn = new ByteArrayInputStream(decodeSMTValue(inputStream));
      readBiometricDataBlock(biometricDataBlockIn, sbh, index);
    } finally {
      tlvBHTIn.close();
    }
  } /* FIXME: return type??? */

  /**
   * Decodes a (protected) data object.
   * Encrypted payloads are not currently supported.
   *
   * @param inputStream the stream to read from
   *
   * @return the decoded value
   *
   * @throws IOException on error reading from the stream
   */
  private byte[] decodeSMTValue(InputStream inputStream) throws IOException {
    TLVInputStream tlvIn = inputStream instanceof TLVInputStream ? (TLVInputStream)inputStream : new TLVInputStream(inputStream);
    int doTag = tlvIn.readTag();
    int doLength = tlvIn.readLength();
    switch (doTag) {
      case SMT_DO_PV /* 0x81 */:
        /* NOTE: Plain value, just return whatever is in the payload */
        return tlvIn.readValue();
      case SMT_DO_CG /* 0x85 */:
        /* NOTE: content of payload is encrypted */
        throw new AccessControlException("Access denied. Biometric Information Template is statically protected.");
      case SMT_DO_CC /* 0x8E */:
        /* NOTE: payload contains a MAC */
        long skippedBytes = 0;
        while (skippedBytes < doLength) {
          skippedBytes += tlvIn.skip(doLength);
        }
        return null;
      case SMT_DO_DS /* 0x9E */:
        /* NOTE: payload contains a signature */
        skippedBytes = 0;
        while (skippedBytes < doLength) {
          skippedBytes += tlvIn.skip(doLength);
        }
        return null;
      default:
        LOGGER.info("Unsupported data object tag " + Integer.toHexString(doTag));
        return null;
    }
  }

  /**
   * Reads a biometric data block from an input stream.
   *
   * @param inputStream the stream to read from
   * @param sbh the niometric header that was already read
   * @param index the index of the biometric data block within the BIT group
   *
   * @return the biometric data block
   *
   * @throws IOException on error reading from the stream
   */
  private BiometricDataBlock readBiometricDataBlock(InputStream inputStream, StandardBiometricHeader sbh, int index) throws IOException {
    TLVInputStream tlvIn = inputStream instanceof TLVInputStream ? (TLVInputStream)inputStream : new TLVInputStream(inputStream);
    int bioDataBlockTag = tlvIn.readTag();
    if (bioDataBlockTag != BIOMETRIC_DATA_BLOCK_TAG /* 5F2E */ &&
        bioDataBlockTag != BIOMETRIC_DATA_BLOCK_CONSTRUCTED_TAG /* 7F2E */) {
      throw new IllegalArgumentException("Expected tag BIOMETRIC_DATA_BLOCK_TAG (" + Integer.toHexString(BIOMETRIC_DATA_BLOCK_TAG)
        + ") or BIOMETRIC_DATA_BLOCK_TAG_ALT (" + Integer.toHexString(BIOMETRIC_DATA_BLOCK_CONSTRUCTED_TAG)
        + "), found " + Integer.toHexString(bioDataBlockTag));
    }
    int length = tlvIn.readLength();
    return bdbDecoder.decode(inputStream, sbh, index, length);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy