gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated (32d23aef -> 3a0df963)


From: gnunet
Subject: [libeufin] branch master updated (32d23aef -> 3a0df963)
Date: Tue, 26 Mar 2024 23:22:42 +0100

This is an automated email from the git hooks/post-receive script.

antoine pushed a change to branch master
in repository libeufin.

    from 32d23aef Update dependencies
     new 5ce5347e Improve testbench and simplify EbicsKeyMng
     new 3a0df963 Support EBICS 3 key management

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 common/build.gradle                                |   1 +
 common/src/main/kotlin/crypto/utils.kt             |  67 +++++++-
 .../main/kotlin/tech/libeufin/nexus/EbicsSetup.kt  |   6 +-
 .../kotlin/tech/libeufin/nexus/XmlCombinators.kt   |   4 +-
 .../tech/libeufin/nexus/ebics/EbicsCommon.kt       |  63 --------
 .../tech/libeufin/nexus/ebics/EbicsConstants.kt    | 100 ++++++++++++
 .../tech/libeufin/nexus/ebics/EbicsKeyMng.kt       | 168 ++++++++++-----------
 testbench/src/main/kotlin/Main.kt                  |  48 +++---
 8 files changed, 270 insertions(+), 187 deletions(-)
 create mode 100644 
nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsConstants.kt

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 b0cbc299..f9c83eba 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
@@ -110,14 +110,14 @@ suspend fun doKeysRequestAndUpdateState(
     orderType: KeysOrderType
 ) {
     logger.info("Doing key request ${orderType.name}")
-    val impl = Ebics3KeyMng(cfg, privs)
+    val impl = EbicsKeyMng(cfg, privs, true)
     val req = when(orderType) {
         KeysOrderType.INI -> impl.INI()
         KeysOrderType.HIA -> impl.HIA()
         KeysOrderType.HPB -> impl.HPB()
     }
     val xml = client.postToBank(cfg.hostBaseUrl, req, "$orderType")
-    val resp = Ebics3KeyMng.parseResponse(xml, privs.encryption_private_key)
+    val resp = EbicsKeyMng.parseResponse(xml, privs.encryption_private_key)
     
     when (orderType) {
         KeysOrderType.INI, KeysOrderType.HIA -> {
@@ -140,7 +140,7 @@ suspend fun doKeysRequestAndUpdateState(
             val orderData = requireNotNull(orderData) {
                 "HPB: missing order data"
             }
-            val (authPub, encPub) = Ebics3KeyMng.parseHpbOrder(orderData)
+            val (authPub, encPub) = EbicsKeyMng.parseHpbOrder(orderData)
             val bankKeys = BankPublicKeysFile(
                 bank_authentication_public_key = authPub,
                 bank_encryption_public_key = encPub,
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
@@ -54,11 +54,6 @@ import java.security.SecureRandom
 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.
  */
@@ -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 a6d965e5..48d10d18 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt
@@ -34,111 +34,99 @@ import javax.xml.datatype.DatatypeFactory
 import java.security.interfaces.*
 
 /** EBICS protocol for key management */
-class Ebics3KeyMng(
+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, "ns2:SignaturePubKeyOrderData", 
"http://www.ebics.org/S001";) {
-            el("ns2:SignaturePubKeyInfo") {
+        val data = XMLOrderData(cfg, "SignaturePubKeyOrderData", 
"http://www.ebics.org/S00${if (ebics3) 2 else 1}") {
+            el("SignaturePubKeyInfo") {
                 RSAKeyXml(clientKeys.signature_private_key)
-                el("ns2:SignatureVersion", "A006")
+                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, "ns2:HIARequestOrderData", 
"urn:org:ebics:H004") {
-            el("ns2:AuthenticationPubKeyInfo") {
+        val data = XMLOrderData(cfg, "HIARequestOrderData", 
"urn:org:ebics:$schema") {
+            el("AuthenticationPubKeyInfo") {
                 RSAKeyXml(clientKeys.authentication_private_key)
-                el("ns2:AuthenticationVersion", "X002")
+                el("AuthenticationVersion", "X002")
             }
-            el("ns2:EncryptionPubKeyInfo") {
+            el("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("EncryptionVersion", "E002")
             }
-            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("ns2: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())
+                }
             }
         }
     }
@@ -146,10 +134,10 @@ class Ebics3KeyMng(
     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)
+            attr("xmlns", schema)
             build()
-            el("ns2:PartnerID", cfg.ebicsPartnerId)
-            el("ns2:UserID", cfg.ebicsUserId)
+            el("PartnerID", cfg.ebicsPartnerId)
+            el("UserID", cfg.ebicsUserId)
         }.inputStream().deflate().encodeBase64()
     }
 
@@ -190,18 +178,30 @@ class Ebics3KeyMng(
         }
 
         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)
             }
diff --git a/testbench/src/main/kotlin/Main.kt 
b/testbench/src/main/kotlin/Main.kt
index 7c6b9db4..d6d9c3e9 100644
--- a/testbench/src/main/kotlin/Main.kt
+++ b/testbench/src/main/kotlin/Main.kt
@@ -41,6 +41,10 @@ fun step(name: String) {
     println("\u001b[35m$name\u001b[0m")
 }
 
+fun msg(msg: String) {
+    println("\u001b[33m$msg\u001b[0m")
+}
+
 fun ask(question: String): String? {
     print("\u001b[;1m$question\u001b[0m")
     System.out.flush()
@@ -138,12 +142,14 @@ class Cli : CliktCommand("Run integration tests on banks 
provider") {
                 put("status", "Fetch CustomerPaymentStatusReport", 
"ebics-fetch $ebicsFlags status")
                 put("notification", "Fetch 
BankToCustomerDebitCreditNotification", "ebics-fetch $ebicsFlags notification")
                 put("submit", "Submit pending transactions", "ebics-submit 
$ebicsFlags")
-                if (kind.test) {
-                    put("reset-keys", suspend {
+                put("reset-keys", suspend {
+                    if (kind.test) {
                         clientKeysPath.deleteIfExists()
-                        bankKeysPath.deleteIfExists()
-                        Unit
-                    })
+                    }
+                    bankKeysPath.deleteIfExists()
+                    Unit
+                })
+                if (kind.test) {
                     put("tx", suspend {
                         step("Submit one transaction")
                         nexusCmd.run("initiate-payment $flags 
\"$payto&amount=CHF:42&message=single%20transaction%20test\"")
@@ -168,41 +174,29 @@ class Cli : CliktCommand("Run integration tests on banks 
provider") {
                     })
                 }
             }
-
             while (true) {
-                // EBICS setup
-                while (true) {
-                    var clientKeys = loadClientKeys(clientKeysPath)
-                    var bankKeys = loadBankKeys(bankKeysPath)
-                    if (!kind.test && clientKeys == null) {
-                        throw Exception("Clients keys are required to run 
netzbon tests")
-                    } else if (clientKeys == null || 
!clientKeys.submitted_ini) {
-                        step("Run INI and HIA order")
-                    } else if (!clientKeys.submitted_hia) {
-                        step("Run HIA order")
-                    } else if (bankKeys == null || !bankKeys.accepted) {
-                        step("Run HPB order")
-                        if (kind.test)
-                            ask("Got to ${kind.settings} and click on 
'Activate EBICS user'.\nPress Enter when done>")
-                    } else {
-                        break
-                    }
+                var clientKeys = loadClientKeys(clientKeysPath)
+                var bankKeys = loadBankKeys(bankKeysPath)
+                if (!kind.test && clientKeys == null) {
+                    throw Exception("Clients keys are required to run netzbon 
tests")
+                } else if (clientKeys == null || !clientKeys.submitted_ini || 
!clientKeys.submitted_hia || bankKeys == null || !bankKeys.accepted) {
+                    step("Run EBICS setup")
                     if (!nexusCmd.run("ebics-setup --auto-accept-keys 
$flags")) {
                         clientKeys = loadClientKeys(clientKeysPath)
                         if (kind.test) {
                             if (clientKeys == null || 
!clientKeys.submitted_ini || !clientKeys.submitted_hia) {
-                                ask("Got to ${kind.settings} and click on 
'Reset EBICS user'.\nPress Enter when done>")
+                                msg("Got to ${kind.settings} and click on 
'Reset EBICS user'")
                             } else {
-                                ask("Got to ${kind.settings} and click on 
'Activate EBICS user'.\nPress Enter when done>")
+                                msg("Got to ${kind.settings} and click on 
'Activate EBICS user'")
                             }
                         } else {
-                            ask("Activate your keys at your bank.\nPress Enter 
when done>")
+                            msg("Activate your keys at your bank")
                         }
                     }
                 }
-
                 val arg = ask("testbench> ")!!.trim()
                 if (arg == "exit") break
+                if (arg == "") continue
                 val cmd = cmds[arg]
                 if (cmd != null) {
                     cmd()

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]