Package org.kapott.hbci.smartcardio

Source Code of org.kapott.hbci.smartcardio.HBCICardService

/*  $Id: HBCICardService.java,v 1.1 2011/11/24 21:59:37 willuhn Exp $

    This file is part of HBCI4Java
    Copyright (C) 2001-2008  Stefan Palme

    HBCI4Java 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 2 of the License, or
    (at your option) any later version.

    HBCI4Java 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, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package org.kapott.hbci.smartcardio;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;

import org.kapott.hbci.exceptions.HBCI_Exception;
import org.kapott.hbci.manager.HBCIUtils;

/**
* HBCI-Cardservice fuer den DDVPCSC-Passport, basierend auf dem OCF-Code
* aus HBCI4Java 2.5.8.
*/
public abstract class HBCICardService
{
  private final static Map<String,String> statusCodes = new HashMap<String,String>();
  static
  {
    // Siehe http://de.wikipedia.org/wiki/Application_Protocol_Data_Unit
    statusCodes.put("6281","Die zur�ckgegebenen Daten k�nnen fehlerhaft sein");
    statusCodes.put("6282","Da das Dateiende vorher erreicht wurde, konnten nur weniger als Le Bytes gelesen werden");
    statusCodes.put("6283","Die ausgew�hlte Datei ist gesperrt");
    statusCodes.put("6284","Die File Control Information (FCI) ist nicht ISO 7816-4 konform");
   
    statusCodes.put("6381","File filled up by the last write");
   
    statusCodes.put("6581","Speicherfehler");
   
    statusCodes.put("6700","L�nge (Lc oder Le) falsch");

    statusCodes.put("6800","Funktionen im Class Byte werden nicht unterst�tzt");
    statusCodes.put("6881","Logische Kan�le werden nicht unterst�tzt");
    statusCodes.put("6882","Secure Messaging wird nicht unterst�tzt");
   
    statusCodes.put("6900","Kommando nicht erlaubt");
    statusCodes.put("6981","Kommando inkompatibel zur Dateistruktur");
    statusCodes.put("6982","Sicherheitszustand nicht erf�llt");
    statusCodes.put("6983","Authentisierungsmethode ist gesperrt");
    statusCodes.put("6984","Referenzierte Daten sind gesperrt");
    statusCodes.put("6985","Nutzungsbedingungen sind nicht erf�llt");
    statusCodes.put("6986","Kommando nicht erlaubt (kein EF selektiert)");
    statusCodes.put("6987","Erwartete Secure Messaging Objekte nicht gefunden");
    statusCodes.put("6988","Secure Messaging Datenobjekte sind inkorrekt");
   
    statusCodes.put("6A00","Falsche Parameter P1/P2");
    statusCodes.put("6A80","Falsche Daten");
    statusCodes.put("6A81","Funktion wird nicht unterst�tzt");
    statusCodes.put("6A82","Datei wurde nicht gefunden");
    statusCodes.put("6A83","Record der Datei nicht gefunden");
    statusCodes.put("6A84","Nicht gen�gend Speicherplatz in der Datei");
    statusCodes.put("6A85","Lc nicht konsistent mit der TLV Struktur");
    statusCodes.put("6A86","Inkorrekte Parameter P1/P2");
    statusCodes.put("6A87","Lc inkonsistent mit P1/P2");
    statusCodes.put("6A88","Referenzierte Daten nicht gefunden");

    statusCodes.put("6B00","Parameter P1/P2 falsch");
    statusCodes.put("6D00","Das Kommando (INS) wird nicht unterst�tzt");
    statusCodes.put("6E00","Die Kommandoklasse (CLA) wird nicht unterst�tzt");
    statusCodes.put("6F00","Kommando wurde mit unbekanntem Fehler abgebrochen");
  }
 
  final static int HBCI_DDV_EF_ID            = 0x19;
  final static int HBCI_DDV_EF_BNK           = 0x1A;
  final static int HBCI_DDV_EF_MAC           = 0x1B;
  final static int HBCI_DDV_EF_SEQ           = 0x1C;
 
  final static int SECCOS_SELECT_RET_NOTHING = 0x0c;

  final static int SECCOS_CLA_EXT            = 0xb0;
  final static int SECCOS_CLA_SM_PROPR       = 0x04;
  final static int SECCOS_CLA_SM1            = 0x08;
  final static int SECCOS_CLA_STD            = 0x00;
 
  final static int SECCOS_INS_GET_CHALLENGE  = 0x84;
  final static int SECCOS_INS_GET_KEYINFO    = 0xee;
  final static int SECCOS_INS_INT_AUTH       = 0x88;
  final static int SECCOS_INS_PUT_DATA       = 0xda;
  final static int SECCOS_INS_READ_RECORD    = 0xb2;
  final static int SECCOS_INS_SELECT_FILE    = 0xa4;
  final static int SECCOS_INS_VERIFY         = 0x20;
  final static int SECCOS_INS_UPDATE_RECORD  = 0xdc;
  final static int SECCOS_INS_WRITE_RECORD   = 0xd2;
 
  final static int SECCOS_KEY_TYPE_DF = 0x80;
  final static int SECCOS_PWD_TYPE_DF = 0x80;
 
  final static byte SECCOS_SM_CRT_CC        = (byte) 0xb4;
  final static byte SECCOS_SM_REF_INIT_DATA = (byte) 0x87;
  final static byte SECCOS_SM_RESP_DESCR    = (byte) 0xba;
  final static byte SECCOS_SM_VALUE_LE      = (byte) 0x96;
 
  private final static String[] FEATURES = new String[]
  {
    "NO_FEATURE",
    "FEATURE_VERIFY_PIN_START",
    "FEATURE_VERIFY_PIN_FINISH",
    "FEATURE_MODIFY_PIN_START",
    "FEATURE_MODIFY_PIN_FINISH",
    "FEATURE_GET_KEY_PRESSED",
    "FEATURE_VERIFY_PIN_DIRECT",
    "FEATURE_MODIFY_PIN_DIRECT",
    "FEATURE_MCT_READER_DIRECT",
    "FEATURE_MCT_UNIVERSAL",
    "FEATURE_IFD_PIN_PROPERTIES",
    "FEATURE_ABORT",
    "FEATURE_SET_SPE_MESSAGE",
    "FEATURE_VERIFY_PIN_DIRECT_APP_ID",
    "FEATURE_MODIFY_PIN_DIRECT_APP_ID",
    "FEATURE_WRITE_DISPLAY",
    "FEATURE_GET_KEY",
    "FEATURE_IFD_DISPLAY_PROPERTIES",
    "FEATURE_GET_TLV_PROPERTIES", // NEU
    "FEATURE_CCID_ESC_COMMAND" //NEU
  };

  final static Byte FEATURE_VERIFY_PIN_START   = new Byte((byte) 0x01);
  final static Byte FEATURE_VERIFY_PIN_FINISH  = new Byte((byte) 0x02);
  final static Byte FEATURE_GET_KEY_PRESSED    = new Byte((byte) 0x05);
  final static Byte FEATURE_VERIFY_PIN_DIRECT  = new Byte((byte) 0x06);
  final static Byte FEATURE_MCT_READER_DIRECT  = new Byte((byte) 0x08);
  final static Byte FEATURE_MCT_UNIVERSAL      = new Byte((byte) 0x09);
  final static Byte FEATURE_IFD_PIN_PROPERTIES = new Byte((byte) 0x0a);

  private final static int IOCTL_GET_FEATURE_REQUEST = SCARD_CTL_CODE(3400);

  private Map<Byte, Integer> features = new HashMap<Byte,Integer>();
  private Card smartCard = null;
 
  /**
   * Ermittelt den Namen der Funktion zum Abrufen der Features aus der Karte.
   * @param code der Code.
   * @return der Funktions-Code.
   */
  private static int SCARD_CTL_CODE(int code)
  {
    if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1)
      return (0x31 << 16 | (code) << 2);
    return 0x42000000 + code;
  }
 
  /**
   * Initialisiert den Service mit der angegebenen Karte.
   * @param card die Karte.
   */
  public void init(Card card)
  {
    this.smartCard = card;
   
    // Liste der Features abrufen
    try
    {
      HBCIUtils.log("querying features",HBCIUtils.LOG_INFO);
      byte[] response = this.smartCard.transmitControlCommand(IOCTL_GET_FEATURE_REQUEST, new byte[0]);
      for (int i = 0; i < response.length; i += 6)
      {
        Byte feature = new Byte(response[i]);
        Integer ioctl = new Integer((0xff & response[i + 2]) << 24)
                                 | ((0xff & response[i + 3]) << 16)
                                 | ((0xff & response[i + 4]) << 8)
                                 | (0xff & response[i + 5]);
       
        String name = null;
        try
        {
          name = FEATURES[feature.intValue()];
        }
        catch (ArrayIndexOutOfBoundsException e)
        {
          name = "FEATURE_UNKNOWN";
        }
        HBCIUtils.log("  " +  name + ": " + Integer.toHexString(ioctl.intValue()),HBCIUtils.LOG_INFO);
        features.put(feature, ioctl);
      }     
    }
    catch (Exception e)
    {
      throw new HBCI_Exception(e);
    }
  }
 
  public Map<Byte, Integer> getFeatures() {
      return Collections.unmodifiableMap(features);
  }
 
  /**
   * Prueft die PIN via Kartenleser.
   * @param pwdId PIN-ID.
   */
  public void verifyHardPIN(int pwdId)
  {
    try
    {
      byte[] response = this.smartCard.transmitControlCommand(features.get(FEATURE_VERIFY_PIN_DIRECT),this.createPINVerificationDataStructure(pwdId));
     
      ResponseAPDU apdu = new ResponseAPDU(response);

      //////////////////////////////////////////////////////////////////////////
      // Extra Checks
      int sw = apdu.getSW();
      if (sw == 0x63c0) throw new HBCI_Exception("PIN falsch. Noch 1 Versuch");
      if (sw == 0x63c1) throw new HBCI_Exception("PIN falsch. Noch 2 Versuche");
      if (sw == 0x63c2) throw new HBCI_Exception("PIN falsch. Noch 3 Versuche");
      if (sw == 0x6400) throw new HBCI_Exception("PIN-Eingabe aufgrund Timeout abgebrochen");
      if (sw == 0x6401) throw new HBCI_Exception("PIN-Eingabe vom User abgebrochen");
      if (sw == 0x6983) throw new HBCI_Exception("Chipkarte ist gesperrt oder besitzt ein unbekanntes Format");
      //
      //////////////////////////////////////////////////////////////////////////
     
      //////////////////////////////////////////////////////////////////////////
      // Standard-Checks
      this.check(apdu,new byte[]{(byte)0x90});
      //
      //////////////////////////////////////////////////////////////////////////
    }
    catch (HBCI_Exception he)
    {
      throw he;
    }
    catch (Exception e)
    {
      throw new HBCI_Exception(e);
    }
  }
 
  /**
   * Prueft die PIN via Software.
   * @param pwdId die PIN-ID.
   * @param softPin die PIN.
   */
  public void verifySoftPIN(int pwdId, byte[] softPin)
  {
    byte[] body = new byte[] {(byte)0x25, (byte)0xff, (byte)0xff, (byte)0xff,
                              (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff};
 
    // pin bcd-kodiert in pin-2-block schreiben
    for (int i=0;i<softPin.length;i++)
    {
      body[1+(i>>1)]&=(byte)(((0x0F)<<(4*(i&1)))&0xFF);
      body[1+(i>>1)]|=(byte)(((softPin[i]-(byte)0x30) << (4-(4*(i&1))))&0xFF);
    }
    CommandAPDU command=new CommandAPDU(SECCOS_CLA_STD, SECCOS_INS_VERIFY,
                                        (byte)0x00, (byte)(SECCOS_PWD_TYPE_DF|pwdId),
                                        body);
    send(command);
  }
 
  protected void writeRecordBySFI(int sfi, int idx, byte[] data)
  {
    CommandAPDU command=new CommandAPDU(SECCOS_CLA_STD, SECCOS_INS_WRITE_RECORD,
                                        (byte)(idx+1), (byte)((sfi<<3)|0x04),
                                        data);
    send(command);
  }
 
  protected void updateRecordBySFI(int sfi, int idx, byte[] data)
  {
    CommandAPDU command=new CommandAPDU(SECCOS_CLA_STD, SECCOS_INS_UPDATE_RECORD,
                                        (idx+1),((sfi<<3)|0x04),data);
    send(command);
  }
 
  protected byte[] readRecordBySFI(int sfi, int idx)
  {
    CommandAPDU command = new CommandAPDU(SECCOS_CLA_STD, SECCOS_INS_READ_RECORD,
                                         (idx+1),((sfi<<3)|0x04),256);
    return receive(command);
  }
 
  protected byte[] readRecord(int idx)
  {
      return readRecordBySFI(0x00, idx);
  }
 
  protected void selectSubFile(int id)
  {
    CommandAPDU command=new CommandAPDU(SECCOS_CLA_STD, SECCOS_INS_SELECT_FILE,
                                        0x02, SECCOS_SELECT_RET_NOTHING,
                                        new byte[] {(byte)((id>>8)&0xFF), (byte)(id&0xFF)});
    send(command);
  }
 
  protected byte[] getKeyInfo(int idx)
  {
    CommandAPDU command=new CommandAPDU(SECCOS_CLA_EXT, SECCOS_INS_GET_KEYINFO,
                                        SECCOS_KEY_TYPE_DF,(idx+1),256);
    return receive(command);
  }
 
  protected void putData(int tag,byte[] data)
  {
    CommandAPDU command=new CommandAPDU(SECCOS_CLA_STD, SECCOS_INS_PUT_DATA,
                                        (byte)((tag>>8)&0xFF), (byte)(tag&0xFF),
                                        data);
    send(command);
  }
 
  protected byte[] getChallenge()
  {
    CommandAPDU command=new CommandAPDU(SECCOS_CLA_STD, SECCOS_INS_GET_CHALLENGE,
                                        0x00,0x00,8);
    return receive(command);
  }
 
  protected byte[] internalAuthenticate(int keynum, byte[] challenge)
  {
    CommandAPDU command=new CommandAPDU(SECCOS_CLA_STD, SECCOS_INS_INT_AUTH,
                                        0x00,(SECCOS_KEY_TYPE_DF|keynum),
                                        challenge,8);
    return receive(command);
  }
 
  /**
   * Sendet ein Kommando an den Kartenleser und prueft, ob es erfolgreich ausgefuehrt wurde.
   * @param command das Kommando.
   */
  void send(CommandAPDU command)
  {
    // 0x90: Alles OK
    // 0x61: Warnung - im Response sind noch Bytes verfuegbar. Da wir das Response
    //       hier aber eh nicht brauchen, koennen wir das tolerieren
    _receive(command,new byte[]{(byte)0x90,(byte)0x61});
  }

  /**
   * Sendet ein Kommando an den Kartenleser, prueft ob es erfolgreich
   * ausgefuehrt wurde und liefert die Antwort zurueck.
   * @param command das Kommando.
   * @return die Antwort.
   */
  byte[] receive(CommandAPDU command)
  {
    return _receive(command,new byte[]{(byte)0x90});
  }

  /**
   * Sendet ein Kommando an den Kartenleser, prueft ob es erfolgreich
   * ausgefuehrt wurde und liefert die Antwort zurueck.
   * @param command das Kommando.
   * @param returncodes zulaessige Return-Codes.
   * @return die Antwort.
   */
  private byte[] _receive(CommandAPDU command, byte[] returncodes)
  {
    try
    {
      //////////////////////////////////////////////////////////////////////////
      // Aufrufer ermitteln
      String caller = "";
      try
      {
        StackTraceElement[] stack = Thread.currentThread().getStackTrace();
        caller = stack[3].getMethodName();
      } catch (Exception e) {} // ignore
      //////////////////////////////////////////////////////////////////////////
     

      CardChannel channel = this.smartCard.getBasicChannel();
      ResponseAPDU response = channel.transmit(command);
     
      // Command und Response loggen
      HBCIUtils.log(caller + " command : " + toHex(command.getBytes()),HBCIUtils.LOG_DEBUG);
      HBCIUtils.log(caller + " response: " + toHex(response.getBytes()),HBCIUtils.LOG_DEBUG);

      this.check(response,returncodes);
      return response.getData();
    }
    catch (HBCI_Exception e1)
    {
      throw e1;
    }
    catch (Exception e2)
    {
      throw new HBCI_Exception(e2);
    }
  }
 
  /**
   * Prueft das Response auf die angegebenen Return-Codes.
   * @param response das Response.
   * @param returncodes zulaessige Return-Codes.
   */
  private void check(ResponseAPDU response, byte[] returncodes)
  {
    // Return-Code pruefen
    byte sw1 = (byte) response.getSW1();
    for (byte b:returncodes)
    {
      if (sw1 == b) // Statuscode gefunden
        return;
    }

    // Checken, ob wir einen Fehlertext haben
    String code = Integer.toHexString(response.getSW()).toUpperCase();
    String msg = statusCodes.get(code);
    if (msg != null)
      throw new HBCI_Exception("Fehler " + code + ": " + msg);
   
    // Ne, dann halt so
    throw new HBCI_Exception("Fehler " + code + " bei Kartenleser-Zugriff");
  }

  /**
   * Konvertiert die Bytes in HEX-Darstellung.
   * @param bytes
   * @return String-Repraesentation.
   */
  private String toHex(byte[] bytes)
  {
    StringBuffer sb = new StringBuffer();
    for (byte b:bytes)
    {
      String s = Integer.toHexString(b & 0xff).toUpperCase();
      if (s.length() == 1)
        sb.append("0");
      sb.append(s);
      sb.append(" ");
    }
    return sb.toString();
  }

  /**
   * Fuellt den String rechtsbuendig mit Leerzeichen auf die angegebene Laenge.
   * @param st der String.
   * @param len die Gesamtlaenge.
   * @return der codierte String mit Leerzeichen auf der rechten Seite.
   */
  protected byte[] expand(String st,int len)
  {
    try {
      StringBuffer st_new=new StringBuffer(st);
      for (int i=st.length();i<len;i++) {
          st_new.append(" ");
      }
      return st_new.toString().getBytes("ISO-8859-1");
    }
    catch (HBCI_Exception e1)
    {
      throw e1;
    }
    catch (Exception e2)
    {
      throw new HBCI_Exception(e2);
    }
  }
 
  /**
   * Erzeugt das PIN-Check-Kommando.
   * @return
   * @throws IOException
   */
  protected byte[] createPINVerificationDataStructure(int pwdId) throws IOException
  {
    ByteArrayOutputStream verifyCommand = new ByteArrayOutputStream();
    verifyCommand.write(0x0f); // bTimeOut
    verifyCommand.write(0x05); // bTimeOut2
    verifyCommand.write(0x89); // bmFormatString
    verifyCommand.write(0x07); // bmPINBlockString
    verifyCommand.write(0x10); // bmPINLengthFormat
    verifyCommand.write(new byte[] {(byte) 8,(byte) 4}); // PIN size (max/min), volker: 12,4=>8,4
    verifyCommand.write(0x02); // bEntryValidationCondition
    verifyCommand.write(0x01); // bNumberMessage
    verifyCommand.write(new byte[] { 0x04, 0x09 }); // wLangId, volker: 13,8=>4,9
    verifyCommand.write(0x00); // bMsgIndex
    verifyCommand.write(new byte[] { 0x00, 0x00, 0x00 }); // bTeoPrologue
    byte[] verifyApdu = new byte[] {
        SECCOS_CLA_STD, // CLA
        SECCOS_INS_VERIFY, // INS
        0x00, // P1
        (byte) (SECCOS_PWD_TYPE_DF|pwdId), // P2 volker: 01=>81
        0x08, // Lc = 8 bytes in command data
        (byte) 0x25, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,//volker:0x20=>0x25
        (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF };
    verifyCommand.write(verifyApdu.length & 0xff); // ulDataLength[0]
    verifyCommand.write(0x00); // ulDataLength[1]
    verifyCommand.write(0x00); // ulDataLength[2]
    verifyCommand.write(0x00); // ulDataLength[3]
    verifyCommand.write(verifyApdu); // abData
    return verifyCommand.toByteArray();
  }
}
TOP

Related Classes of org.kapott.hbci.smartcardio.HBCICardService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.