XML Encryption/Decryption for W3C Recommendation-based payloads via Java Mapping for SAP PO
2023-12-6 19:0:23 Author: blogs.sap.com(查看原文) 阅读量:5 收藏

OVERVIEW

We had requirement to sign and encrypt all API request to our client’s partner bank and then decrypt the response. The bank called this encryption scheme as PKI encryption which upon research with the sample payload lead to the W3C Recommendation.

Here I will discussed how the encryption and decryption process can be achieved. Below is a sample of what an encrypted payload can be:

<xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
	<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
	<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
		<xenc:EncryptedKey>
			<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
			<xenc:CipherData>
				<xenc:CipherValue>b44GKIzOikbz.....</xenc:CipherValue>
			</xenc:CipherData>
		</xenc:EncryptedKey>
	</ds:KeyInfo>
	<xenc:CipherData>
		<xenc:CipherValue>CO+A40a3vGzo47.....</xenc:CipherValue>
	</xenc:CipherData>
</xenc:EncryptedData>

For our scenario, the keys are encrypted using RSA v1.5 algorithm with the actual request encrypted with TripleDES. You would need the Apache Santuario library to this process.

PAYLOAD SIGNATURE

As mentioned, our requirement includes signing the payloads. Thankfully, I was able to skipped on this with Carlos blog SAP PI/PO XML X509 signature by certificate.

ENCRYPTING THE REQUEST

For this process, involves the following steps:

  1. Load the target servers certificate from TrustedCA or from where you have it stored in NWA.
  2. Get the Public key of the certificate which will be used for the encryption
  3. Generate a key will be used by target server for decryption.
  4. Encrypt the generated key.
  5. Encrypt the signed request payload using the generated key.
import java.io.IOException;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.xml.security.encryption.EncryptedData;
import org.apache.xml.security.encryption.EncryptedKey;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.encryption.XMLEncryptionException;
import org.apache.xml.security.keys.KeyInfo;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import com.sap.aii.mapping.api.AbstractTrace;
import com.sap.aii.mapping.api.AbstractTransformation;
import com.sap.aii.mapping.api.StreamTransformationException;
import com.sap.aii.mapping.api.TransformationInput;
import com.sap.aii.mapping.api.TransformationOutput;
import com.sap.aii.security.lib.KeyStoreManager;
import com.sap.security.api.ssf.ISsfProfile;

public class JM_XMLEncryption extends AbstractTransformation {
	static AbstractTrace log = null;
	@Override
	public void transform(TransformationInput arg0, TransformationOutput arg1) throws StreamTransformationException {
		log = this.getTrace();
		String encKeyView = arg0.getInputParameters().getString("encKeyView");
		String encKeyEntry = arg0.getInputParameters().getString("encKeyEntry");

		try {
			org.apache.xml.security.Init.init();
			//load input payload as Document
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			Document document = dbf.newDocumentBuilder().parse(arg0.getInputPayload().getInputStream());

			//load the certificate for Key store
			ISsfProfile encryptProfile = getSsfProfileKeyStore(encKeyView,encKeyEntry);
			X509Certificate certificate = encryptProfile.getCertificate();

			//generate secret key
			KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");
			keyGenerator.init(168);
			SecretKey skey = keyGenerator.generateKey();

			//encrypt the secret key
			PublicKey pubkey = certificate.getPublicKey();
			XMLCipher keyCipher = XMLCipher.getInstance(XMLCipher.RSA_v1dot5);
			keyCipher.init(XMLCipher.WRAP_MODE, pubkey);
			EncryptedKey encKey = keyCipher.encryptKey(document, skey);

			//encrypt the contents of document
			XMLCipher xmlCipher = XMLCipher.getInstance(XMLCipher.TRIPLEDES);
			xmlCipher.init(XMLCipher.ENCRYPT_MODE, skey);

			//add key info to encrypted Data
			EncryptedData encData = xmlCipher.getEncryptedData();
	        KeyInfo keyInfo = new KeyInfo(document);
	        keyInfo.add(encKey);
	        encData.setKeyInfo(keyInfo);

	        //encrypt the document
	        xmlCipher.doFinal(document, document.getDocumentElement(), false);

	        //Output the resulting document.
	        OutputStream os = arg1.getOutputPayload().getOutputStream();
	        TransformerFactory tf = TransformerFactory.newInstance();
	     	Transformer trans = tf.newTransformer();
	     	trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
	     	trans.transform(new DOMSource(document), new StreamResult(os));
	     	
		} catch (SAXException | IOException | ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		catch (XMLEncryptionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		catch (TransformerConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
     	catch (TransformerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private static ISsfProfile getSsfProfileKeyStore(String keyStoreAlias, String keyStoreEntry) throws StreamTransformationException {
		KeyStoreManager managerPriviliged = null;
		try {
			managerPriviliged = com.sap.aii.af.service.resource.SAPSecurityResources.getInstance().getKeyStoreManager(
					com.sap.aii.security.lib.PermissionMode.SYSTEM_LEVEL);
		} catch (KeyStoreException e) {
			throw new StreamTransformationException("SAPSecurityResources", e);
		}
		KeyStore keyStore;
		try {
			keyStore = managerPriviliged.getKeyStore(keyStoreAlias);
		} catch (KeyStoreException e) {
			throw new StreamTransformationException("managerPriviliged.getKeyStore " + keyStoreAlias, e);
		}
		ISsfProfile profile = null;
		try {
			profile = managerPriviliged.getISsfProfile(keyStore, keyStoreEntry, null);
		} catch (KeyStoreException e) {
			throw new StreamTransformationException("Failed to load SsfProfileKeyStore " + keyStoreAlias + " " + keyStoreEntry, e);
		}
		return profile;
	}
}

DECRYPTING THE RESPONSE

For this process, involves the following steps:

  1. Get values from respective encrypted key and data contained in the CipherValue elements.
  2. Load your private key from NWA
  3. Decrypt the encrypted key using your private key.
  4. Generate the secret key using related algorithm.
  5. Decrypt the encrypted data with the secret key
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.sap.aii.af.service.resource.SAPSecurityResources;
import com.sap.aii.mapping.api.AbstractTrace;
import com.sap.aii.mapping.api.AbstractTransformation;
import com.sap.aii.mapping.api.StreamTransformationException;
import com.sap.aii.mapping.api.TransformationInput;
import com.sap.aii.mapping.api.TransformationOutput;
import com.sap.aii.security.lib.KeyStoreManager;
import com.sap.security.api.ssf.ISsfProfile;

public class JM_XMLDecryption extends AbstractTransformation {

	static AbstractTrace log = null;

	@Override
	public void transform(TransformationInput arg0, TransformationOutput arg1) throws StreamTransformationException {
		log = this.getTrace();
		String keyView = arg0.getInputParameters().getString("keyView");
		String keyEntry = arg0.getInputParameters().getString("keyEntry");
		String password = arg0.getInputParameters().getString("password");
		try {
			org.apache.xml.security.Init.init();
			//build document from input payload
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			Document document = dbf.newDocumentBuilder().parse(arg0.getInputPayload().getInputStream());
			
			//get the encrypted key and data in the CipherValue elements
			NodeList cipherValueNodes = document.getElementsByTagName("xenc:CipherValue");
			Element encKey = null;
			Element encData = null;
			for(int i = 0; i < cipherValueNodes.getLength(); i++) {
				if (i == 0) {
					encKey = (Element)cipherValueNodes.item(i);
				}
				else {
					encData = (Element)cipherValueNodes.item(i);
				}
			}
			
			//decrypt the encrypted key
			PrivateKey privKey = getCertProfile(keyView,keyEntry,password).getPrivateKey();
			Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
		    cipher.init(Cipher.DECRYPT_MODE, privKey);
		    byte[] encryptedKeyBytes = Base64.getDecoder().decode(encKey.getTextContent());
		    byte[] decryptedKey = cipher.doFinal(encryptedKeyBytes);

		    //generate key from decrypted key
		    DESedeKeySpec desEdeKeySpec = new DESedeKeySpec(decryptedKey);
		    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
		    SecretKey key = keyFactory.generateSecret(desEdeKeySpec);

		    //decrypt encrypted data
		    int ivLen = 8;
	    	byte[] ivBytes = new byte[ivLen];
	    	byte[] encryptedDataBytes = Base64.getDecoder().decode(encData.getTextContent());
	    	System.arraycopy(encryptedDataBytes, 0, ivBytes, 0, ivLen);
	    	IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
	    	Cipher cipherData = Cipher.getInstance("DESede/CBC/NoPadding");
	    	cipherData.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
	    	String decryptedData = new String(cipherData.doFinal(encryptedDataBytes, ivLen, encryptedDataBytes.length - ivLen));
	    	DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
	    	DocumentBuilder builder = docFactory.newDocumentBuilder();
	    	InputSource is = new InputSource(new StringReader(decryptedData.substring(0, decryptedData.length()-3)));
	    	Document doc = builder.parse(is);

	    	OutputStream os = arg1.getOutputPayload().getOutputStream();
			TransformerFactory tf = TransformerFactory.newInstance();
			Transformer trans = tf.newTransformer();
			trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
			trans.transform(new DOMSource(doc), new StreamResult(os));
		}
		catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidAlgorithmParameterException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransformerConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransformerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeySpecException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private static ISsfProfile getCertProfile(String alias, String entry,String pwd) throws StreamTransformationException {
		KeyStoreManager managerPriviliged;
		try {
			managerPriviliged = SAPSecurityResources.getInstance().getKeyStoreManager(com.sap.aii.security.lib.PermissionMode.SYSTEM_LEVEL);
		} catch (KeyStoreException e) {
			throw new StreamTransformationException("SAPSecurityResources", e);
		}
		KeyStore keyStore;
		try {
			keyStore = managerPriviliged.getKeyStore(alias);
		} catch (KeyStoreException e) {
			throw new StreamTransformationException("managerPriviliged.getKeyStore " + alias, e);
		}
		ISsfProfile profile = null;
		try {
			profile = managerPriviliged.getISsfProfile(keyStore, entry, null);
		} catch (KeyStoreException e) {
			throw new StreamTransformationException(
					"Failed to load SsfProfileKeyStore " + alias + " " + entry, e);
		}
		return profile;
	}
	
}

I would like to highlight specific section of the code as it might not be applicable to your scenario.

Depending on the algorithm, you may have to search for the equivalent Spec class of the algorithm in your scenario. In my case, it is TripleDES hence the DESedeKeySpec.

DESedeKeySpec desEdeKeySpec = new DESedeKeySpec(decryptedKey);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");

For whatever reason, the response from the server has extract three characters at the end of the entire payload hence the substring being applied.

InputSource is = new InputSource(new StringReader(decryptedData.substring(0, decryptedData.length()-3)));

REFERENCES

https://blogs.sap.com/2020/01/09/sap-pi-po-xml-x509-signature-by-certificate/

https://blogs.sap.com/2018/12/24/how-to-encryptdecrypt-xml-payload-with-aes256-cbc-and-rsa-algorithm-in-sap-cpi/

https://answers.sap.com/questions/183771/keystore-access-from-java-mapping.html

https://apache.googlesource.com/santuario-java/+/9155bb454492745edfecc681b0708fe34dc3d511/samples/org/apache/xml/security/samples/encryption/

https://stackoverflow.com/questions/57261251/examples-or-tutorials-about-santuario-java


文章来源: https://blogs.sap.com/2023/12/06/xml-encryption-decryption-for-w3c-recommendation-based-payloads-via-java-mapping-for-sap-po/
如有侵权请联系:admin#unsafe.sh