MessageSecuritySender_wss4j.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */


package org.openspcoop2.security.message.wss4j;


import java.io.FileNotFoundException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.security.auth.callback.CallbackHandler;
import javax.xml.soap.SOAPMessage;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.message.Attachment;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.ExchangeImpl;
import org.apache.cxf.message.MessageImpl;
import org.apache.cxf.phase.PhaseInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.dom.handler.WSHandlerConstants;
import org.openspcoop2.message.MessageUtils;
import org.openspcoop2.message.OpenSPCoop2Message;
import org.openspcoop2.message.OpenSPCoop2SoapMessage;
import org.openspcoop2.message.constants.Costanti;
import org.openspcoop2.message.constants.MessageType;
import org.openspcoop2.message.constants.ServiceBinding;
import org.openspcoop2.protocol.sdk.state.RequestInfo;
import org.openspcoop2.security.SecurityException;
import org.openspcoop2.security.keystore.KeystoreConstants;
import org.openspcoop2.security.message.IMessageSecuritySender;
import org.openspcoop2.security.message.MessageSecurityContext;
import org.openspcoop2.security.message.constants.SecurityConstants;
import org.openspcoop2.security.message.saml.SAMLBuilderConfig;
import org.openspcoop2.security.message.saml.SAMLCallbackHandler;
import org.openspcoop2.security.message.saml.SAMLUtilities;
import org.openspcoop2.security.message.utils.AttachmentProcessingPart;
import org.openspcoop2.security.message.utils.AttachmentsConfigReaderUtils;
import org.openspcoop2.security.message.utils.EncryptionBean;
import org.openspcoop2.security.message.utils.KeystoreUtils;
import org.openspcoop2.security.message.utils.SignatureBean;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.id.IDUtilities;

/**
 * Classe per la gestione della WS-Security (WSDoAllSender).
 *
 * @author Lorenzo Nardi (nardi@link.it)
 * @author Tommaso Burlon (tommaso.burlon@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */

public class MessageSecuritySender_wss4j implements IMessageSecuritySender{

	
     @Override
	public void process(MessageSecurityContext wssContext,OpenSPCoop2Message messageParam,org.openspcoop2.utils.Map<Object> ctx) throws SecurityException{
		try{ 	
			
			if(ServiceBinding.SOAP.equals(messageParam.getServiceBinding())==false){
				throw new SecurityException("WSS4J Engine usable only with SOAP Binding");
			}
			OpenSPCoop2SoapMessage message = messageParam.castAsSoap();
			
    		RequestInfo requestInfo = null;
    		if(ctx!=null && ctx.containsKey(org.openspcoop2.core.constants.Costanti.REQUEST_INFO)) {
    			requestInfo = (RequestInfo) ctx.get(org.openspcoop2.core.constants.Costanti.REQUEST_INFO);
    		}
    		
			
			// ** Inizializzo handler CXF **/
			
	        WSS4JOutInterceptor ohandler = new WSS4JOutInterceptor();
	        PhaseInterceptor<SoapMessage> handler = ohandler.createEndingInterceptor();
	        SoapMessage msgCtx = new SoapMessage(new MessageImpl());
	        msgCtx.setVersion(MessageType.SOAP_12.equals(message.getMessageType()) ? org.apache.cxf.binding.soap.Soap12.getInstance() : org.apache.cxf.binding.soap.Soap11.getInstance());
			Exchange ex = new ExchangeImpl();
	        ex.setInMessage(msgCtx);
	        SOAPMessage soapMessage = MessageUtils.getSOAPMessage(message, false, message.getTransactionId());
	        msgCtx.setContent(SOAPMessage.class, soapMessage);
	        List<?> results = new ArrayList<>();
	        msgCtx.put(WSHandlerConstants.RECV_RESULTS, results);
	        
	        
	        // ** Localizzo attachments da trattare **/
	        
	        AttachmentProcessingPart app = AttachmentsConfigReaderUtils.getSecurityOnAttachments(wssContext);
	        
	        
	        // ** Imposto configurazione nel messaggio **/
	        // NOTA: farlo dopo getSecurityOnAttachments poichè si modifica la regola di quali attachments trattare.
	        
	        setOutgoingProperties(wssContext,msgCtx,messageParam,requestInfo,ctx);
	        
	        
	        // ** Registro attachments da trattare **/
	        
	        List<Attachment> listAttachments = null;
	        if(app!=null){
	        	listAttachments = org.openspcoop2.security.message.wss4j.WSSUtilities.readAttachments(app, message, msgCtx);
	        	if(listAttachments!=null && listAttachments.size()>0){
	        		msgCtx.setAttachments(listAttachments);
	        	}
	        }
	        
	        
	        // ** Applico sicurezza tramite CXF **/
	        
	        handler.handleMessage(msgCtx);
	        wssContext.getLog().debug("Print wssSender results...");
	        org.openspcoop2.security.message.wss4j.WSSUtilities.printWSResult(wssContext.getLog(), results);
			
			
			// ** Riporto modifica degli attachments **/
			
			org.openspcoop2.security.message.wss4j.WSSUtilities.updateAttachments(listAttachments, message, msgCtx);
					
		}
		catch(Exception e){
			
			String msg = Utilities.getInnerNotEmptyMessageException(e).getMessage();
			
			Throwable innerExc = Utilities.getLastInnerException(e);
			String innerMsg = null;
			if(innerExc!=null){
				innerMsg = innerExc.getMessage();
			}
			
			String messaggio = null;
			if(msg!=null){
				messaggio = new String(msg);
				if(innerMsg!=null && !innerMsg.equals(msg)){
					messaggio = messaggio + " ; " + innerMsg;
				}
			}
			else{
				if(innerMsg!=null){
					messaggio = innerMsg;
				}
			}
			
			// L'if scopre l'eventuale motivo preciso riguardo al fallimento della cifratura/firma.
			if(Utilities.existsInnerException(e, WSSecurityException.class)){
				Throwable t = Utilities.getLastInnerException(e);
				if(t instanceof WSSecurityException){
					if(messaggio!=null){
						messaggio = messaggio + " ; " + t.getMessage();
					}
					else{
						messaggio = t.getMessage();
					}
				}
			}
			
			SecurityException wssException = new SecurityException(messaggio, e);
			wssException.setMsgErrore(messaggio);
			throw wssException;
		}
		
    }

    private void setOutgoingProperties(MessageSecurityContext wssContext,SoapMessage msgCtx,OpenSPCoop2Message message, RequestInfo requestInfo,org.openspcoop2.utils.Map<Object> ctx) throws Exception{
    	boolean mustUnderstand = false;
    	boolean signatureUser = false;
    	boolean user = false;
    	Map<String,Object> wssOutgoingProperties = wssContext.getOutgoingProperties();
		if (wssOutgoingProperties != null && wssOutgoingProperties.size() > 0) {
			
			// preprocess per SAML
			SAMLUtilities.injectSignaturePropRefIdIntoSamlConfig(wssOutgoingProperties);
			
			// preprocess per multipropfile
			preprocessMultipropFile(wssContext, msgCtx, wssOutgoingProperties, requestInfo, ctx);
			
			for (String key : wssOutgoingProperties.keySet()) {
				Object oValue = wssOutgoingProperties.get(key);
				String value = null;
				if(oValue!=null && oValue instanceof String) {
					value = (String) oValue;
				}
//				if (SecurityConstants.ENCRYPTION_USER.equals(key) && SecurityConstants.USE_REQ_SIG_CERT.equals(value)) {
//					// value = ...;
//				}
				
				// src/site/xdoc/migration/wss4j20.xml:the "samlPropFile" and "samlPropRefId" configuration tags have been removed. 
				// Per ottenere lo stesso effetto di poter utilizzare tale file di proprietà, si converta la proprietà nella nuova voce: 'samlCallbackRef'
				if(SecurityConstants.SAML_PROF_FILE.equals(key)){
					//System.out.println("CONVERT ["+key+"] ["+value+"] ...");
					SAMLBuilderConfig config = SAMLBuilderConfig.getSamlConfig(value, requestInfo);
					SAMLCallbackHandler samlCallbackHandler = new SAMLCallbackHandler(config);
					msgCtx.put(SecurityConstants.SAML_CALLBACK_REF, samlCallbackHandler);
				}
				else if(SecurityConstants.SAML_PROF_REF_ID.equals(key)){
					if(oValue!=null && oValue instanceof Properties) {
						Properties p = (Properties) oValue;
						SAMLBuilderConfig config = SAMLBuilderConfig.getSamlConfig(p, requestInfo);
						SAMLCallbackHandler samlCallbackHandler = new SAMLCallbackHandler(config);
						msgCtx.put(SecurityConstants.SAML_CALLBACK_REF, samlCallbackHandler);
					}
					else {
						throw new Exception("Property ["+key+"] with uncorrect type: "+(oValue!=null ? oValue.getClass().getName() : "value is null"));
					}
				}
				else if(SecurityConstants.ENCRYPTION_PARTS.equals(key) || SecurityConstants.SIGNATURE_PARTS.equals(key)){
					if(value!=null) {
						msgCtx.put(key, normalizeWss4jParts(value,message));
					}
				}
				else if(SecurityConstants.PASSWORD_CALLBACK_REF.equals(key)) {
					msgCtx.put(key, oValue);
				}
				else if(SecurityConstants.SIGNATURE_PROPERTY_REF_ID.equals(key) || 
						SecurityConstants.SIGNATURE_VERIFICATION_PROPERTY_REF_ID.equals(key) || 
						SecurityConstants.ENCRYPTION_PROPERTY_REF_ID.equals(key) || 
						SecurityConstants.DECRYPTION_PROPERTY_REF_ID.equals(key) ) {
					if(value!=null) {
						msgCtx.put(key, value);
					}
					else { 
						String id = key+"_"+IDUtilities.getUniqueSerialNumber("wssSecurity.setOutgoingProperties");
						msgCtx.put(key, id);
						msgCtx.put(id, oValue);
						if(oValue!=null && oValue instanceof Properties) {
							Properties p = (Properties) oValue;
							p.put(KeystoreConstants.PROPERTY_REQUEST_INFO, requestInfo);
						}
					}
				}
				else if(SecurityConstants.ENCRYPT_ACTION_OLD.equals(key)) {
					// backward compatibility per adeguamento costante rispetto a wss4j 2.3.x
					msgCtx.put(SecurityConstants.ENCRYPTION_ACTION, value);
				}
				else{
					msgCtx.put(key, value);
					if(SecurityConstants.MUST_UNDERSTAND.equals(key)){
						mustUnderstand = true;
					}
					else if(SecurityConstants.SIGNATURE_USER.equals(key)){
						signatureUser = true;
					}
					else if(SecurityConstants.USER.equals(key)){
						user = true;
					}
				}
			}
		}
		if(!mustUnderstand){
			//Il mustUnderstand non e' stato specificato. Lo imposto a false.
			msgCtx.put(SecurityConstants.MUST_UNDERSTAND , SecurityConstants.FALSE);
		}
		if(wssContext.getActor()!=null){
			msgCtx.put(SecurityConstants.ACTOR, wssContext.getActor());
		}
		if(signatureUser && !user) {
			// fix: Caused by: org.apache.cxf.binding.soap.SoapFault: Empty username for specified action.
	        // at org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor$WSS4JOutInterceptorInternal.handleMessageInternal(WSS4JOutInterceptor.java:230) ~[cxf-rt-ws-security-3.2.6.jar:3.1.7]
	        //        at org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor$WSS4JOutInterceptorInternal.handleMessage(WSS4JOutInterceptor.java:135) ~[cxf-rt-ws-security-3.2.6.jar:3.1.7]
			msgCtx.put(SecurityConstants.USER, (String) msgCtx.get(SecurityConstants.SIGNATURE_USER));
		}
    }
    
    private void preprocessMultipropFile(MessageSecurityContext wssContext,SoapMessage msgCtx,Map<String,Object> wssOutgoingProperties, RequestInfo requestInfo,org.openspcoop2.utils.Map<Object> ctx) throws FileNotFoundException, UtilsException, SecurityException, URISyntaxException {
    	
    	String forceSignatureUser = null;
    	String forceEncryptionUser = null;
    	
        HashMap<String, String> mapAliasToPassword = new HashMap<>();
    	for (String key : wssOutgoingProperties.keySet()) {
	    	if(SecurityConstants.SIGNATURE_MULTI_PROPERTY_FILE.equals(key)) {
				SignatureBean bean = KeystoreUtils.getSenderSignatureBean(wssContext, ctx);
				if(bean.getMultiKeystore()==null) {
					throw new SecurityException("Multiproperty config not exists");
				}
				String keyAlias = bean.getUser();
				String internalAlias = bean.getMultiKeystore().getInternalConfigAlias(keyAlias);
				Properties p = new Properties();
				p.put(KeystoreConstants.PROPERTY_KEYSTORE_PATH, bean.getMultiKeystore().getKeystorePath(internalAlias));
				p.put(KeystoreConstants.PROPERTY_KEYSTORE_PASSWORD, bean.getMultiKeystore().getKeystorePassword(internalAlias));
				p.put(KeystoreConstants.PROPERTY_KEYSTORE_TYPE, bean.getMultiKeystore().getKeystoreType(internalAlias));
				p.put(KeystoreConstants.PROPERTY_PROVIDER, KeystoreConstants.PROVIDER_GOVWAY);
				p.put(KeystoreConstants.PROPERTY_REQUEST_INFO, requestInfo);
				String id = SecurityConstants.SIGNATURE_PROPERTY_REF_ID+"_"+IDUtilities.getUniqueSerialNumber("wssSecurity.setOutgoingProperties");
				msgCtx.put(SecurityConstants.SIGNATURE_PROPERTY_REF_ID, id);
				msgCtx.put(id, p);
				
				String password = bean.getPassword();
				msgCtx.put(SecurityConstants.SIGNATURE_PASSWORD, bean.getPassword());
				mapAliasToPassword.put(keyAlias, password);

				forceSignatureUser = keyAlias;
			}
			else if(SecurityConstants.ENCRYPTION_MULTI_PROPERTY_FILE.equals(key)) {
				EncryptionBean bean = KeystoreUtils.getSenderEncryptionBean(wssContext, ctx);
				if(bean.getMultiKeystore()==null) {
					throw new SecurityException("Multiproperty config not exists");
				}
				String keyAlias = bean.getUser();
				String internalAlias = bean.getMultiKeystore().getInternalConfigAlias(keyAlias);
				Properties p = new Properties();
				p.put(KeystoreConstants.PROPERTY_KEYSTORE_PATH, bean.getMultiKeystore().getKeystorePath(internalAlias));
				p.put(KeystoreConstants.PROPERTY_KEYSTORE_PASSWORD, bean.getMultiKeystore().getKeystorePassword(internalAlias));
				p.put(KeystoreConstants.PROPERTY_KEYSTORE_TYPE, bean.getMultiKeystore().getKeystoreType(internalAlias));
				p.put(KeystoreConstants.PROPERTY_PROVIDER, KeystoreConstants.PROVIDER_GOVWAY);
				p.put(KeystoreConstants.PROPERTY_REQUEST_INFO, requestInfo);
				String id = SecurityConstants.ENCRYPTION_PROPERTY_REF_ID +"_"+IDUtilities.getUniqueSerialNumber("wssSecurity.setOutgoingProperties");
				msgCtx.put(SecurityConstants.ENCRYPTION_PROPERTY_REF_ID , id);
				msgCtx.put(id, p);
				
				String password = bean.getPassword();
				mapAliasToPassword.put(keyAlias, password);

				forceEncryptionUser = keyAlias;
			}
    	}
    	
        if (!mapAliasToPassword.isEmpty()) {
            CallbackHandler pwCallbackHandler = MessageSecurityContext.newCallbackHandler(mapAliasToPassword);
            msgCtx.put(SecurityConstants.PASSWORD_CALLBACK_REF, pwCallbackHandler);
        }

    	if(forceSignatureUser!=null) {
    		wssOutgoingProperties.remove(SecurityConstants.SIGNATURE_USER);
    		wssOutgoingProperties.put(SecurityConstants.SIGNATURE_USER, forceSignatureUser);
    	}
    	if(forceEncryptionUser!=null) {
    		wssOutgoingProperties.remove(SecurityConstants.ENCRYPTION_USER);
    		wssOutgoingProperties.put(SecurityConstants.ENCRYPTION_USER, forceEncryptionUser);
    	}
    }
    
    private String normalizeWss4jParts(String parts,OpenSPCoop2Message message){
    	StringBuilder bf = new StringBuilder();
    	String[]split = ((String)parts).split(";");
		for (int i = 0; i < split.length; i++) {
			if(i>0){
				bf.append(";");
			}
			String n = split[i].trim();
			if(n.contains("{"+SecurityConstants.NAMESPACE_ATTACH+"}")){
				if(n.startsWith("{"+SecurityConstants.PART_ELEMENT+"}")){
					bf.append("{"+SecurityConstants.PART_ELEMENT+"}"+SecurityConstants.CID_ATTACH_WSS4J);
				}
				else {
					bf.append("{"+SecurityConstants.PART_CONTENT+"}"+SecurityConstants.CID_ATTACH_WSS4J);
				}
			}
			else{
				bf.append(n);
			}
		}
		//System.out.println("PRIMA ["+parts+"] DOPO ["+bf.toString()+"]");
		
		String newParts = bf.toString();
		
		while(newParts.contains(SecurityConstants.SOAP_NAMESPACE_TEMPLATE)) {
			String namespace = MessageType.SOAP_11.equals(message.getMessageType()) ? Costanti.SOAP_ENVELOPE_NAMESPACE : Costanti.SOAP12_ENVELOPE_NAMESPACE;
			newParts = newParts.replace(SecurityConstants.SOAP_NAMESPACE_TEMPLATE, namespace);
		}
		
		return newParts;
    }
 
}