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