EbicsKeyMng.kt (7983B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2024-2025 Taler Systems S.A. 4 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 package tech.libeufin.ebics 21 22 import org.w3c.dom.Document 23 import tech.libeufin.common.crypto.CryptoUtil 24 import tech.libeufin.common.decodeBase64 25 import tech.libeufin.common.deflate 26 import tech.libeufin.common.encodeBase64 27 import tech.libeufin.common.encodeUpHex 28 import tech.libeufin.ebics.EbicsKeyMng.Order.* 29 import java.io.InputStream 30 import java.security.interfaces.RSAPrivateCrtKey 31 import java.security.interfaces.RSAPublicKey 32 import java.time.Instant 33 34 /** EBICS protocol for key management */ 35 class EbicsKeyMng( 36 private val cfg: EbicsHostConfig, 37 private val clientKeys: ClientPrivateKeysFile, 38 private val ebics3: Boolean 39 ) { 40 private val schema = if (ebics3) "H005" else "H004" 41 42 enum class Order { 43 INI, 44 HIA, 45 HPB 46 } 47 48 fun request(order: Order): ByteArray { 49 val (name, securityMedium, orderAttribute) = when (order) { 50 INI, HIA -> Triple("ebicsUnsecuredRequest", "0200", "DZNNN") 51 HPB -> Triple("ebicsNoPubKeyDigestsRequest", "0000", "DZHNN") 52 } 53 val data = when (order) { 54 INI -> XMLOrderData("SignaturePubKeyOrderData", "http://www.ebics.org/S00${if (ebics3) 2 else 1}") { 55 el("SignaturePubKeyInfo") { 56 RSAKeyXml(clientKeys.signature_private_key) 57 el("SignatureVersion", "A006") 58 } 59 } 60 HIA -> XMLOrderData("HIARequestOrderData", "urn:org:ebics:$schema") { 61 el("AuthenticationPubKeyInfo") { 62 RSAKeyXml(clientKeys.authentication_private_key) 63 el("AuthenticationVersion", "X002") 64 } 65 el("EncryptionPubKeyInfo") { 66 RSAKeyXml(clientKeys.encryption_private_key) 67 el("EncryptionVersion", "E002") 68 } 69 } 70 HPB -> null 71 } 72 val sign = order == HPB 73 val doc = XmlBuilder.toDom(name, "urn:org:ebics:$schema") { 74 attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:$schema") 75 attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") 76 attr("Version", schema) 77 attr("Revision", "1") 78 el("header") { 79 attr("authenticate", "true") 80 el("static") { 81 el("HostID", cfg.hostId) 82 if (order == HPB) { 83 el("Nonce", getNonce(128).encodeUpHex()) 84 el("Timestamp", Instant.now().xmlDateTime()) 85 } 86 el("PartnerID", cfg.partnerId) 87 el("UserID", cfg.userId) 88 el("OrderDetails") { 89 if (ebics3) { 90 el("AdminOrderType", order.name) 91 } else { 92 el("OrderType", order.name) 93 el("OrderAttribute", orderAttribute) 94 } 95 } 96 el("SecurityMedium", securityMedium) 97 } 98 el("mutable") 99 } 100 if (sign) el("AuthSignature") 101 el("body") { 102 if (data != null) el("DataTransfer/OrderData", data) 103 } 104 } 105 if (sign) XMLUtil.signEbicsDocument(doc, clientKeys.authentication_private_key) 106 return XMLUtil.convertDomToBytes(doc) 107 } 108 109 private fun XmlBuilder.RSAKeyXml(key: RSAPrivateCrtKey) { 110 if (ebics3) { 111 val cert = CryptoUtil.X509CertificateFromRSAPrivate(key, "LibEuFin EBICS") 112 el("ds:X509Data") { 113 el("ds:X509Certificate", cert.encoded.encodeBase64()) 114 } 115 } else { 116 el("PubKeyValue") { 117 el("ds:RSAKeyValue") { 118 el("ds:Modulus", key.modulus.encodeBase64()) 119 el("ds:Exponent", key.publicExponent.encodeBase64()) 120 } 121 } 122 } 123 } 124 125 private fun XMLOrderData(name: String, schema: String, build: XmlBuilder.() -> Unit): String { 126 return XmlBuilder.toBytes(name) { 127 attr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") 128 attr("xmlns", schema) 129 build() 130 el("PartnerID", cfg.partnerId) 131 el("UserID", cfg.userId) 132 }.inputStream().deflate().encodeBase64() 133 } 134 135 companion object { 136 fun parseResponse(doc: Document, clientEncryptionKey: RSAPrivateCrtKey): EbicsResponse<InputStream?> { 137 return XmlDestructor.parse(doc, "ebicsKeyManagementResponse") { 138 lateinit var technicalCode: EbicsReturnCode 139 lateinit var bankCode: EbicsReturnCode 140 var payload: InputStream? = null 141 one("header", signed = true) { 142 one("mutable") { 143 technicalCode = EbicsReturnCode.lookup(one("ReturnCode").text()) 144 } 145 } 146 one("body") { 147 bankCode = EbicsReturnCode.lookup(one("ReturnCode", signed = true).text()) 148 payload = opt("DataTransfer") { 149 val descriptionInfo = one("DataEncryptionInfo", signed = true) { 150 DataEncryptionInfo( 151 one("TransactionKey").base64(), 152 one("EncryptionPubKeyDigest").base64() 153 ) 154 } 155 val chunk = one("OrderData").base64() 156 decryptAndDecompressPayload( 157 clientEncryptionKey, 158 descriptionInfo, 159 listOf(chunk) 160 ) 161 } 162 } 163 EbicsResponse( 164 technicalCode = technicalCode, 165 bankCode, 166 content = payload 167 ) 168 } 169 } 170 171 fun parseHpbOrder(data: InputStream): Pair<RSAPublicKey, RSAPublicKey> { 172 return XmlDestructor.parse(data, "HPBResponseOrderData") { 173 val authPub = one("AuthenticationPubKeyInfo") { 174 val version = one("AuthenticationVersion").text() 175 require(version == "X002") { "Expected authentication version X002 got unsupported $version" } 176 rsaPubKey() 177 } 178 val encPub = one("EncryptionPubKeyInfo") { 179 val version = one("EncryptionVersion").text() 180 require(version == "E002") { "Expected encryption version E002 got unsupported $version" } 181 rsaPubKey() 182 } 183 Pair(authPub, encPub) 184 } 185 } 186 } 187 } 188 189 fun XmlDestructor.rsaPubKey(): RSAPublicKey { 190 val cert = opt("X509Data")?.one("X509Certificate")?.text()?.decodeBase64() 191 return if (cert != null) { 192 CryptoUtil.RSAPublicFromCertificate(cert) 193 } else { 194 one("PubKeyValue").one("RSAKeyValue") { 195 CryptoUtil.RSAPublicFromComponents( 196 one("Modulus").base64(), 197 one("Exponent").base64(), 198 ) 199 } 200 } 201 }