commit e387c1b0e30eca3b6faf9dd5a506ce6d69130bc6
parent a70730d13cb85abcd6d5066410785b5fba435ab1
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date: Mon, 11 Nov 2019 20:08:19 +0100
send valid HTD request
Diffstat:
2 files changed, 111 insertions(+), 7 deletions(-)
diff --git a/nexus/src/main/kotlin/Main.kt b/nexus/src/main/kotlin/Main.kt
@@ -137,7 +137,8 @@ suspend inline fun <reified S>HttpClient.postToBank(url: String, body: String):
try {
return XMLUtil.convertStringToJaxb(response)
} catch (e: Exception) {
- throw UnparsableResponse(HttpStatusCode.BadRequest)
+ logger.warn("bank responded: ${response}")
+ throw UnparsableResponse(HttpStatusCode.BadRequest, response)
}
}
@@ -166,19 +167,18 @@ fun getGregorianDate(): XMLGregorianCalendar {
}
data class NotAnIdError(val statusCode: HttpStatusCode) : Exception("String ID not convertible in number")
+data class BankKeyMissing(val statusCode: HttpStatusCode) : Exception("Impossible operation: bank keys are missing")
data class SubscriberNotFoundError(val statusCode: HttpStatusCode) : Exception("Subscriber not found in database")
data class UnreachableBankError(val statusCode: HttpStatusCode) : Exception("Could not reach the bank")
-data class UnparsableResponse(val statusCode: HttpStatusCode) : Exception("Bank responded with non-XML / non-EBICS " +
- "content")
-data class EbicsError(val codeError: String) : Exception("Bank did not accepted EBICS request, error is: " + codeError
-)
+data class UnparsableResponse(val statusCode: HttpStatusCode, val rawResponse: String) : Exception("bank responded: ${rawResponse}")
+data class EbicsError(val codeError: String) : Exception("Bank did not accepted EBICS request, error is: ${codeError}")
fun main() {
dbCreateTables()
testData() // gets always id == 1
val client = HttpClient(){
- expectSuccess = false // this way, does not throw exceptions on != 200 responses
+ expectSuccess = false // this way, it does not throw exceptions on != 200 responses.
}
val logger = LoggerFactory.getLogger("tech.libeufin.nexus")
@@ -214,7 +214,7 @@ fun main() {
exception<UnparsableResponse> { cause ->
logger.error("Exception while handling '${call.request.uri}'", cause)
- call.respondText("Could not parse bank response\n", ContentType.Text.Plain, HttpStatusCode
+ call.respondText("Could not parse bank response (${cause.message})\n", ContentType.Text.Plain, HttpStatusCode
.InternalServerError)
}
@@ -233,6 +233,11 @@ fun main() {
call.respondText("Bank gave EBICS-error response\n", ContentType.Text.Plain, HttpStatusCode.NotAcceptable)
}
+ exception<BankKeyMissing> { cause ->
+ logger.error("Exception while handling '${call.request.uri}'", cause)
+ call.respondText("Impossible operation: get bank keys first\n", ContentType.Text.Plain, HttpStatusCode.NotAcceptable)
+ }
+
exception<javax.xml.bind.UnmarshalException> { cause ->
logger.error("Exception while handling '${call.request.uri}'", cause)
call.respondText(
@@ -256,6 +261,77 @@ fun main() {
return@get
}
+ get("/ebics/subscribers/{id}/sendHtd") {
+ val id = expectId(call.parameters["id"])
+ val (url, body, encPrivBlob) = transaction {
+ val subscriber = EbicsSubscriberEntity.findById(id) ?: throw SubscriberNotFoundError(HttpStatusCode.NotFound)
+ val request = EbicsRequest().apply {
+ version = "H004"
+ revision = 1
+ header = EbicsRequest.Header().apply {
+ authenticate = true
+ static = EbicsRequest.StaticHeaderType().apply {
+ userID = subscriber.userID
+ partnerID = subscriber.partnerID
+ hostID = subscriber.hostID
+ nonce = getNonce(128)
+ timestamp = getGregorianDate()
+ partnerID = subscriber.partnerID
+ orderDetails = EbicsRequest.OrderDetails().apply {
+ orderType = "HTD"
+ orderAttribute = "DZHNN"
+ orderParams = EbicsRequest.StandardOrderParams()
+ }
+ bankPubKeyDigests = EbicsRequest.BankPubKeyDigests().apply {
+ authentication = EbicsTypes.PubKeyDigest().apply {
+ algorithm = "http://www.w3.org/2001/04/xmlenc#sha256"
+ version = "X002"
+ value = CryptoUtil.getEbicsPublicKeyHash(
+ CryptoUtil.loadRsaPublicKey(
+ (subscriber.bankAuthenticationPublicKey ?: throw BankKeyMissing(HttpStatusCode.NotAcceptable)).toByteArray()
+ )
+ )
+ }
+ encryption = EbicsTypes.PubKeyDigest().apply {
+ algorithm = "http://www.w3.org/2001/04/xmlenc#sha256"
+ version = "E002"
+ value = CryptoUtil.getEbicsPublicKeyHash(
+ CryptoUtil.loadRsaPublicKey(
+ (subscriber.bankEncryptionPublicKey ?: throw BankKeyMissing(HttpStatusCode.NotAcceptable)).toByteArray()
+ )
+ )
+ }
+ securityMedium = "0000"
+ }
+ mutable = EbicsRequest.MutableHeader().apply {
+ transactionPhase = EbicsTypes.TransactionPhaseType.INITIALISATION
+ }
+ authSignature = SignatureType()
+ }
+ }
+ body = EbicsRequest.Body()
+ }
+
+ val hpbText = XMLUtil.convertJaxbToString(request)
+ val hpbDoc = XMLUtil.parseStringIntoDom(hpbText)
+
+ XMLUtil.signEbicsDocument(
+ hpbDoc,
+ CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray())
+ )
+
+ Triple(subscriber.ebicsURL, hpbDoc, subscriber.encryptionPrivateKey.toByteArray())
+ }
+
+ val response = client.postToBank<EbicsKeyManagementResponse>(url, body)
+ print("HTD response: " + XMLUtil.convertJaxbToString(response))
+
+ call.respond(
+ HttpStatusCode.NotImplemented,
+ SandboxError("Not implemented")
+ )
+ }
+
get("/ebics/subscribers/{id}/keyletter") {
val id = expectId(call.parameters["id"])
diff --git a/sandbox/src/main/python/libeufin-cli b/sandbox/src/main/python/libeufin-cli
@@ -40,6 +40,34 @@ def ini(obj, customer_id):
print(resp.content.decode("utf-8"))
+@ebics.command(help="send HTD message")
+@click.pass_context
+@click.option(
+ "--customer-id",
+ help="numerical ID of the customer at the Nexus",
+ required=False,
+ default=1)
+@click.option(
+ "--prepare",
+ help="Gets keying done before requesting HTD",
+ required=False,
+ default=True)
+def htd(ctx, customer_id, prepare):
+
+ if prepare:
+ ctx.invoke(ini)
+ ctx.invoke(hia)
+ ctx.invoke(sync)
+
+ url = urljoin(ctx.obj["base_url"], "/ebics/subscribers/{}/sendHtd".format(customer_id))
+ try:
+ resp = get(url)
+ except Exception:
+ print("Could not reach the bank")
+ return
+
+ print(resp.content.decode("utf-8"))
+
@ebics.command(help="send HIA message")
@click.pass_obj
@click.option(