Skip to Content
Technical Articles
Author's profile photo Mandy Krimmel

Cloud Integration – Receiving Encrypted and/or Signed Mails in Mail Sender Adapter

This blog describes how to use the Mail sender adapter in order to receive encrypted and/or signed mails. This feature will be available for customers starting with the 28-October-2018 release. This blog describes the feature in a sample scenario.

Receiving Encrypted and/or Signed Mails in Mail Sender Adapter

In many Cloud Integration scenarios messages are polled from mail servers and then further processed in the Cloud Integration runtime. Starting with the 30-September-2018 release also signed and/or encrypted mails can be received, decrypted and verified. This blog describes a small sample scenario.

Configure the Integration Flow Polling the Mails

First, we configure the integration flow in the Web UI, Design section. Create an integration flow, connect the sender participant with the start message event and select the Mail adapter. Check that the created mail channel has at least version 1.5, as only with this version receiving signed and encrypted mails is possible. This means that if you create a new mail channel after the 28-October-2018 release, the mail sender channel automatically processes unsigned, signed, encrypted and also not encrypted mails.

The integration flow we configure in this blog will look like this:

Configure the Mail Sender Channel

First, you choose the Connection tab in the Mail sender channel. Configure the mail server in the Address field and configure the Proxy Type, Protection and Authentication as required by your mail server.

In the Processing tab, configure from where the mails are to be polled and what shall happen after processing:

In the Scheduler tab, define the poll interval.

Handling Encrypted Mails

To understand how to configure the decryption of the encrypted mails, you need to understand the processing of encrypted mails in the adapter: the mail adapter polls all mails in the configured mail box, regardless whether they are encrypted or not. If a mail is encrypted the adapter tries to decrypt the mail with the private keys available in the tenants keystore. Mails which are not encrypted are processed as they are. The decryption result is provided in the following properties:

  • SAP_MAIL_ENCRYPTION_DETAILS_ENCRYPTED:  Indicates if the mail was encrypted or not. It is true if the mail was encrypted, Type: boolean
  • SAP_MAIL_ENCRYPTION_DETAILS_DECRYPTION_OK: Indicates the overall decryption status. It is true if the mail was successfully decrypted, false if the mail could not be decrypted and not set if the mail was not encrypted, Type: boolean
  • SAP_MAIL_ENCRYPTION_DETAILS_DECRYPTION_ALIAS: Indicates the alias of the private key used for decryption. It is not set if the mail was not decrypted, Type: string
  • SAP_MAIL_ENCRYPTION_DETAILS_ERROR_MESSAGES: Indicates the error message in case the decryption failed, Type: String

For a successful decryption, the mail needs to be encrypted with the public key of a private key available in the tenant’s keystore. Note that the mail can be encrypted by several public keys, one of them needs to fit to a private key pair in the keystore.

Note: if the decryption of an encrypted mail cannot be executed successfully (for instance, because the key is not available in keystore), the encrypted mail is passed to the next processing step. No  error can be raised in this case because the same mail would be polled again and again. Therefore, the integration developer needs to handle such kind of problems: such messages could, for example, be escalated via an escalation end event or be sent to a different receiver or administrator.

In the integration flow you can now do a check of the encryption status.

Let’s configure the check. First, we create a Router which will do a routing based on the decryption status using the property SAP_MAIL_ENCRYPTION_DETAILS_DECRYPTION_OK.

Configure Route for Failed Decryption

If the decryption status is not ok (decryption failed) or the mail was not encrypted the route goes to a script step, where the detailed decryption status is validated and a specific mpl attachment is written for the error.

To configure this route, choose Expression Type as ‘Non-XML’ and define the following Condition: ${property.SAP_MAIL_ENCRYPTION_DETAILS_ENCRYPTED} = ‘false’ or ${property.SAP_MAIL_ENCRYPTION_DETAILS_DECRYPTION_OK} = ‘false’.

The detailed validation of the status is done in a Groovy Script step. Using the following script, we check the detailed status and create an mpl attachment containing the corresponding error message.

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
   
    boolean decryption_result = message.getProperties().get("SAP_MAIL_ENCRYPTION_DETAILS_DECRYPTION_OK");
    boolean decryption_status = message.getProperties().get("SAP_MAIL_ENCRYPTION_DETAILS_ENCRYPTED");
    
    def messageLog = messageLogFactory.getMessageLog(message);
    def errorString = "";
    
    if(decryption_status == false){
         errorString = "The mail was not encrypted.";
        }    
    else if(decryption_result == false){
         errorString = message.getProperties().get("SAP_MAIL_ENCRYPTION_DETAILS_ERROR_MESSAGES");
        }
   messageLog.addAttachmentAsString("Mail decryption issue", errorString, "text/plain");
   return message;     
}

After the script we add an Escalation end event that will set the message status to Escalated and end the processing. The tenant administrator has to check and further handle such messages. As an alternative to the Escalation end event you could also add a Message End event and send a mail with the decryption failure details to the administrator.

Configure Route for Successful Decryption 

If the decryption status is ok (decryption successful) the message is further processed. To configure this route just define it as Default Route, which is executed in case there is no decryption error.

As the next step we want to check the signature of the mail, if the mail was signed.

Configure the Signature Verification

To understand how to configure the verification of the signature, you need to understand the processing of signed mails in the adapter: the mail adapter polls all mails in the configured mail box, regardless whether they are signed or not. If a mail is signed the adapter verifies of the signature against the certificate provided in the mail. The verification result is provided in the following properties:

  • SAP_MAIL_SIGNATURE_OVERALL_VERIFICATION_OK: Indicates the overall verification status. Is true if all signatures could be validated successfully, Type: boolean
  • SAP_MAIL_SIGNATURE_DETAILS_VERIFICATION_OK: Detailed verification statuses, can be multiple in case there are multiple signatures in the mail, Type: boolean[]
  • SAP_MAIL_SIGNATURE_DETAILS_CERTIFICATES: Contains the signer certificates, Type:   java.security.cert.X509Certificate[]
  • SAP_MAIL_SIGNATURE_DETAILS_ERROR_MESSAGES: If the verification failed, this property contains the error message, Type: String[]

If a mail is not signed, those properties are not set.

In the integration flow you can now do a check of the validation status and check if the signature certificate is valid, for example against the keystore or the partner directory.

Let’s configure the check. First, we create another Router which will do a routing based on the verification status using the property SAP_MAIL_SIGNATURE_OVERALL_VERIFICATION_OK.

Configure Route for Failed Verification

If the verification status is not ok (verification failed) the route goes to a script step, where the detailed verification statuses are validated. As there could be multiple signatures, we want to check all of them and raise an exception.

To configure this route, choose Expression Type as ‘Non-XML’ and define the following Condition: ${property.SAP_MAIL_SIGNATURE_OVERALL_VERIFICATION_OK} = ‘false’.

The detailed validation of the status is done in a Groovy Script step. Using the following script, we check the detailed statuses and create an mpl attachment containing the corresponding error messages.

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {

    
    boolean[] verification_result = message.getProperties().get("SAP_MAIL_SIGNATURE_DETAILS_VERIFICATION_OK");
    String[] errors = message.getProperties().get("SAP_MAIL_SIGNATURE_DETAILS_ERROR_MESSAGES");
    def numberOfSignatures = verification_result !=null ? verification_result.length : 0;
    def errorString = "";
    def numberOfErrors = 0;
    def messageLog = messageLogFactory.getMessageLog(message);
    
    
    for(int i=0;i<verification_result.length;i++){
        if(!verification_result[i]){
            numberOfErrors++;
            errorString = errorString + errors[i]+ "\n";
        }
    }
    
    errorString = numberOfErrors +" of "+numberOfSignatures+" signatures could not be verified.\n" + errorString;
    messageLog.addAttachmentAsString("Signature verification issue", errorString, "text/plain");
    return message;
}

After the script we add an Escalation end event that will set the message status to Escalated and end the processing. The tenant administrator has to check and further handle such messages. As an alternative to the Escalation end event you could also add a Message End event and send a mail with the verification failure details to the administrator.

Configure Route for Successful Verification

If the verification status is ok (verification successful) the route goes to a script step where the signing certificates are checked against the keystore. As there could be multiple signing certificates, we want to check that at least one of them is valid.

To configure this route, choose Expression Type as ‘Non-XML’ and define the following Condition: ${property.SAP_MAIL_SIGNATURE_OVERALL_VERIFICATION_OK} = ‘true’.

The detailed validation of the mail signature is done in a Groovy Script step. Using the following script, we retrieve the signature certificates and check them against a defined certificate in the keystore. If the verification is ok, the property CERTIFICATE_VALID is set to ‘true’. If the verification cannot be done successfully (configured certificate not in keystore or signing certificate does not match certificate from keystore), the property CERTIFICATE_VALID is set to ‘false’ and the error details and the signature certificates from the mail and, if available, the certificate from keystore are stored as MPL attachment. Using those details the tenant administrator can further analyze the error.

import com.sap.gateway.ip.core.customdev.util.Message;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.security.cert.Certificate;
import com.sap.it.api.ITApiFactory;
import com.sap.it.api.keystore.KeystoreService;


def Message processData(Message message) {
       def messageLog = messageLogFactory.getMessageLog(message);
       X509Certificate[] certificate = message.getProperties().get("SAP_MAIL_SIGNATURE_DETAILS_CERTIFICATES");
       def KeystoreCertificateString = "";
       def SignerCertificateString = "Signer certificates from mail: ";

       def alias = "verify_signed_mail";
       
       //read cert from keystore
       def service = ITApiFactory.getApi(KeystoreService.class, null);
       Certificate cert = service.getCertificate(alias);
       
       if(cert == null){
         message.setProperty("CERTIFICATE_VALID",false);
         def errorString = "";
         errorString = "There is no certificate with alias "+alias+" in the keystore.";
         messageLog.addAttachmentAsString("Certificate Missing", errorString, "text/plain");
         return message;
         }
       
        for(int i=0;i<certificate.length;i++){
          if(certificate[i]){
           def signerCertificateEncodedBytes = certificate[i].getEncoded(); // get bytes of the signer-certificate
             if(cert.getEncoded() == signerCertificateEncodedBytes){
             message.setProperty("CERTIFICATE_VALID",true);
             return message;
             }
       
          // log certificate infos from keystore-certificate
          KeystoreCertificateString = "Certificate with alias '" +alias+ "' from keystore: "+ "\n" + "\n" +cert.inspect() + "\n" + "in bytes: " + "\n" + cert.getEncoded().inspect();
          messageLog.addAttachmentAsString("CertificateFromKeystore", KeystoreCertificateString, "text/plain");

          // log certificate infos of signer-certificate
          SignerCertificateString = SignerCertificateString + "\n" + "\n" + certificate[i].inspect() + "\n" + "in bytes: " + "\n" + signerCertificateEncodedBytes.inspect();       
          messageLog.addAttachmentAsString("SignerCertificate", SignerCertificateString, "text/plain");

          message.setProperty("CERTIFICATE_VALID",false);

          // log error 
          def CertificateInvalidString = "";
          CertificateInvalidString = "Signer certificate from mail does not match certificate with alias '" +alias+ "' from keystore.";  
          messageLog.addAttachmentAsString("CertificateNotValid", CertificateInvalidString, "text/plain");
          }
        }
       return message;
      }
Alternatively: Add the Signature Certificate to the Partner Directory 

The check can also be done against certificates contained in the partner directory. For this the following script snipped could be used.

import com.sap.gateway.ip.core.customdev.util.Message;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.security.cert.Certificate;
import com.sap.it.api.ITApiFactory;
import com.sap.it.api.pd.PartnerDirectoryService;

def Message processData(Message message) {
       def messageLog = messageLogFactory.getMessageLog(message);
       X509Certificate[] certificate = message.getProperties().get("SAP_MAIL_SIGNATURE_DETAILS_CERTIFICATES");
       def PDCertificateString = "";
       def SignerCertificateString = "Signer certificates from mail: ";
       
       //read cert from partner directory
       def pdService = ITApiFactory.getApi(PartnerDirectoryService.class, null);
         if (pdService == null){
           message.setProperty("CERTIFICATE_VALID",false);
           def errorString = "Partner Directory Service not found.";
           messageLog.addAttachmentAsString("Partner Directory Issue", errorString, "text/plain");
           return message;
         }
       def partnerID = 'PartnerID';
       Certificate certFromPartnerDirectory = pdService.getCertificateParameter("mail_signature", partnerID);
         if (certFromPartnerDirectory == null){
           message.setProperty("CERTIFICATE_VALID",false);
           def errorString = "Parameter 'mail_signature' not found in the Partner Directory for the partner ID '" +partnerID+ "'.";
           messageLog.addAttachmentAsString("Partner Directory Issue", errorString, "text/plain");
           return message;
         }
       
        for(int i=0;i<certificate.length;i++){
          if(certificate[i]){
           def signerCertificateEncodedBytes = certificate[i].getEncoded(); // get bytes of the signer-certificate
             if(certFromPartnerDirectory.getEncoded() == signerCertificateEncodedBytes){
             message.setProperty("CERTIFICATE_VALID",true);
             return message;
             }
       
          // log certificate infos from keystore-certificate
          PDCertificateString = "Certificate for partner '" +partnerID+ "' from Partner Directory: "+ "\n" + "\n" +certFromPartnerDirectory.inspect() + "\n" + "in bytes: " + "\n" + certFromPartnerDirectory.getEncoded().inspect();
          messageLog.addAttachmentAsString("CertificateFromPartnerDirectory", PDCertificateString, "text/plain");

          // log certificate infos of signer-certificates
          SignerCertificateString = SignerCertificateString + "\n" + "\n" + certificate[i].inspect() + "\n" + "in bytes: " + "\n" + signerCertificateEncodedBytes.inspect();       
          messageLog.addAttachmentAsString("SignerCertificate", SignerCertificateString, "text/plain");

          message.setProperty("CERTIFICATE_VALID",false);

          // log error
          def CertificateInvalidString = "";
          CertificateInvalidString = "Signature certificate from mail does not match certificate for partner '" +partnerID+ "' from Partner Directory.";  
          messageLog.addAttachmentAsString("CertificateNotValid", CertificateInvalidString, "text/plain");
          }
        }
       return message;
      }

After the script step, we add another Router to evaluate the property CERTIFICATE_VALID. If the verification was successful (CERTIFICATE_VALID  = ‘true’) the message is further processed. If the verification failed the message is escalated.

To configure the route for the successful validation choose Expression Type as ‘Non-XML’ and define the following Condition: ${property.CERTIFICATE_VALID } = ‘true’.

Add an Escalation end event that will set the message status to Escalated and end the processing. The route to the Escalation end event can be defined as Default Route which is executed in case the property CERTIFICATE_VALID is not set.

The tenant administrator has to check and further handle such escalated messages. As an alternative to the Escalation end event you could also add a Message End event and send a mail with the validation failure details to the administrator.

Configure Route for Unsigned Mails

If the mail was not signed it is processed in a different route. In our scenario it is written to a different sftp directory.

To configure this route just define it as Default Route, which is executed in case the property SAP_MAIL_SIGNATURE_OVERALL_VERIFICATION_OK is not there.

Configure the Outbound Channels

Configure the integration flow with the outbound channels required by your scenario. For the sample scenario we use the SFTP adapter and send the message to two different directories on the SFTP server. But you can use any other adapter as well. Now the integration flow should look similar to this:

Deploy the Integration Flow

Now you can deploy the integration flow. After that, check if the integration flow was started successfully in the Manage Integration Content monitor.

Add the Signature Certificate to the Keystore

To validate the certificate against the keystore, you need to add the certificate to the keystore with the alias defined in the script using the Keystore Monitor. To import the certificate into the keystore open the Keystore Monitor available in the Operations View in the section Manage Security.

In the Keystore Monitor select Add -> Certificate action at the top of the monitor. An upload dialog appears asking for the certificate file to upload and the Alias to be used for the certificate.

Choose Deploy to add the certificate to the keystore.

Alternatively: Add the Signature Certificate to the Partner Directory 

If you want to validate against a certificate in the Partner Directory, you need to upload the Base64-encoded certificate to the Partner Directory as binary parameter with content type ‘crt’ in. For the above script you would need to upload the certificate with the Pid (Partner ID): PartnerID and the Id (Parameter ID): mail_signature.

The upload of binary parameters is described in the blog Partner Directory – Partner Dependent XML Structures and IDs in section ‘Creating Partner Directory Entries’.

Execute the Scenario

Now you can provide signed and unsigned mails in your mail box to poll them.

Assigned Tags

      3 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Pong Chen
      Pong Chen

      Hi Mandy Krimmel 

      if there needs the signature chain verification, how do the groovy code can be handled it?

       

      could it be implemented with

       

      Certificate[] certs = service.getCertificateChain(alias);
      for (int i = 0; i < certificate.length; i++) {
          if (certificate[i]) {
              def signerCertificateEncodedBytes = certificate[i].getEncoded(); // get bytes of the signer-certificate
              if (certs[i].getEncoded() == signerCertificateEncodedBytes) {
                  ///
              }
          }
      }
      Author's profile photo Mandy Krimmel
      Mandy Krimmel
      Blog Post Author

       

      Hello

      what exactly do you want to check? Does the mail sender pass the chain together with the mail?

      The coding you send will probably not work as expected because you compare the 1st certificate from the mail with the 1st certificate from the chain of the private key and the 2nd (if there is a second certificate for the mail signature) with the 2nd (intermediate) from the chain of the private key and the 3rd (if there is a third certificate for the mail signature) with the 3rd (probably the root certificate) from the chain of the private key.

      Regards,

      Mandy

      Author's profile photo Pong Chen
      Pong Chen

      Hi Mandy Krimmel

      thank you for your reply.

      Yes, it could pass the certificate chain with email. In Partner Directory, it could also store the certificate chains.

      There could be difference for the number of certificates (Suppose the number of certificates together with email is less than one stored in Partner Directory , otherwise, signature verification failed).

      For the described case, we have to verify all chains in email comparing with chains in Partner Directory.

      Questions is,

      dose the chains (both in email and Partner Directory)have some sequence or hierarchy in the "certificate" array?

       

      For example,

      certificate[0]  is certificate sub-sub-node

      certificate[1]  is certificate sub-node

      certificate[2]  is certificate root

      Or vice versa

      X509Certificate[] certificate = message.getProperties().get("SAP_MAIL_SIGNATURE_DETAILS_CERTIFICATES");