/*
* This file is part of LibEuFin.
* Copyright (C) 2024 Taler Systems S.A.
* LibEuFin is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3, or
* (at your option) any later version.
* LibEuFin is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
* Public License for more details.
* You should have received a copy of the GNU Affero General Public
* License along with LibEuFin; see the file COPYING. If not, see
*
*/
package tech.libeufin.nexus.ebics
import org.w3c.dom.Document
import tech.libeufin.common.crypto.CryptoUtil
import tech.libeufin.common.*
import tech.libeufin.nexus.*
import tech.libeufin.nexus.BankPublicKeysFile
import tech.libeufin.nexus.ClientPrivateKeysFile
import tech.libeufin.nexus.EbicsSetupConfig
import java.io.InputStream
import java.time.Instant
import java.time.ZoneId
import java.util.*
import javax.xml.datatype.DatatypeFactory
import java.security.interfaces.*
/** EBICS protocol for key management */
class Ebics3KeyMng(
private val cfg: EbicsSetupConfig,
private val clientKeys: ClientPrivateKeysFile
) {
fun INI(): ByteArray {
val inner = XMLOrderData(cfg, "ns2:SignaturePubKeyOrderData", "http://www.ebics.org/S001") {
el("ns2:SignaturePubKeyInfo") {
RSAKeyXml(clientKeys.signature_private_key)
el("ns2:SignatureVersion", "A006")
}
}
val doc = request("ebicsUnsecuredRequest") {
el("header") {
attr("authenticate", "true")
el("static") {
el("HostID", cfg.ebicsHostId)
el("PartnerID", cfg.ebicsPartnerId)
el("UserID", cfg.ebicsUserId)
el("OrderDetails") {
el("OrderType", "INI")
el("OrderAttribute", "DZNNN")
}
el("SecurityMedium", "0200")
}
el("mutable")
}
el("body/DataTransfer/OrderData", inner)
}
return XMLUtil.convertDomToBytes(doc)
}
fun HIA(): ByteArray {
val inner = XMLOrderData(cfg, "ns2:HIARequestOrderData", "urn:org:ebics:H004") {
el("ns2:AuthenticationPubKeyInfo") {
RSAKeyXml(clientKeys.authentication_private_key)
el("ns2:AuthenticationVersion", "X002")
}
el("ns2:EncryptionPubKeyInfo") {
RSAKeyXml(clientKeys.encryption_private_key)
el("ns2:EncryptionVersion", "E002")
}
}
val doc = request("ebicsUnsecuredRequest") {
el("header") {
attr("authenticate", "true")
el("static") {
el("HostID", cfg.ebicsHostId)
el("PartnerID", cfg.ebicsPartnerId)
el("UserID", cfg.ebicsUserId)
el("OrderDetails") {
el("OrderType", "HIA")
el("OrderAttribute", "DZNNN")
}
el("SecurityMedium", "0200")
}
el("mutable")
}
el("body/DataTransfer/OrderData", inner)
}
return XMLUtil.convertDomToBytes(doc)
}
fun HPB(): ByteArray {
val nonce = getNonce(128)
val doc = request("ebicsNoPubKeyDigestsRequest") {
el("header") {
attr("authenticate", "true")
el("static") {
el("HostID", cfg.ebicsHostId)
el("Nonce", nonce.encodeUpHex())
el("Timestamp", Instant.now().xmlDateTime())
el("PartnerID", cfg.ebicsPartnerId)
el("UserID", cfg.ebicsUserId)
el("OrderDetails") {
el("OrderType", "HPB")
el("OrderAttribute", "DZHNN")
}
el("SecurityMedium", "0000")
}
el("mutable")
}
el("AuthSignature")
el("body")
}
XMLUtil.signEbicsDocument(doc, clientKeys.authentication_private_key, "H004")
return XMLUtil.convertDomToBytes(doc)
}
/* ----- Helpers ----- */
private fun request(name: String, build: XmlBuilder.() -> Unit): Document {
return XmlBuilder.toDom(name, "urn:org:ebics:H004") {
attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:H004")
attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#")
attr("Version", "H004")
attr("Revision", "1")
build()
}
}
private fun XmlBuilder.RSAKeyXml(key: RSAPrivateCrtKey) {
el("ns2:PubKeyValue") {
el("ds:RSAKeyValue") {
el("ds:Modulus", key.modulus.encodeBase64())
el("ds:Exponent", key.publicExponent.encodeBase64())
}
}
}
private fun XMLOrderData(cfg: EbicsSetupConfig, name: String, schema: String, build: XmlBuilder.() -> Unit): String {
return XmlBuilder.toBytes(name) {
attr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#")
attr("xmlns:ns2", schema)
build()
el("ns2:PartnerID", cfg.ebicsPartnerId)
el("ns2:UserID", cfg.ebicsUserId)
}.inputStream().deflate().encodeBase64()
}
companion object {
fun parseResponse(doc: Document, clientEncryptionKey: RSAPrivateCrtKey): EbicsResponse {
return XmlDestructor.fromDoc(doc, "ebicsKeyManagementResponse") {
lateinit var technicalCode: EbicsReturnCode
lateinit var bankCode: EbicsReturnCode
var payload: InputStream? = null
one("header") {
one("mutable") {
technicalCode = EbicsReturnCode.lookup(one("ReturnCode").text())
}
}
one("body") {
bankCode = EbicsReturnCode.lookup(one("ReturnCode").text())
payload = opt("DataTransfer") {
val descriptionInfo = one("DataEncryptionInfo") {
DataEncryptionInfo(
one("TransactionKey").text().decodeBase64(),
one("EncryptionPubKeyDigest").text().decodeBase64()
)
}
val chunk = one("OrderData").text().decodeBase64()
decryptAndDecompressPayload(
clientEncryptionKey,
descriptionInfo,
listOf(chunk)
)
}
}
EbicsResponse(
technicalCode = technicalCode,
bankCode,
content = payload
)
}
}
fun parseHpbOrder(data: InputStream): Pair {
return XmlDestructor.fromStream(data, "HPBResponseOrderData") {
val authPub = one("AuthenticationPubKeyInfo").one("PubKeyValue").one("RSAKeyValue") {
CryptoUtil.loadRsaPublicKeyFromComponents(
one("Modulus").text().decodeBase64(),
one("Exponent").text().decodeBase64(),
)
}
val encPub = one("EncryptionPubKeyInfo").one("PubKeyValue").one("RSAKeyValue") {
CryptoUtil.loadRsaPublicKeyFromComponents(
one("Modulus").text().decodeBase64(),
one("Exponent").text().decodeBase64(),
)
}
Pair(authPub, encPub)
}
}
}
}