Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions obp-api/src/main/resources/props/sample.props.template
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ jwt.use.ssl=false
# truststore.path.tpp_signature = path/to/ca.p12
# truststore.password.tpp_signature = truststore-password

# Bypass TPP signature validation
# bypass_tpp_signature_validation = false


## Enable writing API metrics (which APIs are called) to RDBMS
write_metrics=true
Expand Down
20 changes: 14 additions & 6 deletions obp-api/src/main/scala/code/api/util/BerlinGroupSigning.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package code.api.util

import code.api.{CertificateConstants, RequestHeader}
import code.api.RequestHeader
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.model.User
import net.liftweb.common.{Box, Failure, Full}
import net.liftweb.http.provider.HTTPParam

import java.util.Base64
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Paths}
import java.security._
Expand Down Expand Up @@ -113,7 +112,7 @@ object BerlinGroupSigning extends MdcLoggable {
* @param forwardResult Propagated result of calling function
* @return Propagated result of calling function or signing request error
*/
def verifySignedRequest(body: Box[String], verb: String, url: String, reqHeaders: List[HTTPParam], forwardResult: (Box[User], Option[CallContext])) = {
def verifySignedRequest(body: Box[String], verb: String, url: String, reqHeaders: List[HTTPParam], forwardResult: (Box[User], Option[CallContext])): (Box[User], Option[CallContext]) = {
def checkRequestIsSigned(requestHeaders: List[HTTPParam]): Boolean = {
requestHeaders.exists(_.name == RequestHeader.`TPP-Signature-Certificate`) &&
requestHeaders.exists(_.name == RequestHeader.Signature) &&
Expand All @@ -132,8 +131,8 @@ object BerlinGroupSigning extends MdcLoggable {

val signatureHeaderValue = getHeaderValue(RequestHeader.Signature, requestHeaders)
val signature = parseSignatureHeader(signatureHeaderValue).getOrElse("signature", "NONE")
val headersss = parseSignatureHeader(signatureHeaderValue).getOrElse("headers", "").split(" ").toList
val headers = headersss.map(h =>
val headersToSign = parseSignatureHeader(signatureHeaderValue).getOrElse("headers", "").split(" ").toList
val headers = headersToSign.map(h =>
if(h.toLowerCase() == RequestHeader.Digest.toLowerCase()) {
s"$h: SHA-256=$digest"
} else {
Expand All @@ -143,8 +142,10 @@ object BerlinGroupSigning extends MdcLoggable {
val signingString = headers.mkString("\n")
val isVerified = verifySignature(signingString, signature, certificatePem)
val isValidated = CertificateVerifier.validateCertificate(certificatePem)
val bypassValidation = APIUtil.getPropsAsBoolValue("bypass_tpp_signature_validation", defaultValue = false)
(isVerified, isValidated) match {
case (true, true) => forwardResult
case (true, false) if bypassValidation => forwardResult
case (true, false) => (Failure(ErrorMessages.X509PublicKeyCannotBeValidated), forwardResult._2)
case (false, _) => (Failure(ErrorMessages.X509PublicKeyCannotVerify), forwardResult._2)
}
Expand All @@ -157,7 +158,7 @@ object BerlinGroupSigning extends MdcLoggable {
def getHeaderValue(name: String, requestHeaders: List[HTTPParam]): String = {
requestHeaders.find(_.name.toLowerCase() == name.toLowerCase()).map(_.values.mkString).getOrElse("None")
}
def getPem(requestHeaders: List[HTTPParam]): String = {
private def getPem(requestHeaders: List[HTTPParam]): String = {
val certificate = getHeaderValue(RequestHeader.`TPP-Signature-Certificate`, requestHeaders)
// Decode the Base64 string
val decodedBytes = Base64.getDecoder.decode(certificate)
Expand All @@ -184,6 +185,13 @@ object BerlinGroupSigning extends MdcLoggable {
}
}

def getTppSignatureCertificate(requestHeaders: List[HTTPParam]): Option[String] = {
getPem(requestHeaders) match {
case value if value.isEmpty => None
case value => Some(value)
}
}

def parseSignatureHeader(signatureHeader: String): Map[String, String] = {
val regex = new Regex("""(\w+)\s*=\s*"([^"]*)"""", "key", "value")
regex.findAllMatchIn(signatureHeader).map(m => m.group("key") -> m.group("value")).toMap
Expand Down
2 changes: 1 addition & 1 deletion obp-api/src/main/scala/code/api/util/ConsentUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ object Consent extends MdcLoggable {
*/
def getCurrentConsumerViaMtls(callContext: CallContext): Box[Consumer] = {
val clientCert: String = APIUtil.`getPSD2-CERT`(callContext.requestHeaders) // MTLS certificate QWAC (Qualified Website Authentication Certificate)
.orElse(APIUtil.getTppSignatureCertificate(callContext.requestHeaders)) // Signature certificate QSealC (Qualified Electronic Seal Certificate)
.orElse(BerlinGroupSigning.getTppSignatureCertificate(callContext.requestHeaders)) // Signature certificate QSealC (Qualified Electronic Seal Certificate)
.getOrElse(SecureRandomUtil.csprng.nextLong().toString) // Force to fail

{ // 1st search is via the original value
Expand Down
Loading