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: 
CarlosRoggan
Product and Topic Expert
Product and Topic Expert

With other words:


How to
verify digital signature
in a Groovy script


SAP Cloud Integration (CPI) provides functionality to automatically sign a message with a digital  signature (Simple Signer).
However, currently there’s no functionality to automatically verify such a digital signature.
This blog shows how to do such verification programmatically with a groovy script.

Quicklinks:
Quick Guide
Script



Content


0. Prerequisites
1. Introduction
2. Tutorial
Appendix 1: Groovy Script for Verify
Appendix 2: Groovy Script Alternatives
Appendix 3: Groovy Script to search Security Provider

0. Prerequisites


To follow this tutorial, access to a Cloud Integration tenant is required, as well as basic knowledge about creating iFlows.
The previous tutorial explains the basics about digital signatures and how to create them with the Simple Signer in CPI.
For remaining open questions I recommend the Security Glossary.

1. Introduction


Little recap about digital signatures.

Some content (like text of image, etc) is important and should be protected against modification.
A hash code (== digest == fingerprint) is created based on a dedicated algorithm.
This hash code is encrypted using the private part of a key pair.
Afterwards, the content + encrypted hash + algorithm is sent to the receiver.
The receiver can check if the content is not modified while being transferred (== verify).

Note that in context of digital signatures, the content itself is not secret, not encrypted.
Note that the receiver needs the public part of the key pair.
Note that the public key is not secret, can be published anywhere and can be contained in a X.509 certificate.

To verify a digital signature, the following steps are required:

1. View the content
First of all, in this scenario, the original content  has been received in plaintext and can be viewed.
It is desired to check if the content is trustworthy or if it has been modified while transferring over the internet.

2. Decrypt
The receiver has a hold of the public key of the sender.
It can have been sent beforehand or downloaded from the internet.
Using the public key, the encrypted hash code can be decrypted.
If the decryption doesn’t fail, then it is proven that the hash code was encrypted with the private key that belongs to the public key (not any foreign private key of a hacker).
If the decryption fails, then it is clear that some hacker has modified the important original content and created a new signature.
In this case, the original content cannot be restored, but at least the receiver knows that the content is not to be trusted.

3. Verify
Once the hash code is available, it can be verified.
Verification is performed in 2 steps:

3.1. Create new hash code
The receiver creates his own hash code of the received content.
To do so, the same algorithm (like the sender) needs to be applied.

3.2. Compare
The characteristics of a hash code is: it is always the same.
As such, the hash code that is created by the receiver must be equal to the hash code that he has received.
If a hacker would change even one single little character, the resulting hash code would be completely different.
As such, a simple comparison of the 2 hash codes proves that the original content has not been modified.
If the hashes are different, the receiver cannot trust the received content.

3.3. tools
Typically, the 2 mentioned steps (create new hash and compare) are performed by one verification command, using tools or libraries.

Coming to CPI.
In the command palette of the iFlow Designer we have a iFlow step called “Simple Signer” (see my previous blog post).
It creates a digital signature, as sketched above.
However, one additional step is performed:
After creating the signature, the “Simple Signer” converts the binary signature to a base64-encoded String.
This makes sense when transferring it via internet.
As such, when it comes to verification of the signature, the receiver has to decode the signature, before it can be decrypted.

Note:
Decoding a base64-encoded string is just a conversion, no security mechanism. No secret nor password nor key is required.

Coming to this blog post.
So finally at this point we’ve set the ground for our tutorial.
In the command palette of the iFlow Designer we don’t find a “Simple Verifier” (currently).
As such, if we want to verify an incoming digital signature, we have to do it manually.
To do so, we use a Groovy script.
Please read below section to learn how this can be achieved with a few lines of code.

Note:
Below code can be used to verify any digital signature, not only those created in CPI with “Simple Signer”.
Only 2 steps need to be taken care: the base64-encoding and the hashing algorithm.

2. Tutorial


Our goal is to design an iFlow with a Groovy script that verifies an incoming signature (created with “Simple Signer”).
As usual, we keep everything as simple as possible, no connections, no adapters are required.

2.1. Create Key Pair


We let CPI generate a key pair for us.
This is done in the Keystore of Cloud Integration.
Go to your CPI -> "Operations & Monitoring" -> "Manage Security" -> "Keystore"

Direct link:
<cpi>/itspaces/shell/monitoring/Keystore

Choose "Create" -> "Key Pair"


Enter some values of your choice, e.g. "simplecert" and press “Create”.


The Alias is required later, we can take a note of it, or try to remember (or follow my description).
Key Type the default is RSA, which is most widely used.
DSA is rather common in the context of digital signatures, because faster.
See my blog post here for some info about algorithms.
Note that the signer and the verifier (groovy) need to be configured according to the choice that’s being taken here.
Key Size: larger Key Size increases the security. For our tutorial we can leave the default

2.2. Create iFlow


It might not make much sense, but to make things simple, we create an iFlow that
defines some text (which is hardcoded dummy text)
signs it with Simple Signer (which should be done rather outside)
verifies it with groovy script (which is the interesting part)
writes the message to a datastore (which nobody reads)

The iFlow looks like this:


We create an iFlow with the following elements:

  • Start Timer set to “Run Once”.

  • Content Modifier with arbitrary text in Message Body.

  • Simple Signer with "Private Key Alias" and default settings (SHA512/RSA).

  • Groovy Script with code copied from Appendix.

  • Datastore Write operation with arbitrary name.


2.3. The Groovy Script


Let's go through the content of the script.
Note that the code is kept simple and e.g. error handling is missing.

Libraries

First of all, let’s have a look at the libraries that we need to use.
As mentioned above, for verification we need the public key.
As usual, in CPI the keys and certificates are uploaded in the Keystore.
We use the Keystore service API to read fetch the required key:
import com.sap.it.api.ITApiFactory;
import com.sap.it.api.keystore.KeystoreService;

We use the native java.security library for dealing with the Keys.
import java.security.KeyPair;
import java.security.PublicKey;

For processing digital signatures, there’s a convenience class in that library, which is comprehensive and comfortable to use:
import java.security.Signature;

Access content

We need the actual content, the content which was signed and which should be verified.
This is just the message body which we can easily access:
def body = message.getBody(java.lang.String) as String;
byte[] bodyAsBytes = body.getBytes();

We convert the String body to byte array, because it is required by the verification object below.

Public Key

As mentioned above, we need the public key to decrypt the encrypted hash code.
We don’t decrypt manually (we leave it to the helper class), but we have to get a hold of the public key in order to pass it to the convenience class below.
The keys are uploaded to the Keystore in CPI, so we use the KeystoreService to access it.
KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null)

The second parameter is used to specify a context (can be mapping, camel, etc), but we don't need it here.

Note:
The method ITApiFactory.getApi() has been deprecated and replaced by getService().

Once we have the service, we use it to fetch the uploaded artifact by alias name.
In our example, it is the same name like the alias for the private key, which was created in chapter 2.1. and configured in the Simple Signer in chapter 2.2.

The KeystoreService offers a couple of service APIs which are suitable for our needs:
keystoreService.getKeyPair("simplecert")
keystoreService.getKey("simplecert")
keystoreService.getCertificate("simplecert")

See Appendix 2 for usage examples.

I’ve decided to use getKeyPair() because it returns a simple KeyPair object of the native java.security library.
The instance of KeyPair just holds the 2 keys and has no security functionality.
The getCertificate() method returns a java.security.cert.Certificate Object which has a method getPublicKey, which is abstract.
So we would need a JCA-Provider library (IAIK), which is possible, but I prefer to avoid.
The getKey() method returns an interface and at runtime we get an instance of iaik.security.rsa.RSAPrivateKey, which again requires the IAIK library.

Hence, in our example all 3 variants are possible, but I prefer the KeyPair:
KeyPair keyPair  = keystoreService.getKeyPair("myownstore");
PublicKey publicKey = keyPair.getPublic();

Note:
I like the javaish code for the demo script, because we can clearly see what class we get.
If we use the groovyish def notation, then we can as well remove some import statements.

Signature

Up to now, we have the content and the key, now we need a hold of the signature.
In our example, we’re using the Simple Signer to sign the message.
As we’ve learned in the previous blog post, the Simple Signer stores the digital signature in a header called SAPSimpleSignatureValue.
The name of the header can be configured in the property sheet of the Simple Signer, so we need to make sure that we're using the same header name.
To get the signature, we just read the header:
def signature = message.getHeaders().get("SAPSimpleSignatureValue");

Base64

There’s one more thing we need to remember from previous blog post:
The Simple Signer always base64-encodes the signature, before storing it in the header.
As such, we now need to base64-decode the header value:
byte[] signatureAsBytes = Base64.getDecoder().decode(signature); 

Note:
You might prefer the popular apache library with this handy method:
import org.apache.commons.codec.binary.Base64;
. . .
byte[] signatureAsBytes = Base64.decodeBase64(signature);

Verify

Now that we’ve collected the required data, we can go ahead and verify.
We use the native convenience class java.security.Signature.
It needs to be initialized with the algorithms to be applied.
As mentioned above, when verifying, we create a hash code that must be equal to the received hash.
As such, it is mandatory to use the same hashing algorithm like the sender,
In addition, we need to decrypt the hash code.
To do so, we need to know which algorithm was used to generate the key pair.
Most common algorithm is RSA, but also DSA or ECDSA are frequently used for digital signatures (see here for explanations).
So, the initialization of the Signature class must match the configuration of the Simple Signer and must match the key pair.
In our example, we’ve used SHA512 / RSA.

The Signature initialization also requires the information, which “Provider” is able to deal with the specified algorithms.
This is due to the open architecture of the JCA (Java Cryptography Architecture), where the so-called “Providers” bring their implementations of standards or algorithms, etc
SunRsaSign is a built-in provider that supports RSA.

In addition, we feed the Signature with the key, the content and the signature.
Signature sig = Signature.getInstance("SHA512withRSA", "SunRsaSign");
sig.initVerify(publicKey);
sig.update(bodyAsBytes);
boolean verificationResult = sig.verify(signatureAsBytes);

 

Note:
How to  find out the names that have to be passed in the String arguments?
These names must match the registered provider name and must match the name of the service offering.
There’s a native java method that returns all registered providers.
I’ve put together a Groovy script that loops over all providers and searches for the required algorithm name.
The required algorithm name is the one that was used in the Simple Signer for creating the digital signature.
The script can be found in the Appendix3 .

Result

In our simple tutorial, we do only simple check on the result:
If not true, we throw an exception, to let the message processing fail.
In addition, we write the result of verification to the message log.
def messageLog = messageLogFactory.getMessageLog(message);
messageLog.addAttachmentAsString("VerificationResult", "Verification result: " + verificationResult, "text/plain");

if(! verificationResult) throw new Exception("Verification of Digital Signature failed!")

My apologies for simple code.
The full script can be found in the Appendix 1.

2.4. Deploy


After creating the script, we can save and deploy.
After successful execution of the iFlow, we go to our Data Store and have a look at the “body” file of the zip.
It contains the plaintext content that we entered in the Content Modifier step, which means that the verification has been successful.

2.5. Optional: Negative Test


To let the verification fail, we have a few options:

We can modify the message body. Much more easy than a hacker would do, we can just add a Content Modifier step in the iFlow after the signer and before the script.
This Content Modifier can just set a different message body.
Alternatively, we can modify the script.
If we use a different algorithm, e.g. SHA256 to create the verification-signature, then the verification will fail.
One more idea would be to use a different public key, taken from a different key pair that can be created in the CPI Keystore. So if the public key doesn't belong to the private key that was used for signing, then the verification fails.

Summary


In this blog post we’ve learned some basics about digital signatures.
We’ve learned which Java security API can be used to deal with digital signatures.
We’ve put things together in a hands-on tutorial, where we used the Java API to verify a digital signature created with the Simple Signer.

SAP Help Portal
Docu for Groovy API, e.g. KeyStore
Docu for Message-Level Security
Javadoc for Groovy scripting main entry

Java
The JCA reference guide.
Security package reference.
Reference for KeyPair

Blogs
Understanding Simple Signer
How to verify signature in Node.js app
Understanding PKCS #7 / CMS Standard
Security Glossary Blog

Appendix 1: The Groovy Script


Below script is used in our tutorial.
Make sure to adapt the key alias name according to your alias name in your CPI Keystore.
import com.sap.gateway.ip.core.customdev.util.Message;

// used for accessing the PrivateKey by alias
import com.sap.it.api.ITApiFactory;
import com.sap.it.api.keystore.KeystoreService;

// used for digital signature verification. Built-in, so import statements are not required if variables not declared
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.Signature;

def Message processData(Message message) {

// Content
def body = message.getBody(java.lang.String) as String;
byte[] bodyAsBytes = body.getBytes(); //convert body to byte[] is required for signature verification

// Public Key
KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null) // fetch the public key from the CPI keystore
KeyPair keyPair = keystoreService.getKeyPair("simplecert");
PublicKey publicKey = keyPair.getPublic();

// Signature
def signature = message.getHeaders().get("SAPSimpleSignatureValue"); // read base64-encoded signature from header
byte[] signatureAsBytes = Base64.getDecoder().decode(signature); // decode base64

// Verify
Signature sig = Signature.getInstance("SHA512withRSA", "SunRsaSign"); // algorithms and the provider. "SunRsaSign" is a built-in provider that supports RSA
sig.initVerify(publicKey);
sig.update(bodyAsBytes);
boolean verificationResult = sig.verify(signatureAsBytes);

// Handle result
def messageLog = messageLogFactory.getMessageLog(message);
messageLog.addAttachmentAsString("VerificationResult", "Verification result: " + verificationResult, "text/plain");
if(! verificationResult) throw new Exception("Verification of Digital Signature failed!")

return message;
}

 

Appendix 2: Alternatives for retrieving  Public Key


For verifying the digital signature, the public key is required.
The corresponding key pair is uploaded in the Keystore of CPI.
In our Groovy script, we can access a keystore object via API and use it to retrieve the public key.
There are at least 3 ways to access the public key.
Above, we’ve used keystoreService.getKeyPair("alias")
For your convenience, I’m showing alternative ways below.

Using keystoreService.getKey(“Alias”)

This method returns java.security.Key which is a root interface.

At runtime, it returns an instance of iaik.security.rsa.RSAPrivateKey which extends iaik.pkcs.pkcs8.PrivateKeyInfo<(/span> which in turn implements java.security.Key

The code snippet:
import iaik.security.rsa.RSAPrivateKey;
import java.security.PublicKey;

def Message processData(Message message) {
. . .
KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null);
RSAPrivateKey rsaPrivateKey = keystoreService.getKey("simplecert");
PublicKey publicKey = rsaPrivateKey.getPublicKey();
. . .
}

Using keystoreService.getCertificate("Alias")

This method returns class java.security.cert.Certificate which doesn’t contain implementation for getPublicKey() which is abstract, such that, at runtime, an instance of iaik.x509.X509Certificate is returned.
import java.security.cert.Certificate;
import java.security.PublicKey;

def Message processData(Message message) {
. . .
KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null);
java.security.cert.Certificate certificate = keystoreService.getCertificate("simplecert");
PublicKey publicKey = certificate.getPublicKey()
. . .
}

 

Appendix 3: How to find the provider name


We receive a digital signature that was created with a special algorithm, e.g. SHA512withRSA.
In the verification code, we need to specify this algorithm, but also we need to specify by which security provider it is implemented.
This is due to the open architecture of Java Cryptography Architecture (JCA) which allows extensions for special algorithms etc.
How to find the provider name?
Below sample code retrieves all registered providers and their service offerings.
Those providers that offer the service type “Signature” with the required algorithm name, are collected and written to the log.
import com.sap.gateway.ip.core.customdev.util.Message;

import java.security.Security;
import java.security.Provider;
import java.security.Provider.Service;


def Message processData(Message message) {

def requiredAlgorithm = "SHA512withRSA";
StringBuffer text = new StringBuffer();

// get all providers, the built-in providers and extensions, if any
Provider[] providers = Security.getProviders();

// search for providers supporting the desired alg, collect them in a list
Set<String> foundProviders = new HashSet<String>();
for (Provider provider : providers) {
Service service = provider.getService("Signature", requiredAlgorithm);
if(service != null) {
foundProviders.add(provider.getName());
}
}

// print the list, if any, otherwise print all
if (! foundProviders.isEmpty()) {
text.append("The following providers do support the required algorithm (" + requiredAlgorithm + ") :");

foundProviders.each{
s -> text.append("\n " + s);
}
}else {
text.append("No provider found for requiredAlgorithm (" + requiredAlgorithm + "). \nNow printing all " + providers.length +" providers that support signatures:");
for (Provider provider : providers) {
text.append("\nProvider: " + provider.getName());
Set<Service> services = provider.getServices();
for(Service service : services) {
if(service.getType() == "Signature"){
text.append("\n Supports algorithm: '" + service.getAlgorithm() + "'");
}
}
}
}

text.append("\n");
text.append("\nCurrent Groovy version: " + GroovySystem.version);

def messageLog = messageLogFactory.getMessageLog(message);
messageLog.addAttachmentAsString("Provider_Info", text.toString(), "text/plain");

return message;
}


Alternative code for newer Groovy version with Java Stream support:
// get all providers, the built-in providers and extensions, if any
Provider[] providers = Security.getProviders();

// search for providers supporting the desired alg, then put them into a list
List<Provider> foundProviders = Arrays.stream(providers).filter( provider -> provider.getService("Signature", "SHA512withRSA") != null ).collect(Collectors.toList());

// print the list, if any, otherwise print all
if (! foundProviders.isEmpty()) {
System.out.println("The following providers do support the algorithm:");
foundProviders.forEach(System.out::println);
}else {
System.out.println("No provider found. Printing all " + providers.length +" providers that support signatures:");
Arrays.stream(Security.getProviders()).forEach( provider -> {
System.out.println("Provider: " + provider.getName());

provider.getServices().stream().filter( service -> service.getType() == "Signature")
.forEach( service -> {
System.out.println(" Supports algorithm: '" + service.getAlgorithm() + "'");
});
});
}

 

 
import com.sap.gateway.ip.core.customdev.util.Message;

import java.security.Security;
import java.security.Provider;
import java.security.Provider.Service;


def Message processData(Message message) {


def requiredAlgorithm = "SHA512withRSA";
StringBuffer text = new StringBuffer();

// get all providers, the built-in providers and extensions, if any
Provider[] providers = Security.getProviders();

// search for providers supporting the desired alg, collect them in a list
Set<String> foundProviders = new HashSet<String>();
for (Provider provider : providers) {
Service service = provider.getService("Signature", requiredAlgorithm);
if(service != null) {
foundProviders.add(provider.getName());
}
}

// print the list, if any, otherwise print all
if (! foundProviders.isEmpty()) {
text.append("The following providers do support the required algorithm (" + requiredAlgorithm + ") :");
foundProviders.each{
s -> text.append("\n " + s);
}
}else {
text.append("No provider found for requiredAlgorithm (" + requiredAlgorithm + "). \nNow printing all " + providers.length +" providers that support signatures:");
for (Provider provider : providers) {
text.append("\nProvider: " + provider.getName());
Set<Service> services = provider.getServices();
for(Service service : services) {
if(service.getType() == "Signature"){
text.append("\n Supports algorithm: '" + service.getAlgorithm() + "'");
}
}
}
}

text.append("\n");
text.append("\nCurrent Groovy version: " + GroovySystem.version);

def messageLog = messageLogFactory.getMessageLog(message);
messageLog.addAttachmentAsString("Provider_Info", text.toString(), "text/plain");

return message;
}