ProcessPartialEncryptedMessage.java

  1. /*
  2.  * AdroitLogic UltraESB Enterprise Service Bus
  3.  *
  4.  * Copyright (c) 2010-2012 AdroitLogic Private Ltd. (http://adroitlogic.org). All Rights Reserved.
  5.  *
  6.  * GNU Affero General Public License Usage
  7.  *
  8.  * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
  9.  * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
  10.  * any later version.
  11.  *
  12.  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
  13.  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
  14.  * more details.
  15.  *
  16.  * You should have received a copy of the GNU Affero General Public License along with this program (See LICENSE-AGPL.TXT).
  17.  * If not, see http://www.gnu.org/licenses/agpl-3.0.html
  18.  *
  19.  * Commercial Usage
  20.  *
  21.  * Licensees holding valid UltraESB Commercial licenses may use this file in accordance with the UltraESB Commercial
  22.  * License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written
  23.  * agreement between you and AdroitLogic.
  24.  *
  25.  * If you are unsure which license is appropriate for your use, or have questions regarding the use of this file,
  26.  * please contact AdroitLogic at info@adroitlogic.com
  27.  */
  28. /*
  29.  * Modificato da Link.it (https://link.it) per supportare le seguenti funzionalità:
  30.  * - firma e cifratura degli attachments
  31.  * - cifratura con chiave simmetrica
  32.  * - supporto CRL
  33.  *
  34.  * Copyright (c) 2011-2025 Link.it srl (https://link.it).
  35.  *
  36.  */

  37. package org.openspcoop2.security.message.soapbox;

  38. import java.io.ByteArrayInputStream;
  39. import java.io.ByteArrayOutputStream;
  40. import java.io.IOException;
  41. import java.lang.reflect.Constructor;
  42. import java.security.Key;
  43. import java.security.PrivateKey;
  44. import java.util.Enumeration;

  45. import javax.crypto.Cipher;
  46. import javax.crypto.SecretKey;
  47. import javax.crypto.spec.IvParameterSpec;
  48. import javax.crypto.spec.SecretKeySpec;
  49. import javax.mail.Header;
  50. import javax.mail.MessagingException;
  51. import javax.mail.internet.MimeBodyPart;
  52. import javax.xml.soap.AttachmentPart;
  53. import javax.xml.soap.SOAPElement;
  54. import javax.xml.soap.SOAPException;

  55. import org.adroitlogic.soapbox.CryptoSupport;
  56. import org.adroitlogic.soapbox.CryptoUtil;
  57. import org.adroitlogic.soapbox.EncryptionRequest;
  58. import org.adroitlogic.soapbox.InvalidMessageDataException;
  59. import org.adroitlogic.soapbox.MessageSecurityContext;
  60. import org.adroitlogic.soapbox.Processor;
  61. import org.adroitlogic.soapbox.SBConstants;
  62. import org.adroitlogic.soapbox.SecurityFailureException;
  63. import org.apache.xml.security.algorithms.JCEMapper;
  64. import org.apache.xml.security.encryption.XMLCipher;
  65. import org.apache.xml.security.encryption.XMLEncryptionException;
  66. import org.apache.xml.security.exceptions.Base64DecodingException;
  67. import org.openspcoop2.message.OpenSPCoop2SoapMessage;
  68. import org.openspcoop2.message.constants.MessageType;
  69. import org.openspcoop2.security.message.constants.WSSAttachmentsConstants;
  70. import org.openspcoop2.utils.LoggerWrapperFactory;
  71. import org.openspcoop2.utils.io.Base64Utilities;
  72. import org.slf4j.Logger;
  73. import org.w3c.dom.Document;
  74. import org.w3c.dom.Element;
  75. import org.w3c.dom.NodeList;

  76. import com.sun.xml.wss.XWSSecurityException;
  77. import com.sun.xml.wss.core.EncryptedDataHeaderBlock;
  78. import com.sun.xml.wss.swa.MimeConstants;

  79. /**
  80.  * ProcessPartialEncryptedMessage
  81.  *
  82.  * Author of the original AdroitLogic code:
  83.  * @author asankha
  84.  *
  85.  * Authors of the Link.it modification to the code:
  86.  * @author Andrea Poli (apoli@link.it)
  87.  * @author Giovanni Bussu (bussu@link.it)
  88.  * @author $Author$
  89.  * @version $Rev$, $Date$
  90.  */
  91. public class ProcessPartialEncryptedMessage implements Processor {

  92.     protected OpenSPCoop2SoapMessage message;
  93.     public void setMessage(OpenSPCoop2SoapMessage message) {
  94.         this.message = message;
  95.     }
  96.     protected String actor;
  97.     protected boolean mustUnderstand;
  98.     public void setActor(String actor) {
  99.         this.actor = actor;
  100.     }
  101.     public void setMustUnderstand(boolean mustUnderstand) {
  102.         this.mustUnderstand = mustUnderstand;
  103.     }
  104.    

  105.     public static final Logger logger = LoggerWrapperFactory.getLogger(ProcessPartialEncryptedMessage.class);
  106.    
  107.     /**
  108.      *
  109.      * @param secConfig
  110.      * @param msgSecCtx
  111.      */
  112.     @Override
  113.     public void process(org.adroitlogic.soapbox.SecurityConfig secConfig, MessageSecurityContext msgSecCtx) {

  114.         Element wsseSecurityElem = null;
  115.         try{
  116.             wsseSecurityElem = WSSUtils.getWSSecurityHeader(msgSecCtx.getDocument(), this.actor, this.mustUnderstand);
  117.         }catch(Exception e){
  118.             throw new SecurityFailureException(e.getMessage(), e);
  119.         }
  120.        
  121.         Element elem = CryptoUtil.getSecurityProcessorElement(wsseSecurityElem,
  122.             SBConstants.XENC, "EncryptedKey");
  123.         if (elem == null) {
  124.             if (ProcessPartialEncryptedMessage.logger.isDebugEnabled()) {
  125.                 ProcessPartialEncryptedMessage.logger.debug("Message is not encrypted - skipping ProcessEncryptedMessage");
  126.             }
  127.             throw new SecurityFailureException("WS-Security failure - Message is not encrypted");
  128.         }

  129.         Cipher cipher = null;
  130.         byte[] decryptedEphemeralKey = null;
  131.         try {
  132.              SoapBoxSecurityConfig secContextOpenSPCoop = (SoapBoxSecurityConfig)secConfig;
  133.            
  134.             // 1. Find the encryption method
  135.             String algorithm = CryptoUtil.getFirstChild(
  136.                     elem, SBConstants.XENC, SBConstants.ENCRYPTION_METHOD).getAttribute(SBConstants.ATT_ALGORITHM);
  137.             cipher = CryptoSupport.getInstance().getCipherInstance(algorithm);

  138.             if (ProcessPartialEncryptedMessage.logger.isDebugEnabled()) {
  139.                 ProcessPartialEncryptedMessage.logger.debug("Processing EncryptedKey element - encryption method : " + cipher.getAlgorithm());
  140.             }

  141.             // 2. Find the session key used
  142.             byte[] encryptedEphemeralKey = CryptoUtil.decodeBase64EncodedText(
  143.                 CryptoUtil.getFirstChild(
  144.                     CryptoUtil.getFirstChild(elem, SBConstants.XENC, SBConstants.CIPHER_DATA),
  145.                     SBConstants.XENC, "CipherValue"));

  146.            
  147.             if(secContextOpenSPCoop.isSymmetricSharedKey()){
  148.                
  149.                 // 3. Get the symmetric key
  150.                 EncryptionRequest encReq = msgSecCtx.getEncryptionRequest();
  151.                 Key key = secContextOpenSPCoop.getSymmetricKey(encReq.getCertAlias());
  152.                
  153.                 // 4. Decrypt the session key (ephemeral key)
  154.                 cipher.init(Cipher.UNWRAP_MODE, key);
  155.                 String keyAlgorithm = JCEMapper.getJCEKeyAlgorithmFromURI(algorithm);
  156.                 decryptedEphemeralKey = cipher.unwrap(encryptedEphemeralKey, keyAlgorithm, Cipher.SECRET_KEY).getEncoded();
  157.                                
  158.             }
  159.             else{
  160.          
  161.                 // 3. Get the private encryption key
  162.                 PrivateKey privateKey = CryptoUtil.getPrivateKeyFromSecurityTokenReference(secConfig, msgSecCtx,
  163.                     CryptoUtil.getFirstChild(
  164.                         CryptoUtil.getFirstChild(elem, SBConstants.DS, SBConstants.KEY_INFO),
  165.                         SBConstants.WSSE, SBConstants.SECURITY_TOKEN_REFERENCE));
  166.    
  167.                 // 4. Decrypt the session key (ephemeral key)
  168.                 cipher.init(Cipher.DECRYPT_MODE, privateKey);
  169.                 decryptedEphemeralKey = cipher.doFinal(encryptedEphemeralKey);
  170.          
  171.             }

  172.         } catch (Exception e) {
  173.             throw new SecurityFailureException("Error decrypting ephemeral key", e);
  174.         } finally {
  175.             CryptoSupport.getInstance().returnCipherInstance(cipher);
  176.         }
  177.        

  178.         // 5. get Reference list
  179.         // According to the W3C XML-Enc this key is used to decrypt _any_ references contained in the reference list
  180.         // Now lookup the references that are encrypted with this key
  181.         Element refListElem = CryptoUtil.getFirstChildOrNull(elem, SBConstants.XENC, SBConstants.REFERENCE_LIST);
  182.         NodeList nl = refListElem.getElementsByTagNameNS(SBConstants.XENC, SBConstants.DATA_REFERENCE);
  183.         if (nl == null) {
  184.             throw new InvalidMessageDataException("No DataReference elements that are signed");
  185.         } else {
  186.             for (int i=0; i<nl.getLength(); i++) {
  187.                 Element dataRefElem = (Element) nl.item(i);
  188.                 String wsuId = dataRefElem.getAttribute("URI");
  189.                 if (wsuId.charAt(0) == '#') {
  190.                     wsuId = wsuId.substring(1);  // trim first "#" of local ref
  191.                 }

  192.                 decryptDataReference(msgSecCtx, wsuId, decryptedEphemeralKey);
  193.             }
  194.         }
  195.     }
  196.    
  197.     public void decryptDataReference(MessageSecurityContext msgSecCtx, String wsuId, byte[] decryptedeEphemeralKey) {

  198.         Document doc = msgSecCtx.getDocument();
  199.         Element refElem = CryptoUtil.findElementById(doc, wsuId, SBConstants.WSU);
  200.        
  201.         if (refElem == null) {
  202.             refElem = CryptoUtil.findElementById(doc, wsuId, null);
  203.         }
  204.        
  205.         if(refElem != null) {
  206.                
  207.                 Element encData = CryptoUtil.getFirstChild(refElem, SBConstants.XENC, SBConstants.ENCRYPTED_DATA);
  208.                
  209.                 String encAlgo = CryptoUtil.getFirstChild(
  210.                         encData, SBConstants.XENC, "EncryptionMethod").getAttribute(SBConstants.ALGORITHM);
  211.                
  212.                 SecretKey symmetricKey = new SecretKeySpec(decryptedeEphemeralKey, JCEMapper.getJCEKeyAlgorithmFromURI(encAlgo));
  213.                
  214.                 String type = encData.getAttribute("Type");
  215.                 //attachments
  216.                 if(type.equals(WSSAttachmentsConstants.ATTACHMENT_COMPLETE_URI) || type.equals(WSSAttachmentsConstants.ATTACHMENT_CONTENT_ONLY_URI)) {
  217.                     try {
  218.                         SOAPElement encDataSoapElement = null;
  219.                         if(encData instanceof SOAPElement) {
  220.                             encDataSoapElement = (SOAPElement) encData;
  221.                         }
  222.                         else {
  223.                             encDataSoapElement = MessageType.SOAP_11.equals(this.message.getMessageType()) ?
  224.                                     this.message.getFactory().getSoapFactory11().createElement(encData) :
  225.                                     this.message.getFactory().getSoapFactory12().createElement(encData);
  226.                         }
  227.                        
  228.                         Class<?> edhb = Class.forName(this.message.getEncryptedDataHeaderBlockClass());
  229.                         Constructor<?> constructor = edhb.getConstructor(SOAPElement.class);
  230.                         EncryptedDataHeaderBlock xencEncryptedData = (EncryptedDataHeaderBlock) constructor.newInstance(encDataSoapElement);
  231.                        
  232.                         String uri = xencEncryptedData.getCipherReference(false, null).getAttribute("URI");
  233.                         AttachmentPart part = (AttachmentPart) msgSecCtx.getProperty(uri.substring(4));
  234.                        
  235.                         if(part != null)
  236.                             ProcessPartialEncryptedMessage.decryptAttachment(part, xencEncryptedData, symmetricKey, type);
  237.                        
  238.                        
  239.                     } catch (Exception e) {
  240.                         throw new InvalidMessageDataException("Failed to decrypt attachment referenced by element with WSU-ID : " + wsuId, e);
  241.                     }
  242.                 } else { //xml elements
  243.                     XMLCipher xmlCipher = null;
  244.                     try {
  245.                         xmlCipher = CryptoSupport.getInstance().getXMLCipher(encAlgo);
  246.                         xmlCipher.init(XMLCipher.DECRYPT_MODE, symmetricKey);
  247.                         xmlCipher.doFinal(doc, encData);
  248.        
  249.                     } catch (XMLEncryptionException e) {
  250.                         throw new InvalidMessageDataException("Unsupported algorithm for decryption : " + encAlgo, e);
  251.                     } catch (Exception e) {
  252.                         throw new InvalidMessageDataException("Failed to decrypt element with WSU-ID : " + wsuId, e);
  253.                     } finally {
  254.                         CryptoSupport.getInstance().returnXMLCipherInstance(encAlgo, xmlCipher);
  255.                     }
  256.                 }
  257.             }
  258.     }
  259.    
  260.     public static AttachmentPart decryptAttachment(AttachmentPart part, EncryptedDataHeaderBlock edhb,
  261.                     SecretKey key, String type) throws XWSSecurityException, IOException, SOAPException, MessagingException, Base64DecodingException {
  262.        
  263.         String mimeType = edhb.getMimeType();
  264.         Element dsTransform = (Element)edhb.getTransforms().next();
  265.        
  266.         if (!(dsTransform.getAttribute("Algorithm").equals(
  267.                 WSSAttachmentsConstants.ATTACHMENT_CONTENT_ONLY_TRANSFORM_URI) || dsTransform.getAttribute("Algorithm").equals(
  268.                         WSSAttachmentsConstants.ATTACHMENT_COMPLETE_TRANSFORM_URI) || dsTransform.getAttribute("Algorithm").equals(
  269.                                 WSSAttachmentsConstants.ATTACHMENT_CIPHERTEXT_TRANSFORM_URI))) {
  270. //            logger.log(Level.SEVERE, "WSS1234.invalid.transform=");
  271.             throw new XWSSecurityException("Unexpected ds:Transform, " + dsTransform.getAttribute("Algorithm"));
  272.         }
  273.        
  274.         // initialize Cipher
  275.         Cipher decryptor = null;
  276.         byte[] cipherOutput = null;
  277.         try {
  278.             ByteArrayOutputStream baos = new ByteArrayOutputStream();
  279.             part.getDataHandler().writeTo(baos);
  280.            
  281.             byte[] cipherInput  = baos.toByteArray();
  282.             decryptor = CryptoSupport.getInstance().getCipherInstance(edhb.getEncryptionMethodURI());
  283.            
  284.             int ivLen = decryptor.getBlockSize();
  285.             byte[] ivBytes = new byte[ivLen];
  286.            
  287.             System.arraycopy(cipherInput, 0, ivBytes, 0, ivLen);
  288.             IvParameterSpec iv = new IvParameterSpec(ivBytes);
  289.            
  290.             decryptor.init(Cipher.DECRYPT_MODE, key, iv);
  291.            
  292.             cipherOutput = decryptor.doFinal(cipherInput, ivLen, cipherInput.length-ivLen);
  293.         } catch (Exception e) {
  294. //            logger.log(Level.SEVERE, "WSS1232.failedto.decrypt.attachment", e);
  295.             throw new XWSSecurityException(e);
  296.         } finally {
  297.             CryptoSupport.getInstance().returnCipherInstance(decryptor);
  298.         }
  299.        
  300.         if (type.equals(WSSAttachmentsConstants.ATTACHMENT_CONTENT_ONLY_URI) || type.equals(WSSAttachmentsConstants.ATTACHMENT_CIPHERTEXT_TRANSFORM_URI)) {
  301.             // update headers and content
  302.             part.setContentType(mimeType);
  303.            
  304.             String[] cLength = part.getMimeHeader(MimeConstants.CONTENT_LENGTH);
  305.             if (cLength != null && !cLength[0].equals(""))
  306.                 part.setMimeHeader(MimeConstants.CONTENT_LENGTH, Integer.toString(cipherOutput.length));
  307.            
  308.             part.removeMimeHeader(MimeConstants.CONTENT_TRANSFER_ENCODING);
  309.            
  310.             part.clearContent();
  311.             part.setDataHandler(new javax.activation.DataHandler(new _DS(Base64Utilities.decode(cipherOutput), mimeType)));
  312.            
  313.         } else {
  314.             MimeBodyPart decryptedAttachment = new MimeBodyPart(new ByteArrayInputStream(cipherOutput));

  315.             // validate cid
  316.             String uri = edhb.getCipherReference(false, null).getAttribute("URI");
  317.             String dcId = decryptedAttachment.getContentID();
  318.             if (dcId == null || !uri.substring(4).equals(dcId.substring(1, dcId.length()-1))) {
  319. //                logger.log(Level.SEVERE, "WSS1234.unmatched.content-id");
  320.                 throw new XWSSecurityException("Content-Ids in encrypted and decrypted attachments donot match");
  321.             }
  322.            
  323.             part.removeAllMimeHeaders();
  324.            
  325.             // copy headers
  326.             Enumeration<?> h_enum = decryptedAttachment.getAllHeaders();
  327.             while (h_enum.hasMoreElements()) {
  328.                 Header hdr = (Header)h_enum.nextElement();
  329.                 String hname = hdr.getName();
  330.                 String hvale = hdr.getValue();
  331.                 part.setMimeHeader(hname, hvale);
  332.             }
  333.            
  334.             // set content
  335.             part.clearContent();
  336.            
  337.             ByteArrayOutputStream bs = new ByteArrayOutputStream();
  338.             decryptedAttachment.getDataHandler().writeTo(bs);
  339.             part.setDataHandler(new javax.activation.DataHandler(new _DS(Base64Utilities.decode(bs.toByteArray()), mimeType)));
  340.         }
  341.         return part;
  342.     }
  343.    
  344.     private static class _DS implements javax.activation.DataSource {
  345.         byte[] content = null;
  346.         String contentType = null;
  347.        
  348.         _DS(byte[] content, String contentType) { this.content = content; this.contentType = contentType; }
  349.        
  350.         @Override
  351.         public java.io.InputStream getInputStream() throws java.io.IOException {
  352.             return new java.io.ByteArrayInputStream(this.content);
  353.         }
  354.        
  355.         @Override
  356.         public java.io.OutputStream getOutputStream() throws java.io.IOException {
  357.             java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
  358.             baos.write(this.content, 0, this.content.length);
  359.             return baos;
  360.         }
  361.        
  362.         @Override
  363.         public String getName() { return "_DS"; }
  364.        
  365.         @Override
  366.         public String getContentType() { return this.contentType; }
  367.     }
  368. }