Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
arun_g_kumar
Employee
Employee

Generate CDC Bearer Token from JWT hashed with RSA Private Key


REST API requests to SAP Customer Data Cloud should be made securely, the recommended authentication mechanism is to use a bearer token constructed using a unique RSA key. An API request to SAP Customer Data Cloud should be signed using an HTTP bearer token. This replaces the application / user key and secret signature method.

What is JWT?

JSON Web Tokens is an open standard URL-safe way for securely transmitting information between a request & response system. Data in a JWT is encoded as a JSON object. This data is digitally signed using a private key pair using RSA.

JSON Web Tokens consist of three parts separated by a dot,

  • Header

  • Payload

  • Signature


Format of JSON Web Token

[header].[body].[signature]

Header:
{
"alg": "RS256" – JWT Signing algorithm
"typ": "JWT", - JWT
"kid": "<app key>" - User key from the application
}

Body:
{
"iat": 1548189000, - Timestamp that identifies when the JWT was issued
"jti": "b3t567895-r56y-7659-5t6i-t67y64456784",
- A nonce ensuring that this JWT can be used only once
}

Signature:
RSA Private Key converted to PKCS#8 Private Key signed with RSA-256 hashing algorithm

Let’s discuss how to generate the JWT bearer token in CPI Groovy Script assuming CPI plays the cloud middleware between the requesting cloud tenant and target Customer Data Cloud.

First step is to upload the RSA Private Key to Keystore in CPI. The RSA private key is provided in SAP Customer Data Cloud console


RSA Private Key copied from CDC Console



Add RSA Private Key to Security Material



RSA Private Key in Security Material


Now let us create a simple integration flow which will accept a request and return the JWT Bearer Token. Assume the incoming request contains the user key and jti – JWT ID unique for each token request


Integration Flow


Integration flow receives the incoming request and has a content modifier which reads the user key and jti and assigns it to exchange parameters


Content Modifier


Next component is the integration flow is the Groovy Script which will read the RSA Private Key from the key store and encoded in base64 format. This will be converted to PKCS8 format and hashed with RSASHA256 algorithm for asymmetric encryption.

Declare the headers,
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import io.jsonwebtoken.*
import com.sap.it.api.ITApiFactory;
import java.util.Base64
import java.security.spec.PKCS8EncodedKeySpec
import com.sap.it.api.keystore.KeystoreService;
import java.security.KeyFactory
import java.security.Key;
import java.math.BigDecimal

//Get the timestamp with validity of 1 minute from now
def timeStamp = (System.currentTimeMillis() / 1000 + 60)
timeStamp = timeStamp.setScale(0, BigDecimal.ROUND_DOWN);

//Get the JWT ID and User key from exchange property
jwtid = message.getProperty("JWT_ID")
userKey = message.getProperty("UserKey")


Now formulate the jsonstring with the unique JWT ID and the timestamp generated
String jsonString = """{"iat":${timeStamp}, "jti":"${jwtid}"}""";

Read the RSA private key stored in security material
Key privateKey = keyService.getKey("rsa_private_key");

//Encode the key in Base 64
def base64EncodedPrivateKey = (Base64.getEncoder().encode(privateKey.getEncoded()));

Next step is to conver the base 64 encoded RSA private key to Public-Key Cryptography Standards (PKCS)#8
//Create an instance of PKCS8EncodedKeySpec with the given encoded key
def pkcs8KeySpec = new PKCS8EncodedKeySpec(base64EncodedPrivateKey)

//Get an instance of KeyFactory to convert keys to RSA algorithm
def pkcs8KeyFactory = KeyFactory.getInstance("RSA")

//Generate a private key in PKCS8 format
def pkcs8Key = pkcs8KeyFactory.generatePrivate(pkcs8KeySpec)

Call JWTBuilder API to generate a JSON Web Token signed with the PKCS8 private key
String jwtToken = Jwts.builder()       	    
.setHeaderParam("typ","JWT")
.setHeaderParam("alg","RS256")
.setHeaderParam("kid",userKey)
.setPayload(jsonString)
.signWith(SignatureAlgorithm.RS256, pkcs8Key)
.compact();

//Assign the token to message body
message.body = jwtToken

Now, let’s test the integration flow from Postman. Call the integration flow and pass the jti - JWT ID and CDC user key in the payload


Call Integration Flow from Postman


Integration flow returns the JSON Web Token which can be used to sign requests to Customer Data Cloud


Generated JSON Web Token


The validity time of the JSON Web Token can be increased by changing the timestamp which is currently set to 60 seconds
//Get the timestamp of 1 minute from now
def timeStamp = (System.currentTimeMillis() / 1000 + 60)
timeStamp = timeStamp.setScale(0, BigDecimal.ROUND_DOWN);

The jti – unique JWT ID can be generated within groovy script using UUID.randomUUID() method instead of passing it in the call request
jwtid = UUID.randomUUID().toString()

Thanks for your time!
Regards,

ArunKumar Balakrishnan

 
4 Comments
0 Kudos
Hello Arun Kumar,

I am using a website called https://jwt.io/ , having a similar requirement, where I need to encrypt the upcoming payload using the Private Key.

Could you please help sharing the groovy code for the same.

 

Regards

Ashish

 
prathmesh009
Explorer
0 Kudos
Hello Ashish,

Please refer to the groovy code below :
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;

def Message processData(Message message) {

//Properties
map = message.getProperties();
def clientSecret = map.get("clientSecret");
def clientId = map.get("clientId");

//convert Hex String to Byte array
def clientSecretBlob = hexStringToByteArray(clientSecret);

// Prepare JWT Header
def jwtHeader = JWTHeader()

// Prepare JWT JWTClaim
def jwtClaim = JWTClaim()

// Concat JWT Header and JWT Claim
def unsignedToken= "${jwtHeader}" + "." + "${jwtClaim}";

//JWT Signature
def hash = hmac_sha256(clientSecretBlob, unsignedToken)
signature = hash.encodeBase64().toString().replaceAll("\n","").replaceAll("\\+","-").replaceAll("/", "_").replaceAll("\\=","");

//Headers
def content = "application/json";
message.setHeader("Content-Type", content);
message.setHeader("api-version", 2);
message.setHeader("jwt", unsignedToken + "." + signature);

return message;
}

def JWTHeader() {
def jwtHeader = """
{"alg":"HS256","typ":"JWT"}
"""
def jwtHeader_str = Base64.encodeBase64URLSafeString(jwtHeader.replaceAll("\\s","").getBytes("UTF-8"));

return jwtHeader_str;
}

def JWTClaim() {
def iat = (int)(new Date().getTime() / 1000);
def exp = (int)((new Date().getTime() / 1000) + 30);
def nbf = (int)(new Date().getTime() / 1000);
def iss = map.get("clientId");
def method = map.get("method");
def path = map.get("path");
def host = map.get("host");
def request_id = UUID.randomUUID().toString();
def region = map.get("region");
def jwtClaim = """
{"exp":${exp},"iat":${iat},"nbf":${nbf},"iss":"${iss}","method":"${method}","path":"${path}","host":"${host}","request_id":"${request_id}","region":"${region}"}
"""
def jwtClaim_str = Base64.encodeBase64URLSafeString(jwtClaim.replaceAll("\n","").replaceAll("\\\\","/").getBytes("UTF-8"));

return jwtClaim_str;
}

def hexStringToByteArray(String s){
def len = (int) s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int j = i / 2;
String byte_hex = s.substring(i, i + 2);
try {
int byte_value = Integer.parseInt(byte_hex, 16);
data[j] = (byte) byte_value;
} catch (NumberFormatException error) {
throw new IllegalArgumentException("Bad hex byte value " + byte_hex);
}
}
return data;
}
def hmac_sha256(secretKey, String data) {
try {
Mac mac = Mac.getInstance("HmacSHA256")
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, "HmacSHA256")
mac.init(secretKeySpec)
byte[] digest = mac.doFinal(data.getBytes())
return digest
} catch (InvalidKeyException e) {
throw new RuntimeException("Invalid key exception while converting to HMac SHA256")
}
}

 

You can refer more from here

 

Regards,

Prathmesh
maik_bosch
Contributor
0 Kudos
Thank you for the blog 🙂

As an alternative we can also store the Private Key as Secure Parameter and then use script below to access the value

https://help.sap.com/docs/cloud-integration/sap-cloud-integration/access-secure-parameters-in-script...
ssuazo98
Explorer
0 Kudos
how do you create the rsa key ?? that i quite didnt understand that part