Integration Blog Posts
Whether you’re a beginner or an experienced developer, this page is your go to resource for how to guides & tutorials, FAQs, and feature highlights
cancel
Showing results for 
Search instead for 
Did you mean: 
felipe_correa
Explorer
917

Introduction

In today's hyper-connected enterprise landscape, integrations with banking APIs represent one of the most critical security challenges. Financial institutions maintain extremely stringent security requirements when exposing their services via APIs, often beyond what standard integration platforms offer out-of-the-box.

In this article, I'll share technical insights based on a real implementation with the API of one of the world's largest American banks using SAP Integration Suite (formerly known as SAP Cloud Platform Integration). This implementation required a sophisticated multi-layer security architecture that goes well beyond traditional integration approaches.

What makes this solution particularly valuable is how it implements a true "defense in depth" strategy that:

  • Withstands multiple attack vectors
  • Complies with the strictest financial industry security standards
  • Creates a flexible security foundation adaptable to various banking APIs

While this implementation focuses specifically on obtaining an authentication token, the same security patterns and techniques can be applied to consume any banking API services that follow similar standards.

Understanding JOSE: The Foundation of Modern API Security

The JOSE (JavaScript Object Signing and Encryption) framework serves as the foundation for our security architecture. JOSE consists of a collection of specifications that standardize methods for secure data exchange:

  • JWT (JSON Web Token): A compact, URL-safe means of representing claims
  • JWS (JSON Web Signature): A mechanism to add digital signatures to JWT, ensuring integrity and authenticity
  • JWE (JSON Web Encryption): A method to encrypt JWT, providing confidentiality
  • JWK (JSON Web Key): A format for representing cryptographic keys in JSON
  • JWA (JSON Web Algorithms): Defines the cryptographic algorithms used with the above formats

Our banking integration relies heavily on JWS for signing and JWE for encryption, with both working together to create a comprehensive security solution.

JWT Structure and Formats

Understanding the structure of JWT tokens is crucial for implementing this security architecture correctly:

JWT/JWS Format:

Base64UrlEncode(Header) + "." + Base64UrlEncode(Payload) + "." + Base64UrlEncode(Signature)

Example JWT/JWS:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlELi4uIl19.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWE Format:

Base64UrlEncode(Header) + "." + 
Base64UrlEncode(EncryptedKey) + "." + 
Base64UrlEncode(IV) + "." + 
Base64UrlEncode(Ciphertext) + "." + 
Base64UrlEncode(AuthenticationTag)

Example JWE:

eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0.a4OJ4Me9Qqz2y1DbJvEfH0M6U6BtM6CipNjFIgZ3W8M.yZW18nJiHMrRfJaR.xLI9vX-TFG_OchACM-MZZg.DNdcijA0zxgtgWKT7vn7Vw

The Security Pyramid in Modern Banking Integrations

Our approach implements a three-layer security pyramid that provides comprehensive protection:

arqui.jpg

1. Mutual TLS Authentication (mTLS)

The foundation of any banking security architecture is mutual TLS authentication:

  • The bank server verifies the client's identity through its digital certificate
  • The client verifies the server's identity to prevent impersonation
  • An encrypted communication channel is established at the transport level
  • Certificates must be part of a verifiable trust chain
  • Banks typically require certificates from a Certificate Authority (CA) with Organization Validation (OV)

Note on Certificate Separation: Most banking institutions require using separate certificates for different security functions - one certificate exclusively for establishing the mTLS connection, and another for JWS signing and JWE decryption operations. This separation of concerns is a security best practice that prevents compromise of one function from affecting the others.

2. Digital Signature with JWS (JSON Web Signature)

The second layer implements digital signatures using the JWS standard (RFC 7515):

  • Tripartite structure (header.payload.signature) that guarantees message integrity
  • Incorporation of the complete certificate in the header through the x5c field
  • RS256 signature algorithm (RSA + SHA-256) for cryptographic validation
  • Complete non-repudiation of the request, allowing full audit
// JWS Header Example
{
  "alg": "RS256",
  "typ": "JWT",
  "x5c": ["MIID...base64 encoded certificate..."]
}

Implementation insight: Standard Integration Suite components such as PKCS7/CMS Signer are not suitable for this scenario, as they generate signatures in PKCS#7 format incompatible with JWS. Instead, we implemented custom Groovy scripts using the Nimbus JOSE+JWT library, which perfectly handles the JWS standard.

3. Data Encryption with JWE (JSON Web Encryption)

The third layer implements hybrid encryption using JWE (RFC 7516):

  • Five-part structure (header.encrypted_key.iv.ciphertext.tag)
  • Key encryption with RSA-OAEP-256 (asymmetric)
  • Content encryption with A256GCM (AES-256 in GCM mode)
  • Complete message protection even if the TLS layer were compromised
// JWE Header Example
{
  "alg": "RSA-OAEP-256",
  "enc": "A256GCM"
}

The critical component: AAD (Additional Authenticated Data)

A sophisticated aspect of JWE is the handling of AAD in GCM encryption:

  • AAD is data that is authenticated but not encrypted during the GCM process
  • In JWE, the standard requires that the Base64URL-encoded header be used as AAD
  • During decryption, the same AAD must be provided for tag verification to be successful
  • If the AAD is missing or incorrect, it results in the InvalidTag error during decryption

The Nimbus JOSE+JWT library handles this complexity automatically, ensuring that the AAD is correctly applied according to the JWE standard. This was a critical factor in choosing Nimbus for our implementation, as it eliminates a common source of errors in manual implementations.

Technical Prerequisites

Before beginning implementation, it's necessary to ensure the following elements:

  1. Certificates and Cryptographic Material:
    • Client certificate (with private key) for signature
    • Client certificate for mTLS authentication. You can use the certificate provided by SAP in your Integration Suite tenant, but its private key isn’t accessible for debugging.
    • Bank's public certificate for encryption and TLS verification
    • Depending of th bank all certificates must be CA-issued with Organization Validation (OV)
  2. Libraries in SAP Integration Suite:
    • Nimbus JOSE+JWT library (for handling JWS/JWE)
    • Uploading the necessary JARs to the Resources section of your integration package in SAP Integration Suite
  3. Bank's Technical Documentation:
    • Specification of required algorithms (RS256, RSA-OAEP-256, A256GCM)
    • Request and response formats
    • Specific requirements for headers and fields
  4. Integration Suite Keystore Access:
    • Permissions to manage certificates in the Integration Suite keystore
    • Certificate alias configured for access from scripts

Certificate Exchange with the Banking Institution

Before implementing the integration flow, a critical step is the exchange of certificates between your organization and the bank:

  • Outbound Certificates: Both the mTLS certificate and the public part of the signing/encryption certificate must be registered with the bank's portal or shared as .pem files with the bank's security team. This separation of certificates for different security functions is a requirement from most banking institutions.
  • Inbound Certificates: The bank will provide their server certificates and/or certificate chain that must be added to your SAP Integration Suite keystore.

This bidirectional certificate exchange establishes the mutual trust necessary for the first layer of our security pyramid (mTLS). Without this exchange properly configured, API communication will fail at the transport level, regardless of the correctness of the message content.

Many banking institutions provide specific portals or processes for certificate registration and management, often requiring additional verification steps to ensure certificate authenticity.

Certificate Generation and Preparation

For this implementation, we generated the necessary key pairs using OpenSSL and then imported them into the SAP Integration Suite keystore. Here's the general approach we followed (with anonymized names):

# Generate CSR for signing and encryption certificate
openssl req -newkey rsa:2048 -keyout sign_encrypt.key -out sign_encrypt.csr

# Submit CSR to Certificate Authority (CA) for OV validation
# After approval, the CA provides you with certificate.crt (signed with OV validation):

# Package as PKCS12 (.p12) for import into SAP Integration Suite
openssl pkcs12 -export \  
  -out sign_encrypt.p12 \  
  -inkey sign_encrypt.key \  
  -in certificate.crt \  
  -name "sign-encrypt.company.com"

The same process was followed for both required certificates: one for mTLS connection and another for JWS signing and JWE decryption operations. It's important to note that certificate.crt is the signed certificate provided by a Certificate Authority (CA) with Organization Validation (OV) after submitting the CSR.

Implementation Guide

1. Certificate Configuration in SAP Integration Suite

  1. Import client certificate:
    • Navigate to Manage Keystores in SAP Integration Suite
    • Create new keystore or use existing one
    • Import the P12/PFX certificate with its private key
    • Note the alias (needed for the scripts)
  2. Import bank's trust chain:
    • Import root and intermediate certificates to the Trust Store
    • Verify that the entire chain is correctly configured

2. Create iFlow Structure

The integration flow follows this structure:

  1. Configure the flow start:

    • Initial Content Modifier to prepare the authentication payload
    • Configure necessary properties such as URLs, IDs, etc.
  2. Add Groovy Scripts in sequence:

    • JWS Signature Script
    • JWE Encryption Script
    • Configure Request Reply with HTTPS connection to the bank
  3. Configure HTTPS connection:

    • Set up the HTTPS adapter in Request Reply or Call activity with "Client Certificate" authentication
    • Select your dedicated mTLS certificate as "Private Key Alias" (different from signing/encryption certificate)
    • Configure the endpoint URL to the bank's API service
    • Verify that the bank's certificates are properly imported in your Integration Suite trust store
    • This configuration implements the mTLS security layer of our architecture
  4. Process the response:

    • JWE Decryption Script
    • Token Extraction Script
    • Content Modifier to format the final response

3. Implementation Flow

Below is a detailed representation of the integration flow showing the transformations that occur at each step:

[Original JSON Payload]
   ↓
[Groovy Script: JWS Signature]
   ↓ Transformation: JSON → JWS (header.payload.signature)
   ↓ Algorithm: RS256
   ↓
[Signed JWS]
   ↓
[Groovy Script: JWE Encryption]
   ↓ Transformation: JWS → JWE (header.encrypted_key.iv.ciphertext.tag)
   ↓ Algorithms: RSA-OAEP-256 (key) + A256GCM (content) with AAD
   ↓
[Encrypted JWE]
   ↓
[Send to Bank API]
   ↓
[JWE Response from Bank]
   ↓
[Groovy Script: JWE Decryption]
   ↓ Transformation: JWE → JSON/JWT
   ↓ Verification: AAD + GCM tag
   ↓
[Decrypted Response]
   ↓
[Groovy Script: Token Extraction]
   ↓ Extraction: access_token, token_type, expires_in
   ↓
[Token and Metadata]

Efficient Implementation with Nimbus JOSE+JWT

Originally, we implemented the JWE decryption manually, handling components like Base64URL decoding, RSA transformation, AAD configuration, and GCM tag validation. However, this approach was complex and error-prone.

By leveraging the Nimbus JOSE+JWT library, we've significantly simplified the implementation while maintaining the same high security standards. Let's look at each of the three key scripts:

Script 1: JWS Signing (Optimized with Nimbus)

import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.JWSSigner
import com.nimbusds.jose.JWSObject
import com.nimbusds.jose.Payload
import com.nimbusds.jose.crypto.RSASSASigner
import com.nimbusds.jose.util.Base64
import com.sap.gateway.ip.core.customdev.util.Message
import java.security.KeyPair
import java.security.interfaces.RSAPrivateKey
import java.security.cert.X509Certificate

def Message processData(Message message) {
    def messageLog = messageLogFactory.getMessageLog(message)
    final String SIGNING_KEY_ALIAS = "sign_n_encrypt_cert"

    // Enable/disable logging (change to true for debugging)
    final boolean DEBUG = false

    try {
        // 1. Get the payload to sign
        String payload = message.getBody(String.class)
        
        // 2. Load private key and certificate using KeystoreService
        def keystoreService = com.sap.it.api.ITApiFactory.getService(com.sap.it.api.keystore.KeystoreService.class, null)
        if (keystoreService == null) throw new IllegalStateException("Could not obtain keystore service")
        
        KeyPair keyPair = keystoreService.getKeyPair(SIGNING_KEY_ALIAS)
        if (keyPair == null) throw new IllegalStateException("Could not retrieve KeyPair with alias: " + SIGNING_KEY_ALIAS)
        
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate()
        
        X509Certificate certificate = (X509Certificate) keystoreService.getCertificate(SIGNING_KEY_ALIAS)
        if (certificate == null) throw new IllegalStateException("Could not retrieve certificate with alias: " + SIGNING_KEY_ALIAS)
        
        // 3. Prepare certificate for x5c (Base64 of DER)
        String certB64 = Base64.encode(certificate.getEncoded()).toString()
        
        // 4. Create JWS header with x5c
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256)
            .x509CertChain(Collections.singletonList(new Base64(certB64)))
            .build()
        
        if (DEBUG) {
            messageLog?.addAttachmentAsString("JWS-Debug", 
                "Signing process initialized:\n" +
                "- Certificate: " + SIGNING_KEY_ALIAS + "\n" +
                "- Algorithm: RS256\n" +
                "- Payload preview: " + payload.substring(0, Math.min(50, payload.length())) + "...\n" +
                "- JWS Header: " + jwsHeader.toString(),
                "text/plain")
        }
        
        // 5. Create signer with private key
        JWSSigner signer = new RSASSASigner(privateKey)
        
        // 6. Create JWS object and sign it
        JWSObject jwsObject = new JWSObject(jwsHeader, new Payload(payload))
        jwsObject.sign(signer)
        
        // 7. Get compact serialization of JWS
        String jwsEncoded = jwsObject.serialize()
        
        // 8. Set JWS as the new message body
        message.setBody(jwsEncoded)
        
        return message
        
    } catch (Exception e) {
        messageLog?.addAttachmentAsString("Error", "JWS signing failed: " + e.getMessage(), "text/plain")
        throw e
    }
}

Script 2: JWE Encryption (Optimized with Nimbus)

import com.nimbusds.jose.EncryptionMethod
import com.nimbusds.jose.JWEAlgorithm
import com.nimbusds.jose.JWEHeader
import com.nimbusds.jose.JWEObject
import com.nimbusds.jose.Payload
import com.nimbusds.jose.crypto.RSAEncrypter
import com.sap.gateway.ip.core.customdev.util.Message
import java.security.interfaces.RSAPublicKey

def Message processData(Message message) {
    def messageLog = messageLogFactory.getMessageLog(message)
    
    // Certificate alias for encryption
    final String BANK_CERT_ALIAS = "bank_encryption_cert"
    
    // Enable/disable logging (change to true for debugging)
    final boolean DEBUG = false
    
    try {
        // 1. Get JWS payload from message body
        String jwsPayload = message.getBody(String.class)
        
        // 2. Load certificate using KeystoreService API
        def keystoreService = com.sap.it.api.ITApiFactory.getApi(com.sap.it.api.keystore.KeystoreService.class, null)
        if (keystoreService == null) throw new IllegalStateException("Could not obtain keystore service")
        
        def certificate = keystoreService.getCertificate(BANK_CERT_ALIAS)
        if (certificate == null) throw new IllegalStateException("Could not retrieve certificate with alias: " + BANK_CERT_ALIAS)
        
        RSAPublicKey publicKey = (RSAPublicKey) certificate.getPublicKey()
        
        // 3. Create JWE header with RSA-OAEP-256 and A256GCM
        JWEHeader jweHeader = new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
            .build()
        
        // 4. Create encrypter and JWE object with JWS payload
        RSAEncrypter encrypter = new RSAEncrypter(publicKey)
        JWEObject jweObject = new JWEObject(jweHeader, new Payload(jwsPayload))
        
        // 5. Perform encryption
        jweObject.encrypt(encrypter)
        
        // 6. Get serialized JWE and set as message body
        String jweEncoded = jweObject.serialize()
        message.setBody(jweEncoded)
        
        if (DEBUG) {
            // Log basic info about input and output
            messageLog?.addAttachmentAsString("JWE-Debug", 
                "Encryption completed:\n" +
                "- Input (JWS) length: " + jwsPayload.length() + " chars\n" +
                "- Output (JWE) length: " + jweEncoded.length() + " chars\n" +
                "- JWE preview: " + jweEncoded.substring(0, Math.min(50, jweEncoded.length())) + "...",
                "text/plain")
        }
        
        return message
        
    } catch (Exception e) {
        messageLog?.addAttachmentAsString("Error", "JWE encryption error: " + e.getMessage(), "text/plain")
        throw e
    }
}

Script 3: JWE Decryption (Optimized with Nimbus)

This script represents our biggest improvement. Instead of manually handling the complex JWE structure and GCM decryption with AAD, we leverage Nimbus to handle these details automatically:

import com.nimbusds.jose.JWEDecrypter
import com.nimbusds.jose.JWEObject
import com.nimbusds.jose.Payload
import com.nimbusds.jose.crypto.RSADecrypter
import com.sap.gateway.ip.core.customdev.util.Message
import java.security.KeyPair
import java.security.interfaces.RSAPrivateKey
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import java.util.Base64

def Message processData(Message message) {
    def messageLog = messageLogFactory.getMessageLog(message)
    final String DECRYPT_KEY_ALIAS = "sign_n_encrypt_cert"
    
    // Enable/disable logging (change to true for debugging)
    final boolean DEBUG = false

    try {
        // 1. Get and validate the JWE token
        String jweToken = message.getBody(String.class)
        if (jweToken == null || jweToken.count('.') != 4) {
            messageLog?.addAttachmentAsString("Warning", "Invalid JWE token format", "text/plain")
            return message
        }
        
        // 2. Get the private key from keystore
        def keystoreService = com.sap.it.api.ITApiFactory.getService(com.sap.it.api.keystore.KeystoreService.class, null)
        if (keystoreService == null) throw new IllegalStateException("KeystoreService not available")
        
        KeyPair keyPair = keystoreService.getKeyPair(DECRYPT_KEY_ALIAS)
        if (keyPair == null) throw new IllegalStateException("KeyPair not found for alias: " + DECRYPT_KEY_ALIAS)
        
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate()
        
        // 3. Decrypt the JWE using Nimbus
        JWEObject jweObject = JWEObject.parse(jweToken)
        JWEDecrypter decrypter = new RSADecrypter(privateKey)
        jweObject.decrypt(decrypter)
        
        // 4. Get the decrypted payload
        Payload payload = jweObject.getPayload()
        String decryptedContent = payload.toString()
        
        // Only one strategic debug point
        if (DEBUG) {
            messageLog?.addAttachmentAsString("Decrypted-Content", 
                "JWE successfully decrypted.\n" +
                "Content type: " + (decryptedContent.startsWith("eyJ") ? "JWT/JWS token" : "Raw data") + "\n" +
                "Preview: " + decryptedContent.substring(0, Math.min(50, decryptedContent.length())) + "...", 
                "text/plain")
        }
        
        // 5. Process the content based on its type
        String finalOutput
        JsonSlurper jsonSlurper = new JsonSlurper()
        
        // Handle JWT/JWS content
        if (decryptedContent.startsWith("eyJ") && decryptedContent.contains(".")) {
            // Extract payload from JWT 
            String[] parts = decryptedContent.split("\\.")
            if (parts.length >= 2) {
                String payloadBase64 = parts[1]
                byte[] payloadBytes = Base64.getUrlDecoder().decode(payloadBase64)
                String payloadJson = new String(payloadBytes, "UTF-8")
                
                // Parse and format JSON
                def jsonObject = jsonSlurper.parseText(payloadJson)
                finalOutput = JsonOutput.prettyPrint(JsonOutput.toJson(jsonObject))
            } else {
                finalOutput = decryptedContent
            }
        } else {
            // Try to parse as regular JSON
            try {
                def jsonObject = jsonSlurper.parseText(decryptedContent)
                finalOutput = JsonOutput.prettyPrint(JsonOutput.toJson(jsonObject))
            } catch (Exception e) {
                // Not JSON, use as plain text
                finalOutput = decryptedContent
            }
        }
        
        // 6. Set the result as message body
        message.setBody(finalOutput)
        return message
        
    } catch (Exception e) {
        messageLog?.addAttachmentAsString("Error", "Decryption failed: " + e.getMessage(), "text/plain")
        throw e
    }
}

Why This Approach Is Superior

The optimized implementation using Nimbus JOSE+JWT offers several significant advantages:

  1. Standards Compliance: Nimbus correctly implements all aspects of the JOSE standards (JWS/JWE), ensuring compatibility with financial institutions.
  2. Automatic AAD Handling: Perhaps the biggest advantage is that Nimbus automatically handles the critical AAD component in GCM encryption/decryption, which was a frequent source of errors in manual implementations.
  3. Code Simplicity: Compare the optimized JWE decryption script to a manual implementation that would require more lines of complex cryptographic operations.
  4. Security Robustness: By delegating cryptographic operations to a well-tested library, we reduce the risk of implementation vulnerabilities.
  5. Maintenance Efficiency: The simplified code is easier to understand, debug, and maintain.

Required JAR Configuration

For the scripts to work correctly, it's necessary to add the following libraries to the integration package in SAP Integration Suite:

  1. nimbus-jose-jwt-x.x.x.jar - For JWS/JWE handling (download from Maven Repository)

To add these JARs to your SAP Integration Suite package:

  1. Navigate to your integration package
  2. Select "Edit" mode
  3. Go to "References" tab
  4. Click "Add" → "Archive"
  5. Upload the JAR file
  6. Deploy your integration flow after adding all resources

Common Troubleshooting

Even with our optimized implementation, some issues may still arise:

  1. Certificate Errors:
    • Symptom: KeyStoreException or certificate not found
    • Solution: Verify the alias and that the certificate is correctly imported
  2. Class Loading Errors:
    • Symptom: ClassNotFoundException or NoClassDefFoundError
    • Solution: Verify that the Nimbus JAR is added to the package resources
  3. Decryption Failures:
    • Symptom: Exceptions during JWE decryption
    • Solution: Verify that the correct private key is being used and that it corresponds to the certificate the bank has on record

Beyond Authentication: Applying These Patterns to API Calls

While this implementation focuses on obtaining an authentication token, the same security patterns can be adapted for any API endpoint that requires this level of security. Once you've obtained the access token:

  1. For subsequent API calls:
    • Include the token in the Authorization header (Authorization: Bearer {token})
    • Apply the same JWS/JWE pattern to the API payload
    • Process responses using the same decryption pattern
  2. Token management:
    • Store the token and its expiration time
    • Implement token renewal logic based on expiration
    • Consider creating a reusable subflow for token management

Debugging Support

Each script includes a DEBUG flag (default is false) that can be set to true for troubleshooting:

final boolean DEBUG = true

This provides diagnostic information without excessive logging, making it ideal for implementation and troubleshooting.

Conclusion

Implementing this multi-layer security architecture in SAP Integration Suite requires a deep understanding of cryptographic standards and careful configuration of each component. By leveraging the Nimbus JOSE+JWT library, we've achieved a robust, standards-compliant implementation with significantly less complexity than manual approaches.

The key takeaways from this implementation are:

  1. Defense in Depth: The combination of mTLS, JWS, and JWE creates a security architecture that protects against multiple attack vectors.
  2. Standards Compliance: Using Nimbus ensures proper implementation of the JOSE standards required by financial institutions.
  3. Simplified Implementation: By leveraging a specialized library, we reduce code complexity while enhancing security.
  4. Flexibility: This approach can be adapted to work with various banking APIs that require similar security measures.

While this implementation specifically targets the authentication token flow, the same patterns can be applied to any API integration that requires advanced security measures. By following this approach, you can meet the most stringent security requirements while maintaining compatibility with modern API standards.

References

  1. Nimbus JOSE+JWT Library
  2. RFC 7515: JSON Web Signature (JWS)
  3. RFC 7516: JSON Web Encryption (JWE)
  4. SAP Integration Suite Documentation
  5. AES-GCM Authenticated Encryption

 

4 Comments
Top kudoed authors