Implemented 'encrypting' message
This commit is contained in:
parent
bed585fc04
commit
b9733189e6
|
@ -40,7 +40,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
|
||||||
): String {
|
): String {
|
||||||
|
|
||||||
return createDialogInitMessage(bankCountryCode, bankCode, KundenID.Anonymous, KundensystemID.Anonymous, KundensystemStatusWerte.NichtBenoetigt,
|
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(
|
open fun createDialogInitMessage(
|
||||||
|
@ -54,36 +54,49 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
|
||||||
language: Dialogsprache,
|
language: Dialogsprache,
|
||||||
productName: String,
|
productName: String,
|
||||||
productVersion: String,
|
productVersion: String,
|
||||||
signMessage: Boolean = true
|
signMessage: Boolean = true,
|
||||||
|
encryptMessage: Boolean = true
|
||||||
): String {
|
): 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),
|
IdentifikationsSegment(generator.resetSegmentNumber(if (signMessage) 2 else 1), bankCountryCode, bankCode, customerId, customerSystemId, status),
|
||||||
Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), bpdVersion, updVersion, language, productName, productVersion)
|
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 {
|
payloadSegments: List<Segment>): String {
|
||||||
|
|
||||||
val signedPayload = if (signMessage) { signPayload(2, bankCountryCode, bankCode, customerId, payloadSegments) }
|
var payload = payloadSegments
|
||||||
else { 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 messageNumber = Nachrichtennummer.FirstMessageNumber
|
||||||
|
|
||||||
val header = Nachrichtenkopf(ISegmentNumberGenerator.FirstSegmentNumber, messageSize, "0", messageNumber)
|
val header = Nachrichtenkopf(ISegmentNumberGenerator.FirstSegmentNumber, messageSize, "0", messageNumber)
|
||||||
|
|
||||||
val closing = Nachrichtenabschluss(generator.getNextSegmentNumber(), 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)
|
.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> {
|
payloadSegments: List<Segment>): List<Segment> {
|
||||||
val controlReference = "1" // TODO
|
val controlReference = "1" // TODO
|
||||||
|
|
||||||
|
@ -102,10 +115,26 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
|
||||||
val signatureClosing = Signaturabschluss(
|
val signatureClosing = Signaturabschluss(
|
||||||
generator.getNextSegmentNumber(),
|
generator.getNextSegmentNumber(),
|
||||||
controlReference,
|
controlReference,
|
||||||
"" // TODO
|
"12345" // TODO
|
||||||
)
|
)
|
||||||
|
|
||||||
return listOf(signatureHeader, *payloadSegments.toTypedArray(), signatureClosing)
|
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() }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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")
|
||||||
|
|
||||||
|
}
|
|
@ -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>()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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")
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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")
|
||||||
|
|
||||||
|
}
|
|
@ -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>()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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")
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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")
|
||||||
|
|
||||||
|
}
|
|
@ -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>()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -209,6 +209,8 @@ enum class Sicherheitsfunktion(override val code: String) : ICodeEnum {
|
||||||
|
|
||||||
PIN_TAN_997("997"),
|
PIN_TAN_997("997"),
|
||||||
|
|
||||||
PIN_TAN_Einschritt_Verfahren("999")
|
Klartext("998"),
|
||||||
|
|
||||||
|
Einschritt_Verfahren("999")
|
||||||
|
|
||||||
}
|
}
|
|
@ -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. X’00 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)
|
|
@ -27,4 +27,17 @@ open class Dialoginitialisierung(
|
||||||
IdentifikationsSegment(2, bankCountryCode, bankCode, customerId, customerSystemId, KundensystemStatusWerte.NichtBenoetigt), // TODO: KundensystemStatusWerte
|
IdentifikationsSegment(2, bankCountryCode, bankCode, customerId, customerSystemId, KundensystemStatusWerte.NichtBenoetigt), // TODO: KundensystemStatusWerte
|
||||||
Verarbeitungsvorbereitung(3, bpdVersion, updVersion, language, productName, productVersion),
|
Verarbeitungsvorbereitung(3, bpdVersion, updVersion, language, productName, productVersion),
|
||||||
Nachrichtenabschluss(4, FirstMessageNumber)
|
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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package net.dankito.fints.messages.segmente.implementierte
|
||||||
import net.dankito.fints.messages.datenelemente.implementierte.signatur.*
|
import net.dankito.fints.messages.datenelemente.implementierte.signatur.*
|
||||||
|
|
||||||
|
|
||||||
class PinTanSignaturkopf(
|
open class PinTanSignaturkopf(
|
||||||
segmentNumber: Int,
|
segmentNumber: Int,
|
||||||
securityFunction: Sicherheitsfunktion,
|
securityFunction: Sicherheitsfunktion,
|
||||||
securityControlReference: String,
|
securityControlReference: String,
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -48,6 +48,7 @@ open class Signaturkopf(
|
||||||
BereichDerSicherheitsapplikationKodiert(BereichDerSicherheitsapplikation.SignaturkopfUndHBCINutzdaten), // allowed: 1 ?
|
BereichDerSicherheitsapplikationKodiert(BereichDerSicherheitsapplikation.SignaturkopfUndHBCINutzdaten), // allowed: 1 ?
|
||||||
RolleDesSicherheitslieferantenKodiert(), // allowed: 1
|
RolleDesSicherheitslieferantenKodiert(), // allowed: 1
|
||||||
SicherheitsidentifikationDetails(partyIdentification),
|
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?
|
Sicherheitsreferenznummer(1), // TODO: is this always 1?
|
||||||
SicherheitsdatumUndUhrzeit(date, time),
|
SicherheitsdatumUndUhrzeit(date, time),
|
||||||
HashalgorithmusDatenelementgruppe(),
|
HashalgorithmusDatenelementgruppe(),
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -1,13 +1,49 @@
|
||||||
package net.dankito.fints.messages
|
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.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.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
class MessageBuilderTest {
|
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
|
@Test
|
||||||
|
@ -15,7 +51,7 @@ class MessageBuilderTest {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
val underTest = underTest.createAnonymousDialogInitMessage(
|
val underTest = underTest.createAnonymousDialogInitMessage(
|
||||||
Laenderkennzeichen.Germany, "12345678", "FinTS-TestClient25Stellen", "1")
|
Laenderkennzeichen.Germany, BankCode, ProductName, ProductVersion)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val result = underTest.format()
|
val result = underTest.format()
|
||||||
|
@ -24,9 +60,36 @@ class MessageBuilderTest {
|
||||||
assertThat(result).isEqualTo(
|
assertThat(result).isEqualTo(
|
||||||
"HNHBK:1:3+000000000125+300+0+1'" +
|
"HNHBK:1:3+000000000125+300+0+1'" +
|
||||||
"HKIDN:2:2+280:12345678+9999999999+0+0'" +
|
"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'"
|
"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(), ' ')
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue