summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-03-26 23:20:58 +0100
committerAntoine A <>2024-03-26 23:20:58 +0100
commit3a0df963d964e5f1dc8c3105c4ad689525441408 (patch)
tree00339cd54e668cbc4b814843e52405fe5f8057ab
parent5ce5347e053d1222a9ac929de2f5373dd22337ad (diff)
downloadlibeufin-3a0df963d964e5f1dc8c3105c4ad689525441408.tar.gz
libeufin-3a0df963d964e5f1dc8c3105c4ad689525441408.tar.bz2
libeufin-3a0df963d964e5f1dc8c3105c4ad689525441408.zip
Support EBICS 3 key management
-rw-r--r--common/build.gradle1
-rw-r--r--common/src/main/kotlin/crypto/utils.kt67
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt2
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt4
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt63
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsConstants.kt100
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt148
7 files changed, 237 insertions, 148 deletions
diff --git a/common/build.gradle b/common/build.gradle
index 439950d7..446910ec 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -19,6 +19,7 @@ dependencies {
implementation("ch.qos.logback:logback-classic:1.5.3")
// Crypto
implementation("org.bouncycastle:bcprov-jdk18on:1.77")
+ implementation("org.bouncycastle:bcpkix-jdk18on:1.77")
// Database helper
implementation("org.postgresql:postgresql:$postgres_version")
implementation("com.zaxxer:HikariCP:5.1.0")
diff --git a/common/src/main/kotlin/crypto/utils.kt b/common/src/main/kotlin/crypto/utils.kt
index 2f98e064..1228eb91 100644
--- a/common/src/main/kotlin/crypto/utils.kt
+++ b/common/src/main/kotlin/crypto/utils.kt
@@ -20,12 +20,21 @@
package tech.libeufin.common.crypto
import org.bouncycastle.jce.provider.BouncyCastleProvider
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
+import org.bouncycastle.operator.ContentSigner
+import org.bouncycastle.cert.jcajce.*
+import org.bouncycastle.asn1.x509.*
+import org.bouncycastle.asn1.x509.Extension
+import org.bouncycastle.asn1.x500.X500Name
+import org.bouncycastle.asn1.ASN1ObjectIdentifier
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.math.BigInteger
+import java.util.*
import java.security.*
import java.security.interfaces.RSAPrivateCrtKey
import java.security.interfaces.RSAPublicKey
+import java.security.cert.*
import java.security.spec.*
import javax.crypto.*
import javax.crypto.spec.IvParameterSpec
@@ -53,7 +62,7 @@ object CryptoUtil {
val plainTransactionKey: SecretKey
)
- private val bouncyCastleProvider = BouncyCastleProvider()
+ private val provider = BouncyCastleProvider()
/**
* Load an RSA private key from its binary PKCS#8 encoding.
@@ -93,6 +102,12 @@ object CryptoUtil {
return keyFactory.generatePublic(tmp) as RSAPublicKey
}
+ fun loadRsaPublicKeyFromCertificate(certificate: ByteArray): RSAPublicKey {
+ val cf = CertificateFactory.getInstance("X.509");
+ val c = cf.generateCertificate(certificate.inputStream());
+ return c.getPublicKey() as RSAPublicKey
+ }
+
/**
* Load an RSA public key from its binary X509 encoding.
*/
@@ -104,6 +119,42 @@ object CryptoUtil {
return pub
}
+ fun certificateFromPrivate(rsaPrivateCrtKey: RSAPrivateCrtKey): X509Certificate {
+ val now = System.currentTimeMillis()
+ val calendar = Calendar.getInstance()
+ calendar.time = Date(now)
+ calendar.add(Calendar.YEAR, 1) // TODO certificate validity
+
+ val builder = JcaX509v3CertificateBuilder(
+ X500Name("CN=test"), // TODO certificate CN
+ BigInteger(now.toString()), // TODO certificate serial number
+ Date(now),
+ calendar.time,
+ X500Name("CN=test"),
+ getRsaPublicFromPrivate(rsaPrivateCrtKey)
+ )
+
+
+ builder.addExtension(Extension.keyUsage, true, KeyUsage(
+ KeyUsage.digitalSignature
+ or KeyUsage.nonRepudiation
+ or KeyUsage.keyEncipherment
+ or KeyUsage.dataEncipherment
+ or KeyUsage.keyAgreement
+ or KeyUsage.keyCertSign
+ or KeyUsage.cRLSign
+ or KeyUsage.encipherOnly
+ or KeyUsage.decipherOnly
+ ))
+ builder.addExtension(Extension.basicConstraints, true, BasicConstraints(true))
+
+ val certificate = JcaContentSignerBuilder("SHA256WithRSA").build(rsaPrivateCrtKey)
+ return JcaX509CertificateConverter()
+ .setProvider(provider)
+ .getCertificate(builder.build(certificate))
+
+ }
+
/**
* Generate a fresh RSA key pair.
*
@@ -135,7 +186,7 @@ object CryptoUtil {
}
fun encryptEbicsE002(data: InputStream, encryptionPublicKey: RSAPublicKey): EncryptionResult {
- val keygen = KeyGenerator.getInstance("AES", bouncyCastleProvider)
+ val keygen = KeyGenerator.getInstance("AES", provider)
keygen.init(128)
val transactionKey = keygen.generateKey()
return encryptEbicsE002withTransactionKey(
@@ -154,14 +205,14 @@ object CryptoUtil {
): EncryptionResult {
val symmetricCipher = Cipher.getInstance(
"AES/CBC/X9.23Padding",
- bouncyCastleProvider
+ provider
)
val ivParameterSpec = IvParameterSpec(ByteArray(16))
symmetricCipher.init(Cipher.ENCRYPT_MODE, transactionKey, ivParameterSpec)
val encryptedData = CipherInputStream(data, symmetricCipher).readAllBytes()
val asymmetricCipher = Cipher.getInstance(
"RSA/None/PKCS1Padding",
- bouncyCastleProvider
+ provider
)
asymmetricCipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey)
val encryptedTransactionKey = asymmetricCipher.doFinal(transactionKey.encoded)
@@ -189,14 +240,14 @@ object CryptoUtil {
): CipherInputStream {
val asymmetricCipher = Cipher.getInstance(
"RSA/None/PKCS1Padding",
- bouncyCastleProvider
+ provider
)
asymmetricCipher.init(Cipher.DECRYPT_MODE, privateKey)
val transactionKeyBytes = asymmetricCipher.doFinal(encryptedTransactionKey)
val secretKeySpec = SecretKeySpec(transactionKeyBytes, "AES")
val symmetricCipher = Cipher.getInstance(
"AES/CBC/X9.23Padding",
- bouncyCastleProvider
+ provider
)
val ivParameterSpec = IvParameterSpec(ByteArray(16))
symmetricCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
@@ -211,7 +262,7 @@ object CryptoUtil {
* uses a hash internally.
*/
fun signEbicsA006(data: ByteArray, privateKey: RSAPrivateCrtKey): ByteArray {
- val signature = Signature.getInstance("SHA256withRSA/PSS", bouncyCastleProvider)
+ val signature = Signature.getInstance("SHA256withRSA/PSS", provider)
signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1))
signature.initSign(privateKey)
signature.update(data)
@@ -219,7 +270,7 @@ object CryptoUtil {
}
fun verifyEbicsA006(sig: ByteArray, data: ByteArray, publicKey: RSAPublicKey): Boolean {
- val signature = Signature.getInstance("SHA256withRSA/PSS", bouncyCastleProvider)
+ val signature = Signature.getInstance("SHA256withRSA/PSS", provider)
signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1))
signature.initVerify(publicKey)
signature.update(data)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
index e9bb3713..f9c83eba 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
@@ -110,7 +110,7 @@ suspend fun doKeysRequestAndUpdateState(
orderType: KeysOrderType
) {
logger.info("Doing key request ${orderType.name}")
- val impl = EbicsKeyMng(cfg, privs)
+ val impl = EbicsKeyMng(cfg, privs, true)
val req = when(orderType) {
KeysOrderType.INI -> impl.INI()
KeysOrderType.HIA -> impl.HIA()
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt
index cfb93782..fbed0b7b 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt
@@ -191,8 +191,8 @@ class XmlDestructor internal constructor(private val el: Element) {
}
fun <T> fromDoc(doc: Document, root: String, f: XmlDestructor.() -> T): T {
- if (doc.documentElement.tagName != root) {
- throw DestructionError("expected root '$root' got '${doc.documentElement.tagName}'")
+ if (doc.documentElement.localName != root) {
+ throw DestructionError("expected root '$root' got '${doc.documentElement.localName}'")
}
val destr = XmlDestructor(doc.documentElement)
return f(destr)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
index 3c8120d9..30bcd8de 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
@@ -55,11 +55,6 @@ import org.w3c.dom.Document
import org.xml.sax.SAXException
/**
- * Available EBICS versions.
- */
-enum class EbicsVersion { two, three }
-
-/**
* Which documents can be downloaded via EBICS.
*/
enum class SupportedDocument {
@@ -392,62 +387,4 @@ class EbicsResponse<T>(
}
return content
}
-}
-
-// TODO import missing using a script
-@Suppress("SpellCheckingInspection")
-enum class EbicsReturnCode(val code: String) {
- EBICS_OK("000000"),
- EBICS_DOWNLOAD_POSTPROCESS_DONE("011000"),
- EBICS_DOWNLOAD_POSTPROCESS_SKIPPED("011001"),
- EBICS_TX_SEGMENT_NUMBER_UNDERRUN("011101"),
- EBICS_AUTHENTICATION_FAILED("061001"),
- EBICS_INVALID_REQUEST("061002"),
- EBICS_INTERNAL_ERROR("061099"),
- EBICS_TX_RECOVERY_SYNC("061101"),
- EBICS_AUTHORISATION_ORDER_IDENTIFIER_FAILED("090003"),
- EBICS_INVALID_ORDER_DATA_FORMAT("090004"),
- EBICS_NO_DOWNLOAD_DATA_AVAILABLE("090005"),
- EBICS_INVALID_USER_OR_USER_STATE("091002"),
- EBICS_USER_UNKNOWN("091003"),
- EBICS_INVALID_USER_STATE("091004"),
- EBICS_INVALID_ORDER_IDENTIFIER("091005"),
- EBICS_UNSUPPORTED_ORDER_TYPE("091006"),
- EBICS_INVALID_XML("091010"),
- EBICS_TX_MESSAGE_REPLAY("091103"),
- EBICS_TX_SEGMENT_NUMBER_EXCEEDED("091104"),
- EBICS_INVALID_REQUEST_CONTENT("091113"),
- EBICS_PROCESSING_ERROR("091116"),
- EBICS_ACCOUNT_AUTHORISATION_FAILED("091302"),
- EBICS_AMOUNT_CHECK_FAILED("091303");
-
- enum class Kind {
- Information,
- Note,
- Warning,
- Error
- }
-
- fun kind(): Kind {
- return when (val errorClass = code.substring(0..1)) {
- "00" -> Kind.Information
- "01" -> Kind.Note
- "03" -> Kind.Warning
- "06", "09" -> Kind.Error
- else -> throw Exception("Unknown EBICS status code error class: $errorClass")
- }
- }
-
- companion object {
- fun lookup(code: String): EbicsReturnCode {
- for (x in entries) {
- if (x.code == code) {
- return x
- }
- }
- throw Exception(
- "Unknown EBICS status code: $code"
- )
- }
- }
} \ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsConstants.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsConstants.kt
new file mode 100644
index 00000000..fc217c2a
--- /dev/null
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsConstants.kt
@@ -0,0 +1,100 @@
+/*
+ * 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
+ * <http://www.gnu.org/licenses/>
+ */
+
+package tech.libeufin.nexus.ebics
+
+
+// TODO import missing using a script
+@Suppress("SpellCheckingInspection")
+enum class EbicsReturnCode(val code: String) {
+ EBICS_OK("000000"),
+ EBICS_DOWNLOAD_POSTPROCESS_DONE("011000"),
+ EBICS_DOWNLOAD_POSTPROCESS_SKIPPED("011001"),
+ EBICS_TX_SEGMENT_NUMBER_UNDERRUN("011101"),
+ EBICS_AUTHENTICATION_FAILED("061001"),
+ EBICS_INVALID_REQUEST("061002"),
+ EBICS_INTERNAL_ERROR("061099"),
+ EBICS_TX_RECOVERY_SYNC("061101"),
+ EBICS_AUTHORISATION_ORDER_IDENTIFIER_FAILED("090003"),
+ EBICS_INVALID_ORDER_DATA_FORMAT("090004"),
+ EBICS_NO_DOWNLOAD_DATA_AVAILABLE("090005"),
+
+ // Transaction administration
+ EBICS_INVALID_USER_OR_USER_STATE("091002"),
+ EBICS_USER_UNKNOWN("091003"),
+ EBICS_INVALID_USER_STATE("091004"),
+ EBICS_INVALID_ORDER_IDENTIFIER("091005"),
+ EBICS_UNSUPPORTED_ORDER_TYPE("091006"),
+ EBICS_INVALID_XML("091010"),
+ EBICS_TX_MESSAGE_REPLAY("091103"),
+ EBICS_TX_SEGMENT_NUMBER_EXCEEDED("091104"),
+ EBICS_INVALID_REQUEST_CONTENT("091113"),
+ EBICS_PROCESSING_ERROR("091116"),
+
+
+ // Key-Management errors
+ EBICS_X509_WRONG_KEY_USAGE("091210"),
+ EBICS_X509_WRONG_ALGORITHM("091211"),
+ EBICS_X509_INVALID_THUMBPRINT("091212"),
+ EBICS_X509_CTL_INVALID("091213"),
+ EBICS_X509_UNKNOWN_CERTIFICATE_AUTHORITY("091214"),
+ EBICS_X509_INVALID_POLICY("091215"),
+ EBICS_X509_INVALID_BASIC_CONSTRAINTS("091216"),
+ EBICS_ONLY_X509_SUPPORT("091217"),
+ EBICS_KEYMGMT_DUPLICATE_KEY("091218"),
+ EBICS_CERTIFICATE_VALIDATION_ERROR("091219"),
+
+ // Pre-erification errors
+ EBICS_SIGNATURE_VERIFICATION_FAILED("091301"),
+ EBICS_ACCOUNT_AUTHORISATION_FAILED("091302"),
+ EBICS_AMOUNT_CHECK_FAILED("091303"),
+ EBICS_SIGNER_UNKNOWN("091304"),
+ EBICS_INVALID_SIGNER_STATE("091305"),
+ EBICS_DUPLICATE_SIGNATURE("091306");
+
+ enum class Kind {
+ Information,
+ Note,
+ Warning,
+ Error
+ }
+
+ fun kind(): Kind {
+ return when (val errorClass = code.substring(0..1)) {
+ "00" -> Kind.Information
+ "01" -> Kind.Note
+ "03" -> Kind.Warning
+ "06", "09" -> Kind.Error
+ else -> throw Exception("Unknown EBICS status code error class: $errorClass")
+ }
+ }
+
+ companion object {
+ fun lookup(code: String): EbicsReturnCode {
+ for (x in entries) {
+ if (x.code == code) {
+ return x
+ }
+ }
+ throw Exception(
+ "Unknown EBICS status code: $code"
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt
index 666ed2ed..48d10d18 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt
@@ -36,37 +36,22 @@ import java.security.interfaces.*
/** EBICS protocol for key management */
class EbicsKeyMng(
private val cfg: EbicsSetupConfig,
- private val clientKeys: ClientPrivateKeysFile
+ private val clientKeys: ClientPrivateKeysFile,
+ private val ebics3: Boolean
) {
+ private val schema = if (ebics3) "H005" else "H004"
fun INI(): ByteArray {
- val inner = XMLOrderData(cfg, "SignaturePubKeyOrderData", "http://www.ebics.org/S001") {
+ val data = XMLOrderData(cfg, "SignaturePubKeyOrderData", "http://www.ebics.org/S00${if (ebics3) 2 else 1}") {
el("SignaturePubKeyInfo") {
RSAKeyXml(clientKeys.signature_private_key)
el("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)
+ return request("ebicsUnsecuredRequest", "INI", "0200", data)
}
fun HIA(): ByteArray {
- val inner = XMLOrderData(cfg, "HIARequestOrderData", "urn:org:ebics:H004") {
+ val data = XMLOrderData(cfg, "HIARequestOrderData", "urn:org:ebics:$schema") {
el("AuthenticationPubKeyInfo") {
RSAKeyXml(clientKeys.authentication_private_key)
el("AuthenticationVersion", "X002")
@@ -76,69 +61,72 @@ class EbicsKeyMng(
el("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)
+ return request("ebicsUnsecuredRequest", "HIA", "0200", data)
}
fun HPB(): ByteArray {
val nonce = getNonce(128)
- val doc = request("ebicsNoPubKeyDigestsRequest") {
+ return request("ebicsNoPubKeyDigestsRequest", "HPB", "0000", timestamp = Instant.now(), sign = true)
+ }
+
+ /* ----- Helpers ----- */
+
+ private fun request(
+ name: String,
+ order: String,
+ securityMedium: String,
+ data: String? = null,
+ timestamp: Instant? = null,
+ sign: Boolean = false
+ ): ByteArray {
+ val doc = XmlBuilder.toDom(name, "urn:org:ebics:$schema") {
+ attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:$schema")
+ attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#")
+ attr("Version", "$schema")
+ attr("Revision", "1")
el("header") {
attr("authenticate", "true")
el("static") {
el("HostID", cfg.ebicsHostId)
- el("Nonce", nonce.encodeUpHex())
- el("Timestamp", Instant.now().xmlDateTime())
+ if (timestamp != null) {
+ el("Nonce", getNonce(128).encodeUpHex())
+ el("Timestamp", timestamp.xmlDateTime())
+ }
el("PartnerID", cfg.ebicsPartnerId)
el("UserID", cfg.ebicsUserId)
el("OrderDetails") {
- el("OrderType", "HPB")
- el("OrderAttribute", "DZHNN")
+ if (ebics3) {
+ el("AdminOrderType", order)
+ } else {
+ el("OrderType", order)
+ el("OrderAttribute", if (order == "HPB") "DZHNN" else "DZNNN")
+ }
}
- el("SecurityMedium", "0000")
+ el("SecurityMedium", securityMedium)
}
el("mutable")
}
- el("AuthSignature")
- el("body")
+ if (sign) el("AuthSignature")
+ el("body") {
+ if (data != null) el("DataTransfer/OrderData", data)
+ }
}
- XMLUtil.signEbicsDocument(doc, clientKeys.authentication_private_key, "H004")
+ if (sign) XMLUtil.signEbicsDocument(doc, clientKeys.authentication_private_key, schema)
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("PubKeyValue") {
- el("ds:RSAKeyValue") {
- el("ds:Modulus", key.modulus.encodeBase64())
- el("ds:Exponent", key.publicExponent.encodeBase64())
+ if (ebics3) {
+ val cert = CryptoUtil.certificateFromPrivate(key)
+ el("ds:X509Data") {
+ el("ds:X509Certificate", cert.encoded.encodeBase64())
+ }
+ } else {
+ el("PubKeyValue") {
+ el("ds:RSAKeyValue") {
+ el("ds:Modulus", key.modulus.encodeBase64())
+ el("ds:Exponent", key.publicExponent.encodeBase64())
+ }
}
}
}
@@ -190,18 +178,30 @@ class EbicsKeyMng(
}
fun parseHpbOrder(data: InputStream): Pair<RSAPublicKey, RSAPublicKey> {
+ fun XmlDestructor.rsaPubKey(): RSAPublicKey {
+ val cert = opt("X509Data")?.one("X509Certificate")?.text()?.decodeBase64()
+ return if (cert != null) {
+ CryptoUtil.loadRsaPublicKeyFromCertificate(cert)
+ } else {
+ one("PubKeyValue").one("RSAKeyValue") {
+ CryptoUtil.loadRsaPublicKeyFromComponents(
+ one("Modulus").text().decodeBase64(),
+ one("Exponent").text().decodeBase64(),
+ )
+ }
+
+ }
+ }
return XmlDestructor.fromStream(data, "HPBResponseOrderData") {
- val authPub = one("AuthenticationPubKeyInfo").one("PubKeyValue").one("RSAKeyValue") {
- CryptoUtil.loadRsaPublicKeyFromComponents(
- one("Modulus").text().decodeBase64(),
- one("Exponent").text().decodeBase64(),
- )
+ val authPub = one("AuthenticationPubKeyInfo") {
+ val version = one("AuthenticationVersion").text()
+ require(version == "X002") { "Expected authentication version X002 got unsupported $version" }
+ rsaPubKey()
}
- val encPub = one("EncryptionPubKeyInfo").one("PubKeyValue").one("RSAKeyValue") {
- CryptoUtil.loadRsaPublicKeyFromComponents(
- one("Modulus").text().decodeBase64(),
- one("Exponent").text().decodeBase64(),
- )
+ val encPub = one("EncryptionPubKeyInfo") {
+ val version = one("EncryptionVersion").text()
+ require(version == "E002") { "Expected encryption version E002 got unsupported $version" }
+ rsaPubKey()
}
Pair(authPub, encPub)
}