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
2,420
SAP Cloud Integration (CPI) provides functionality to automatically sign a message with a digital signature using the Simple Signer.
In a previous blog post we've learned how to verify such signature with Node.js in an HTTP receiver. Then we've showed the weakness of that scenario.
Today we'll make the scenario more secure.

Quicklinks:
Quick Guide
Sample Code



Content


0.1. Prerequisites
0.2. Preparation
1. Introduction: Security Enhancement
2. Create Certificate Chain
3. Create iFlow
4. Create Safe Node.js App
5. Run
Appendix 1: OpenSSL Config Files
Appendix 2: Safe Scenario Code

0.1. Prerequisites



  • Today's tutorial builds upon the Node.js scenario created in this previous blog post.
    So please follow that blog first to create the sample project and iFlow.

  • The previous blog post showed the possible weak aspects of this scenario

  • The Simple Signer Blog Post 1 explains the basics about digital signatures and how to create them in CPI using the Simple Signer.

  • One previous blog post showed how to verify a signature with Groovy and it contains a little recap about digital signatures.

  • For remaining open questions I recommend the Security Glossary.

  • To follow this tutorial, access to a Cloud Integration tenant is required, as well as basic knowledge about creating iFlows.

  • You should be familiar with Node.js, even though the tutorial can be followed without local Node.js installation.

  • The command line tool OpenSSL is used in this tutorial, so don't be afraid of using it


0.2. Preparation


OpenSSL is the most commonly used tool (and library) for security-related operations, dealing with certificates, keys, converting, etc etc
Basically, it consists of libraries (for “c”) and a command line tool.

If you have GIT installed, you can just open the git bash which contains openssl.
Otherwise, follow these instructions to install OpenSSL under Windows:

Download openssl starting from here.
Afterwards it might be required to add the openssl.exe to the PATH.
If you get this error:
Unable to load config info from /usr/local/ssl/openssl.cnf
You need to set an environment variable pointing to the config file
e.g. on command line:

set OPENSSL_CONF=c:/tools/openssl/openssl.cfg

Note:
For windows, the config-file extension .cnf has to be adjusted to .cfg
A template .cnf file can be found in GIT installation folder.

1. Introduction: Security Enhancement 🛡


In the previous blog post we've simulated a hacker who is intercepting the network segment between CPI and our Node.js server app.
The hacker got hold of the public key and was able to create an own fake digital signature.
So exploited our scenario to redirect some payment 💰 to his own bank account on some beautiful sunny island. 🏝

The weakness of our scenario was the fact that we cannot determine the origin of the received public key (apart from the weak design).


This can be solved by using a certificate.

The certificate has the following advantages:
🔹 It contains the public key.
🔹 It binds the public key to an identity (the owner).
🔹 It is signed by a Certification Authority (CA).
🔹 It contains information about the owner (subject).
🔹 If compromised, that can be checked via CRL (certificate revocation list).

Note:
A certificate is public as well.
So anybody could steal a certificate and send it within his request.
To ensure that a sender is the owner of the certificate, the sender has to prove that he possesses the corresponding private key.
This is performed via "handshake":
Sender sends certificate.
The receiver generates an arbitrary string, sends it to the sender.
Sender signs it with the private key.
Receiver verifies the signature with public key of the certificate.
Thus the identity is guaranteed.

In our scenario we don’t have handshake, but we can easily use a certificate because at CPI we anyways can only upload key pairs with certificates.

What makes a certificate useful?
A certificate contains a public key and information about the user (subject).
As such, it binds the key to an identity.
However, a certificate is only a piece of text.

What makes a certificate trustworthy?
A certificate in a PKI system (Public Key Infrastructure) is always signed by a trustworthy parent certificate.

What makes the parent certificate trustworthy?
There’s a relatively short list of worldwide accepted Certification Authorities (CA).
They are trustworthy as per definition.
They issue and store certificates.
They also maintain certificate revocation lists (CRL).
Examples for CAs: DigiCert, GlobalSign, IdenTrust, Let’sEncrypt, GoDaddy, Verisign, etc.

As such, if we have a certificate that is signed by a CA, then we can be sure that it is trustworthy and that the owning identity is verified.

The certificate contains the public key but not the private key.
However, the private key is mathematically bound to the corresponding public key.
Hence, a certificate can prove that a digital signature was created by a private key that corresponds to the identity as stated in the certificate.

As the amount of certificates is huge, the CAs don’t directly sign the end-user certificates, but there are intermediate certificates that are signed by CAs and thus entitled to sign user certificates.
This is how a chain of trust is built.

If any certificate is compromised, then it is added to a “certificate revocation list” that can be checked before trusting a certificate.

What does it have to do with us?
Coming back to our sample scenario:
Remember:
We sign content and send it to node app, and we send the public key as well.
Now we want to make use of the trust system (PKI) and use a certificate chain to prove trustworthiness of the public key.
Like that, we close the weak entry for the hacker scenario.

How to get a trustworthy chain?
However, getting a certificate chain, signed by a CA is costly and overhead for our tutorial.
(if you have, you can use it of course)
As such, we create our own self-signed CA root certificate, to simulate the safe scenario.
We sign ourselves a user certificate.
Afterwards, we upload the little chain to CPI and use it in the Simple Signer iFlow.
The Node.js server app will finally verify not only the digital signature, but also the certificate and the little chain. 🔗

2. Create Certificate Chain


We’re using OpenSSL to create the certificate chain, which consists of 2 certificates:
🔹 The root CA certificate
🔹 The user certificate which is signed by the CA certificate.

For the sake of simplicity, we skip intermediate certificate(s).
For both entities, CA and user, the key pairs are required as well.

The common format of certificates in a PKI system is X.509.
The spec describes e.g. the following characteristics:
🔹 A certificate contains textual information about the owner, which is the “subject”.
🔹 The format of the information is a “Distinguished Name” (DN) which is a unique name.
A DN consists of several pieces of info, like “Common Name” (CN), “Country” (C), “Organization” (O), “Organization Unit” (OU).
🔹 Apart from the “Subject DN”, a certificate contains some metadata like key length, algorithm name, etc.
🔹 Since version 3, an X.509 certificate may contain "extensions".
"Extensions" are some optional pieces of info that can be added to certificates.
Examples:
Basic Constraints, Subject Alternative Name, Key Usage, Subject Key Identifier, Authority Key Identifier, etc

How is a CA-signed certificate created?
There are 2 steps:
1.
The user sends a “Certificate Signing Request” (CSR) to the Certifying Authority.
The CSR contains the info that the user wants to have included in the certificate.
The user decides about his "Common Name" and "Organization" etc.
2.
The CA verifies it, then issues and signs a new certificate for the user.

How is a chain created?
Root:
Create key pair and certificate.
User:
Create user key pair and csr.
Create signed user certificate.
Chain:
Compose a pem file containing the little chain of 2 certificates.

How do I execute the commands?
We can use any empty folder on our file system.
We create the 2 config files there and execute the commands there in.
After uploading the final container to CPI, we don't need the fo

2.0. Compose config files


We don’t want to overengineer our little sample, but to show relevant techniques, we need some basic amount of data for creating the certificates.
That data (names, extensions) could be passed on command line, but common practice is to store the info in a config file that is passed to the openssl command.
As we’re representing 2 entities, the CA and the user, we create 2 config files.
The config files are passed to the openssl-command that is used for certificate requests.

Note:
The configuration file is divided in named sections, such that we could store all data in only one config file. But I think it is more clear and simple using 2 files.

Let’s have a look at the configuration for the self-signed root certificate request:

reqCA.cfg
[ req ]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
prompt = no

[ req_distinguished_name ]
C = DE
L = Walldorf
O = Root
OU = RootCertification
CN = RootCASelf
emailAddress = root_ca@caself.com

[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
basicConstraints = critical, CA:true

We can see that there are several sections that can be referenced inside a config file, but also can be referenced from outside by the openssl-command itself.

The second file reqUSR.cfg will be used for generating the user-CSR and can be found in the Appendix 1.

2.1. Root Certificate


First step: create root key pair and certificate.
We create a self-signed certificate which represents the certificate of the CA Certification Authority.
This means, it will represent the root certificate and will be used to sign our user certificate.

2.1.1. Generate Root CA key

We create the key pair for the root.
To simplify the command, we leave key length and algorithm to the defaults.
In productive scenarios, bigger key size should be used.
openssl genrsa -out caPrivKey.pem

Result is the private key in PEM format.

Note: the command genrsa has been deprecated and genpkey should be used instead.

2.1.2. Create Root CA certificate

Below command creates a certificate request and generates the resulting certificate in one step.
This is achieved by adding the -x509 parameter to the req command
openssl req -x509 -new -nodes -key caPrivKey.pem -config reqCA.cfg -out caCert.pem

Optional: View content

We can use below command to view the content of the resulting certificate.
openssl x509 -text -noout -in caCert.pem

We can see that it contains all attributes as configured in the config file.

2.2. User Certificate


Second step: create user key pair, create certificate request, create user certificate itself (sign)
We skip the intermediate certificate and create directly the user certificate as child of the root.

2.2.1. Create Key Pair for user

Same command as above is  used to create a private key for the user:
openssl genrsa -out usrPrivkey.pem

2.2.2. Create CSR

Below command creates a certificate signing request based on the config file.
The private key is required for CSRs.
openssl req -new -key usrPrivKey.pem -out usrCSR.pem -config reqUSR.cfg

The result is a pem file containing the info which would be sent to a CA.
In our case, we don't send it to CA because we take over the role of CA ourselves.

Optional: View content of CSR

Below req command with -text parameter is used to view the content of the generated CSR:
openssl req -text -noout -verify -in usrCSR.pem

The result shows that the content of our config file has been properly added to the CSR.

2.2.3. Create / Sign User Certificate

To create and sign the certificate, the x509 command is used.
Note:
v3 extensions aren't included as per default.
Therefore, we need to explicitly specify the the config file and its extension-section name.
openssl x509 -req -in usrCSR.pem -CA caCert.pem -CAkey caPrivKey.pem -CAcreateserial -out usrCert.pem -extfile reqUSR.cfg -extensions v3_req

In addition to the user certificate, the command will create a file containing the serial number which can be compared to the one in the user certificate.

Optional: View user certificate
openssl x509 -text -in usrCert.pem

2.3. Create Chain


Third step.
Now that we have created both certificates, where the user certificate is signed by the root certificate, we can go ahead and store this little chain in a file.
For this purpose, the PEM format is typically used.

What is PEM?
PEM is a file format for a kind of container that can be used to store e.g. certificates and keys.
The content is base64-encoded.
The content is surrounded with BEGIN and END statements that define the type of the content.
That’s all.

As it is so simple, we can just manually copy the 2 certificates into one new .pem file.
This is possible, as our certificates were generated in pem format as well.

Note:
Only important to note: the order of the certificates is important:
See spec:
User certificate on top.
Signing certificates come below.
Root at bottom end of the file.

As such, we can proceed as follows:
🔹 Create new file usrChain.pem
🔹 Open usrCert.pem and copy content into usrChain.pem
🔹 Open caCert.pem and copy content into usrChain.pem, just after user certificate
🔹 Save

Of course, you can use more powerful commands on command line.
At this point, we’re done with creation of certificate chain.
We can now use it in CPI, which is described in the next 2 steps.

2.4. Create PFX


To use the certificate chain in CPI, we need to upload the private key (used for signing) and the chain, altogether in a keystore file.
The p12 (or pfx) format is supported here.

2.4.1. Create .p12 file

To create a container, we use the pkcs12 command.
The pkcs#12 standard defines a binary format to store certificates (etc), protected with password and thus suitable for import/export.
openssl pkcs12 -export -out nodeintegrationstore.p12 -inkey usrPrivKey.pem -in usrChain.pem -passout pass:abcd

Optional: View content of pfx

Even the content of a pfx file can be viewed:
openssl pkcs12 -in nodeintegrationstore.p12 -info -nodes -passin pass:abcd

Finally, this is how my folder looks on file system:



2.5. Upload to CPI


In our CPI tenant, we go to Monitoring -> Keystore.
We choose Add-> Key Pair.
We point to our generated nodeintegrationstore.p12 file.
We enter the password given in the command line step above: abcd
Alias name: nodeintegrationstore

Result:
In dashboard, we can see details about both certificates that are contained in one path (chain), root and own certs

Remember the Alias name (“nodeintegrationstore”) for later use.
During upload we get an expected warning because we’re using a certificate that is signed by ourselves instead of a trusted CA.
The popup suggests to verify the fingerprint.
We can do so by viewing and comparing the fingerprint of the caCert.pem on our local machine:
openssl x509 -noout -fingerprint -sha256 -inform pem -in caCert.pem

3. Create iFlow


Now that we have new key pair and certificate chain in place, and uploaded in CPI, we can used it in our iFlow.
The iFlow was created in the blog nr 3 and needs to be slightly adapted today.
For those of you who haven’t created the iFlow yet: the description can be found here.

To avoid confusion, let's briefly go through the iFlow elements.

🔸 Start Timer
set to “Run Once”.
🔸 Content Modifier
Message Body with some arbitrary text.
Headers:
Name: ‘content-type’ – Value: ‘text/plain’
Name: ‘digialgi’ – Value: ‘RSA-SHA512’
🔸 Simple Signer
Private Key Alias name: “nodeintegrationstore”.
Signature: “SHA512/RSA”
Signature Header Name: "digisigi"
🔸 Groovy Script
Content copied from Appendix 2.
🔸 HTTP Adapter
Address: e.g. https://digisigiapp.cfapps.eu12.hana.ondemand.com/process
Request Headers: *

The Groovy Script

In the script we now fetch the certificate chain from the keystore.
It is returned as array of java.security.cert.Certificate instances.
KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null) 
Certificate[] certificateArray = keystoreService.getCertificateChain("nodeintegrationstore");

According to documentation, the user certificate is returned as first entry of the array.
Certificate certificateUser = certificateArray[0];
Certificate certificateRoot = certificateArray[1];

Please forgive the simple-minded code.

We need to base64-encode each certificate:
byte[] certUserBytes = certificateUser.getEncoded();
String certUserBase64 = Base64.getEncoder().encodeToString(certUserBytes);

byte[] certRootBytes = certificateRoot.getEncoded();
String certRootBase64 = Base64.getEncoder().encodeToString(certRootBytes);

String certChain = certUserBase64 + "," + certRootBase64
message.setHeader("certificatechain", certChain );

In order to send the chain in a header, we concatenate the base64-certificates in a string.
Note the name of the new header, as it will be used below: certificatechain

The full Groovy script can be found in Appendix 2.

4.  Create Safe Node.js Application


Our existing digisigi node app needs to be adapted with respect to the following changes:
- different header name for certificate chain.
- the value of header is a string with 2 certificates.
- public key is now contained in the user certificate.
- we can now validate the certificates, to increase security.

Those of you who don’t have the digisigi app yet can follow this chapter of the previous tutorial.

package.json
"dependencies": {
"express": "^4.16.2",
"node-forge": "latest",
"pemtools": "latest"

Today we’re adding 2 new dependencies, as the task couldn’t be fulfilled with native node api.

server.js

Today we're not adding a hacker interceptor.
The endpoint implementation looks as follows now:
app.post('/process', (req, res)=>{
. . .
const certificateChainb64 = headers.certificatechain

// validate the certificate incl chain and extensions
const certificate = validateCertificate(certificateChainb64)
. . .

// verify the digital signature
const publicKey = certificate.publicKey
const verificationResult = verifySignature(content, signature, publicKey, algorithmCombi)
. . .

We can see 3 changes, compared to first version of the app:
- The new header
- The validation of the certificate
- access the public key from certificate

Let's have a look in detail.

Validation of certificate consists of 2 basic steps:
1. Validate the chain and root certificate.
2. Validate the user certificate.

But first, we need to decompose the header which contains the chain, as composed in the Groovy script.
// split the cert chain
const [certificateUserb64, certificateRootb64] = certificateHeader.split(",")

// user cert
const certificateUserBuffer = Buffer.from(certificateUserb64, 'base64');
const certificateUser = new crypto.X509Certificate(certificateUserBuffer)

//root
const certificateRootBuffer = Buffer.from(certificateRootb64, 'base64');
const certificateRoot = new crypto.X509Certificate(certificateRootBuffer)

Again, apologies for simple-minded code

1. Validate chain:

We can use native methods to verify the signature and the issuer:
const valid = userCert.verify(rootCert.publicKey)
. . .
const isIssued = userCert.checkIssued(rootCert)
. . .

What is missing in our code:
Validating the root certificate itself, which would mean to check if it is a valid CA and if it is who it claims to be.
In addition, we could check if the certificate isn’t included in a revocation list (this check would imply performance loss)

2. Validate the user certificate

There are multiple checks to be done:
Is it expired?
const certDate = new Date(certificate.validTo)
if(certDate < new Date()){

Is it self-signed?
if(certificate.issuer == certificate.subject){

Is the owner as expected?
To check it, we need to copy the values from CPI Keystore (or from local disk)
if(! certificate.checkEmail('simplesign@iflowtonode.com')){

and
const cpiFinger = new String('ED:A8:D7:77:71:BC:54:80:1D:F0:78:AA:D8:6F:BC:CA:63:C7:C0:3F:80:3B:32:CB:9B:AC:EB:75:DA:94:18:1B')
if(! certificate.fingerprint256 == cpiFinger){

and
if(! certificate.subject.includes('simplesignernode')){

Finally, as we’ve added v3 extensions, we can add this validation step for enhanced security.
To access the v3 extensions, the native support is missing (currently), so we need the 2 libraries for conversion and access.
const certDER = certificate.raw 
const certPEM = pemtools(certDER, 'CERTIFICATE').toString()
const extensions = forge.pki.certificateFromPem(certPEM).extensions

Then we can check key usage purpose:
extensions.forEach(element => {
if(element.name == 'keyUsage'){
if(! element.nonRepudiation || ! element.digitalSignature){

What is missing her:
We should also validate the extensions for Subject Key Identifier and Authority Key Identifier.

Note:
Theoretically, the DN should be unique and if the certificate is signed by CA, then it should be proven that:
-> The certificate is the same as uploaded in CPI Keystore and not exchanged by a hacker in between.

Note:
We could check the serial number of the certificate, but in case of new generation we would have a valid certificate with different serial number.

Note:
The checks showed here are suggestions and may not suit all scenarios.
For instance, we could have a scenario where we trust all certificates that belong to one organization (the “O” in the subject), so we wouldn’t care about the CN.

Finally, once we have validated the certificate chain, we can be sure that we can trust the included public key.
To access the public key, we need to return the user certificate.
So we can access it and use it to verify the digital signature.
function validateCertificate(certificateHeader){
. . .
// if valid, return the user certificate
return certificateUser

 

Again, my apologies for simple code, without error handling etc
The full code can be copied from the Appendix 2.

5. Run Safe Scenario


After deploy and trigger the iFlow, we should get a success result in CPI and in the Cloud Foundry log.
This means that the certificate which we configured in CPI and which was used for signing has reached the server app without being tampered with.
As such, we could use the included public key for verification of the digital signature.
And it means that the signed content hasn’t been altered.

Why is it safe?
A hacker could intercept and create an own certificate chain.
The hacked user certificate could even contain a copy of the DN and Key Usage extension.
However, a hacker couldn’t achieve to get it signed by a trusted CA.
As such, that chain would be signed by a self-signed root, and that would be lead to validation error in our node app.
Furthermore, the fingerprint would not match (similar like serial number, Subject Key Identifier, if we would check that).

Summary


In this blog post we’ve learned how to create a certificate chain with OpenSSL.
This included the required configuration for v3 extensions of X.509 certificates.
We’ve used the chain to configure the Simple Signer in an iFlowin CPI.
We’ve learned how to access the certificate chain in a Groovy script
Furthermore, we’ve learned how to deal with a certificate and chain in a Node.js application.
We’ve learned some possible checks for validating an incoming certificate chain.

The purpose of all these learnings was:
Improve security when using “normal” digital signatures (as created by Simple Signer) in the net.

Key Takeaways


Summary of noteworthy settings:

OpenSSL
To create X.509 v3 certificates, the config should contain a section for req_extensions
To sign a certificate, the config should contain CA:true in "Basic Constraints".
To create CSR, the req command is used
To create CSR and certificate in one step, the req command is used together with -x509 parameter

iFlow
To access the certificate chain from Keystore, there’s a method getCertificateChain().
It returns an ordered array containing the user cert in first place and root cert in last place.

Node.js
The native crypto module can be used for most certificate processing operations.
Currently, it doesn’t support access to v3 extensions.
To overcome the situation the module “node-forge” can be used (no guarantee from my side).
As node-forge requires pem format, the module “pemtools” can be used (no guarantee from my side).

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

Node.js
Official documentation of crypto package

Blogs
Understanding Simple Signer
Signature verification in Groovy Script
Signature verification in Node.js
Previous blog post: the weak aspects of the node scenario
Security Glossary Blog

Specification for X.509 v3 with extensions: rfc5280
Certificate Authority in wikipedia.

OpenSSL
Certificate Request: req command
Certificate Operations: x509 command
Reference for v3 extensions

Appendix 1: OpenSSL Config Files


reqCA.cfg

[ req ]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
prompt = no

[ req_distinguished_name ]
C = DE
L = Walldorf
O = Root
OU = RootCertification
CN = RootCASelf
emailAddress = root_ca@caself.com

[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
basicConstraints = critical, CA:true

reqUSR.cfg
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[ req_distinguished_name ]
C = DE
L = Walldorf
O = Cloud
OU = integration
CN= simplesignernode
emailAddress = simplesign@iflowtonode.com

[ v3_req ]
basicConstraints = CA:false
keyUsage = nonRepudiation, digitalSignature
subjectKeyIdentifier=hash

 

Appendix 2: Safe Scenario Code


Note:
You might need to adapt the app names in manifest and the domain of the routes.
Also, if you changed names of headers, make sure to adapt them in the code below

App

manifest.yml

---
applications:
- name: digisigiapp
path: .
memory: 64M
routes:
- route: digisigiapp.cfapps.eu12.hana.ondemand.com

package.json

{
"dependencies": {
"express": "^4.16.2",
"node-forge": "latest",
"pemtools": "latest"
}
}

server.js
const crypto = require('crypto')
const forge = require('node-forge')
const pemtools = require('pemtools')
const express = require('express')
const app = express()
app.use(express.text())


/* App server */
app.listen(process.env.PORT)


/* App endpoint */
app.post('/process', (req, res)=>{
const content = req.body
const headers = req.headers
const signature = headers.digisigi
const algorithmCombi = headers.digialgi
const certificateChainb64 = headers.certificatechain

// validate the certificate incl chain and extensions
const certificate = validateCertificate(certificateChainb64)
if(!certificate){
return res.status(404).send("Invalid content: certificate validation failed.")
}

// verify the digital signature
const publicKey = certificate.publicKey
const verificationResult = verifySignature(content, signature, publicKey, algorithmCombi)

if (verificationResult) {
res.status(204).end()
} else {
res.status(404).send("Invalid content: digital signature verification failed.")
}
})


/* Helper */

function validateCertificate(certificateHeader){

// split the cert chain
const [certificateUserb64, certificateRootb64] = certificateHeader.split(",")
// user cert
const certificateUserBuffer = Buffer.from(certificateUserb64, 'base64');
const certificateUser = new crypto.X509Certificate(certificateUserBuffer)
//root
const certificateRootBuffer = Buffer.from(certificateRootb64, 'base64');
const certificateRoot = new crypto.X509Certificate(certificateRootBuffer)

// validate
const isChainValid = validateChain(certificateUser, certificateRoot)
if (! isChainValid){
return false
}

const isCertificateValid = validateUserCertificate(certificateUser)
if(! isCertificateValid){
return false
}

// if valid, return the user certificate
return certificateUser
}

function validateChain(userCert, rootCert){

const valid = userCert.verify(rootCert.publicKey)
if(! valid){
console.log(`===> [validate certificate chain]: INVALID: user certificate verification failed.`)
return false
}

const isIssued = userCert.checkIssued(rootCert)
if(! isIssued){
console.log(`===> [validate certificate chain]: INVALID: user certificate has invalid issuer`)
return false
}

// TODO check if CA is trusted

return true
}

function validateUserCertificate(certificate){

// check if certificate has expired
const certDate = new Date(certificate.validTo)
if(certDate < new Date()){
console.log(`===> [validate certificate]: INVALID: certificate has expired`)
return false
}

// compare fingerprint with the one in CPI keystore
const cpiFinger = new String('ED:A8:D7:77:71:BC:54:80:1D:F0:78:AA:D8:6F:BC:CA:63:C7:C0:3F:80:3B:32:CB:9B:AC:EB:75:DA:94:18:1B')
if(! certificate.fingerprint256 == cpiFinger){
console.log(`===> [validate certificate]: INVALID: fingerprint not as expected.`)
return false
}

// check if certificate is self-signed
if(certificate.issuer == certificate.subject){
console.log(`===> [validate certificate]: INVALID: certificate is self-signed`)
// return false
}

// validate certificate subject
if(! certificate.checkEmail('simplesign@iflowtonode.com')){
console.log(`===> [validate certificate]: INVALID: eMail not as expected.`)
return false
}

if(! certificate.subject.includes('simplesignernode')){
console.log(`===> [validate certificate]: INVALID: subject CN not as expected.`)
return false
}

// validate extensions
const certDER = certificate.raw
const certPEM = pemtools(certDER, 'CERTIFICATE').toString()
var extensions = forge.pki.certificateFromPem(certPEM).extensions

extensions.forEach(element => {
console.log("=====EXT: " + element.name) // basicConstraints, keyUsage, subjectKeyIdentifier, etc
if(element.name == 'keyUsage'){
console.log("==>>>> EXT nonRepudiation" + element.nonRepudiation)
console.log("==>>>> EXT digitalSignature" + element.digitalSignature)
if(! element.nonRepudiation || ! element.digitalSignature){
console.log(`===> [validate certificate]: INVALID: v3 extension 'key usage' does not permit for signature or nonrepudiation.`)
return false
}
}
});

return true
}

// digital signature
function verifySignature(content, signature, publicKey, algorithmCombi){
const verifier = crypto.createVerify(algorithmCombi)
verifier.write(content)
verifier.end()

const result = verifier.verify(publicKey, signature, 'base64')
console.log(`===> [verify digital signature] Result of digital signature verification: ${result}`);
return result
}

iFlow

Groovy script
import com.sap.gateway.ip.core.customdev.util.Message;
import com.sap.it.api.ITApiFactory;
import com.sap.it.api.keystore.KeystoreService;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.cert.Certificate;

def Message processData(Message message) {

KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null)
Certificate[] certificateArray = keystoreService.getCertificateChain("nodeintegrationstore");

Certificate certificateUser = certificateArray[0];
Certificate certificateRoot = certificateArray[1];

byte[] certUserBytes = certificateUser.getEncoded();
String certUserBase64 = Base64.getEncoder().encodeToString(certUserBytes);

byte[] certRootBytes = certificateRoot.getEncoded();
String certRootBase64 = Base64.getEncoder().encodeToString(certRootBytes);

// compose string of the 2 base64-certificates
String certChain = certUserBase64 + "," + certRootBase64

// store the cert-chain-string in a header
message.setHeader("certificatechain", certChain );

return message;
}

 
2 Comments
Ignaci
Discoverer
0 Kudos

How can I create a signature using RSASSA-PSS signature algorithm?

Ignaci
Discoverer
0 Kudos

Can I use "forge" in iFlow? I would like to migrate the below code, which is used in the PostMan environment, to build the signature header.

function loadPrivateKey(privateKeyPem, privateKeyPassphrase) {
return forge.pki.decryptRsaPrivateKey(privateKeyPem, privateKeyPassphrase);
}


function buildEncodedSignature(privateKey, signingString) {
let messageDigest = forge.md.sha256.create();
messageDigest.update(signingString, "utf8");
let pss = forge.pss.create({
md: forge.md.sha256.create(),
mgf: forge.mgf.mgf1.create(forge.md.sha256.create()),
saltLength: 32
});
let signature = privateKey.sign(messageDigest, pss);
return base64UrlEncode(signature)
}