/* $Id: Sig.java,v 1.2 2012/03/27 21:33:13 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.security; import java.lang.reflect.Field; import java.util.Date; import java.util.Hashtable; import java.util.List; import java.util.Random; import org.kapott.hbci.comm.Comm; import org.kapott.hbci.exceptions.HBCI_Exception; import org.kapott.hbci.manager.HBCIUtils; import org.kapott.hbci.manager.HBCIUtilsInternal; import org.kapott.hbci.manager.IHandlerData; import org.kapott.hbci.manager.MsgGen; import org.kapott.hbci.passport.HBCIPassportInternal; import org.kapott.hbci.passport.HBCIPassportList; import org.kapott.hbci.protocol.MSG; import org.kapott.hbci.protocol.MultipleSEGs; import org.kapott.hbci.protocol.MultipleSyntaxElements; import org.kapott.hbci.protocol.SEG; import org.kapott.hbci.protocol.SyntaxElement; import org.kapott.hbci.protocol.factory.SEGFactory; import org.w3c.dom.Element; import org.w3c.dom.Node; public final class Sig { public final static String SECFUNC_HBCI_SIG_RDH="1"; public final static String SECFUNC_HBCI_SIG_DDV="2"; public final static String SECFUNC_FINTS_SIG_DIG="1"; public final static String SECFUNC_FINTS_SIG_SIG="2"; public final static String SECFUNC_SIG_PT_1STEP="999"; public final static String SECFUNC_SIG_PT_2STEP_MIN="900"; public final static String SECFUNC_SIG_PT_2STEP_MAX="997"; public final static String HASHALG_SHA1="1"; public final static String HASHALG_SHA256="3"; public final static String HASHALG_SHA384="4"; public final static String HASHALG_SHA512="5"; public final static String HASHALG_SHA256_SHA256="6"; public final static String HASHALG_RIPEMD160="999"; public final static String SIGALG_DES="1"; public final static String SIGALG_RSA="10"; public final static String SIGMODE_ISO9796_1="16"; public final static String SIGMODE_ISO9796_2="17"; public final static String SIGMODE_PKCS1="18"; public final static String SIGMODE_PSS="19"; public final static String SIGMODE_RETAIL_MAC="999"; private IHandlerData handlerdata; private MSG msg; private HBCIPassportList passports; private String u_secfunc; private String u_cid; private String u_role; private String u_range; private String u_keyblz; private String u_keycountry; private String u_keyuserid; private String u_keynum; private String u_keyversion; private String u_sysid; private String u_sigid; private String u_sigalg; private String u_sigmode; private String u_hashalg; private String sigstring; private void initData(IHandlerData handlerdata, MSG msg, HBCIPassportList passports) { this.msg = msg; this.handlerdata = handlerdata; this.passports = passports; } public Sig(IHandlerData handlerdata, MSG msg, HBCIPassportList passports) { initData(handlerdata,msg,passports); } public void init(IHandlerData handlerdata, MSG msg, HBCIPassportList passports) { initData(handlerdata,msg,passports); } // sighead-segment mit werten aus den lokalen variablen f�llen private void fillSigHead(SEG sighead) { String sigheadName = sighead.getPath(); String seccheckref = Integer.toString(Math.abs(new Random().nextInt())); Date d=new Date(); sighead.propagateValue(sigheadName + ".secfunc",u_secfunc, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".seccheckref", seccheckref, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); /* TODO: enable this later (when other range types are supported) sighead.propagateValue(sigheadName+".range",range,false); */ sighead.propagateValue(sigheadName + ".role", u_role, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName+".SecIdnDetails.func",(msg.getName().endsWith("Res")?"2":"1"), SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); if (u_cid.length()!=0) { // DDV sighead.propagateValue(sigheadName + ".SecIdnDetails.cid", "B"+u_cid, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); } else { // RDH und PinTan sighead.propagateValue(sigheadName + ".SecIdnDetails.sysid", u_sysid, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); } sighead.propagateValue(sigheadName + ".SecTimestamp.date", HBCIUtils.date2StringISO(d), SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".SecTimestamp.time", HBCIUtils.time2StringISO(d), SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".secref", u_sigid, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".HashAlg.alg",u_hashalg, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".SigAlg.alg", u_sigalg, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".SigAlg.mode", u_sigmode, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".KeyName.KIK.country", u_keycountry, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".KeyName.KIK.blz", u_keyblz, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".KeyName.userid", u_keyuserid, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".KeyName.keynum", u_keynum, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".KeyName.keyversion", u_keyversion, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".SecProfile.method", passports.getMainPassport().getProfileMethod(), SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); sighead.propagateValue(sigheadName + ".SecProfile.version", passports.getMainPassport().getProfileVersion(), SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); } // sigtail-segment mit werten aus den lokalen variablen f�llen private void fillSigTail(SEG sighead, SEG sigtail) { String sigtailName = sigtail.getPath(); sigtail.propagateValue(sigtailName + ".seccheckref", sighead.getValueOfDE(sighead.getPath() + ".seccheckref"), SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); } /* daten zusammensammeln, die signiert werden m�ssen; idx gibt dabei an, * die wievielte signatur erzeugt werden soll - wird ben�tigt, um festzustellen, * welche sighead- und sigtail-segmente in die signatur eingehen */ private String collectHashData(int idx) { int numOfPassports=passports.size(); StringBuffer ret=new StringBuffer(1024); List<MultipleSyntaxElements> msgelementslist = msg.getChildContainers(); List<SyntaxElement> sigheads = ((MultipleSEGs)(msgelementslist.get(1))).getElements(); List<SyntaxElement> sigtails = ((MultipleSEGs)(msgelementslist.get(msgelementslist.size() - 2))).getElements(); // alle ben�tigten sighead-segmente zusammensuchen for (int i=numOfPassports-1-idx; i<(u_range.equals("1")?(numOfPassports-idx):numOfPassports);i++) { ret.append(((SEG)(sigheads.get(i))).toString(0)); } // alle nutzdaten hinzuf�gen for (int i=2; i<msgelementslist.size()-2;i++) { ret.append(msgelementslist.get(i).toString(0)); } // bei schalen-modell-signaturen alle "inneren" sigtails mit hinzuf�gen for (int i=0;i<(u_range.equals("1")?0:idx);i++) { ret.append(((SEG)(sigtails.get(i))).toString(0)); } return ret.toString(); } private String collectHashData(int idx,int dummy) { // wird beim verifizieren benutzt, da msg.toString(0) u.u. // nicht den selben string erzeugt wie die eingehende nachricht MsgGen gen=handlerdata.getMsgGen(); String msgstring=gen.get("_origSignedMsg"); return msgstring.substring(msgstring.indexOf("HNSHK:2:"),msgstring.lastIndexOf("HNSHA:")); } public boolean signIt() { boolean ret=false; HBCIPassportInternal mainPassport=passports.getMainPassport(); if (mainPassport.hasMySigKey()) { String msgName = msg.getName(); MsgGen gen=handlerdata.getMsgGen(); Node msgNode = msg.getSyntaxDef(msgName, gen.getSyntax()); String dontsignAttr = ((Element)msgNode).getAttribute("dontsign"); if (dontsignAttr.length()==0) { try { int numOfPassports=passports.size(); // create an empty sighead and sigtail segment for each required signature for (int idx=0;idx<numOfPassports;idx++) { SEG sighead=SEGFactory.getInstance().createSEG("SigHeadUser","SigHead",msgName,numOfPassports-1-idx,gen.getSyntax()); SEG sigtail=SEGFactory.getInstance().createSEG("SigTailUser","SigTail",msgName,idx,gen.getSyntax()); List<MultipleSyntaxElements> msgelements=msg.getChildContainers(); List<SyntaxElement> sigheads=((MultipleSEGs)(msgelements.get(1))).getElements(); List<SyntaxElement> sigtails=((MultipleSEGs)(msgelements.get(msgelements.size()-2))).getElements(); // insert sighead segment in msg if ((numOfPassports-1-idx)<sigheads.size()) { SEGFactory.getInstance().unuseObject(sigheads.get(numOfPassports-1-idx)); } else { for (int i=sigheads.size()-1;i<numOfPassports-1-idx;i++) { sigheads.add(null); } } sigheads.set(numOfPassports-1-idx,sighead); // insert sigtail segment in message if (idx<sigtails.size()) { SEGFactory.getInstance().unuseObject(sigtails.get(idx)); } else { for (int i=sigtails.size()-1;i<idx;i++) { sigtails.add(null); } } sigtails.set(idx,sigtail); } // fill all sighead and sigtail segments for (int idx=0;idx<numOfPassports;idx++) { HBCIPassportInternal passport=passports.getPassport(idx); String role=passports.getRole(idx); setParam("secfunc",passport.getSigFunction()); setParam("cid",passport.getCID()); setParam("role",role); setParam("range","1"); setParam("keyblz",passport.getBLZ()); setParam("keycountry",passport.getCountry()); setParam("keyuserid",passport.getMySigKeyName()); setParam("keynum",passport.getMySigKeyNum()); setParam("keyversion",passport.getMySigKeyVersion()); setParam("sysid",passport.getSysId()); setParam("sigid",passport.getSigId().toString()); setParam("sigalg",passport.getSigAlg()); setParam("sigmode",passport.getSigMode()); setParam("hashalg",passport.getHashAlg()); passport.incSigId(); passport.saveChanges(); List<MultipleSyntaxElements> msgelements=msg.getChildContainers(); List<SyntaxElement> sigheads=((MultipleSEGs)(msgelements.get(1))).getElements(); List<SyntaxElement> sigtails=((MultipleSEGs)(msgelements.get(msgelements.size()-2))).getElements(); SEG sighead=(SEG)sigheads.get(numOfPassports-1-idx); SEG sigtail=(SEG)sigtails.get(idx); fillSigHead(sighead); fillSigTail(sighead,sigtail); } msg.enumerateSegs(0,SyntaxElement.ALLOW_OVERWRITE); msg.validate(); msg.enumerateSegs(1,SyntaxElement.ALLOW_OVERWRITE); // calculate signatures for each segment for (int idx=0;idx<numOfPassports;idx++) { HBCIPassportInternal passport=passports.getPassport(idx); List<MultipleSyntaxElements> msgelements=msg.getChildContainers(); List<SyntaxElement> sigtails=((MultipleSEGs)(msgelements.get(msgelements.size()-2))).getElements(); SEG sigtail=(SEG)sigtails.get(idx); /* first calculate hash-result, then sign the hashresult. In * most cases, the hash() step will be executed by the signature * algorithm, so the hash() call returns the message as-is. * Currently the only exception is PKCS#1-10, where an extra * round of hashing must be executed before applying the * signature process */ String hashdata=collectHashData(idx); byte[] hashresult=passport.hash(hashdata.getBytes(Comm.ENCODING)); byte[] signature=passport.sign(hashresult); if (passport.needUserSig()) { String pintan=new String(signature,Comm.ENCODING); int pos=pintan.indexOf("|"); if (pos!=-1) { // wenn �berhaupt eine signatur existiert // (wird f�r server ben�tigt) String pin=pintan.substring(0,pos); msg.propagateValue(sigtail.getPath()+".UserSig.pin",pin, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); if (pos<pintan.length()-1) { String tan=pintan.substring(pos+1); msg.propagateValue(sigtail.getPath()+".UserSig.tan",tan, SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); } } } else { // normale signatur msg.propagateValue(sigtail.getPath()+".sig","B"+new String(signature,Comm.ENCODING), SyntaxElement.DONT_TRY_TO_CREATE, SyntaxElement.DONT_ALLOW_OVERWRITE); } msg.validate(); msg.enumerateSegs(1,SyntaxElement.ALLOW_OVERWRITE); msg.autoSetMsgSize(gen); } } catch (Exception ex) { throw new HBCI_Exception("*** error while signing",ex); } } else HBCIUtils.log("did not sign - message does not want to be signed",HBCIUtils.LOG_DEBUG); ret=true; } else HBCIUtils.log("can not sign - no signature key available",HBCIUtils.LOG_WARN); return ret; } private void readSigHead() { HBCIPassportInternal mainPassport=passports.getMainPassport(); String sigheadName=msg.getName()+".SigHead"; u_secfunc=msg.getValueOfDE(sigheadName+".secfunc"); // TODO: das ist abgeschaltet, weil das Thema "Sicherheitsfunktion, kodiert" // ab FinTS-3 anders behandelt wird - siehe Spez. /* if (u_secfunc.equals("2")) { // DDV u_cid=msg.getValueOfDE(sigheadName+".SecIdnDetails.cid"); if (!u_cid.equals(mainPassport.getCID())) { String errmsg=HBCIUtilsInternal.getLocMsg("EXCMSG_CRYPTCIDFAIL"); if (!HBCIUtilsInternal.ignoreError(null,"client.errors.ignoreSignErrors",errmsg)) throw new HBCI_Exception(errmsg); } } else { // RDH und PinTan (= 2 und 999) try { // falls noch keine system-id ausgehandelt wurde, so sendet der // hbci-server auch keine... deshalb der try-catch-block u_sysid=msg.getValueOfDE(sigheadName+".SecIdnDetails.sysid"); } catch (Exception e) { u_sysid="0"; } } */ u_role = msg.getValueOfDE(sigheadName + ".role"); u_range = msg.getValueOfDE(sigheadName + ".range"); u_keycountry = msg.getValueOfDE(sigheadName + ".KeyName.KIK.country"); u_keyuserid = msg.getValueOfDE(sigheadName + ".KeyName.userid"); u_keynum = msg.getValueOfDE(sigheadName + ".KeyName.keynum"); u_keyversion = msg.getValueOfDE(sigheadName + ".KeyName.keyversion"); u_sigid = msg.getValueOfDE(sigheadName + ".secref"); u_sigalg = msg.getValueOfDE(sigheadName + ".SigAlg.alg"); u_sigmode = msg.getValueOfDE(sigheadName + ".SigAlg.mode"); u_hashalg = msg.getValueOfDE(sigheadName + ".HashAlg.alg"); // Die Angabe der BLZ ist nicht unbedingt verpflichtend (f�r 280 aber schon...). Trotzdem gibt es wohl // Banken die das nicht interessiert... try { u_keyblz = msg.getValueOfDE(sigheadName + ".KeyName.KIK.blz"); } catch (Exception e) { HBCIUtils.log("missing bank code in message signature, ignoring...",HBCIUtils.LOG_WARN); } if (mainPassport.needUserSig()) { // TODO: bei anderen user-signaturen hier allgemeineren code schreiben Hashtable<String,String> values=new Hashtable<String, String>(); msg.extractValues(values); String pin=values.get(msg.getName()+".SigTail.UserSig.pin"); String tan=values.get(msg.getName()+".SigTail.UserSig.tan"); sigstring=((pin!=null)?pin:"")+"|"+((tan!=null)?tan:""); } else { sigstring = msg.getValueOfDE(msg.getName() + ".SigTail.sig"); } String checkref=msg.getValueOfDE(msg.getName()+".SigHead.seccheckref"); String checkref2=msg.getValueOfDE(msg.getName()+".SigTail.seccheckref"); if (checkref==null || !checkref.equals(checkref2)) { String errmsg=HBCIUtilsInternal.getLocMsg("EXCMSG_SIGREFFAIL"); if (!HBCIUtilsInternal.ignoreError(null,"client.errors.ignoreSignErrors",errmsg)) throw new HBCI_Exception(errmsg); } // TODO: dieser test ist erst mal deaktiviert. grund: beim pin/tan-zwei- // schritt-verfahren ist die passport.getSigFunction()==922 (z.B.). // wenn jedoch zeitgleich HITAN �ber eine bankensignatur abgesichert // wird, steht in der antwort secfunc=1 (RDH) drin. /* if (!u_secfunc.equals(mainPassport.getSigFunction())) { String errmsg=HBCIUtilsInternal.getLocMsg("EXCMSG_SIGTYPEFAIL",new String[] {u_secfunc,mainPassport.getSigFunction()}); if (!HBCIUtilsInternal.ignoreError(null,"client.errors.ignoreSignErrors",errmsg)) throw new HBCI_Exception(errmsg); } */ // TODO: hier auch die DEG SecProfile lesen und �berpr�fen // TODO: diese checks werden vorerst abgeschaltet, damit die pin-tan sigs // ohne probleme funktionieren /* if (!u_sigalg.equals(passport.getSigAlg())) throw new HBCI_Exception(HBCIUtils.getLocMsg("EXCMSG_SIGALGFAIL",new String[] {u_sigalg,passport.getSigAlg()})); if (!u_sigmode.equals(passport.getSigMode())) throw new HBCI_Exception(HBCIUtils.getLocMsg("EXCMSG_SIGMODEFAIL",new String[] {u_sigmode,passport.getSigMode()})); if (!u_hashalg.equals(passport.getHashAlg())) throw new HBCI_Exception(HBCIUtils.getLocMsg("EXCMSG_SIGHASHFAIL",new String[] {u_hashalg,passport.getHashAlg()})); */ } private boolean hasSig() { boolean ret = true; MultipleSyntaxElements seglist = (msg.getChildContainers().get(1)); if (seglist instanceof MultipleSEGs) { SEG sighead = null; try { /* TODO: multiple signatures not supported until now */ sighead = (SEG)(seglist.getElements().get(0)); } catch (IndexOutOfBoundsException e) { ret = false; } if (ret) { String sigheadCode = "HNSHK"; MsgGen gen=handlerdata.getMsgGen(); if (!sighead.getCode(gen).equals(sigheadCode)) ret = false; } } else ret = false; return ret; } public boolean verify() { HBCIPassportInternal mainPassport=passports.getMainPassport(); boolean ret=false; if (mainPassport.hasInstSigKey()) { String msgName = msg.getName(); MsgGen gen=handlerdata.getMsgGen(); Node msgNode = msg.getSyntaxDef(msgName, gen.getSyntax()); String dontsignAttr = ((Element)msgNode).getAttribute("dontsign"); if (dontsignAttr.length()==0) { if (hasSig()) { readSigHead(); try { /* first calculate hash-result, then verify the hashresult. In * most cases, the hash() step will be executed by the signature * algorithm, so the hash() call returns the message as-is. * Currently the only exception is PKCS#1-10, where an extra * round of hashing must be executed before applying the * signature process */ String hashdata=collectHashData(0,0); byte[] hashresult=mainPassport.hash(hashdata.getBytes(Comm.ENCODING)); ret=mainPassport.verify(hashresult, sigstring.getBytes(Comm.ENCODING)); } catch (Exception e) { ret=false; } } else { HBCIUtils.log("message has no signature",HBCIUtils.LOG_WARN); /* das ist nur f�r den fall, dass das institut prinzipiell nicht signiert (also f�r den client-code); die verify()-funktion f�r den server-code �berpr�ft selbstst�ndig, ob tats�chlich eine ben�tigte signatur vorhanden ist (verl�sst sich also nicht auf dieses TRUE, was beim fehlen einer signatur zur�ckgegeben wird */ ret=true; } } else { HBCIUtils.log("message does not need a signature",HBCIUtils.LOG_DEBUG); ret=true; } } else { HBCIUtils.log("can not check signature - no signature key available",HBCIUtils.LOG_WARN); ret=true; } return ret; } public void setParam(String key, String value) { try { Field f=this.getClass().getDeclaredField("u_"+key); HBCIUtils.log("setting "+key+" to "+value,HBCIUtils.LOG_DEBUG); f.set(this,value); } catch (Exception ex) { throw new HBCI_Exception("*** error while setting sig parameter",ex); } } public void destroy() { handlerdata=null; msg=null; passports=null; sigstring=null; u_cid=null; u_hashalg=null; u_keyblz=null; u_keycountry=null; u_keynum=null; u_keyuserid=null; u_keyversion=null; u_range=null; u_role=null; u_secfunc=null; u_sigalg=null; u_sigid=null; u_sigmode=null; u_sysid=null; } }