Implemented signing messages (but cannot tell yet if it's working)

This commit is contained in:
dankl 2019-10-04 17:00:54 +02:00 committed by dankito
parent a6a7930c29
commit bed585fc04
73 changed files with 1496 additions and 58 deletions

View File

@ -1,12 +1,17 @@
package net.dankito.fints
import net.dankito.fints.messages.MessageBuilder
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.model.AccountCredentials
import net.dankito.fints.model.BankInfo
import net.dankito.fints.model.ProductInfo
import net.dankito.fints.util.IBase64Service
import net.dankito.utils.web.client.IWebClient
import net.dankito.utils.web.client.OkHttpWebClient
import net.dankito.utils.web.client.RequestParameters
import net.dankito.utils.web.client.WebClientResponse
open class FinTsClient(
@ -16,15 +21,37 @@ open class FinTsClient(
) {
fun getBankInfo(bankInfo: BankInfo, productInfo: ProductInfo) {
fun getAnonymousBankInfo(bankInfo: BankInfo, productInfo: ProductInfo) {
val requestBody = messageBuilder.createAnonymousDialogInitMessage(bankInfo.countryCode, bankInfo.bankCode,
productInfo.productName, productInfo.productVersion)
val response = getResponseForMessage(requestBody, bankInfo)
handleResponse(response)
}
fun getBankInfo(credentials: AccountCredentials, bankInfo: BankInfo, productInfo: ProductInfo) {
val requestBody = messageBuilder.createDialogInitMessage(bankInfo.countryCode, bankInfo.bankCode,
credentials.customerId, KundensystemID.PinTan, KundensystemStatusWerte.Benoetigt, 0, 0, Dialogsprache.German,
productInfo.productName, productInfo.productVersion)
val response = getResponseForMessage(requestBody, bankInfo)
handleResponse(response)
}
protected open fun getResponseForMessage(requestBody: String, bankInfo: BankInfo): WebClientResponse {
val encodedRequestBody = base64Service.encode(requestBody)
val response = webClient.post(RequestParameters(bankInfo.finTsServerAddress, encodedRequestBody, "application/octet-stream"))
return webClient.post(
RequestParameters(bankInfo.finTsServerAddress, encodedRequestBody, "application/octet-stream")
)
}
protected open fun handleResponse(response: WebClientResponse) {
val responseBody = response.body
if (response.isSuccessful && responseBody != null) {
val decodedResponse = decodeBase64Response(responseBody)

View File

@ -18,7 +18,7 @@ package net.dankito.fints.messages
* nicht den Richtlinien entspricht, so ist dieser abzuweisen. Eine kreditinstitutsseitige Korrektur
* der Auftragsdaten erfolgt nicht.
*/
class HbciCharset {
open class HbciCharset {
companion object {
val DefaultCharset = Charsets.ISO_8859_1

View File

@ -1,21 +1,21 @@
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.nachrichten.Nachricht
import net.dankito.fints.messages.segmente.ISegmentNumberGenerator
import net.dankito.fints.messages.segmente.Segment
import net.dankito.fints.messages.segmente.SegmentNumberGenerator
import net.dankito.fints.messages.segmente.implementierte.IdentifikationsSegment
import net.dankito.fints.messages.segmente.implementierte.Nachrichtenabschluss
import net.dankito.fints.messages.segmente.implementierte.Nachrichtenkopf
import net.dankito.fints.messages.segmente.implementierte.Verarbeitungsvorbereitung
import net.dankito.fints.messages.segmente.implementierte.*
import net.dankito.fints.util.FinTsUtils
/**
* Takes the Segments of they payload, may signs and encrypts them, calculates message size,
* adds the message header and closing, and formats the whole message to string.
*/
open class MessageBuilder(protected val generator: ISegmentNumberGenerator = SegmentNumberGenerator()) {
open class MessageBuilder(protected val generator: ISegmentNumberGenerator = SegmentNumberGenerator(),
protected val utils: FinTsUtils = FinTsUtils()) {
companion object {
const val MessageHeaderLength = 30
@ -39,8 +39,8 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
productVersion: String
): String {
return createDialogInitMessage(bankCountryCode, bankCode, KundenID.Anonymous, KundensystemID.Anonymous,
BPDVersion.VersionNotReceivedYet, UPDVersion.VersionNotReceivedYet, Dialogsprache.Default, productName, productVersion)
return createDialogInitMessage(bankCountryCode, bankCode, KundenID.Anonymous, KundensystemID.Anonymous, KundensystemStatusWerte.NichtBenoetigt,
BPDVersion.VersionNotReceivedYet, UPDVersion.VersionNotReceivedYet, Dialogsprache.Default, productName, productVersion, false)
}
open fun createDialogInitMessage(
@ -48,23 +48,29 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
bankCode: String,
customerId: String,
customerSystemId: String,
status: KundensystemStatusWerte,
bpdVersion: Int,
updVersion: Int,
language: Dialogsprache,
productName: String,
productVersion: String
productVersion: String,
signMessage: Boolean = true
): String {
return createMessage(listOf(
IdentifikationsSegment(generator.resetSegmentNumber(1), bankCountryCode, bankCode, customerId, customerSystemId),
return createMessage(signMessage, 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(payloadSegments: List<Segment>): String {
open fun createMessage(signMessage: Boolean, bankCountryCode: Int, bankCode: String, customerId: String,
payloadSegments: List<Segment>): String {
val payload = payloadSegments.joinToString(Nachricht.SegmentSeparator) { it.format() }
val signedPayload = if (signMessage) { signPayload(2, bankCountryCode, bankCode, customerId, payloadSegments) }
else { payloadSegments }
val payload = signedPayload.joinToString(Nachricht.SegmentSeparator) { it.format() }
val messageSize = payload.length + MessageHeaderLength + MessageClosingLength + AddedSeparatorsLength
val messageNumber = Nachrichtennummer.FirstMessageNumber
@ -77,4 +83,29 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
.joinToString(Nachricht.SegmentSeparator, postfix = Nachricht.SegmentSeparator)
}
protected open fun signPayload(headerSegmentNumber: Int, bankCountryCode: Int, bankCode: String, customerId: String,
payloadSegments: List<Segment>): List<Segment> {
val controlReference = "1" // TODO
val signatureHeader = PinTanSignaturkopf(
headerSegmentNumber,
Sicherheitsfunktion.PIN_TAN_911, // TODO
controlReference,
"0",
utils.formatDateTodayAsInt(),
utils.formatTimeNowAsInt(),
bankCountryCode,
bankCode,
customerId
)
val signatureClosing = Signaturabschluss(
generator.getNextSegmentNumber(),
controlReference,
"" // TODO
)
return listOf(signatureHeader, *payloadSegments.toTypedArray(), signatureClosing)
}
}

View File

@ -0,0 +1,18 @@
package net.dankito.fints.messages.datenelemente.abgeleiteteformate
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Format: JJJJMMTT gemäß ISO 8601
*
* Erlaubt sind alle existenten Datumsangaben.
*/
open class Datum(date: Int, existenzstatus: Existenzstatus) : NumerischesDatenelement(date, 8, existenzstatus) {
companion object {
const val HbciDateFormat = "yyyyMMdd"
}
}

View File

@ -9,5 +9,5 @@ import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDat
*
* Maximal 30 Zeichen
*/
abstract class Identifikation(identifikation: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(identifikation, existenzstatus, 30)
abstract class Identifikation(identification: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(identification, existenzstatus, 30)

View File

@ -0,0 +1,19 @@
package net.dankito.fints.messages.datenelemente.abgeleiteteformate
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.ZiffernDatenelement
/**
* Format: hhmmss gemäß ISO 8601
*
* Gültige Uhrzeit. Es ist immer Ortszeit des sendenden Systems einzustellen.
* Unterschiedliche Zeitzonen werden nicht unterstützt
*/
open class Uhrzeit(time: Int, existenzstatus: Existenzstatus) : ZiffernDatenelement(time, 6, existenzstatus) {
companion object {
const val HbciTimeFormat = "HHmmss"
}
}

View File

@ -15,7 +15,7 @@ import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenele
* die im Nachrichtenkopf eingestellt ist und lediglich das syntaktische Format der
* Nachricht, nicht jedoch deren Inhalt kennzeichnet.
*/
class BPDVersion(version: Int, existenzstatus: Existenzstatus) : NumerischesDatenelement(version, 3, existenzstatus) {
open class BPDVersion(version: Int, existenzstatus: Existenzstatus) : NumerischesDatenelement(version, 3, existenzstatus) {
companion object {
const val VersionNotReceivedYet = 0

View File

@ -0,0 +1,14 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**
* Eindeutig vergebene Kennung, anhand deren die Identifizierung des Benutzers erfolgt.
* Die Vergabe obliegt dem Kreditinstitut. Das Kreditinstitut hat zu gewährleisten,
* dass die Benutzerkennung institutsweit eindeutig ist. Sie kann beliebige Informationen
* enthalten, darf aber bei Verwendung des RAH-Verfahrens aus Sicherheitsgründen nicht aus
* benutzer- oder kreditinstitutsspezifischen Merkmalen hergeleitet werden.
*/
open class Benutzerkennung(identification: String) : Identifikation(identification, Existenzstatus.Mandatory)

View File

@ -4,4 +4,4 @@ import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Identifikation
class DialogId(dialogId: String) : Identifikation(dialogId, Existenzstatus.Mandatory)
open class DialogId(dialogId: String) : Identifikation(dialogId, Existenzstatus.Mandatory)

View File

@ -1,7 +1,7 @@
package net.dankito.fints.messages.datenelemente.implementierte
enum class Dialogsprache(val code: String) {
enum class Dialogsprache(override val code: String) : ICodeEnum {
Default("0"),

View File

@ -20,16 +20,11 @@ import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
* - 2: Englisch, Code en (English), Subset Englisch, Codeset 1 (Latin 1)
* - 3: Französisch, Code fr (French), Subset Französisch, Codeset 1 (Latin 1)
*/
class DialogspracheDatenelement(language: Dialogsprache, existenzstatus: Existenzstatus)
open class DialogspracheDatenelement(language: Dialogsprache, existenzstatus: Existenzstatus)
: Code(language.code, AllowedValues, existenzstatus) {
companion object {
val AllowedValues = listOf(
Dialogsprache.Default.code,
Dialogsprache.German.code,
Dialogsprache.English.code,
Dialogsprache.French.code
)
val AllowedValues = allCodes<Dialogsprache>()
}
}

View File

@ -1,6 +0,0 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
open class FinTsKundensystemStatus : KundensystemStatus(KundensystemStatusWerte.NichtBenoetigt, Existenzstatus.Mandatory)

View File

@ -31,4 +31,4 @@ import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenele
* - Version 2.2: 220 (Spezifikationsstatus: obsolet)
* - Version 3.0: 300
*/
class HbciVersionDatenelement(version: HbciVersion) : NumerischesDatenelement(version.versionNumber, 3, Existenzstatus.Mandatory)
open class HbciVersionDatenelement(version: HbciVersion) : NumerischesDatenelement(version.versionNumber, 3, Existenzstatus.Mandatory)

View File

@ -0,0 +1,12 @@
package net.dankito.fints.messages.datenelemente.implementierte
inline fun <reified T : Enum<T>> allCodes(): List<String> {
return enumValues<T>().map { (it as ICodeEnum).code }
}
interface ICodeEnum {
val code: String
}

View File

@ -21,6 +21,8 @@ open class KundensystemID(customerSystemId: String) : Identifikation(customerSys
companion object {
const val Anonymous = "0"
const val PinTan = Anonymous
}
}

View File

@ -17,12 +17,7 @@ open class KundensystemStatus(status: KundensystemStatusWerte, existenzstatus: E
: Code(status.code, AllowedValues, existenzstatus) {
companion object {
val AllowedValues = listOf(
KundensystemStatusWerte.NichtBenoetigt.code,
KundensystemStatusWerte.Benoetigt.code
)
val Anonymous = KundensystemStatusWerte.NichtBenoetigt
val AllowedValues = allCodes<KundensystemStatusWerte>()
}
}

View File

@ -1,7 +1,7 @@
package net.dankito.fints.messages.datenelemente.implementierte
enum class KundensystemStatusWerte(val code: String) {
enum class KundensystemStatusWerte(override val code: String) : ICodeEnum {
NichtBenoetigt("0"),

View File

@ -9,4 +9,4 @@ import net.dankito.fints.messages.datenelemente.basisformate.ZiffernDatenelement
* Das DE ist mit führenden Nullen auf die vorgegebene feste Länge aufzufüllen.
* Dies ist erforderlich, damit die Nachrichtenlänge nicht mit der Länge des DE variiert.
*/
class Nachrichtengroesse(messageSize: Int) : ZiffernDatenelement(messageSize, 12, Existenzstatus.Mandatory)
open class Nachrichtengroesse(messageSize: Int) : ZiffernDatenelement(messageSize, 12, Existenzstatus.Mandatory)

View File

@ -17,7 +17,7 @@ import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenele
* Nachrichten, deren Nummerierung nicht streng monoton aufsteigend erfolgt ist, werden
* institutsseitig bzw. kundenseitig abgelehnt.
*/
class Nachrichtennummer(number: Int) : NumerischesDatenelement(number, 4, Existenzstatus.Mandatory) {
open class Nachrichtennummer(number: Int) : NumerischesDatenelement(number, 4, Existenzstatus.Mandatory) {
companion object {
const val FirstMessageNumber = 1

View File

@ -0,0 +1,7 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.TextDatenelement
open class NotAllowedDatenelement : TextDatenelement("", Existenzstatus.NotAllowed)

View File

@ -15,5 +15,5 @@ import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDat
* Kundenprodukte, die nach dem durch die Deutsche Kreditwirtschaft festgelegten Verfahren registriert
* sind, müssen in dieses DE die vergebene Produktregistrierungsnummer einstellen.
*/
class Produktbezeichnung(name: String, existenzstatus: Existenzstatus)
open class Produktbezeichnung(name: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(name, existenzstatus, 25)

View File

@ -11,5 +11,5 @@ import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDat
* Kundenprodukt, nicht eine ggf. verwendete interne FinTS-/HBCI-Bibliothek, zu füllen, um
* Support-Anfragen leichter beantworten zu können.
*/
class Produktversion(version: String, existenzstatus: Existenzstatus)
open class Produktversion(version: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(version, existenzstatus, 5)

View File

@ -8,7 +8,7 @@ import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenele
* Versionsnummer der Userparameterdaten (UPD). Bei jeder kreditinstitutsseitigen Änderung
* wird die Version inkrementiert. (S. auch DE BPD-Version).
*/
class UPDVersion(version: Int, existenzstatus: Existenzstatus) : NumerischesDatenelement(version, 3, existenzstatus) {
open class UPDVersion(version: Int, existenzstatus: Existenzstatus) : NumerischesDatenelement(version, 3, existenzstatus) {
companion object {
const val VersionNotReceivedYet = 0

View File

@ -0,0 +1,12 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class BereichDerSicherheitsapplikation(val abbreviation: String, override val code: String) : ICodeEnum {
SignaturkopfUndHBCINutzdaten("SHM", "1"),
VonSignaturkopfBisSignaturabschluss("SHT", "2")
}

View File

@ -0,0 +1,31 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Information darüber, welche Daten vom kryptographischen Prozess verarbeitet werden.
* Diese Information wird benötigt um z. B. zwischen relevanter und belangloser Reihenfolge
* von Signaturen zu unterscheiden (vgl. [HBCI], Kapitel VI.4).
*
* Wenn SHM gewählt wird, so bedeutet dies, dass nur über den eigenen Signaturkopf sowie die
* HBCI-Nutzdaten ein Hashwert gebildet wird, der in die Signatur eingeht. Dies entspricht
* bei Mehrfachsignaturen einer bedeutungslosen Reihenfolge.
*
* Wenn SHT gewählt wird, dann werden auch alle schon vorhandenen Signaturköpfe und
* -abschlüsse mitsigniert. Das heißt, dass die Reihenfolge der Signaturen relevant ist.
*
* Codierung:
* - 1: Signaturkopf und HBCI-Nutzdaten (SHM)
* - 2: Von Signaturkopf bis Signaturabschluss (SHT)
*/
open class BereichDerSicherheitsapplikationKodiert(domain: BereichDerSicherheitsapplikation)
: Code(domain.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<BereichDerSicherheitsapplikation>()
}
}

View File

@ -0,0 +1,13 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Bezeichner für den Hashalgorithmusparameter.
*
* Codierung:
* 1: IVC (Initialization value, clear text)
*/
open class BezeichnerFuerHashalgorithmusparameter : AlphanumerischesDatenelement("1", Existenzstatus.Mandatory, 3)

View File

@ -0,0 +1,14 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Identifikation der Funktion der beschriebenen Partei, in diesem Falle des Kunden.
*
* Codierung:
* - 1: Message Sender (MS), wenn ein Kunde etwas an sein Kreditinstitut sendet.
* - 2: Message Receiver (MR), wenn das Kreditinstitut etwas an seinen Kunden sendet.
*/
open class BezeichnerFuerSicherheitspartei : AlphanumerischesDatenelement("1", Existenzstatus.Mandatory, 3)

View File

@ -0,0 +1,12 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class DatumUndZeitbezeichner(override val code: String) : ICodeEnum {
Sicherheitszeitstempel("1"),
CertificateRevocationTime("2")
}

View File

@ -0,0 +1,22 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Enthält die Bedeutung des Zeitstempels.
*
* Codierung:
* 1: Sicherheitszeitstempel (STS)
* 6: Certificate Revocation Time (CRT)
*/
open class DatumUndZeitbezeichnerKodiert(identifier: DatumUndZeitbezeichner)
: Code(identifier.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<DatumUndZeitbezeichner>()
}
}

View File

@ -0,0 +1,18 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class Hashalgorithmus(override val code: String) : ICodeEnum {
SHA_256("3"),
SHA_384("4"),
SHA_512("5"),
SHA_256_SHA_256("6"),
Gegenseitig_vereinbart("999")
}

View File

@ -0,0 +1,26 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Code des verwendeten Hash-Algorithmus.
*
* Codierung:
* 1: SHA-1 (nicht zugelassen)
* 2: belegt
* 3: SHA-256
* 4: SHA-384
* 5: SHA-512
* 6: SHA-256 / SHA-256
* 999: Gegenseitig vereinbart (ZZZ); (nicht zugelassen)
*/
open class HashalgorithmusKodiert(algorithm: Hashalgorithmus) : Code(algorithm.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<Hashalgorithmus>()
}
}

View File

@ -0,0 +1,17 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**
* Code, welcher die (Kommunikations-)Partei identifiziert. Bei Verwendung des
* RAH-Verfahrens ist die Kundensystem-ID einzustellen.
*/
open class IdentifizierungDerPartei(identification: String) : Identifikation(identification, Existenzstatus.Optional) {
companion object {
const val SynchronizingCustomerSystemId = "0"
}
}

View File

@ -0,0 +1,43 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class Operationsmodus(override val code: String) : ICodeEnum {
/**
* Nur für Verschlüsselung erlaubt (vgl. [HBCI], Kapitel VI.2.2)
*/
Cipher_Block_Chaining("2"),
/**
* nicht zugelassen
*/
ISO_9796_1("16"),
/**
* nicht zugelassen
*/
ISO_9796_2_mit_Zufallszahl("17"),
/**
* nicht zugelassen
*/
RSASSA_PKCS_1_V1_5("18"),
/**
* Nur für Verschlüsselung erlaubt
*/
RSAES_PKCS_1_V1_5("18"),
/**
* Nur für Signatur erlaubt
*/
RSASSA_PSS("19"),
/**
* nicht zugelassen
*/
Gegenseitig_vereinbart("999")
}

View File

@ -0,0 +1,29 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Information über den Operationsmodus für den jeweils verwendeten Kryptoalgorithmus
* (zur Signaturbildung oder zur Verschlüsselung).
*
* Codierung:
* Siehe S. 102 oder [Operationsmodus]
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.6 DEG Signaturalgorithmus, S. 58):
*
* Operationsmodus, kodiert
* FinTS-Füllwert, z. B. 16
*/
open class OperationsmodusKodiert(mode: Operationsmodus) : Code(mode.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<Operationsmodus>()
val FinTsMockValue = Operationsmodus.ISO_9796_1
}
}

View File

@ -0,0 +1,7 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
open class PinOrTan(pinOrTan: String) : AlphanumerischesDatenelement(pinOrTan, Existenzstatus.Mandatory)

View File

@ -0,0 +1,27 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Der Inhalt dieses Feldes sollte derzeit nicht ausgewertet werden. Optional können aber
* die nachfolgenden Festlegungen angewendet werden, sofern dies zwischen Kunde und
* Kreditinstitut zuvor vereinbart wurde:
*
* 1. Dialoginitialisierung und -ende:
* Die Rolle wird durch den Dialogführenden bestimmt. Es ist nur eine Signatur erlaubt.
* Erlaubt ist nur der Wert ISS/wert12.
*
* 2. Auftragsnachricht:
* Grundsätzlich gilt: Sobald die Rolle WIT verwendet wird, muss dieser Benutzer mit der
* Benutzerkennung aus der Dialoginitialisierung arbeiten. Auch der Benutzer WIT muss
* bankseitig entsprechend der Auftragsart am Konto des Benutzers ISS berechtigt sein.
* Die Reihenfolge der Signaturen ist beliebig.
*
* [Tabelle mit Werten]
*
* Auch bei Belegung dieses Feldes kann das Kundenprodukt nicht davon ausgehen, dass das
* Feld kreditinstitutsseitig ausgewertet wird.
*/
open class RolleDesSicherheitslieferantenKodiert : AlphanumerischesDatenelement("1", Existenzstatus.Mandatory, 3)

View File

@ -0,0 +1,14 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class Schluesselart(override val code: String) : ICodeEnum {
SchluesselZurErzeugungDigitalerSignaturen("D"),
Signierschluessel("S"),
Chiffrierschluessel("V")
}

View File

@ -0,0 +1,33 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Information über die Art des Schlüssels.
*
* Beim Sicherheitsverfahren RAH steht die Schlüsselart in engem Zusammenhang mit dem
* Datenelement "Verwendungszweck für öffentlichen Schlüssel". Die Inhalte beider
* Datenelemente sind konsistent zu halten.
*
* Codierung:
* D: Schlüssel zur Erzeugung digitaler Signaturen (DS-Schlüssel)
* S: Signierschlüssel
* V: Chiffrierschlüssel
*
* Der DS-Schlüssel steht nur im Zusammenhang mit einer Bankensignaturkarte zur Verfügung.
*
* Im Falle der Bankensignaturkarte ergibt sich folgende Zuordnung zu den Kartenschlüsseln:
* - DS-Schlüssel: SK.CH.DS
* - Signierschlüssel: SK.CH.AUT
* - Chiffrierschlüssel: SK.CH.KE
*/
open class SchluesselartDatenelement(key: Schluesselart) : Code(key.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<Schluesselart>()
}
}

View File

@ -0,0 +1,16 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Schlüsselnummer des entsprechenden Schlüssels.
*/
open class Schluesselnummer(number: Int) : NumerischesDatenelement(number, 3, Existenzstatus.Mandatory) {
companion object {
const val FinTsMockValue = 0
}
}

View File

@ -0,0 +1,16 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Versionsnummer des entsprechenden Schlüssels.
*/
open class Schluesselversion(version: Int) : NumerischesDatenelement(version, 3, Existenzstatus.Mandatory) {
companion object {
const val FinTsMockValue = 0
}
}

View File

@ -0,0 +1,214 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class Sicherheitsfunktion(override val code: String) : ICodeEnum {
RAH_7_S("2"),
RAH_7_D("1"),
RAH_9_S("2"),
RAH_10_S("2"),
PIN_TAN_900("900"),
PIN_TAN_901("901"),
PIN_TAN_902("902"),
PIN_TAN_903("903"),
PIN_TAN_904("904"),
PIN_TAN_905("905"),
PIN_TAN_906("906"),
PIN_TAN_907("907"),
PIN_TAN_908("908"),
PIN_TAN_909("909"),
PIN_TAN_910("910"),
PIN_TAN_911("911"),
PIN_TAN_912("912"),
PIN_TAN_913("913"),
PIN_TAN_914("914"),
PIN_TAN_915("915"),
PIN_TAN_916("916"),
PIN_TAN_917("917"),
PIN_TAN_918("918"),
PIN_TAN_919("919"),
PIN_TAN_920("920"),
PIN_TAN_921("921"),
PIN_TAN_922("922"),
PIN_TAN_923("923"),
PIN_TAN_924("924"),
PIN_TAN_925("925"),
PIN_TAN_926("926"),
PIN_TAN_927("927"),
PIN_TAN_928("928"),
PIN_TAN_929("929"),
PIN_TAN_930("930"),
PIN_TAN_931("931"),
PIN_TAN_932("932"),
PIN_TAN_933("933"),
PIN_TAN_934("934"),
PIN_TAN_935("935"),
PIN_TAN_936("936"),
PIN_TAN_937("937"),
PIN_TAN_938("938"),
PIN_TAN_939("939"),
PIN_TAN_940("940"),
PIN_TAN_941("941"),
PIN_TAN_942("942"),
PIN_TAN_943("943"),
PIN_TAN_944("944"),
PIN_TAN_945("945"),
PIN_TAN_946("946"),
PIN_TAN_947("947"),
PIN_TAN_948("948"),
PIN_TAN_949("949"),
PIN_TAN_950("950"),
PIN_TAN_951("951"),
PIN_TAN_952("952"),
PIN_TAN_953("953"),
PIN_TAN_954("954"),
PIN_TAN_955("955"),
PIN_TAN_956("956"),
PIN_TAN_957("957"),
PIN_TAN_958("958"),
PIN_TAN_959("959"),
PIN_TAN_960("960"),
PIN_TAN_961("961"),
PIN_TAN_962("962"),
PIN_TAN_963("963"),
PIN_TAN_964("964"),
PIN_TAN_965("965"),
PIN_TAN_966("966"),
PIN_TAN_967("967"),
PIN_TAN_968("968"),
PIN_TAN_969("969"),
PIN_TAN_970("970"),
PIN_TAN_971("971"),
PIN_TAN_972("972"),
PIN_TAN_973("973"),
PIN_TAN_974("974"),
PIN_TAN_975("975"),
PIN_TAN_976("976"),
PIN_TAN_977("977"),
PIN_TAN_978("978"),
PIN_TAN_979("979"),
PIN_TAN_980("980"),
PIN_TAN_981("981"),
PIN_TAN_982("982"),
PIN_TAN_983("983"),
PIN_TAN_984("984"),
PIN_TAN_985("985"),
PIN_TAN_986("986"),
PIN_TAN_987("987"),
PIN_TAN_988("988"),
PIN_TAN_989("989"),
PIN_TAN_990("990"),
PIN_TAN_991("991"),
PIN_TAN_992("992"),
PIN_TAN_993("993"),
PIN_TAN_994("994"),
PIN_TAN_995("995"),
PIN_TAN_996("996"),
PIN_TAN_997("997"),
PIN_TAN_Einschritt_Verfahren("999")
}

View File

@ -0,0 +1,31 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Abhängig von Sicherheitsprofil und Schlüsseltyp und HBCI-Version ist folgender Wert einzustellen:
*
* | Sicherheitsprofil | Schlüsseltyp | ab FinTS V3.0 |
* | RAH-7 | S | 2 |
* | RAH-7 | D | 1 |
* | RAH-9 | S | 2 |
* | RAH-10 | S | 2 |
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.4 Segment Signaturkopf, S. 58):
*
* Sicherheitsfunktion, kodiert
* Beim Ein-Schritt-Verfahren ist der Wert 999 einzustellen, beim Zwei-Schritt-Verfahren der entsprechende
* in der BPD mitgeteilte Wert für das konkrete Verfahren 900 bis 997 (vgl. Kapitel B.8.2).
*/
open class SicherheitsfunktionKodiert(securityFunction: Sicherheitsfunktion)
: Code(securityFunction.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<Sicherheitsfunktion>()
}
}

View File

@ -0,0 +1,12 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Referenzinformation, mit der die Verbindung zwischen Signaturkopf und dazu gehörigem
* Signaturabschluss hergestellt werden kann. Die Sicherheitskontrollreferenz im
* Signaturkopf muss mit der entsprechenden Information im Signaturabschluss übereinstimmen.
*/
open class Sicherheitskontrollreferenz(reference: String) : AlphanumerischesDatenelement(reference, Existenzstatus.Mandatory, 14)

View File

@ -0,0 +1,18 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Sicherheitsrelevante Nachrichtenidentifikation (Signatur-ID), welche zur Verhinderung der
* Doppeleinreichung, respektive Garantie der Nachrichtensequenzintegrität eingesetzt werden kann.
*
* Bei chipkartenbasierten Verfahren ist der Sequenzzähler der Chipkarte einzustellen. Dies ist
* bei Typ-1 Karten der Wert EF_SEQ in der Application DF_BANKING und bei SECCOS Banken-
* Signaturkarten der Wert usage counter der beiden Signierschlüssel SK.CH.DS und SK.CH.AUT.
*
* Bei softwarebasierten Verfahren wird die Sicherheitsreferenznummer auf Basis des DE
* Kundensystem-ID und des DE Benutzerkennung der DEG Schlüsselnamen verwaltet.
*/
open class Sicherheitsreferenznummer(number: Int) : NumerischesDatenelement(number, 16, Existenzstatus.Mandatory)

View File

@ -0,0 +1,12 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class Sicherheitsverfahren(override val code: String) : ICodeEnum {
RSA_AES_Hybridverfahren("RAH"),
PIN_TAN_Verfahren("PIN")
}

View File

@ -0,0 +1,23 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.fints.messages.datenelemente.implementierte.allCodes
/**
* Code des unterstützten Signatur- bzw. Verschlüsselungsalgorithmus.
*
* Weitere Informationen zu den Verfahren sind Kapitel B.1 zu entnehmen.
*
* Codierung:
* - RAH: RSA-AES-Hybridverfahren
* - PIN: PIN/TAN-Verfahren
*/
open class SicherheitsverfahrenCode(method: Sicherheitsverfahren) : Code(method.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<Sicherheitsverfahren>()
}
}

View File

@ -0,0 +1,12 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.datenelemente.implementierte.ICodeEnum
enum class Signaturalgorithmus(override val code: String) : ICodeEnum {
NichtZugelassen("1"),
RSA_Algorithmus("10")
}

View File

@ -0,0 +1,30 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
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 Signaturalgorithmus.
*
* Codierung:
* 1: nicht zugelassen
* 10: RSA-Algorithmus (bei RAH)
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.6 DEG Signaturalgorithmus, S. 58):
*
* Signaturalgorithmus, kodiert
* FinTS-Füllwert, z. B. 10
*/
open class SignaturalgorithmusKodiert(algorithm: Signaturalgorithmus)
: Code(algorithm.code, AllowedValues, Existenzstatus.Mandatory) {
companion object {
val AllowedValues = allCodes<Signaturalgorithmus>()
val FinTsMockValue = Signaturalgorithmus.RSA_Algorithmus
}
}

View File

@ -0,0 +1,16 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
enum class VersionDesSicherheitsverfahrens(val methodNumber: Int) {
PIN_Ein_Schritt(1),
PIN_Zwei_Schritt(2),
RAH_7(7),
RAH_9(9),
RAH_10(10)
}

View File

@ -0,0 +1,33 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Version des unterstützten Sicherheitsverfahrens (s. Sicherheitsverfahren, Code).
*
* In Kombination mit dem Sicherheitsverfahren RAH sind die folgenden Versionen gültig:
*
* | Version | Signaturverfahren | Schlüssellänge (bit) | Hashverfahren | Schlüsselart* |
* | 7 | PKCS#1 PSS | ..2048 | SHA-256 | D, S, V |
* | 9 | PKCS#1 PSS | ..2048 | SHA-256 | S, V |
* | 10 | PKCS#1 PSS | ..2048 | SHA-256 | S, V |
*
* *s. Element Schlüsselart
*
* Andere als die genannten Profile sind nicht zulässig.
*
* Um Multibankfähigkeit zu gewährleisten, ist die Unterstützung des Verfahrens RAH-9 kundenund
* kreditisinstitutsseitig verpflichtend.
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.1 DEG Sicherheitsprofil, S. 58):
*
* Version des Sicherheitsverfahrens
* - 1 : bei allen Nachrichten, wenn Dialog im Einschritt-Verfahren
* - 2 : bei allen Nachrichten, wenn Dialog im Zwei-Schritt-Verfahren
*/
open class VersionDesSicherheitsverfahrensDatenelement(version: VersionDesSicherheitsverfahrens)
: NumerischesDatenelement(version.methodNumber, 3, Existenzstatus.Mandatory)

View File

@ -0,0 +1,15 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Code
/**
* Kodierte Information über die Verwendung des Hashalgorithmus.
*
* Im Zusammenhang mit Hash-Funktionen ist derzeit nur folgender Wert möglich:
*
* Codierung:
* 1: Owner Hashing (OHA)
*/
open class VerwendungDesHashalgorithmusKodiert: Code("1", listOf("1"), Existenzstatus.Mandatory)

View File

@ -0,0 +1,15 @@
package net.dankito.fints.messages.datenelemente.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Kodierte Information über die Verwendung des Signaturalgorithmus.
*
* Im Zusammenhang mit Signaturbildung ist derzeit nur folgender Wert möglich:
*
* Codierung:
* 6: Owner Signing (OSG)
*/
open class VerwendungDesSignaturalgorithmusKodiert : AlphanumerischesDatenelement("6", Existenzstatus.Mandatory, 3)

View File

@ -9,8 +9,8 @@ import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
open class Segmentkopf @JvmOverloads constructor(
identifier: String,
segmentNumber: Int,
segmentVersion: Int,
segmentNumber: Int = 0,
bezugssegment: Int? = null
) : Datenelementgruppe(listOf(

View File

@ -0,0 +1,36 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.signatur.PinOrTan
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
/**
* Bei nicht-schlüsselbasierten Sicherheitsverfahren kann der Benutzer hier Angaben
* zur Authentisierung machen. Ob das Feld verpflichtend ist, ist vom jeweiligen
* Sicherheitsverfahren abhängig.
*
* Format: s. Spezifikation Sicherheitsverfahren PIN/TAN
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.7 Segment Signaturabschluss, S. 59)
*
* Es ist der Signaturabschluss gemäß [HBCI] ab Segmentversion 2 zu verwenden.
*
* Validierungsresultat
* Dieses Feld darf nicht belegt werden.
*
* Benutzerdefinierte Signatur
* Hier werden bei Verwendung des PIN/TAN-Verfahrens PIN und TAN eingestellt. Bei
* Verwendung des Zwei-Schritt-Verfahrens mit Prozessvariante 2 darf eine TAN
* ausschließlich über den Geschäftsvorfall HKTAN eingereicht werden, wobei pro
* HKTAN nur die Verarbeitung einer einzelnen TAN zulässig ist. Ansonsten darf die
* DE TAN im Signaturabschluss nicht belegt werden; ihr Inhalt wird in diesem Fall
* ignoriert und die TAN vom Institut entwertet. Gleiches gilt bei der nicht
* zulässigen Übermittlung von mehreren TANs mit HKTAN. Bei der Verwendung im Rahmen
* des Sicherheitsverfahrens HBCI darf die DEG nicht belegt werden. Ihr Inhalt wird
* in diesem Fall ignoriert.
*/
open class BenutzerdefinierteSignatur(pinOrTan: String)
: Datenelementgruppe(listOf(
PinOrTan(pinOrTan)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,25 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.signatur.BezeichnerFuerHashalgorithmusparameter
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Hashalgorithmus
import net.dankito.fints.messages.datenelemente.implementierte.signatur.HashalgorithmusKodiert
import net.dankito.fints.messages.datenelemente.implementierte.signatur.VerwendungDesHashalgorithmusKodiert
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
/**
* Angaben zu einem kryptographischen Algorithmus, seinen Operationsmodus, sowie dessen Einsatz.
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.5 DEG Hashalgorithmus, S. 58):
*
* Wert des Hashalgorithmusparameters
* Dieses Feld darf nicht belegt werden.
*/
open class HashalgorithmusDatenelementgruppe
: Datenelementgruppe(listOf(
VerwendungDesHashalgorithmusKodiert(),
HashalgorithmusKodiert(Hashalgorithmus.Gegenseitig_vereinbart), // allowed: 3, 4, 5, 6
BezeichnerFuerHashalgorithmusparameter()
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,46 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.Benutzerkennung
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Schluesselart
import net.dankito.fints.messages.datenelemente.implementierte.signatur.SchluesselartDatenelement
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Schluesselnummer
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Schluesselversion
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
import net.dankito.fints.messages.datenelementgruppen.implementierte.Kreditinstitutskennung
/**
* Verwendeter Schlüsselname in strukturierter Form. Mit dieser Information kann die Referenz
* auf einen Schlüssel hergestellt werden.
*
* Dabei enthält das DE Benutzerkennung bei Schlüsseln des Kunden die Benutzerkennung, mit
* der der Kunde eindeutig identifiziert wird. Bei Schlüsseln des Kreditinstituts ist dagegen
* eine beliebige Kennung einzustellen, die dazu dient, den Kreditinstitutsschlüssel eindeutig
* zu identifizieren. Diese Kennung darf weder einer anderen gültigen Benutzerkennung des
* Kreditinstituts noch der Benutzerkennung für den anonymen Zugang entsprechen.
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.2 DEG Schlüsselname, S. 58):
*
* Schlüsselnummer
* FinTS-Füllwert, z. B. 0
*
* Schlüsselversion
* FinTS-Füllwert, z. B. 0
*/
open class Schluesselname(
bankCountryCode: Int,
bankCode: String,
userIdentification: String,
key: Schluesselart,
keyNumber: Int,
keyVersion: Int
)
: Datenelementgruppe(listOf(
Kreditinstitutskennung(bankCountryCode, bankCode),
Benutzerkennung(userIdentification),
SchluesselartDatenelement(key),
Schluesselnummer(keyNumber),
Schluesselversion(keyVersion)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,16 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte.signatur
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.datenelemente.implementierte.signatur.DatumUndZeitbezeichner
import net.dankito.fints.messages.datenelemente.implementierte.signatur.DatumUndZeitbezeichnerKodiert
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),
Uhrzeit(time, Existenzstatus.Optional)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,32 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.NotAllowedDatenelement
import net.dankito.fints.messages.datenelemente.implementierte.signatur.BezeichnerFuerSicherheitspartei
import net.dankito.fints.messages.datenelemente.implementierte.signatur.IdentifizierungDerPartei
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
/**
* Identifikation der im Sicherheitsprozess involvierten Parteien. Dient zur Übermittlung
* der CID bei kartenbasierten Sicherheitsverfahren bzw. der Kundensystem-ID bei
* softwarebasierten Verfahren (z. B. Speicherung der Schlüssel in einer Schlüsseldatei).
*
* Wenn eine Synchronisierung der Kundensystem-ID durchgeführt wird, ist als Identifizierung der Partei 0 einzustellen.
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.3 DEG Sicherheitsidentifikation, Details, S. 58):
*
* CID
* Dieses Feld darf nicht belegt werden.
*
* Identifizierung der Partei
* Dieses Feld muss eine gültige, zuvor vom Banksystem angeforderte Kundensystem-ID enthalten
* (analog zu RAH-/RDH-Verfahren). Dies gilt auch für Zweit- und Drittsignaturen.
*/
open class SicherheitsidentifikationDetails(partyIdentification: String)
: Datenelementgruppe(listOf(
BezeichnerFuerSicherheitspartei(),
NotAllowedDatenelement(),
IdentifizierungDerPartei(partyIdentification)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,33 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsverfahren
import net.dankito.fints.messages.datenelemente.implementierte.signatur.SicherheitsverfahrenCode
import net.dankito.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens
import net.dankito.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrensDatenelement
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
/**
* Verfahren zur Absicherung der Transaktionen, das zwischen Kunde und Kreditinstitut
* vereinbar wurde. Das Sicherheitsprofil wird anhand der Kombination der beiden Elemente
* Sicherheitsverfahren und Version bestimmt (z. B. RDH-9). Für das Sicherheitsverfahren
* PINTAN ist als Code der Wert PIN und als Version der Wert 1 einzustellen.
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.1 DEG Sicherheitsprofil, S. 58):
*
* Sicherheitsverfahren, Code
* - PIN : bei allen Nachrichten
*
* Version des Sicherheitsverfahrens
* - 1 : bei allen Nachrichten, wenn Dialog im Einschritt-Verfahren
* - 2 : bei allen Nachrichten, wenn Dialog im Zwei-Schritt-Verfahren
*/
open class Sicherheitsprofil(
method: Sicherheitsverfahren,
version: VersionDesSicherheitsverfahrens
) : Datenelementgruppe(listOf(
SicherheitsverfahrenCode(method),
VersionDesSicherheitsverfahrensDatenelement(version)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,26 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte.signatur
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.signatur.*
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 Signaturbildung über RAH.
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.6 DEG Signaturalgorithmus, S. 58):
*
* Signaturalgorithmus, kodiert
* FinTS-Füllwert, z. B. 10
*
* Operationsmodus, kodiert
* FinTS-Füllwert, z. B. 16
*/
open class SignaturalgorithmusDatenelementgruppe (algorithm: Signaturalgorithmus, mode: Operationsmodus)
: Datenelementgruppe(listOf(
VerwendungDesSignaturalgorithmusKodiert(),
SignaturalgorithmusKodiert(algorithm),
OperationsmodusKodiert(mode)
), Existenzstatus.Mandatory)

View File

@ -1,6 +1,7 @@
package net.dankito.fints.messages.nachrichten.implementierte
import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte
import net.dankito.fints.messages.datenelemente.implementierte.Nachrichtennummer.Companion.FirstMessageNumber
import net.dankito.fints.messages.nachrichten.Nachricht
import net.dankito.fints.messages.segmente.implementierte.IdentifikationsSegment
@ -23,7 +24,7 @@ open class Dialoginitialisierung(
)
: Nachricht(listOf(
Nachrichtenkopf(1, messageSize, "0", FirstMessageNumber),
IdentifikationsSegment(2, bankCountryCode, bankCode, customerId, customerSystemId),
IdentifikationsSegment(2, bankCountryCode, bankCode, customerId, customerSystemId, KundensystemStatusWerte.NichtBenoetigt), // TODO: KundensystemStatusWerte
Verarbeitungsvorbereitung(3, bpdVersion, updVersion, language, productName, productVersion),
Nachrichtenabschluss(4, FirstMessageNumber)
))

View File

@ -1,9 +1,10 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.FinTsKundensystemStatus
import net.dankito.fints.messages.datenelemente.implementierte.KundenID
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemID
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatus
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte
import net.dankito.fints.messages.datenelementgruppen.implementierte.Kreditinstitutskennung
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.segmente.Segment
@ -14,12 +15,13 @@ open class IdentifikationsSegment(
bankCountryCode: Int,
bankCode: String,
customerId: String,
customerSystemId: String
customerSystemId: String,
status: KundensystemStatusWerte
) : Segment(listOf(
Segmentkopf("HKIDN", segmentNumber, 2),
Segmentkopf("HKIDN", 2, segmentNumber),
Kreditinstitutskennung(bankCountryCode, bankCode),
KundenID(customerId),
KundensystemID(customerSystemId),
FinTsKundensystemStatus()
KundensystemStatus(status, Existenzstatus.Mandatory)
), Existenzstatus.Mandatory)

View File

@ -9,10 +9,10 @@ import net.dankito.fints.messages.segmente.Segment
/**
* Dieses Segment beendet alle Kunden- und Kreditinstitutsnachrichten.
*/
class Nachrichtenabschluss(
open class Nachrichtenabschluss(
segmentNumber: Int,
messageNumber: Int
) : Segment(listOf(
Segmentkopf("HNHBS", segmentNumber, 1),
Segmentkopf("HNHBS", 1, segmentNumber),
Nachrichtennummer(messageNumber)
), Existenzstatus.Mandatory)

View File

@ -13,7 +13,7 @@ open class Nachrichtenkopf(
messageNumber: Int
) : Segment(listOf(
Segmentkopf("HNHBK", segmentNumber, 3),
Segmentkopf("HNHBK", 3, segmentNumber),
Nachrichtengroesse(messageSize),
HbciVersionDatenelement(HbciVersion.FinTs_3_0_0),
DialogId(dialogId),

View File

@ -0,0 +1,36 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.datenelemente.implementierte.signatur.*
class PinTanSignaturkopf(
segmentNumber: Int,
securityFunction: Sicherheitsfunktion,
securityControlReference: String,
/**
* Wenn eine Synchronisierung der Kundensystem-ID durchgeführt wird, ist als Identifizierung der Partei 0 einzustellen.
*/
partyIdentification: String,
date: Int,
time: Int,
bankCountryCode: Int,
bankCode: String,
userIdentification: String
) : Signaturkopf(
segmentNumber,
Sicherheitsverfahren.PIN_TAN_Verfahren,
VersionDesSicherheitsverfahrens.PIN_Zwei_Schritt,
securityFunction,
securityControlReference,
partyIdentification,
date,
time,
SignaturalgorithmusKodiert.FinTsMockValue,
OperationsmodusKodiert.FinTsMockValue,
bankCountryCode,
bankCode,
userIdentification,
Schluesselnummer.FinTsMockValue,
Schluesselversion.FinTsMockValue
)

View File

@ -0,0 +1,25 @@
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.signatur.Sicherheitskontrollreferenz
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.datenelementgruppen.implementierte.signatur.BenutzerdefinierteSignatur
import net.dankito.fints.messages.segmente.Segment
/**
* Der Signaturabschluss stellt die Verbindung mit dem dazugehörigen Signaturkopf
* her und enthält als "Validierungsresultat" die elektronische Signatur.
*/
open class Signaturabschluss(
segmentNumber: Int,
securityControlReference: String,
pinOrTan: String
)
: Segment(listOf(
Segmentkopf("HNSHA", 2, segmentNumber),
Sicherheitskontrollreferenz(securityControlReference), // has to be the same as in Signaturkopf
NotAllowedDatenelement(), // only used for HBCI, not allowed for PIN/TAN
BenutzerdefinierteSignatur(pinOrTan)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,56 @@
package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.signatur.*
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.datenelementgruppen.implementierte.signatur.*
import net.dankito.fints.messages.segmente.Segment
/**
* Der Signaturkopf enthält Informationen über den damit verbundenen Sicherheitsservice, sowie über den Absender.
*
*
* Abweichende Belegung für PIN/TAN Verfahren (Dokument Sicherheitsverfahren PIN/TAN, B.9.4 Segment Signaturkopf, S. 58):
*
* Sicherheitsfunktion, kodiert
* Beim Ein-Schritt-Verfahren ist der Wert 999 einzustellen, beim Zwei-Schritt-Verfahren der entsprechende
* in der BPD mitgeteilte Wert für das konkrete Verfahren 900 bis 997 (vgl. Kapitel B.8.2).
*
* Zertifikat
* Dieses Feld darf nicht belegt werden.
*/
open class Signaturkopf(
segmentNumber: Int,
method: Sicherheitsverfahren,
version: VersionDesSicherheitsverfahrens,
securityFunction: Sicherheitsfunktion,
securityControlReference: String,
/**
* Wenn eine Synchronisierung der Kundensystem-ID durchgeführt wird, ist als Identifizierung der Partei 0 einzustellen.
*/
partyIdentification: String,
date: Int,
time: Int,
algorithm: Signaturalgorithmus,
mode: Operationsmodus,
bankCountryCode: Int,
bankCode: String,
userIdentification: String,
keyNumber: Int,
keyVersion: Int
) : Segment(listOf(
Segmentkopf("HNSHK", 4, segmentNumber), // allowed
Sicherheitsprofil(method, version), // allowed: method: RAH, PIN;
SicherheitsfunktionKodiert(securityFunction), // allowed: 1, 2
Sicherheitskontrollreferenz(securityControlReference), // allowed: <>0
BereichDerSicherheitsapplikationKodiert(BereichDerSicherheitsapplikation.SignaturkopfUndHBCINutzdaten), // allowed: 1 ?
RolleDesSicherheitslieferantenKodiert(), // allowed: 1
SicherheitsidentifikationDetails(partyIdentification),
Sicherheitsreferenznummer(1), // TODO: is this always 1?
SicherheitsdatumUndUhrzeit(date, time),
HashalgorithmusDatenelementgruppe(),
SignaturalgorithmusDatenelementgruppe(algorithm, mode),
Schluesselname(bankCountryCode, bankCode, userIdentification, Schluesselart.Signierschluessel, keyNumber, keyVersion)
), Existenzstatus.Mandatory)

View File

@ -6,7 +6,7 @@ import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.segmente.Segment
class Verarbeitungsvorbereitung(
open class Verarbeitungsvorbereitung(
segmentNumber: Int,
bpdVersion: Int,
updVersion: Int,
@ -14,7 +14,7 @@ class Verarbeitungsvorbereitung(
productName: String,
productVersion: String
) : Segment(listOf(
Segmentkopf("HKVVB", segmentNumber, 3),
Segmentkopf("HKVVB", 3, segmentNumber),
BPDVersion(bpdVersion, Existenzstatus.Mandatory),
UPDVersion(updVersion, Existenzstatus.Mandatory),
DialogspracheDatenelement(language, Existenzstatus.Mandatory),

View File

@ -0,0 +1,56 @@
package net.dankito.fints.util
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Datum
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Uhrzeit
import java.text.SimpleDateFormat
import java.util.*
open class FinTsUtils {
companion object {
val HbciDateFormat = SimpleDateFormat(Datum.HbciDateFormat)
val HbciTimeFormat = SimpleDateFormat(Uhrzeit.HbciTimeFormat)
}
open fun formatDateToday(): String {
return formatDate(Date())
}
open fun formatDate(date: Date): String {
return HbciDateFormat.format(date)
}
open fun formatDateTodayAsInt(): Int {
return convertToInt(formatDateToday())
}
open fun formatDateAsInt(date: Date): Int {
return convertToInt(formatDate(date))
}
open fun formatTimeNow(): String {
return formatTime(Date())
}
open fun formatTime(time: Date): String {
return HbciTimeFormat.format(time)
}
open fun formatTimeNowAsInt(): Int {
return convertToInt(formatTimeNow())
}
open fun formatTimeAsInt(time: Date): Int {
return convertToInt(formatTime(time))
}
protected fun convertToInt(string: String): Int {
return string.toInt()
}
}

View File

@ -2,6 +2,7 @@ package net.dankito.fints.messages.segmente.implementierte
import net.dankito.fints.messages.datenelemente.implementierte.KundenID
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 org.assertj.core.api.Assertions.assertThat
import org.junit.Test
@ -13,7 +14,7 @@ class IdentifikationsSegmentTest {
fun format() {
// given
val underTest = IdentifikationsSegment(2, Laenderkennzeichen.Germany, "12345678", KundenID.Anonymous, KundensystemID.Anonymous)
val underTest = IdentifikationsSegment(2, Laenderkennzeichen.Germany, "12345678", KundenID.Anonymous, KundensystemID.Anonymous, KundensystemStatusWerte.NichtBenoetigt)
// when
val result = underTest.format()

View File

@ -0,0 +1,25 @@
package net.dankito.fints.messages.segmente.implementierte
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class SignaturabschlussTest {
@Test
fun format() {
// given
val segmentNumber = 7
val controlReference = "1902675680"
val pin = "MyPin"
val underTest = Signaturabschluss(segmentNumber, controlReference, pin)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HNSHA:$segmentNumber:2+$controlReference++$pin")
}
}

View File

@ -0,0 +1,36 @@
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 net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class SignaturkopfTest {
@Test
fun format() {
// given
val securityFunction = Sicherheitsfunktion.PIN_TAN_911
val controlReference = "1902675680"
val partyIdentification = IdentifizierungDerPartei.SynchronizingCustomerSystemId
val date = 20191002
val time = 212757
val bankCode = "12345678"
val customerId = "0987654321"
val keyNumber = 0
val keyVersion = 0
val underTest = PinTanSignaturkopf(2, securityFunction, controlReference, partyIdentification,
date, time, Laenderkennzeichen.Germany, bankCode, customerId)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("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:$keyNumber:$keyVersion")
}
}

View File

@ -0,0 +1,91 @@
package net.dankito.fints.util
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.util.*
class FinTsUtilsTest {
private val underTest = FinTsUtils()
@Test
fun formatDate() {
// given
val date = Date(88, 2, 27)
// when
val result = underTest.formatDate(date)
// then
assertThat(result).isEqualTo("19880327")
}
@Test
fun formatDateAsInt() {
// given
val date = Date(88, 2, 27)
// when
val result = underTest.formatDateAsInt(date)
// then
assertThat(result).isEqualTo(19880327)
}
@Test
fun formatTime_AM() {
// given
val date = Date(119, 9, 1, 8, 2, 1)
// when
val result = underTest.formatTime(date)
// then
assertThat(result).isEqualTo("080201")
}
@Test
fun formatTime_PM() {
// given
val date = Date(119, 9, 1, 18, 22, 51)
// when
val result = underTest.formatTime(date)
// then
assertThat(result).isEqualTo("182251")
}
@Test
fun formatTimeAsInt_AM() {
// given
val date = Date(119, 9, 1, 8, 2, 1)
// when
val result = underTest.formatTimeAsInt(date)
// then
assertThat(result).isEqualTo(80201)
}
@Test
fun formatTimeAsInt_PM() {
// given
val date = Date(119, 9, 1, 18, 22, 51)
// when
val result = underTest.formatTimeAsInt(date)
// then
assertThat(result).isEqualTo(182251)
}
}