Skip to content

Commit

Permalink
Erster Code für PushTAN Decoupled
Browse files Browse the repository at this point in the history
  • Loading branch information
willuhn committed Dec 21, 2023
1 parent 8614bc6 commit 555dde8
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 26 deletions.
5 changes: 5 additions & 0 deletions src/main/java/org/kapott/hbci/callback/HBCICallback.java
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,11 @@ werden, dann sollte die andere Variante (<code>None</code>) ausprobiert
**/
public final static int NEED_PT_QRTAN=34;

/**
* Ursache des Callback-Aufrufes: PushTAN 2.0 nötig.
**/
public final static int NEED_PT_DECOUPLED=35;

/** <p>Ursache des Callbacks: falsche PIN eingegeben */
public final static int WRONG_PIN=40;

Expand Down
18 changes: 15 additions & 3 deletions src/main/java/org/kapott/hbci/dialog/KnownTANProcess.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ public enum KnownTANProcess
* Prozess-Variante 2, Schritt 2.
*/
PROCESS2_STEP2("2"),


/**
* Prozess-Variante 2, Schritt S.
* Das ist das Abfragen des Auftragsstatus bei Decoupled-Verfahren.
*/
PROCESS2_STEPS("S"),

;

/**
Expand Down Expand Up @@ -133,12 +139,18 @@ public String getCode()
* @param step die Schritt-Nummer.
* @return der TAN-Prozess. Nie NULL sondern im Zweifel {@link KnownTANProcess#PROCESS2_STEP1}.
*/
public static KnownTANProcess get(Variant v, int step)
public static KnownTANProcess get(Variant v, String step)
{
// Hier gibts nur einen
if (v == Variant.V1)
return PROCESS1;

return step == 2 ? PROCESS2_STEP2 : PROCESS2_STEP1;
if (Objects.equals(step,"1"))
return PROCESS2_STEP1;

if (Objects.equals(step,"S"))
return PROCESS2_STEPS;

return PROCESS2_STEP2;
}
}
50 changes: 46 additions & 4 deletions src/main/java/org/kapott/hbci/manager/HHDVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ public enum HHDVersion
* Fallback.
*/
HHD_1_2(Type.CHIPTAN,null,null,-1,"hhd12"),


/**
* Push-TAN 2.0 (Decoupled)
*/
DECOUPLED(Type.DECOUPLED,"Decouple.*"),

;

/**
Expand All @@ -87,15 +92,33 @@ public static enum Type
/**
* QR-Code.
*/
QRCODE
QRCODE,

/**
* Push-TAN 2.0 (Decoupled)
*/
DECOUPLED

}

private Type type = null;
private String nameMatch = null;
private String idMatch = null;
private String versionStart = null;
private int segVersion = 0;
private String challengeVersion = null;


/**
* ct.
* @param type die Art des TAN-Verfahrens.
* @param nameMatch Pattern für DK-Verfahrensbezeichnung.
*/
private HHDVersion(Type type, String nameMatch)
{
this.type = type;
this.nameMatch = nameMatch;
}

/**
* ct.
* @param type die Art des TAN-Verfahrens.
Expand Down Expand Up @@ -141,6 +164,25 @@ public Type getType()
public static HHDVersion find(Properties secmech)
{
HBCIUtils.log("trying to determine HHD version for secmech: " + secmech,HBCIUtils.LOG_DEBUG);

// DK-TAN-Verfahren
String name = secmech.getProperty("zkamethod_name","");
if (name != null && name.length() > 0)
{
HBCIUtils.log(" DK name: " + name,HBCIUtils.LOG_DEBUG);
for (HHDVersion v:values())
{
String s = v.nameMatch;
if (s == null)
continue;
if (name.matches(s))
{
HBCIUtils.log(" identified as " + v,HBCIUtils.LOG_DEBUG);
return v;
}
}
}

// Das ist die "Technische Kennung"
// Siehe "Belegungsrichtlinien TANve1.4 mit Erratum 1-3 final version vom 2010-11-12.pdf"
// Der Name ist standardisiert, wenn er mit "HHD1...." beginnt, ist
Expand Down
53 changes: 36 additions & 17 deletions src/main/java/org/kapott/hbci/passport/AbstractPinTanPassport.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public abstract class AbstractPinTanPassport extends AbstractHBCIPassport
* Hier speichern wir zwischen, ob wir eine HKTAN-Anfrage in der Dialog-Initialisierung gesendet haben und wenn ja, welcher Prozess-Schritt es war
*/
private final static String CACHE_KEY_SCA_STEP = "__sca_step__";

/**
* Hier speichern wir, ob wir eine SCA-Ausnahme fuer einen GV von der Bank erhalten haben
*/
Expand All @@ -100,6 +100,11 @@ public abstract class AbstractPinTanPassport extends AbstractHBCIPassport
*/
public final static String KEY_PD_ORDERREF = "__pintan_orderref___";

/**
* Hier speichern wir zwischen, ob es sich um ein Decoupled-Verfahren handelt
*/
public final static String KEY_PD_DECOUPLED = "__pintan_decoupled__";

private String certfile;
private boolean checkCert;

Expand Down Expand Up @@ -418,15 +423,29 @@ private void checkSCARequest(DialogContext ctx)
if (sca == null)
return;

Integer step = (Integer) ctx.getMeta().get(CACHE_KEY_SCA_STEP);
// Wir haben noch kein HKTAN gesendet. Dann senden wir jetzt Schritt 1
if (step == null)
step = 1;
String step = (String) ctx.getMeta().get(CACHE_KEY_SCA_STEP);

ctx.getMeta().put(CACHE_KEY_SCA_STEP,step);
// Wir haben noch kein HKTAN gesendet. Dann senden wir jetzt Schritt 1
if (step == null || step.length() == 0)
{
step = "1";
ctx.getMeta().put(CACHE_KEY_SCA_STEP,step);
}

final Variant variant = sca.getVariant();
final KnownTANProcess tp = KnownTANProcess.get(variant,step.intValue());
KnownTANProcess tp = KnownTANProcess.get(variant,step);

// OK, wir verwenden das Decoupled Verfahren
if (tp == KnownTANProcess.PROCESS2_STEP2)
{
final String dec = (String) ctx.getPassport().getPersistentData(KEY_PD_DECOUPLED);
if (dec != null && dec.length() > 0)
{
tp = KnownTANProcess.PROCESS2_STEPS;
ctx.getPassport().setPersistentData(KEY_PD_DECOUPLED,null);
}
}

final int version = sca.getVersion();

// wir fuegen die Daten des HKTAN ein
Expand All @@ -439,10 +458,10 @@ private void checkSCARequest(DialogContext ctx)
String segcode = sca.getTanReference();
if (Feature.PINTAN_SEGCODE_STRICT.isEnabled())
{
if (tp == KnownTANProcess.PROCESS2_STEP2)
if (tp == KnownTANProcess.PROCESS2_STEP2 || tp == KnownTANProcess.PROCESS2_STEPS)
segcode = "";
}
HBCIUtils.log("creating HKTAN for SCA [process : " + tp + ", order code: " + segcode + "]",HBCIUtils.LOG_DEBUG);
HBCIUtils.log("creating HKTAN for SCA [process : " + tp + ", order code: " + segcode + ", step: " + step + "]",HBCIUtils.LOG_DEBUG);

k.rawSet(prefix + ".ordersegcode",segcode);
k.rawSet(prefix + ".OrderAccount.bic","");
Expand All @@ -452,8 +471,8 @@ private void checkSCARequest(DialogContext ctx)
k.rawSet(prefix + ".OrderAccount.KIK.blz","");
k.rawSet(prefix + ".OrderAccount.KIK.country","");
k.rawSet(prefix + ".orderhash",(variant == Variant.V2) ? "" : ("B00000000"));
k.rawSet(prefix + ".orderref",(step == 2) ? (String) this.getPersistentData(KEY_PD_ORDERREF) : "");
k.rawSet(prefix + ".notlasttan",(tp == KnownTANProcess.PROCESS1 || tp == KnownTANProcess.PROCESS2_STEP2) ? "N" : ""); // Darf nur bei TAN-Prozess 1 und 2 belegt sein
k.rawSet(prefix + ".orderref",(tp == KnownTANProcess.PROCESS2_STEP2 || tp == KnownTANProcess.PROCESS2_STEPS) ? (String) this.getPersistentData(KEY_PD_ORDERREF) : "");
k.rawSet(prefix + ".notlasttan",(tp == KnownTANProcess.PROCESS1 || tp == KnownTANProcess.PROCESS2_STEP2 || tp == KnownTANProcess.PROCESS2_STEPS) ? "N" : ""); // Darf nur bei TAN-Prozess 1, 2 und S belegt sein
k.rawSet(prefix + ".challengeklass",(variant == Variant.V2) ? "" : "99");
k.rawSet(prefix + ".tanmedia",sca.getTanMedia());
}
Expand Down Expand Up @@ -567,10 +586,10 @@ private void checkSCAResponse(DialogContext ctx)
return;
}

Integer scaStep = (Integer) ctx.getMeta().get(CACHE_KEY_SCA_STEP);
String scaStep = (String) ctx.getMeta().get(CACHE_KEY_SCA_STEP);

// Wenn wir keinen SCA-Request gesendet haben, brauchen wir auch nicht nach dem Response suchen
if (scaStep == null)
if (scaStep == null || scaStep.length() == 0)
return;

// Ohne Status brauchen wir es gar nicht erst versuchen
Expand All @@ -587,7 +606,7 @@ private void checkSCAResponse(DialogContext ctx)
}

// Schritt 1: Wir haben eine HKTAN-Anfrage gesendet. Mal schauen, ob die Bank tatsaechlich eine TAN will
if (scaStep.intValue() == 1)
if (scaStep.equals("1"))
{
HBCIUtils.log("HKTAN step 1 for SCA sent, checking for HITAN response [step: " + scaStep + "]",HBCIUtils.LOG_DEBUG);

Expand All @@ -612,7 +631,7 @@ private void checkSCAResponse(DialogContext ctx)
/////////////////////////////////////////////////////
// Dialog-Init wiederholen, um den zweiten HKTAN-Schritt durchzufuehren
// OK, wir senden jetzt das finale HKTAN. Die Message darf nichts anderes enthalten. Daher aendern wir das Template.
ctx.getMeta().put(CACHE_KEY_SCA_STEP,2);
ctx.getMeta().put(CACHE_KEY_SCA_STEP,"2");
ctx.getDialogInit().setTemplate(KnownDialogTemplate.INIT_SCA);
ctx.setRepeat(true);
//
Expand All @@ -621,7 +640,7 @@ private void checkSCAResponse(DialogContext ctx)
return;
}

if (scaStep.intValue() == 2)
if (scaStep.equals("2") || scaStep.equals("S"))
{
ctx.getMeta().remove(CACHE_KEY_SCA_STEP); // Geschafft
HBCIUtils.log("HKTAN step 2 for SCA sent, checking for HITAN response [step: " + scaStep + "]",HBCIUtils.LOG_DEBUG);
Expand Down Expand Up @@ -1533,7 +1552,7 @@ private void patchMessagesFor2StepMethods(DialogContext ctx)
// Neue Nachricht fuer das zweite HKTAN
HBCIUtils.log("process variant 2: creating new msg with HKTAN(p=2,orderref=DELAYED)",HBCIUtils.LOG_DEBUG);

// HKTAN-job für das einreichen der TAN erzeugen
// HKTAN-job für das Einreichen der TAN erzeugen
final GVTAN2Step hktan2 = (GVTAN2Step) handler.newJob("TAN2Step");
hktan2.setProcess(KnownTANProcess.PROCESS2_STEP2);
hktan2.setExternalId(task.getExternalId()); // externe ID auch an HKTAN2 durchreichen
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/org/kapott/hbci/passport/HBCIPassportPinTan.java
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,11 @@ else if (hhd.getType() == Type.QRCODE && (QRCode.tryParse(hhduc,msg) != null))
payload.append(hhduc);
callback = HBCICallback.NEED_PT_QRTAN;
}
else if (hhd.getType() == Type.DECOUPLED)
{
callback = HBCICallback.NEED_PT_DECOUPLED;
setPersistentData(KEY_PD_DECOUPLED,"true");
}
else
{
FlickerCode flicker = FlickerCode.tryParse(hhd,challenge,hhduc);
Expand All @@ -403,9 +408,15 @@ else if (hhd.getType() == Type.QRCODE && (QRCode.tryParse(hhduc,msg) != null))
HBCIUtilsInternal.getCallback().callback(this,callback,msg,HBCICallback.TYPE_TEXT,payload);

setPersistentData("externalid",null); // External-ID aus Passport entfernen
if (payload == null || payload.length()==0) {

// Beim Decoupled-Verfahren erhalten wir keine TAN. Daher müssen wir hier auch nichts signieren.
// Wir ignorieren die Antwort aus dem Callback komplett
if (callback == HBCICallback.NEED_PT_DECOUPLED)
return (getPIN()).getBytes("ISO-8859-1");

if (payload == null || payload.length()==0)
throw new HBCI_Exception(HBCIUtilsInternal.getLocMsg("EXCMSG_TANZERO"));
}

tan=payload.toString();
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/hbci-300.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5615,6 +5615,8 @@
<DE name="process" type="Code" maxsize="1"/>
<DE name="orderhash" type="Bin" maxsize="256" minnum="0"/>
<DE name="orderref" type="AN" maxsize="35" minnum="0"/>

<!-- Hier kann "nochallenge" drin stehen. Dann ignorieren. Sonst dem User anzeigen. Kann Infos von der Bank enthalten. -->
<DE name="challenge" type="AN" maxsize="2048" minnum="0"/>
<DE name="challenge_hhd_uc" type="Bin" maxsize="0" minnum="0" />
<DEG type="ChallengeValidity" minnum="0"/>
Expand Down
11 changes: 11 additions & 0 deletions src/test/java/org/kapott/hbci4java/secmech/TestHHDVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,17 @@ public static void initClass()
testdata.add(t);
}

{
TestData t = new TestData(HHDVersion.DECOUPLED);
t.props.put("zkamethod_name","Decoupled");
testdata.add(t);
}
{
TestData t = new TestData(HHDVersion.DECOUPLED);
t.props.put("zkamethod_name","DecoupledPush");
testdata.add(t);
}

}


Expand Down

0 comments on commit 555dde8

Please sign in to comment.