/* $Id: HBCIPassportDDV.java,v 1.1 2011/05/04 22:37:43 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.passport; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.StreamCorruptedException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.Arrays; import java.util.Properties; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESedeKeySpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEParameterSpec; import org.kapott.hbci.callback.HBCICallback; import org.kapott.hbci.exceptions.HBCI_Exception; import org.kapott.hbci.exceptions.InvalidPassphraseException; import org.kapott.hbci.manager.HBCIKey; import org.kapott.hbci.manager.HBCIUtils; import org.kapott.hbci.manager.HBCIUtilsInternal; import org.kapott.hbci.manager.LogFilter; /** <p>Passport-Klasse f�r Sicherheitsverfahren DDV mit Medium Chipkarte. Bei dieser Variante gibt die Bank eine Chipkarte aus, auf der die Zugangsdaten des Nutzers f�r den HBCI-Zugang gespeichert sind. Au�erdem befinden sich auf der Karte die (symmetrischen) Schl�ssel f�r die Erzeugung der Signaturen und f�r die Verschl�sselung der Nachrichten.</p><p> Diese Klasse unterst�tzt DDV-Chipkarten vom Typ 0 und 1. Auf einer DDV-Chipkarte k�nnen prinzipiell bis zu f�nf HBCI-Zugangsdatens�tze (f�r unterschiedliche Banken) gespeichert werden. Diese Klasse erm�glicht die Benutzung eines beliebigen dieser Datens�tze. Das hat aber in der Praxis kaum Relevanz, weil dann alle HBCI-Zug�nge die gleichen kryptografischen Schl�ssel benutzen m�ssten (es gibt nur ein Schl�sselpaar pro Chipkarte). F�r Chipkarten, die von Betreibern f�r HBCI-Testzug�ngen ausgegeben werden, ist diese Option jedoch n�tzlich, da hier h�ufig tats�chlich mehrere Zug�nge existieren und diese Zugangsdaten auf einer einzigen Chipkarte gespeichert werden k�nnen.</p><p> Prinzipiell ben�tigt diese Passport-Variante also keine zus�tzliche Schl�sseldatei, da alle <em>ben�tigten</em> HBCI-Daten auf der Chipkarte gespeichert sind. Dennoch verwendet diese Klasse eine zus�tzliche Datei. In dieser werden u.a. die zuletzt empfangenen BPD und UPD sowie die zuletzt benutzte HBCI-Version gespeichert, um beim n�chsten Benutzen dieses HBCI-Zuganges diese Daten nicht erneut abfragen zu m�ssen. Diese zus�tzliche Datei wird automatisch angelegt, der Dateiname setzt sich aus einem definierbaren Prefix (Pfad) und der Seriennummer der Chipkarte zusammen.</p>*/ public class HBCIPassportDDV extends AbstractDDVPassport { private String paramHeader; private String filename; private HBCIKey[] keys; private int comport; private int ctnumber; private String cardid; private boolean pinEntered; private int useBio; private int useSoftPin; private byte[] softPin; private SecretKey passportKey; private int entryIdx; protected native void initCT(); protected native void ctReadBankData(); protected native void ctReadKeyData(); protected native void ctEnterPIN(); protected native void ctSaveBankData(); protected native void ctSaveSigId(); protected native byte[] ctSign(byte[] data); protected native byte[][] ctEncrypt(); protected native byte[] ctDecrypt(byte[] cryptedKey); protected native void closeCT(); protected final static byte[] CIPHER_SALT={(byte)0x56,(byte)0xbc,(byte)0x1c,(byte)0x88, (byte)0x1f,(byte)0xe3,(byte)0x73,(byte)0xcc}; protected final static int CIPHER_ITERATIONS=987; public HBCIPassportDDV(Object init,int dummy) { super(init); setParamHeader("client.passport.DDV"); keys=new HBCIKey[2]; for (int i=0;i<2;i++) { keys[i]=null; } } public HBCIPassportDDV(Object init) { this(init,0); // get ddv-parameters String path=HBCIUtils.getParam(paramHeader+".path","./"); // set parameters for initializing card int comport=Integer.parseInt(HBCIUtils.getParam(paramHeader+".port","0")); int ctnumber=Integer.parseInt(HBCIUtils.getParam(paramHeader+".ctnumber","0")); String ddvLib=HBCIUtils.getParam(paramHeader+".libname.ddv"); if (ddvLib==null) { throw new NullPointerException(paramHeader+".libname.ddv must not be null"); } setComPort(comport); setCTNumber(ctnumber); setUseBio(Integer.parseInt(HBCIUtils.getParam(paramHeader+".usebio","-1"))); setUseSoftPin(Integer.parseInt(HBCIUtils.getParam(paramHeader+".softpin","-1"))); setSoftPin(new byte[0]); setPINEntered(false); setEntryIdx(Integer.parseInt(HBCIUtils.getParam(paramHeader+".entryidx","1"))); HBCIUtils.log("trying to load native DDV library "+ddvLib,HBCIUtils.LOG_DEBUG); System.load(ddvLib); // init card HBCIUtils.log("using chipcard terminal with port "+comport+" and terminal number "+ctnumber, HBCIUtils.LOG_DEBUG); try { HBCIUtilsInternal.getCallback().callback(this, HBCICallback.NEED_CHIPCARD, HBCIUtilsInternal.getLocMsg("CALLB_NEED_CHIPCARD"), HBCICallback.TYPE_NONE, null); initCT(); } catch (Exception e) { try { closeCT(); } catch (Exception e2) { HBCIUtils.log(e2); } throw new HBCI_Exception(HBCIUtilsInternal.getLocMsg("EXCMSG_CTERR"),e); } finally { HBCIUtilsInternal.getCallback().callback(this, HBCICallback.HAVE_CHIPCARD, "", HBCICallback.TYPE_NONE, null); } // init basic bank data try { setPort(new Integer(3000)); setFilterType("None"); ctReadBankData(); if (askForMissingData(true,true,true,false,false,true,false)) saveBankData(); ctReadKeyData(); } catch (Exception e) { try { closeCT(); } catch (Exception e2) { HBCIUtils.log(e2); } throw new HBCI_Exception(HBCIUtilsInternal.getLocMsg("EXCMSG_PASSPORT_INSTDATAERR"),e); } setFileName(HBCIUtilsInternal.withCounter(path+getCardId(),getEntryIdx()-1)); HBCIUtils.log("loading passport data from file "+getFileName(),HBCIUtils.LOG_DEBUG); FileInputStream f=null; try { f=new FileInputStream(getFileName()); } catch (Exception e) { HBCIUtils.log("error while reading passport file",HBCIUtils.LOG_WARN); } if (f!=null) { ObjectInputStream o=null; try { f.close(); int retries=Integer.parseInt(HBCIUtils.getParam("client.retries.passphrase","3")); while (true) { // loop for entering the correct passphrase if (passportKey==null) passportKey=calculatePassportKey(FOR_LOAD); PBEParameterSpec paramspec=new PBEParameterSpec(CIPHER_SALT,CIPHER_ITERATIONS); Cipher cipher=Cipher.getInstance("PBEWithMD5AndDES"); cipher.init(Cipher.DECRYPT_MODE,passportKey,paramspec); o=null; try { o=new ObjectInputStream(new CipherInputStream(new FileInputStream(getFileName()),cipher)); } catch (StreamCorruptedException e) { passportKey=null; retries--; if (retries<=0) throw new InvalidPassphraseException(); } if (o!=null) break; } setBPD((Properties)(o.readObject())); setUPD((Properties)(o.readObject())); setHBCIVersion((String)o.readObject()); } catch (Exception e) { try { closeCT(); } catch (Exception e2) { HBCIUtils.log(e2); } throw new HBCI_Exception(HBCIUtilsInternal.getLocMsg("EXCMSG_PASSPORT_READERR"),e); } finally { try { if (o!=null) o.close(); } catch (Exception e) { HBCIUtils.log(e); } } } } /** * @see org.kapott.hbci.passport.HBCIPassportChipcard#getFileName() */ @Override public String getFileName() { return filename; } /** * @see org.kapott.hbci.passport.HBCIPassportChipcard#setFileName(java.lang.String) */ @Override public void setFileName(String filename) { this.filename=filename; } public void setComPort(int comport) { this.comport=comport; } public void setCTNumber(int ctnumber) { this.ctnumber=ctnumber; } /** Gibt zur�ck, welcher logische Port f�r die Kommunikation mit der Chipkarte benutzt wird. Dieser Wert wird vom CTAPI-Treiber des jeweils verwendeten Chipkartenterminals interpretiert. @return Schnittstellennummer, an der der Chipkartenleser angeschlossen ist*/ public int getComPort() { return comport; } /** Gibt die logische Nummer zur�ck, unter der der Chipkartenleser zu verwenden ist. Wird nur ein Chipkartenleser verwendet, so kann dieser Wert immer 0 sein. Bei gleichzeitiger Verwendung mehrerer Chipkartenleser sollten die einzelnen Leser hier unterschiedliche Werte zugewiesen bekommen. Dieser Wert wird vom CTAPI-Treiber benutzt, um die Chipkartenleser intern auseinander zu halten. @return logische Nummer des Chipkartenlesers */ public int getCTNumber() { return ctnumber; } /** Gibt zur�ck, ob zur PIN-Eingabe am Chipkartenterminal das Biometric-Interface verwendet werden soll. Diese Funktion steht zur Zeit nur f�r Reiner-SCT- Chipkartenterminals zur Verf�gung. @return <code>1</code>, wenn die Biometrie-Einheit des Chipkartenterminals f�r die PIN-Eingabe benutzt werden soll; <code>0</code>, wenn die Biometrie-Einheit nicht benutzt werden soll, oder <code>-1</code>, wenn die Verwendung der Biometrie-Einheit automatisch erkannt werden soll.*/ public int getUseBio() { return useBio; } public void setUseBio(int useBio) { this.useBio=useBio; } /** Gibt zur�ck, ob die PIN-Eingabe f�r die Chipkarte �ber das Keypad des Chipkartenterminals oder �ber die PC-Tastatur erfolgen soll. Dieser Wert wird benutzt, um die PIN-Eingabe sowohl bei Klasse-2-Lesern mit eigener Tastatur wir auch f�r Klasse-1-Leser ohne separate Tastatur zu erm�glichen. @return PIN-Eingabe �ber welche Tastatur <ul> <li>=0 PIN-Eingabe zwingend �ber Terminal-Keypad</li> <li>=1 PIN-Eingabe zwingend �ber PC-Tastatur</li> <li>=-1 automatische Erkennung, ob bevorzugtes Chipkarten-Terminal-Keypad verf�gbar ist</li> </ul>*/ public int getUseSoftPin() { return useSoftPin; } public void setUseSoftPin(int useSoftPin) { this.useSoftPin=useSoftPin; } public byte[] getSoftPin() { return softPin; } public void setSoftPin(byte[] softPin) { LogFilter.getInstance().addSecretData(new String(softPin),"X",LogFilter.FILTER_SECRETS); this.softPin=softPin; } public void setEntryIdx(int idx) { this.entryIdx=idx; } /** Gibt die Indexnummer des Datensatzes zur�ck, dessen Inhalt als HBCI-Account-Informationen benutzt werden sollen. Auf einer Chipkarte k�nnen bis zu f�nf Zugangsdatens�tze gespeichert sein, dieser Wert enth�lt die Nummer des benutzten Eintrages (von 1-5). Normalerweise wird der Eintrag Nummer 1 (welcher auch meist der einzige Eintrag ist) verwendet. @return Indexnummer des verwendeten Account-Datensatzes */ public int getEntryIdx() { return entryIdx; } public void setCardId(String cardid) { this.cardid=cardid; } /** Gibt eine 16-stellige Identifikationsnummer f�r die verwendete Chipkarte zur�ck @return Chipkarten-Identifikationsnummer */ public String getCardId() { return cardid; } public boolean isSupported() { boolean ret=false; if (getBPD()!=null) { String[][] methods=getSuppSecMethods(); for (int i=0;i<methods.length;i++) { if (methods[i][0].equals("DDV")) { ret=true; break; } } } else { ret=true; } return ret; } private HBCIKey getKey(int i) { return keys[i]; } public HBCIKey getInstSigKey() { return getKey(0); } public String getInstSigKeyName() { return getInstSigKey()!=null?getInstSigKey().userid:null; } public String getInstSigKeyNum() { return getInstSigKey()!=null?getInstSigKey().num:null; } public String getInstSigKeyVersion() { return getInstSigKey()!=null?getInstSigKey().version:null; } public HBCIKey getInstEncKey() { return getKey(1); } public String getInstEncKeyName() { return getInstEncKey()!=null?getInstEncKey().userid:null; } public String getInstEncKeyNum() { return getInstEncKey()!=null?getInstEncKey().num:null; } public String getInstEncKeyVersion() { return getInstEncKey()!=null?getInstEncKey().version:null; } public HBCIKey getMyPublicSigKey() { return getInstSigKey(); } public HBCIKey getMyPublicEncKey() { return getInstEncKey(); } public HBCIKey getMyPublicDigKey() { return null; } public HBCIKey getMyPrivateSigKey() { return getMyPublicSigKey(); } public HBCIKey getMyPrivateEncKey() { return getMyPublicEncKey(); } public HBCIKey getMyPrivateDigKey() { return getMyPublicDigKey(); } public String getMySigKeyName() { return getInstSigKeyName(); } public String getMySigKeyNum() { return getInstSigKeyNum(); } public String getMySigKeyVersion() { return getInstSigKeyVersion(); } public String getMyEncKeyName() { return getInstEncKeyName(); } public String getMyEncKeyNum() { return getInstEncKeyNum(); } public String getMyEncKeyVersion() { return getInstEncKeyVersion(); } private void setKey(int i,HBCIKey key) { // System.out.println("passportDDV: setting key "+i+" to "+(key==null?"null":key.country+":"+key.blz+":"+key.cid+":"+key.num+":"+key.version)); keys[i]=key; } public void setInstSigKey(HBCIKey key) { setKey(0,key); } public void setInstEncKey(HBCIKey key) { setKey(1,key); } public void setMyPublicDigKey(HBCIKey key) { } public void setMyPrivateDigKey(HBCIKey key) { } public void setMyPublicSigKey(HBCIKey key) { } public void setMyPrivateSigKey(HBCIKey key) { } public void setMyPublicEncKey(HBCIKey key) { } public void setMyPrivateEncKey(HBCIKey key) { } private void checkPIN() { try { if (!pinEntered) { if (useSoftPin==1) { String pin=HBCIUtils.getParam(paramHeader+".pin"); if (pin==null || pin.length()==0) { StringBuffer temppin=new StringBuffer(); HBCIUtilsInternal.getCallback().callback(this, HBCICallback.NEED_SOFTPIN, HBCIUtilsInternal.getLocMsg("CALLB_NEED_SOFTPIN"), HBCICallback.TYPE_SECRET, temppin); if (temppin.length()==0) throw new HBCI_Exception(HBCIUtilsInternal.getLocMsg("EXCMSG_PINZERO")); pin=temppin.toString(); LogFilter.getInstance().addSecretData(pin,"X",LogFilter.FILTER_SECRETS); } setSoftPin(pin.getBytes("ISO-8859-1")); } else { HBCIUtilsInternal.getCallback().callback(this, HBCICallback.NEED_HARDPIN, HBCIUtilsInternal.getLocMsg("CALLB_NEED_HARDPIN"), HBCICallback.TYPE_NONE, null); } try { ctEnterPIN(); pinEntered=true; } catch (Exception e) { HBCIUtils.setParam(paramHeader+".pin",null); setSoftPin(new byte[0]); } finally { if (useSoftPin!=1) { HBCIUtilsInternal.getCallback().callback(this, HBCICallback.HAVE_HARDPIN, null, HBCICallback.TYPE_NONE, null); } } } } catch (Exception e) { throw new HBCI_Exception(HBCIUtilsInternal.getLocMsg("EXCMSG_PINERR"),e); } } /** * @see org.kapott.hbci.passport.HBCIPassportChipcard#saveBankData() */ @Override public void saveBankData() { try { checkPIN(); ctSaveBankData(); } catch (Exception e) { throw new HBCI_Exception(HBCIUtilsInternal.getLocMsg("EXCMSG_PASSPORT_INSTSAVEERR"),e); } } /** * @see org.kapott.hbci.passport.HBCIPassportInternal#resetPassphrase() */ public void resetPassphrase() { passportKey=null; } /** * @see org.kapott.hbci.passport.HBCIPassport#saveChanges() */ public void saveChanges() { try { checkPIN(); ctSaveSigId(); if (passportKey==null) passportKey=calculatePassportKey(FOR_SAVE); File passportfile=new File(getFileName()); File directory=passportfile.getAbsoluteFile().getParentFile(); String prefix=passportfile.getName()+"_"; File tempfile=File.createTempFile(prefix,"",directory); PBEParameterSpec paramspec=new PBEParameterSpec(CIPHER_SALT,CIPHER_ITERATIONS); Cipher cipher=Cipher.getInstance("PBEWithMD5AndDES"); cipher.init(Cipher.ENCRYPT_MODE,passportKey,paramspec); ObjectOutputStream o=new ObjectOutputStream(new CipherOutputStream(new FileOutputStream(tempfile),cipher)); o.writeObject(getBPD()); o.writeObject(getUPD()); o.writeObject(getHBCIVersion()); o.close(); this.safeReplace(passportfile,tempfile); } catch (Exception e) { throw new HBCI_Exception(HBCIUtilsInternal.getLocMsg("EXCMSG_PASSPORT_WRITEERR"),e); } } public byte[] hash(byte[] data) { /* hash-before-sign creates a RMD-160 hash, which will encrypted by * the sign() method later */ MessageDigest dig; try { dig = MessageDigest.getInstance("RIPEMD160","CryptAlgs4Java"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (NoSuchProviderException e) { throw new RuntimeException(e); } return dig.digest(data); } public byte[] sign(byte[] data) { /* data is hash value calculated by the hash() method */ checkPIN(); return ctSign(data); } public boolean verify(byte[] data,byte[] sig) { /* data is the hash value calculated by the hash() method */ checkPIN(); byte[] correctSig=ctSign(data); return Arrays.equals(sig,correctSig); } public byte[][] encrypt(byte[] plainMsg) { try { checkPIN(); byte[][] msgkeys=ctEncrypt(); // 0=plain,1=encrypted byte[] longKey=new byte[24]; int posi=msgkeys[0].length-16; System.arraycopy(msgkeys[0],posi,longKey,0,16); System.arraycopy(msgkeys[0],posi,longKey,16,8); DESedeKeySpec spec=new DESedeKeySpec(longKey); SecretKeyFactory fac=SecretKeyFactory.getInstance("DESede"); SecretKey key=fac.generateSecret(spec); // nachricht verschluesseln Cipher cipher=Cipher.getInstance("DESede/CBC/NoPadding"); byte[] ivarray=new byte[8]; Arrays.fill(ivarray,(byte)(0)); IvParameterSpec iv=new IvParameterSpec(ivarray); cipher.init(Cipher.ENCRYPT_MODE,key,iv); byte[] cryptedMsg=cipher.doFinal(plainMsg); byte[][] ret=new byte[2][]; ret[0]=msgkeys[1]; ret[1]=cryptedMsg; return ret; } catch (Exception ex) { throw new HBCI_Exception(HBCIUtilsInternal.getLocMsg("EXCMSG_CANTCRYPT"),ex); } } public byte[] decrypt(byte[] cryptedKey,byte[] cryptedMsg) { try { checkPIN(); byte[] plainKey=ctDecrypt(cryptedKey); byte[] longKey=new byte[24]; int posi=plainKey.length-16; System.arraycopy(plainKey,posi,longKey,0,16); System.arraycopy(plainKey,posi,longKey,16,8); DESedeKeySpec spec=new DESedeKeySpec(longKey); SecretKeyFactory fac=SecretKeyFactory.getInstance("DESede"); SecretKey key=fac.generateSecret(spec); // nachricht entschluesseln Cipher cipher=Cipher.getInstance("DESede/CBC/NoPadding"); byte[] ivarray=new byte[8]; Arrays.fill(ivarray,(byte)(0)); IvParameterSpec iv=new IvParameterSpec(ivarray); cipher.init(Cipher.DECRYPT_MODE,key,iv); return cipher.doFinal(cryptedMsg); } catch (Exception ex) { throw new HBCI_Exception(HBCIUtilsInternal.getLocMsg("EXCMSG_DECRYPTERR"),ex); } } public void setPINEntered(boolean pinEntered) { this.pinEntered=pinEntered; } public void close() { super.close(); passportKey=null; setPINEntered(false); closeCT(); } /** Gibt den Dateinamen der verwendeten CTAPI-Treiberbibliothek zur�ck. @return Dateiname der CTAPI-Bibliothek */ public String getLibName() { return HBCIUtils.getParam(paramHeader+".libname.ctapi"); } protected void setParamHeader(String p) { this.paramHeader=p; } protected String getParamHeader() { return this.paramHeader; } protected void setPassportKey(SecretKey key) { this.passportKey=key; } protected SecretKey getPassportKey() { return this.passportKey; } }