Added ZweiSchrittTanEinreichung. Authenticating with it works, but is not really configurable / usable.

This commit is contained in:
dankl 2019-10-04 23:32:42 +02:00 committed by dankito
parent b9733189e6
commit 9ac4af58ff
13 changed files with 273 additions and 13 deletions

View File

@ -2,6 +2,7 @@ package net.dankito.fints.messages
import net.dankito.fints.messages.datenelemente.implementierte.*
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanProcess
import net.dankito.fints.messages.nachrichten.Nachricht
import net.dankito.fints.messages.segmente.ISegmentNumberGenerator
import net.dankito.fints.messages.segmente.Segment
@ -39,8 +40,12 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
productVersion: String
): String {
return createDialogInitMessage(bankCountryCode, bankCode, KundenID.Anonymous, KundensystemID.Anonymous, KundensystemStatusWerte.NichtBenoetigt,
BPDVersion.VersionNotReceivedYet, UPDVersion.VersionNotReceivedYet, Dialogsprache.Default, productName, productVersion, false, false)
val customerId = KundenID.Anonymous
return createMessage(false, false, bankCountryCode, bankCode, customerId, listOf(
IdentifikationsSegment(generator.resetSegmentNumber(1), bankCountryCode, bankCode, customerId, KundensystemID.Anonymous, KundensystemStatusWerte.NichtBenoetigt),
Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), BPDVersion.VersionNotReceivedYet, UPDVersion.VersionNotReceivedYet, Dialogsprache.Default, productName, productVersion)
))
}
open fun createDialogInitMessage(
@ -53,14 +58,13 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
updVersion: Int,
language: Dialogsprache,
productName: String,
productVersion: String,
signMessage: Boolean = true,
encryptMessage: Boolean = true
productVersion: String
): String {
return createMessage(signMessage, encryptMessage, bankCountryCode, bankCode, customerId, listOf(
IdentifikationsSegment(generator.resetSegmentNumber(if (signMessage) 2 else 1), bankCountryCode, bankCode, customerId, customerSystemId, status),
Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), bpdVersion, updVersion, language, productName, productVersion)
return createMessage(true, true, bankCountryCode, bankCode, customerId, listOf(
IdentifikationsSegment(generator.resetSegmentNumber(2), bankCountryCode, bankCode, customerId, customerSystemId, status),
Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), bpdVersion, updVersion, language, productName, productVersion),
ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, "HKIDN")
))
}

View File

@ -25,7 +25,11 @@ open class BinaerDatenelement @JvmOverloads constructor(val data: ByteArray, exi
* Spezifikation vorzusehen.
*/
override fun format(): String {
return "@${data.size}@" + String(data)
if (data.size > 0) {
return "@${data.size}@" + String(data)
}
return ""
}
override fun validate() {

View File

@ -0,0 +1,29 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
/**
* Ab FinTS 3.0 existieren beim RAH-Verfahren drei Schlüssel (DS-Schlüssel für Non-Repudiation,
* Signierschlüssel für Authentication und Chiffrierschlüssel für Verschlüsselung) und somit
* auch drei Sicherheitsfunktionen (Sicherheitsfunktion 1 bei Verwendung des DS-Schlüssels,
* Sicherheitsfunktion 2 bei Verwendung des Signierschlüssels und Sicherheitsfunktion 4 bei
* Verwendung des Chiffrierschlüssels) beim RAH-Verfahren.
*
* Die Sicherheitsfunktion hat ab FinTS 3.0 lediglich informatorischen Wert, da die eigentliche
* Steuerung über die Sicherheitsprofile und Klassen erfolgt.
*
* Kodierte Information über die Sicherheitsfunktion, die auf die Nachricht angewendet wird.
*
* Codierung:
* - 1: Non-Repudiation of Origin (NRO)
* - 2: Message Origin Authentication (AUT)
* - 4: Encryption, Verschlüsselung und evtl. Komprimierung (ENC)
*/
enum class SicherheitsfunktionWhatIsThisShit(val code: String) {
NonRepudiationOfOrigin("1"),
MessageOriginAuthentication("2"),
Encryption("4")
}

View File

@ -0,0 +1,30 @@
package net.dankito.fints.messages.datenelemente.implementierte.tan
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.BinaerDatenelement
/**
* Er enthält im Falle des Zwei-Schritt-TAN-Verfahrens bei TAN-Prozess=1 den Hashwert über die
* Daten eines Kundenauftrags (z. B. HKCCS). Dieser wird z. B. im Rahmen des Geschäftsvorfalls
* HKTAN vom Kunden übermittelt und vom Kreditinstitut in der Antwortnachricht HITAN gespiegelt.
*
* Das vom Institut verwendete Auftrags-Hashwertverfahren wird in der BPD übermittelt. In der
* vorliegenden Version wird RIPEMD-160 verwendet.
*
* In die Berechnung des Auftrags-Hashwerts geht der Bereich vom ersten bit des Segmentkopfes
* bis zum letzten bit des Trennzeichens ein.
*
* RIPEMD-160
*
* Der Hash-Algorithmus RIPEMD-160 bildet Eingabe-Bitfolgen beliebiger Länge auf einen als
* Bytefolge dargestellten Hash-Wert von 20 Byte (160 Bit) Länge ab. Teil des Hash-Algorithmus
* ist das Padding von Eingabe-Bitfolgen auf ein Vielfaches von 64 Byte. Das Padding erfolgt
* auch dann, wenn die Eingabe-Bitfolge bereits eine Länge hat, die ein Vielfaches von 64 Byte ist.
* RIPEMD-160 verarbeitet die Eingabe-Bitfolgen in Blöcken von 64 Byte Länge.
*
* Als Initialisierungsvektor dient die binäre Zeichenfolge
* X01 23 45 67 89 AB CD EF FE DC BA 98 76 54 32 10 F0 E1 D2 C3.
*/
open class AuftragsHashwert(hash: ByteArray, existenzstatus: Existenzstatus)
: BinaerDatenelement(hash, existenzstatus, 256)

View File

@ -0,0 +1,17 @@
package net.dankito.fints.messages.datenelemente.implementierte.tan
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Enthält im Falle des Zwei-Schritt-TAN-Verfahrens die Referenz auf einen eingereichten Auftrag.
* Die Auftragsreferenz wird bei der späteren Einreichung der zugehörigen TANs (mittels HKTAN bei
* TAN-Prozess=2 bzw. 3) zur Referenzierung des Auftrags verwendet.
*
* Da die Auftragsreferenz immer eindeutig ist, sollten Kundenprodukte diese als zentrale
* Referenzierung verwenden und dem Kunden auch zusammen mit den Auftragsdaten präsentieren bzw.
* für die Problemverfolgung leicht zugänglich machen.
*/
open class Auftragsreferenz(reference: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(reference, existenzstatus, 35)

View File

@ -0,0 +1,13 @@
package net.dankito.fints.messages.datenelemente.implementierte.tan
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Symbolischer Name für ein TAN-Medium wie z. B. TAN-Generator oder Mobiltelefon. Diese Bezeichnung
* kann in Verwaltungs-Geschäftsvorfällen benutzt werden, wenn z. B. die Angabe der echten Handynummer
* aus Datenschutzgründen nicht möglich ist oder auch um die Benutzerfreundlichkeit zu erhöhen.
*/
open class BezeichnungDesTANMediums(identifier: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(identifier, existenzstatus, 32)

View File

@ -0,0 +1,16 @@
package net.dankito.fints.messages.datenelemente.implementierte.tan
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.BinaerDatenelement
/**
* Bei Verwendung von Zwei-Schritt-Verfahren mit unidirektionaler Kopplung (vgl. hierzu [HHD_UC])
* müssen zusätzlich zum Datenelement Challenge die Daten für die Übertragung z. B. über eine
* optische Schnittstelle bereitgestellt werden. Die einzelnen Datenelemente der Challenge HHD_UC
* sind in [HHD_UC] beschrieben und werden hier im FinTS Data Dictionary nicht näher erläutert.
* Da HHD_UC einen anderen Basiszeichensatz verwendet (ISO 646) wird die HHD_UC-Struktur als binär
* definiert. Als maximale Länge kann ein Wert von 128 angenommen werden.
*/
open class ChallengeHHD_UC(challenge: ByteArray, existenzstatus: Existenzstatus)
: BinaerDatenelement(challenge, existenzstatus, 128)

View File

@ -0,0 +1,27 @@
package net.dankito.fints.messages.datenelemente.implementierte.tan
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Dieses Datenelement enthält im Falle des Zwei-Schritt-TAN-Verfahrens die Challenge zu einem
* eingereichten Auftrag. Aus der Challenge wird vom Kunden die eigentliche TAN ermittelt.
* Die Challenge wird unabhängig vom Prozessvariante 1 oder 2 in der Kreditinstitutsantwort im
* Segment HITAN übermittelt.
*
* Ist der BPD-Parameter Challenge strukturiert mit J belegt, so können im Text folgende
* Formatsteuerzeichen enthalten sein, die kundenseitig entsprechend zu interpretieren sind.
* Eine Kaskadierung von Steuerzeichen ist nicht erlaubt.
*
* <br> Zeilenumbruch
* <p> Neuer Absatz
* <b> ... </b> Fettdruck
* <i> ... </i> Kursivdruck
* <u> ... </u> Unterstreichen
* <ul> ... </ul> Beginn / Ende Aufzählung
* <ol> ... </ol> Beginn / Ende Nummerierte Liste
* <li> ... </li> Listenelement einer Aufzählung / Nummerierten Liste
*/
open class ChallengeVersion3(challenge: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(challenge, existenzstatus, 2048)

View File

@ -0,0 +1,57 @@
package net.dankito.fints.messages.datenelemente.implementierte.tan
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Beim Zwei-Schritt-Verfahren werden die notwendigen Prozess-Schritte mittels des Geschäftsvorfalls
* HKTAN durchgeführt. Dieser unterstützt flexibel vier unterschiedliche Ausprägungen für die beiden
* Prozessvarianten für Zwei-Schritt-Verfahren, wobei die TAN-Prozesse 3 und 4 nicht isoliert und nur
* in Verbindung mit TAN-Prozess=2 auftreten können. Der TAN-Prozess wird wie folgt kodiert:
*
* Codierung:
*
* Prozessvariante 1:
*
* TAN-Prozess=1:
* Im ersten Schritt wird der Auftrags-Hashwert über den Geschäftsvorfall HKTAN mitgeteilt, im
* zweiten Schritt erfolgt nach Ermittlung der TAN aus der zurückgemeldeten Challenge die
* Einreichung des eigentlichen Auftrags inklusive der TAN über das normale Auftragssegment.
* Abfolge der Segmente am Beispiel HKCCS:
* 1. Schritt: HKTAN <-> HITAN
* 2. Schritt: HKCCS <-> HIRMS zu HKCCS
*
* Prozessvariante 2:
* Im ersten Schritt wird der Auftrag komplett über das normale Auftragssegment eingereicht,
* jedoch ohne Übermittlung der TAN. Im zweiten Schritt erfolgt nach Ermittlung der TAN aus
* der zurückgemeldeten Challenge die Einreichung der TAN über den Geschäftsvorfall HKTAN.
* Abfolge der Segmente am Beispiel HKCCS:
* Schritt 1: HKCCS und HKTAN HITAN
* Schritt 2: HKTAN HITAN und HIRMS zu HICCS
*
* TAN-Prozess=2:
* kann nur im zweiten Schritt auftreten. Er dient zur Übermittlung der TAN mittels HKTAN,
* nachdem der Auftrag selbst zuvor bereits mit TAN-Prozess=3 oder 4 eingereicht wurde.
* Dieser Geschäftsvorfall wird mit HITAN, TAN-Prozess=2 beantwortet.
*
* TAN-Prozess=3:
* kann nur im ersten Schritt bei Mehrfach-TANs für die zweite und ggf. dritte TAN auftreten.
* Hierdurch wird die Einreichung eingeleitet, wenn zeitversetzte Einreichung von
* Mehrfach-TANs erlaubt ist.
*
* TAN-Prozess=4:
* kann nur im ersten Schritt auftreten. Hiermit wird das Zwei-Schritt-Verfahren nach
* Prozessvariante 2 für die erste TAN eingeleitet. HKTAN wird zusammen mit dem Auftragssegment
* übertragen und durch HITAN mit TAN-Prozess=4 beantwortet. TAN-Prozess=4 wird auch beim
* Geschäftsvorfall Prüfen / Verbrennen von TANs eingesetzt.
*/
open class TANProzessDatenelement(process: TanProcess) : Code(process.code,
AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<TanProcess>()
}
}

View File

@ -0,0 +1,16 @@
package net.dankito.fints.messages.datenelemente.implementierte.tan
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class TanProcess(override val code: String) : ICodeEnum {
TanProcess1("1"),
TanProcess2("2"),
TanProcess3("3"),
TanProcess4("4"),
}

View File

@ -0,0 +1,17 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte.tan
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Datum
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Uhrzeit
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
/**
* Datum und Uhrzeit, bis zu welchem Zeitpunkt eine TAN auf Basis der gesendeten Challenge
* gültig ist. Nach Ablauf der Gültigkeitsdauer wird die entsprechende TAN entwertet.
*/
open class GueltigkeitsdatumUndUhrzeitFuerChallenge(date: Int, time: Int)
: Datenelementgruppe(listOf(
Datum(date, Existenzstatus.Mandatory),
Uhrzeit(time, Existenzstatus.Mandatory)
), Existenzstatus.Optional)

View File

@ -0,0 +1,29 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.NotAllowedDatenelement
import net.dankito.fints.messages.datenelemente.implementierte.tan.*
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.segmente.Segment
open class ZweiSchrittTanEinreichung(
segmentNumber: Int,
process: TanProcess,
jobReference: String,
challenge: String = "",
challgendHHD_UC: ByteArray = byteArrayOf(),
date: Int? = null,
time: Int? = null,
tanMediaIdentifier: String? = "N" // TODO: why 'N'?
) : Segment(listOf(
Segmentkopf("HKTAN", 6, segmentNumber),
TANProzessDatenelement(process),
// AuftragsHashwert(), // M: bei AuftragsHashwertverfahren<>0 und TAN-Prozess=1. N: sonst
Auftragsreferenz(jobReference, Existenzstatus.Mandatory), // M: bei TAN-Prozess=2, 3, 4. O: bei TAN-Prozess=1
ChallengeVersion3(challenge, Existenzstatus.Mandatory), // M: bei TAN-Prozess=1, 3, 4. O: bei TAN-Prozess=2
ChallengeHHD_UC(challgendHHD_UC, Existenzstatus.Optional),
NotAllowedDatenelement(), // GueltigkeitsdatumUndUhrzeitFuerChallenge // TODO: how to not write an element if it's optional and its paramters (date and time) are not set?
BezeichnungDesTANMediums(tanMediaIdentifier ?: "", Existenzstatus.Optional)// M: bei TAN-Prozess=1, 3, 4 und „Anzahl unterstützter aktiver TAN-Medien“ nicht vorhanden. O: sonst
), Existenzstatus.Mandatory)

View File

@ -78,13 +78,14 @@ class MessageBuilderTest {
// then
assertThat(normalizeBinaryData(result)).isEqualTo(normalizeBinaryData(
"HNHBK:1:3+000000000363+300+0+1'" +
"HNHBK:1:3+000000000386+300+0+1'" +
"HNVSK:998:3+PIN:2+998+1+1::0+1:$Date:$Time+2:16:14:@8@ :5:1+280:$BankCode:$CustomerId:V:0:0+0+'" +
"HNVSD:999:1+@198@" + "HNSHK:2:4+PIN:2+${SecurityFunction.code}+$ControlReference+1+1+1::0+1+1:$Date:$Time+1:999:1+6:10:16+280:$BankCode:$CustomerId:S:0:0'" +
"HNVSD:999:1+@221@" + "HNSHK:2:4+PIN:2+${SecurityFunction.code}+$ControlReference+1+1+1::0+1+1:$Date:$Time+1:999:1+6:10:16+280:$BankCode:$CustomerId:S:0:0'" +
"HKIDN:3:2+280:$BankCode+$CustomerId+0+1'" +
"HKVVB:4:3+0+0+${Language.code}+$ProductName+$ProductVersion'" +
"HNSHA:5:2+$ControlReference++$Pin''" +
"HNHBS:6:1+1'"
"HKTAN:5:6+4+HKIDN++++N'" +
"HNSHA:6:2+$ControlReference++$Pin''" +
"HNHBS:7:1+1'"
))
}