Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
rogcoimbra
Participant
1,724

Summary


Develop an Adapter Framework Module (AFM) capable of signing the contents of a payload in the pattern "PKCS # 7" and encode it in Base64.

 

Use


In my case the module will be used to convert the data generated by a file receiver adapter to the PKCS#7 pattern and then encode them in Base64.

 

Steps


»Step 1: check the version of the development tool


SAP NetWeaver Developer Studio (NWDS) version 7.31 With JDK 6.0 (SAP JVM 6):





 

»Step 2: opening the NWDS perspective for developing Enterprise Java Beans (EJB) projects


Navigate to "NWDS > Window > Open Perspective > Other":



 

Select option "Java EE" and click in "OK" button:



 

»Step 3: Create EJB project


Navigate to "NWDS > File > New > EJB Project":



 

Select the options as shown in the print and click the "Next" button:



 

Click the "Next" button:



 

Click the "Finish" button:



 

»Step 4: Configure Build Path


Right click on "CustomSignPKCS7 > Build Path > Configure Build Path":



 

Navigate to the tab "Libraries" and click on "Add Library" button:



 

Select "XPI Library" and a click the "Next" button:



 

Select the Library Type "XPI Adapter Libraries" and click the "Finish" button:



 

Click the "Ok" button:



 

»Step 5: Create a "Session Bean"


Right click on "CustomSignPKCS7" an navigate to "New > Session Bean (EJB 3.x)":



 

Click the "Next" button:



 

Click the "Finish" button:



 

Copy the code below and paste it into the "SignaturePKCS7" class of your project:
package com.sap.pi.afm.pkcs7;

/**
*--------------------------------------------------------------
* @autor: Rogério Coimbra de Oliveira.
* Purpose: To sign data in the PKCS # 7 pattern and Base64 base.
* Version: 001 (create)
*--------------------------------------------------------------
*/

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.rmi.RemoteException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Local;
import javax.ejb.LocalHome;
import javax.ejb.Remote;
import javax.ejb.RemoteHome;
import javax.ejb.Stateless;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import sun.misc.BASE64Encoder;
import com.sap.aii.af.lib.mp.module.Module;
import com.sap.aii.af.lib.mp.module.ModuleContext;
import com.sap.aii.af.lib.mp.module.ModuleData;
import com.sap.aii.af.lib.mp.module.ModuleException;
import com.sap.aii.af.lib.mp.module.ModuleHome;
import com.sap.aii.af.lib.mp.module.ModuleLocal;
import com.sap.aii.af.lib.mp.module.ModuleLocalHome;
import com.sap.aii.af.lib.mp.module.ModuleRemote;
import com.sap.engine.interfaces.keystore.KeystoreManager;
import com.sap.engine.interfaces.messaging.api.Message;
import com.sap.engine.interfaces.messaging.api.MessageKey;
import com.sap.engine.interfaces.messaging.api.PublicAPIAccessFactory;
import com.sap.engine.interfaces.messaging.api.auditlog.AuditAccess;
import com.sap.engine.interfaces.messaging.api.auditlog.AuditLogStatus;
import com.sap.engine.interfaces.messaging.api.exception.InvalidParamException;
import com.sap.security.api.ssf.ISsfData;
import com.sap.security.core.server.ssf.SsfDataPKCS7;
import com.sap.security.core.server.ssf.SsfInvalidAlgException;
import com.sap.security.core.server.ssf.SsfInvalidKeyException;
import com.sap.security.core.server.ssf.SsfProfileKeyStore;

/**
* Session Bean implementation class SignaturePKCS7
*/
@Stateless(name = "SignaturePKCS7Bean")
@Local(value = { ModuleLocal.class })
@Remote(value = { ModuleRemote.class })
@LocalHome(value = ModuleLocalHome.class)
@RemoteHome(value = ModuleHome.class)
public class SignaturePKCS7 implements Module {

private AuditAccess audit;
private byte[] content;
private Message msg;
MessageKey msgKey;

private String aliasPrivateKey;
private boolean base64Encode;
private boolean formatPKCS7;
private boolean applySignature;
private String pwdPrivateKey;
private String aliasKeyStore;
private String mdAlg;
private int incCerts;
private boolean detached;
private boolean paramMandatoryEmpty;

private static final String PARAM_ALIAS_PRIVATE_KEY = "aliasPrivateKey";
private static final String PARAM_BASE64_ENCODE = "base64Encode";
private static final String PARAM_APPLY_SIGNATURE = "applySignature";
private static final String PARAM_PWD_PRIVATE_KEY = "pwdPrivateKey";
private static final String PARAM_ALIAS_KEY_STORE = "aliasKeyStore";
private static final String PARAM_MD_ALG = "mdAlg";
private static final String PARAM_INC_CERTS = "incCerts";
private static final String PARAM_DETACHED = "detached";
private static final String PARAM_FORMAT_PKCS7 = "formatPKCS7";

@Override
public ModuleData process(ModuleContext moduleContext,
ModuleData inputModuleData) throws ModuleException {
//Load message:
this.msg = (Message) inputModuleData.getPrincipalData();
this.msgKey = this.msg.getMessageKey();

//Loads the contents of the incoming message:
this.content = msg.getDocument().getContent();

//Load configuration parameters:
this.loadParameters(moduleContext);

if (this.applySignature && !this.paramMandatoryEmpty) {
//Sign the file digitally in PKCS # 7 standard:
content = this.signPKCS7(content);
}

if (this.base64Encode) {
if (this.formatPKCS7) {
//Encode data in Base64 and non-standard format PKCS7:
content = this.formatPKCS7(this.encodeBase64(content)).getBytes();
} else {
// Encode data in Base64:
content = this.encodeBase64(content).getBytes();
}
}

//Update output:
try {
msg.getDocument().setContent(content);
} catch (InvalidParamException e) {
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR, e
.getMessage());
throw new ModuleException(e.getMessage(), e);
}
inputModuleData.setPrincipalData(msg);

return inputModuleData;
}

@PostConstruct
public void initializeResources() {
try {
this.audit = PublicAPIAccessFactory.getPublicAPIAccess()
.getAuditAccess();
} catch (Exception e) {
throw new RuntimeException("Error initializing resources: "
+ e.getMessage());
}
}

@PreDestroy
public void releaseResources() {
this.audit.flushAuditLogEntries(this.msgKey);
}

private void loadParameters(ModuleContext moduleContext)
throws ModuleException {

//Determine the data must be signed:
if (moduleContext.getContextData(PARAM_APPLY_SIGNATURE) != null
&& !moduleContext.getContextData(PARAM_APPLY_SIGNATURE)
.isEmpty()) {
this.applySignature = Boolean.valueOf(moduleContext
.getContextData(PARAM_APPLY_SIGNATURE));
} else {
this.applySignature = true;
}

if (this.applySignature) {
//Private key or digital certificate name:
if (moduleContext.getContextData(PARAM_ALIAS_PRIVATE_KEY) == null
|| moduleContext.getContextData(PARAM_ALIAS_PRIVATE_KEY)
.isEmpty()) {
this.paramMandatoryEmpty = true;
audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR,
"Parameter " + PARAM_ALIAS_PRIVATE_KEY
+ " required when the "
+ PARAM_APPLY_SIGNATURE
+ " is equal to \"true\"");
} else {
this.aliasPrivateKey = moduleContext
.getContextData(PARAM_ALIAS_PRIVATE_KEY);
}

//Private key password:
if (moduleContext.getContextData(PARAM_PWD_PRIVATE_KEY) != null
&& !moduleContext.getContextData(PARAM_PWD_PRIVATE_KEY)
.isEmpty()) {
this.pwdPrivateKey = moduleContext
.getContextData(PARAM_PWD_PRIVATE_KEY);
} else {
this.pwdPrivateKey = null;
}

//Key Store Name:
if (moduleContext.getContextData(PARAM_ALIAS_KEY_STORE) != null
&& !moduleContext.getContextData(PARAM_ALIAS_KEY_STORE)
.isEmpty()) {
this.aliasKeyStore = moduleContext
.getContextData(PARAM_ALIAS_KEY_STORE);
} else {
this.aliasKeyStore = "DEFAULT";
}

//Digital signature algorithm:
if (moduleContext.getContextData(PARAM_MD_ALG) != null
&& !moduleContext.getContextData(PARAM_MD_ALG).isEmpty()) {
this.mdAlg = moduleContext.getContextData(PARAM_MD_ALG);
} else {
this.mdAlg = SsfDataPKCS7.ALG_SHA;
}

//Determines how the certificate should be included in the content:
if (moduleContext.getContextData(PARAM_INC_CERTS) != null
&& !moduleContext.getContextData(PARAM_INC_CERTS).isEmpty()) {
try {
this.incCerts = Integer.valueOf(moduleContext
.getContextData(PARAM_INC_CERTS));
} catch (NumberFormatException e) {
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR, e
.getMessage());
throw new ModuleException(e.getMessage(), e);
}
} else {
this.incCerts = 1;
}

// Determines whether signed content should be included with signature:
if (moduleContext.getContextData(PARAM_DETACHED) != null
&& !moduleContext.getContextData(PARAM_DETACHED).isEmpty()) {
this.detached = Boolean.valueOf(moduleContext
.getContextData(PARAM_DETACHED));
} else {
this.detached = false;
}
}

//Determines whether content should be formatted in the PKCS7 standard:
if (moduleContext.getContextData(PARAM_FORMAT_PKCS7) != null
&& !moduleContext.getContextData(PARAM_FORMAT_PKCS7).isEmpty()) {
this.formatPKCS7 = Boolean.valueOf(moduleContext
.getContextData(PARAM_FORMAT_PKCS7));
} else {
this.formatPKCS7 = false;
}

//Determines whether content should be encoded for Base64:
if (moduleContext.getContextData(PARAM_BASE64_ENCODE) != null
&& !moduleContext.getContextData(PARAM_BASE64_ENCODE).isEmpty()) {
this.base64Encode = Boolean.valueOf(moduleContext
.getContextData(PARAM_BASE64_ENCODE));
} else {
this.base64Encode = true;
}

//Generates audit logs:
audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS, "Parameter ("
+ PARAM_APPLY_SIGNATURE + ") loaded: "
+ this.applySignature);

audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS, "Parameter ("
+ PARAM_ALIAS_PRIVATE_KEY + ") loaded: "
+ this.aliasPrivateKey);

audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS, "Parameter ("
+ PARAM_PWD_PRIVATE_KEY + ") loaded!" );

audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS, "Parameter ("
+ PARAM_ALIAS_KEY_STORE + ") loaded: " + this.aliasKeyStore);

audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS, "Parameter ("
+ PARAM_MD_ALG + ") loaded: " + this.mdAlg);

audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS, "Parameter ("
+ PARAM_INC_CERTS + ") loaded: " + this.incCerts);

audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS, "Parameter ("
+ PARAM_DETACHED + ") loaded: " + this.detached);

audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS, "Parameter ("
+ PARAM_BASE64_ENCODE + ") loaded: " + this.base64Encode);

}

private byte[] signPKCS7(byte[] content) throws ModuleException {

SsfProfileKeyStore profile;

//Loads data to be signed:
InputStream stream = new ByteArrayInputStream(content);
ISsfData data = null;
try {
data = new SsfDataPKCS7(stream);
} catch (IOException e) {
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR, e
.getMessage());
throw new ModuleException(e.getMessage(), e);
}

//Get sign-up profile from Key Storage:
InitialContext ctx = null;
try {
ctx = new InitialContext();
} catch (NamingException e) {
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR, e
.getMessage());
throw new ModuleException(e.getMessage(), e);
}

Object o = null;
try {
o = (Object) ctx.lookup("keystore");
audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS,
"Context \"Keystore\" loaded!");
} catch (NamingException e) {
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR, e
.getMessage());
throw new ModuleException(e.getMessage(), e);
}

KeystoreManager manager = (KeystoreManager) o;
KeyStore keyStore = null;
try {
keyStore = manager.getKeystore(this.aliasKeyStore);
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS,
"Private Key Loaded Successfully:" + this.aliasKeyStore
+ ".");
} catch (RemoteException e) {
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR, e
.getMessage());
throw new ModuleException(e.getMessage(), e);
}

try {
profile = new SsfProfileKeyStore(keyStore, this.aliasPrivateKey,
this.pwdPrivateKey);
} catch (KeyStoreException e) {
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR, e
.getMessage());
throw new ModuleException(e.getMessage(), e);
}

try {
//Sign data in default PKCS # 7:
boolean res = data.sign(profile, this.mdAlg, this.incCerts,
this.detached);
audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS,
"Data signed successfully: Format \"PKCS#7\".");
if (!res) {
// Failed to sign data:
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR,
"Failed to sign file!");
throw new ModuleException("Failed to sign file!");
}
} catch (SsfInvalidKeyException e) {
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR, e
.getMessage());
throw new ModuleException(e.getMessage(), e);
} catch (SsfInvalidAlgException e) {
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR, e
.getMessage());
throw new ModuleException(e.getMessage(), e);
}

//Get signed data:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
data.writeTo(baos);
} catch (IOException e) {
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.ERROR, e
.getMessage());
throw new ModuleException(e.getMessage(), e);
}
return baos.toByteArray();
}

private String encodeBase64(byte[] content) throws ModuleException {
String encoded = new BASE64Encoder().encode(content).trim();
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS,
"Data encoded successfully: Base64.");
return encoded;
}

private String formatPKCS7(String encoded) throws ModuleException {
final String bgn_sign = "-----BEGIN PKCS7-----";
final String end_sign = "-----END PKCS7-----";
String formated;

//Format string encoded in BASE64 in the PKCS7 pattern:
formated = bgn_sign + "\r\n" + encoded + "\r\n" + end_sign;
this.audit.addAuditLogEntry(msgKey, AuditLogStatus.SUCCESS,
"Successfully formatted data: PKCS7 standard.");
return formated;
}
}

 

»Step 6: Add External Libraries (JARs...)



  • Libraries required for digital signature in the PKCS # 7 pattern:

    • sap.com~tc~je~keystore_api~API.jar;

    • sap.com~tc~sec~ssf.jar.




Error due to missing required libraries:



 

Right click on "CustomSignPKCS7 > Build Path > Configure Build Path":



 

Navigate to the tab "Libraries" and click on "Add External JARs..." button:



 

Select the files (.JAR) for the libraries in question:



Note: Click here to learn how to get the libraries.

 

Click the "OK" button:



 

Fixed bug due to missing required libraries:



 

 

»Step 7: Configure "ejb-j2ee-engine.xml"


Right click on "ejb-j2ee-engine.xml" and click on "Open":



 

Copy the code below and paste it into the "ejb-j2ee-engine.xml" file of your project:
<?xml version="1.0" encoding="UTF-8"?>
<ejb-j2ee-engine xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ejb-j2ee-engine_3_0.xsd">
<enterprise-beans>
<enterprise-bean>
<ejb-name>SignaturePKCS7Bean</ejb-name>
<jndi-name>SignPKCS7</jndi-name>
</enterprise-bean>
</enterprise-beans>
</ejb-j2ee-engine>

 

Navigate to the tab "Source":



 

»Step 8: Configure "application-j2ee-engine.xml"


Right click on "application-j2ee-engine.xml" and click on "Open":



 

Copy the code below and paste it into the "application-j2ee-engine.xml" file of your project:
<?xml version="1.0" encoding="UTF-8"?>
<application-j2ee-engine xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="application-j2ee-engine.xsd">
<reference reference-type="hard">
<reference-target target-type="service" provider-name="sap.com">engine.security.facade</reference-target>
</reference>
<reference reference-type="hard">
<reference-target target-type="library" provider-name="sap.com">engine.j2ee14.facade</reference-target>
</reference>
<reference reference-type="hard">
<reference-target target-type="service" provider-name="sap.com">com.sap.aii.af.svc.facade</reference-target>
</reference>
<reference reference-type="hard">
<reference-target target-type="interface" provider-name="sap.com">com.sap.aii.af.ifc.facade</reference-target>
</reference>
<reference reference-type="hard">
<reference-target target-type="library" provider-name="sap.com">com.sap.aii.af.lib.facade</reference-target>
</reference>
<reference reference-type="hard">
<reference-target target-type="library" provider-name="sap.com">com.sap.base.technology.facade</reference-target>
</reference>
<fail-over-enable xsi:type="fail-over-enableType_disable"
mode="disable" />
</application-j2ee-engine>

 

Navigate to the tab "Source":



 

»Step 9: Export SAP EAR


Right click on "CustomSignPKCS7EAR" and navigate to "Export > SAP EAR File":



 

Click the "Finish" button:



 

»Step 10: Deploy EAR File on SAP PI Server



 

»Step 11: Check if the AFM module (EJB) has been deployed successfully on the SAP PI server



  1. Access SAP PI via Web Browser (http://<host>:<port>/startPage);

  2. Clique the "EJB Explorer";

  3. Find the module "CustomSignPKCS7_EJB".






 

»Step 12: Configuration of the AFM in the File Adapter.



  • Configuration parameters:

    • Parameter "aliasKeyStore": Name of the Key Store where the digital certificate (private key) is stored;

    • Parameter "aliasPrivateKey": Name of the digital certificate (private key) used to digitally sign the file in PKCS7 pattern;

    • ApplySignature parameter: Determines whether or not the module should digitally sign the file, with the default values ​​being: "true" and "false".

    • Parameter "base64Encode": Determines whether or not the module should convert/ encode the file to the Base64 pattern, and the proposed values ​​are:

      • "true" (convert);

      • "false" (does not convert);



    • Parameter "detached": Determines whether the signed data should be included in the signature, and the values ​​proposed are:

      • "true" (Does not include signature data);

      • "false" (Includes signature data);



    • Parameter "incCerts": Determines whether the certificates should be included in the signed data, and the values ​​proposed are:

      • "0" (Does not include any certificates when creating a digital signature);

      • "1" (Includes the certificate itself when creating a digital signature);

      • "2" (Includes certificate chain without root when creating a digital signature [default]);

      • "3" (Includes root certificate chain when creating a digital signature);



    • Parameter "mdAlg": digest message algorithm used for hash generation of the signed data, the proposed values ​​being:

      • "ALG_SHA" (Digest digest algorithm SHA-1);

      • "ALG_SHA256" (digest digest algorithm SHA-256);

      • "ALG_SHA512" (Digest Digest Algorithm SHA-512);

      • "ALG_MD5" (MD5 message digest algorithm (not recommended));

      • "ALG_AES128_CBC" (AES symmetric encryption algorithm (128 bit) in CBC mode);

      • "ALG_AES192_CBC" (AES symmetric encryption algorithm (192 bit) in CBC mode);

      • "ALG_AES256_CBC" (AES symmetric encryption algorithm (256 bit) in CBC mode);

      • "ALG_DES_EDE3_CBC" (Triple DES Symmetric Encryption Algorithm in CBC mode);

      • "ALG_RC2_40_CBC" (Symmetric Encryption Algorithm RC2 (40 bit) in CBC mode);

      • "ALG_RC2_CBC" (Symmetric Encryption Algorithm RC2 (128 bit) in CBC mode);



    • Parameter "pwdPrivateKey": Password of the private key used to digitally sign the file;

    • Parameter "formatPKCS7": Determines whether data should be formatted in the PKCS7 pattern.




 

Example of the configuration in the communication channel of type file:



 

»Step 13: Test result


Content of the signed PKCS # 7 file and converted to BASE64:



 

»Step 14: Download code without libraries:


https://github.com/rogcoimbra/SAP/blob/master/CustomSignPKCS7EAR.ear

 

Best regards,

Rogério Coimbra de Oliveira
1 Comment
Labels in this area