libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 0e3fc336694f42c2844b940015ec8e43b42f851c
parent 8547eeb81f0a6a433331571a9eaaa148851e89fa
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Thu, 21 Nov 2019 04:50:06 +0100

Importing passphrase protection into the Web server.

Diffstat:
Mnexus/src/main/kotlin/JSON.kt | 12+++++++++++-
Mnexus/src/main/kotlin/Main.kt | 62++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt | 4++--
Msandbox/src/test/kotlin/CryptoUtilTest.kt | 6+++---
4 files changed, 68 insertions(+), 16 deletions(-)

diff --git a/nexus/src/main/kotlin/JSON.kt b/nexus/src/main/kotlin/JSON.kt @@ -4,8 +4,18 @@ import com.google.gson.annotations.JsonAdapter import com.squareup.moshi.JsonClass -data class EbicsKeysBackup( +data class EbicsBackupRequest( + val passphrase: String +) + +/** + * This object is used twice: as a response to the backup request, + * and as a request to the backup restore. Note: in the second case + * the client must provide the passphrase. + */ +data class EbicsKeysBackup( + val passphrase: String? = null, val authBlob: ByteArray, val encBlob: ByteArray, val sigBlob: ByteArray diff --git a/nexus/src/main/kotlin/Main.kt b/nexus/src/main/kotlin/Main.kt @@ -30,14 +30,13 @@ import io.ktor.features.ContentNegotiation import io.ktor.features.StatusPages import io.ktor.gson.gson import io.ktor.http.ContentType +import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.request.receive import io.ktor.request.uri import io.ktor.response.respond import io.ktor.response.respondText -import io.ktor.routing.get -import io.ktor.routing.post -import io.ktor.routing.routing +import io.ktor.routing.* import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty import org.apache.commons.codec.digest.Crypt @@ -63,6 +62,7 @@ import java.time.Instant.now import java.util.* import java.util.zip.DeflaterInputStream import java.util.zip.InflaterInputStream +import javax.crypto.EncryptedPrivateKeyInfo import javax.xml.datatype.DatatypeFactory import javax.xml.datatype.XMLGregorianCalendar @@ -241,6 +241,7 @@ data class UnreachableBankError(val statusCode: HttpStatusCode) : Exception("Cou 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}") data class BadSignature(val statusCode: HttpStatusCode) : Exception("Signature verification unsuccessful") +data class BadBackup(val statusCode: HttpStatusCode) : Exception("Could not restore backed up keys") @@ -282,6 +283,11 @@ fun main() { call.respondText("Bad request\n", ContentType.Text.Plain, HttpStatusCode.BadRequest) } + exception<BadBackup> { cause -> + logger.error("Exception while handling '${call.request.uri}'", cause) + call.respondText("Bad backup, or passphrase incorrect\n", ContentType.Text.Plain, HttpStatusCode.BadRequest) + } + exception<UnparsableResponse> { cause -> logger.error("Exception while handling '${call.request.uri}'", cause) @@ -743,11 +749,32 @@ fun main() { val body = call.receive<EbicsKeysBackup>() val id = expectId(call.parameters["id"]) + val (authKey, encKey, sigKey) = try { + + val authKey = CryptoUtil.decryptKey( + EncryptedPrivateKeyInfo(body.authBlob), body.passphrase!! + ) + + val encKey = CryptoUtil.decryptKey( + EncryptedPrivateKeyInfo(body.encBlob), body.passphrase + ) + + val sigKey = CryptoUtil.decryptKey( + EncryptedPrivateKeyInfo(body.sigBlob), body.passphrase + ) + + Triple(authKey, encKey, sigKey) + + } catch (e: Exception) { + throw BadBackup(HttpStatusCode.BadRequest) + } + transaction { val subscriber = EbicsSubscriberEntity.findById(id) ?: throw SubscriberNotFoundError(HttpStatusCode.NotFound) - subscriber.encryptionPrivateKey = SerialBlob(body.encBlob) - subscriber.authenticationPrivateKey = SerialBlob(body.authBlob) - subscriber.signaturePrivateKey = SerialBlob(body.sigBlob) + + subscriber.encryptionPrivateKey = SerialBlob(authKey.encoded) + subscriber.authenticationPrivateKey = SerialBlob(encKey.encoded) + subscriber.signaturePrivateKey = SerialBlob(sigKey.encoded) } call.respondText( @@ -758,15 +785,30 @@ fun main() { } - get("/ebics/subscribers/{id}/backup") { + put("/ebics/subscribers/{id}/backup") { val id = expectId(call.parameters["id"]) + val body = call.receive<EbicsBackupRequest>() + val content = transaction { val subscriber = EbicsSubscriberEntity.findById(id) ?: throw SubscriberNotFoundError(HttpStatusCode.NotFound) + + EbicsKeysBackup( - authBlob = subscriber.authenticationPrivateKey.toByteArray(), - encBlob = subscriber.encryptionPrivateKey.toByteArray(), - sigBlob = subscriber.signaturePrivateKey.toByteArray() + + authBlob = CryptoUtil.encryptKey( + subscriber.authenticationPrivateKey.toByteArray(), + body.passphrase + ), + + encBlob = CryptoUtil.encryptKey( + subscriber.encryptionPrivateKey.toByteArray(), + body.passphrase), + + sigBlob = CryptoUtil.encryptKey( + subscriber.signaturePrivateKey.toByteArray(), + body.passphrase + ) ) } diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt @@ -213,7 +213,7 @@ object CryptoUtil { } - fun decryptSecret(data: EncryptedPrivateKeyInfo, passphrase: String): RSAPrivateCrtKey { + fun decryptKey(data: EncryptedPrivateKeyInfo, passphrase: String): RSAPrivateCrtKey { /* make key out of passphrase */ val pbeKeySpec = PBEKeySpec(passphrase.toCharArray()) @@ -236,7 +236,7 @@ object CryptoUtil { return priv } - fun encryptSecret(data: ByteArray, passphrase: String): ByteArray { + fun encryptKey(data: ByteArray, passphrase: String): ByteArray { /* Cipher parameters: salt and hash count */ val hashIterations = 30 diff --git a/sandbox/src/test/kotlin/CryptoUtilTest.kt b/sandbox/src/test/kotlin/CryptoUtilTest.kt @@ -85,12 +85,12 @@ class CryptoUtilTest { val secret = CryptoUtil.encryptEbicsE002(data, keyPair.public) /* encrypt and decrypt private key */ - val encPriv = CryptoUtil.encryptSecret(keyPair.private.encoded, "secret") - val plainPriv = CryptoUtil.decryptSecret(EncryptedPrivateKeyInfo(encPriv),"secret") + val encPriv = CryptoUtil.encryptKey(keyPair.private.encoded, "secret") + val plainPriv = CryptoUtil.decryptKey(EncryptedPrivateKeyInfo(encPriv),"secret") /* decrypt with decrypted private key */ val revealed = CryptoUtil.decryptEbicsE002(secret, plainPriv) - + assertEquals( String(revealed, charset = Charsets.UTF_8), String(data, charset = Charsets.UTF_8)