Implemented 'encrypting' message

This commit is contained in:
dankl 2019-10-04 22:25:23 +02:00 committed by dankito
parent bed585fc04
commit b9733189e6
25 changed files with 579 additions and 19 deletions

View File

@ -40,7 +40,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
): String {
return createDialogInitMessage(bankCountryCode, bankCode, KundenID.Anonymous, KundensystemID.Anonymous, KundensystemStatusWerte.NichtBenoetigt,
BPDVersion.VersionNotReceivedYet, UPDVersion.VersionNotReceivedYet, Dialogsprache.Default, productName, productVersion, false)
BPDVersion.VersionNotReceivedYet, UPDVersion.VersionNotReceivedYet, Dialogsprache.Default, productName, productVersion, false, false)
}
open fun createDialogInitMessage(
@ -54,36 +54,49 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
language: Dialogsprache,
productName: String,
productVersion: String,
signMessage: Boolean = true
signMessage: Boolean = true,
encryptMessage: Boolean = true
): String {
return createMessage(signMessage, bankCountryCode, bankCode, customerId, listOf(
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)
))
}
open fun createMessage(signMessage: Boolean, bankCountryCode: Int, bankCode: String, customerId: String,
open fun createMessage(signMessage: Boolean, encryptMessage: Boolean, bankCountryCode: Int, bankCode: String, customerId: String,
payloadSegments: List<Segment>): String {
val signedPayload = if (signMessage) { signPayload(2, bankCountryCode, bankCode, customerId, payloadSegments) }
else { payloadSegments }
var payload = payloadSegments
val partyIdentification = "0"
val date = utils.formatDateTodayAsInt()
val time = utils.formatTimeNowAsInt()
val payload = signedPayload.joinToString(Nachricht.SegmentSeparator) { it.format() }
if (signMessage) {
payload = signPayload(2, partyIdentification, date, time, bankCountryCode, bankCode, customerId, payload)
}
val messageSize = payload.length + MessageHeaderLength + MessageClosingLength + AddedSeparatorsLength
if (encryptMessage) {
payload = encryptPayload(partyIdentification, date, time, bankCountryCode, bankCode, customerId, payload)
}
val formattedPayload = formatPayload(payload)
val messageSize = formattedPayload.length + MessageHeaderLength + MessageClosingLength + AddedSeparatorsLength
val messageNumber = Nachrichtennummer.FirstMessageNumber
val header = Nachrichtenkopf(ISegmentNumberGenerator.FirstSegmentNumber, messageSize, "0", messageNumber)
val closing = Nachrichtenabschluss(generator.getNextSegmentNumber(), messageNumber)
return listOf(header.format(), payload, closing.format())
return listOf(header.format(), formattedPayload, closing.format())
.joinToString(Nachricht.SegmentSeparator, postfix = Nachricht.SegmentSeparator)
}
protected open fun signPayload(headerSegmentNumber: Int, bankCountryCode: Int, bankCode: String, customerId: String,
protected open fun signPayload(headerSegmentNumber: Int, partyIdentification: String, date: Int, time: Int,
bankCountryCode: Int, bankCode: String, customerId: String,
payloadSegments: List<Segment>): List<Segment> {
val controlReference = "1" // TODO
@ -102,10 +115,26 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
val signatureClosing = Signaturabschluss(
generator.getNextSegmentNumber(),
controlReference,
"" // TODO
"12345" // TODO
)
return listOf(signatureHeader, *payloadSegments.toTypedArray(), signatureClosing)
}
private fun encryptPayload(partyIdentification: String, date: Int, time: Int,
bankCountryCode: Int, bankCode: String, customerId: String, payload: List<Segment>): List<Segment> {
val encryptionHeader = PinTanVerschluesselungskopf(partyIdentification, date, time, bankCountryCode, bankCode, customerId)
val encryptedData = VerschluesselteDaten(formatPayload(payload) + Nachricht.SegmentSeparator)
return listOf(encryptionHeader, encryptedData)
}
protected open fun formatPayload(payload: List<Segment>): String {
return payload.joinToString(Nachricht.SegmentSeparator) { it.format() }
}
}

View File

@ -0,0 +1,42 @@
package net.dankito.fints.messages.datenelemente.basisformate
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.Datenelement
/**
* Binäre Daten werden unverändert in den FinTS-Datensatz eingestellt. Eine Umwandlung in
* eine Zeichendarstellung erfolgt nicht. Es ist zu beachten, dass der FinTS-Basiszeichensatz
* für binäre Daten keine Gültigkeit besitzt. Ferner gelten die speziellen Syntaxregeln für
* binäre Daten (s. Kap. H.1.3).
*/
open class BinaerDatenelement @JvmOverloads constructor(val data: ByteArray, existenzstatus: Existenzstatus, val maxLength: Int? = null)
: Datenelement(existenzstatus) {
/**
* Für binäre Daten gilt eine besondere Syntaxregelung: Das Auftreten dieser Daten wird eingeleitet mit dem
* Binärdatenkennzeichen (@). Anschließend folgt die Längenangabe zu den binären Daten und der binäre Wert selbst,
* der ebenfalls mit dem Binärdatenkennzeichen eingeleitet wird. Die Länge wird angegeben in Byte (nicht die Länge
* der darstellbaren Zeichen). Hierzu muss sichergestellt sein, dass der binäre Datenstrom in vollen Byte
* dargestellt werden kann (binäre Daten, die nicht im Byteformat vorliegen, können nicht über FinTS transportiert
* werden). Syntaxzeichen, die in binären Daten auftreten, dürfen nicht als solche interpretiert werden.
*
* Bei Elementen, die entsprechende Zeichen enthalten können (z. B. DE SEPAName) ist eine base64-Kodierung in der
* Spezifikation vorzusehen.
*/
override fun format(): String {
return "@${data.size}@" + String(data)
}
override fun validate() {
// binary data aren't checked, so they are always valid
maxLength?.let {
if (data.size > maxLength) {
throwValidationException("Binäre Daten dürfen nur eine maximale Größe von $maxLength Bytes haben, " +
"haben aber ${data.size} Bytes.")
}
}
}
}

View File

@ -0,0 +1,10 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class BezeichnerFuerAlgorithmusparameterIV(override val code: String) : ICodeEnum {
InitializationValue_ClearText("1")
}

View File

@ -0,0 +1,22 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Eigenschaft betreffend den Initialisierungswert für die RAH-Verfahren (Die
* Steuerung erfolgt in den BPD, vgl. [Formals]).
*
* Codierung:
* 1: Initialization value, clear text (IVC)
*/
open class BezeichnerFuerAlgorithmusparameterIVDatenelement(parameter: BezeichnerFuerAlgorithmusparameterIV, existenzstatus: Existenzstatus)
: Code(parameter.code, AllowedValues, existenzstatus) {
companion object {
val AllowedValues = allCodes<BezeichnerFuerAlgorithmusparameterIV>()
}
}

View File

@ -0,0 +1,15 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class BezeichnerFuerAlgorithmusparameterSchluessel(override val code: String) : ICodeEnum {
/**
* nicht zugelassen
*/
SymmetrischerSchluessel("5"),
SymmetrischerSchluessel_VerschluesseltMitOeffentlichenSchluessel("6")
}

View File

@ -0,0 +1,24 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Eigenschaft des Schlüssels für die RAH-Verfahren (Die Steuerung erfolgt in den BPD, vgl. [Formals]).
*
* Codierung:
* 5: Symmetrischer Schlüssel (nicht zugelassen)
* 6: Symmetrischer Schlüssel, verschlüsselt mit einem öffentlichen Schlüssel bei RAH und RDH (KYP).
*/
open class BezeichnerFuerAlgorithmusparameterSchluesselDatenelement(identifier: BezeichnerFuerAlgorithmusparameterSchluessel)
: Code(identifier.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<BezeichnerFuerAlgorithmusparameterSchluessel>()
val FinTsMock = BezeichnerFuerAlgorithmusparameterSchluessel.SymmetrischerSchluessel
}
}

View File

@ -0,0 +1,26 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class Komprimierungsfunktion(val abbreviation: String, override val code: String) : ICodeEnum {
Keine_Kompression("NULL", "0"),
Lempel_Ziv_Welch("LZW", "1"),
Optimized_LZW("COM", "2"),
Lempel_Ziv("LZSS", "3"),
LZ_Huffman_Coding("LZHuf", "4"),
PKZIP("ZIP", "5"),
deflate("GZIP", "6"),
bzip2("bzip2", "7"),
Gegenseitig_vereinbart("ZZZ", "999")
}

View File

@ -0,0 +1,29 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Code der unterstützten Komprimierungsfunktion.
*
* Codierung:
* 0: Keine Kompression (NULL)
* 1: Lempel, Ziv, Welch (LZW)
* 2: Optimized LZW (COM)
* 3: Lempel, Ziv (LZSS)
* 4: LZ + Huffman Coding (LZHuf)
* 5: PKZIP (ZIP)
* 6: deflate (GZIP) (http://www.gzip.org/zlib)
* 7: bzip2 (http://sourceware.cygnus.com/bzip2/)
* 999: Gegenseitig vereinbart (ZZZ)
*/
open class KomprimierungsfunktionDatenelement(algorithm: Komprimierungsfunktion)
: Code(algorithm.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<Komprimierungsfunktion>()
}
}

View File

@ -0,0 +1,22 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.Datenelement
/**
* For PIN/TAN data doesn't get encrypted at all.
*
* It simply gets, prefixed by '@<payload_length>@', appended to VerschluesselteDaten segment header
*/
class PinTanVerschluesselteDatenDatenelement(val payload: String) : Datenelement(Existenzstatus.Mandatory) {
override fun format(): String {
return "@${payload.length}@" + payload
}
override fun validate() {
// payload has already been validated, nothing to do
}
}

View File

@ -0,0 +1,15 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class Verschluesselungsalgorithmus(override val code: String) : ICodeEnum {
/**
* nicht zugelassen
*/
Two_Key_Triple_DES("13"),
AES_256("14")
}

View File

@ -0,0 +1,24 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Kodierte Information über den verwendeten Verschlüsselungsalgorithmus.
*
* Codierung:
* 13: 2-Key-Triple-DES (nicht zugelassen)
* 14: AES-256 [AES]
*/
open class VerschluesselungsalgorithmusKodiert(algorithm: Verschluesselungsalgorithmus)
: Code(algorithm.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<Verschluesselungsalgorithmus>()
val FinTsMock = Verschluesselungsalgorithmus.AES_256
}
}

View File

@ -0,0 +1,10 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class VerwendungDesVerschluesselungsalgorithmus(override val code: String) : ICodeEnum {
OwnerSymmetric("2")
}

View File

@ -0,0 +1,23 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Kodierte Information über die Verwendung des Verschlüsselungsalgorithmus.
*
* Im Zusammenhang mit der Verschlüsselung sind derzeit folgende Werte möglich:
*
* Codierung:
* 2: Owner Symmetric (OSY)
*/
open class VerwendungDesVerschluesselungsalgorithmusKodiert
: Code(VerwendungDesVerschluesselungsalgorithmus.OwnerSymmetric.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<VerwendungDesVerschluesselungsalgorithmus>()
}
}

View File

@ -0,0 +1,16 @@
package net.dankito.fints.messages.datenelemente.implementierte.encryption
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.BinaerDatenelement
/**
* Verschlüsselter Nachrichtenschlüssel für den kryptographischen Algorithmusparameter.
*/
open class WertDesAlgorithmusparametersSchluessel(key: ByteArray) : BinaerDatenelement(key, Existenzstatus.Mandatory, 512) {
companion object {
val FinTsMock = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0)
}
}

View File

@ -209,6 +209,8 @@ enum class Sicherheitsfunktion(override val code: String) : ICodeEnum {
PIN_TAN_997("997"),
PIN_TAN_Einschritt_Verfahren("999")
Klartext("998"),
Einschritt_Verfahren("999")
}

View File

@ -0,0 +1,37 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte.encryption
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.encryption.*
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Operationsmodus
import net.dankito.fints.messages.datenelemente.implementierte.signatur.OperationsmodusKodiert
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
/**
* Angaben zum kryptographischen Algorithmus, zu seinem Operationsmodus, so wie zu
* dessen Einsatz, in diesem Fall für die Nachrichtenverschlüsselung.
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.9 DEG Verschlüsselungsalgorithmus, S. 59):
*
* Wert des Algorithmusparameters, Schlüssel
* FinTS-Füllwert, z.B. X00 00 00 00 00 00 00 00
*
* Bezeichner für Algorithmusparameter, Schlüssel
* FinTS-Füllwert, z.B. 5
*
* Wert des Algorithmusparameters, IV
* Belegung nicht zulässig.
*/
open class VerschluesselungsalgorithmusDatenelementgruppe(
mode: Operationsmodus
) : Datenelementgruppe(listOf(
VerwendungDesVerschluesselungsalgorithmusKodiert(), // allowed: 2
OperationsmodusKodiert(mode), // allowed: 2, 18, 19
VerschluesselungsalgorithmusKodiert(VerschluesselungsalgorithmusKodiert.FinTsMock), // allowed: 13, 14
WertDesAlgorithmusparametersSchluessel(WertDesAlgorithmusparametersSchluessel.FinTsMock),
BezeichnerFuerAlgorithmusparameterSchluesselDatenelement(BezeichnerFuerAlgorithmusparameterSchluesselDatenelement.FinTsMock), // allowed: 6
// even though spec says for PIN/TAN no value may be set here ("Belegung nicht zulässig"), this value has to be set:
BezeichnerFuerAlgorithmusparameterIVDatenelement(BezeichnerFuerAlgorithmusparameterIV.InitializationValue_ClearText, Existenzstatus.Mandatory)
), Existenzstatus.Mandatory)

View File

@ -10,7 +10,7 @@ import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
open class SicherheitsdatumUndUhrzeit(date: Int, time: Int)
: Datenelementgruppe(listOf(
DatumUndZeitbezeichnerKodiert(DatumUndZeitbezeichner.Sicherheitszeitstempel), // Als Bezeichner wird „1“ eingestellt, da es sich um einen Sicherheitszeitstempel handelt.
Datum(date, Existenzstatus.Optional),
DatumUndZeitbezeichnerKodiert(DatumUndZeitbezeichner.Sicherheitszeitstempel), // Als Bezeichner wird „1“ eingestellt, da es sich um einen Sicherheitszeitstempel handelt.
Datum(date, Existenzstatus.Optional),
Uhrzeit(time, Existenzstatus.Optional)
), Existenzstatus.Mandatory)

View File

@ -27,4 +27,17 @@ open class Dialoginitialisierung(
IdentifikationsSegment(2, bankCountryCode, bankCode, customerId, customerSystemId, KundensystemStatusWerte.NichtBenoetigt), // TODO: KundensystemStatusWerte
Verarbeitungsvorbereitung(3, bpdVersion, updVersion, language, productName, productVersion),
Nachrichtenabschluss(4, FirstMessageNumber)
))
)) {
/**
* Zur Einleitung des Prozesses der Gewährleistung einer starken Kun-
denauthentifizierung gemäß [PSD2] muss bei TAN-Verfahren ein HKTAN-
Segment ab Segmentversion #6 eingestellt werden, wenn ein Kreditinstitut
die Verwendung von HKTAN #6 unterstützt (BPD). Wenn HKTAN #6
nicht gesendet wird, kann der Dialog vom Institut mit dem Rückmeldungs-
code 9075 Dialog abgebrochen - Starke Authentifizierung
erforderlich abgewiesen werden.
*/
}

View File

@ -3,7 +3,7 @@ package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.datenelemente.implementierte.signatur.*
class PinTanSignaturkopf(
open class PinTanSignaturkopf(
segmentNumber: Int,
securityFunction: Sicherheitsfunktion,
securityControlReference: String,

View File

@ -0,0 +1,29 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.datenelemente.implementierte.encryption.Komprimierungsfunktion
import net.dankito.fints.messages.datenelemente.implementierte.signatur.*
open class PinTanVerschluesselungskopf(
partyIdentification: String,
date: Int,
time: Int,
bankCountryCode: Int,
bankCode: String,
userIdentification: String
) : Verschluesselungskopf(
Sicherheitsverfahren.PIN_TAN_Verfahren,
VersionDesSicherheitsverfahrens.PIN_Zwei_Schritt,
partyIdentification,
date,
time,
OperationsmodusKodiert.FinTsMockValue,
bankCountryCode,
bankCode,
userIdentification,
Schluesselart.Chiffrierschluessel,
Schluesselnummer.FinTsMockValue,
Schluesselversion.FinTsMockValue,
Komprimierungsfunktion.Keine_Kompression
)

View File

@ -48,6 +48,7 @@ open class Signaturkopf(
BereichDerSicherheitsapplikationKodiert(BereichDerSicherheitsapplikation.SignaturkopfUndHBCINutzdaten), // allowed: 1 ?
RolleDesSicherheitslieferantenKodiert(), // allowed: 1
SicherheitsidentifikationDetails(partyIdentification),
// "Bei softwarebasierten Verfahren wird die Sicherheitsreferenznummer auf Basis des DE Kundensystem-ID und des DE Benutzerkennung der DEG Schlüsselnamen verwaltet.
Sicherheitsreferenznummer(1), // TODO: is this always 1?
SicherheitsdatumUndUhrzeit(date, time),
HashalgorithmusDatenelementgruppe(),

View File

@ -0,0 +1,18 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.encryption.PinTanVerschluesselteDatenDatenelement
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.segmente.Segment
/**
* Dieses Segment enthält die verschlüsselten (und komprimierten) Daten.
*/
open class VerschluesselteDaten(
payload: String
) : Segment(listOf(
Segmentkopf("HNVSD", 1, 999),
PinTanVerschluesselteDatenDatenelement(payload)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,60 @@
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.encryption.Komprimierungsfunktion
import net.dankito.fints.messages.datenelemente.implementierte.encryption.KomprimierungsfunktionDatenelement
import net.dankito.fints.messages.datenelemente.implementierte.signatur.*
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.datenelementgruppen.implementierte.encryption.VerschluesselungsalgorithmusDatenelementgruppe
import net.dankito.fints.messages.datenelementgruppen.implementierte.signatur.Schluesselname
import net.dankito.fints.messages.datenelementgruppen.implementierte.signatur.SicherheitsdatumUndUhrzeit
import net.dankito.fints.messages.datenelementgruppen.implementierte.signatur.SicherheitsidentifikationDetails
import net.dankito.fints.messages.datenelementgruppen.implementierte.signatur.Sicherheitsprofil
import net.dankito.fints.messages.segmente.Segment
/**
* Der Verschlüsselungskopf enthält Informationen über die Art des Sicherheitsservice, die
* Verschlüsselungsfunktion und die zu verwendenden Chiffrierschlüssel.
*
* Zum Abgleich mit dem in den BPD definierten RAH-Verschlüsselungsverfahren wird das Feld
* Bezeichner für Algorithmusparameter, Schlüssel in der DEG Verschlüsselungsalgorithmus herangezogen.
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.8 Segment Verschlüsselungskopf, S. 59)
*
* Sicherheitsfunktion, kodiert
* Es wird der Wert 998 (Klartext) verwendet.
*
* Zertifikat
* Dieses Feld darf nicht belegt werden.
*/
open class Verschluesselungskopf(
method: Sicherheitsverfahren,
version: VersionDesSicherheitsverfahrens,
partyIdentification: String,
date: Int,
time: Int,
mode: Operationsmodus,
bankCountryCode: Int,
bankCode: String,
userIdentification: String,
key: Schluesselart,
keyNumber: Int,
keyVersion: Int,
algorithm: Komprimierungsfunktion
) : Segment(listOf(
Segmentkopf("HNVSK", 3, 998),
Sicherheitsprofil(method, version),
SicherheitsfunktionKodiert(Sicherheitsfunktion.Klartext), // allowed: 4
RolleDesSicherheitslieferantenKodiert(), // allowed: 1, 4
SicherheitsidentifikationDetails(partyIdentification),
SicherheitsdatumUndUhrzeit(date, time),
VerschluesselungsalgorithmusDatenelementgruppe(mode),
Schluesselname(bankCountryCode, bankCode, userIdentification, key, keyNumber, keyVersion),
KomprimierungsfunktionDatenelement(algorithm),
// Certificate not applicapable for PIN/TAN; it should be also fine to write nothing at all and therefore leave NotAllowedDatenelement away
NotAllowedDatenelement() // Zertifikat is actually a Datenelementgruppe, not a Datenelement
), Existenzstatus.Mandatory)

View File

@ -1,13 +1,49 @@
package net.dankito.fints.messages
import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemID
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte
import net.dankito.fints.messages.datenelemente.implementierte.Laenderkennzeichen
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
import net.dankito.fints.util.FinTsUtils
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.util.*
class MessageBuilderTest {
private val underTest = MessageBuilder()
companion object {
const val BankCode = "12345678"
const val CustomerId = "0987654321"
const val Pin = "12345"
const val Date = 19880327
const val Time = 182752
val Language = Dialogsprache.German
val SecurityFunction = Sicherheitsfunktion.PIN_TAN_911
const val ControlReference = "1"
const val ProductName = "FinTS-TestClient25Stellen"
const val ProductVersion = "1"
}
private val underTest = MessageBuilder(utils = object : FinTsUtils() {
override fun formatDate(date: Date): String {
return Date.toString()
}
override fun formatTime(time: Date): String {
return Time.toString()
}
})
@Test
@ -15,7 +51,7 @@ class MessageBuilderTest {
// given
val underTest = underTest.createAnonymousDialogInitMessage(
Laenderkennzeichen.Germany, "12345678", "FinTS-TestClient25Stellen", "1")
Laenderkennzeichen.Germany, BankCode, ProductName, ProductVersion)
// when
val result = underTest.format()
@ -24,9 +60,36 @@ class MessageBuilderTest {
assertThat(result).isEqualTo(
"HNHBK:1:3+000000000125+300+0+1'" +
"HKIDN:2:2+280:12345678+9999999999+0+0'" +
"HKVVB:3:3+0+0+0+FinTS-TestClient25Stellen+1'" +
"HKVVB:3:3+0+0+0+$ProductName+$ProductVersion'" +
"HNHBS:4:1+1'"
)
}
@Test
fun createDialogInitMessage() {
// given
val underTest = underTest.createDialogInitMessage(Laenderkennzeichen.Germany, BankCode, CustomerId,
KundensystemID.PinTan, KundensystemStatusWerte.Benoetigt, 0, 0, Language,
ProductName, ProductVersion)
// when
val result = underTest.format()
// then
assertThat(normalizeBinaryData(result)).isEqualTo(normalizeBinaryData(
"HNHBK:1:3+000000000363+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'" +
"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'"
))
}
protected open fun normalizeBinaryData(message: String): String {
return message.replace(0.toChar(), ' ')
}
}

View File

@ -0,0 +1,30 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.datenelemente.implementierte.Laenderkennzeichen
import net.dankito.fints.messages.datenelemente.implementierte.signatur.IdentifizierungDerPartei
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class VerschluesselungskopfTest {
@Test
fun format() {
// given
val partyIdentification = IdentifizierungDerPartei.SynchronizingCustomerSystemId
val date = 20191002
val time = 212757
val bankCode = "12345678"
val customerId = "0987654321"
val underTest = PinTanVerschluesselungskopf(partyIdentification, date, time,
Laenderkennzeichen.Germany, bankCode, customerId)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HNVSK:998:3+PIN:2+998+1+1::0+1:$date:$time+2:2:13:@8@ :5:1+280:$bankCode:$customerId:V:0:0+0")
}
}