MessageUtilities.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.engine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.soap.AttachmentPart;

import org.openspcoop2.message.OpenSPCoop2SoapMessage;
import org.openspcoop2.message.soap.SoapUtils;
import org.openspcoop2.message.soap.reference.AttachmentReference;
import org.openspcoop2.message.soap.reference.ElementReference;
import org.openspcoop2.message.soap.reference.Reference;
import org.openspcoop2.security.SecurityException;
import org.openspcoop2.security.message.MessageSecurityContext;
import org.openspcoop2.security.message.SubErrorCodeSecurity;
import org.openspcoop2.security.message.constants.SecurityConstants;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * MessageUtilities
 *
 * @author Andrea Poli (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class MessageUtilities {

	
	public static void checkEncryptionPartElements(Map<QName, QName> notResolved, OpenSPCoop2SoapMessage message, List<SubErrorCodeSecurity> listaErroriInElementi) throws SecurityException {
		
		try{
			
			if(notResolved != null && !notResolved.isEmpty()) {
				for (QName expected : notResolved.keySet()) {
					QName actualQName = notResolved.get(expected);
					boolean found = false;
					NodeList it = message.getSOAPPart().getElementsByTagNameNS(actualQName.getNamespaceURI(), actualQName.getLocalPart());
					for (int j = 0; j < it.getLength() && !found; j++) {
						Node elementFather = (Node) it.item(j);
						//SOAPElement elementFather = (SOAPElement) it.item(j);
						//List<SOAPElement> encryptedElements = SoapUtils.getNotEmptyChildSOAPElement(elementFather);
						List<Node> encryptedElements = SoapUtils.getNotEmptyChildNodes(message.getFactory(), elementFather, false);
						for (int i = 0; i < encryptedElements.size() && !found; i++) {
							//SOAPElement actual = encryptedElements.get(i);
							Node actual = encryptedElements.get(i);
							String actualNamespaceURI = actual.getNamespaceURI();
							String actualLocalName = actual.getLocalName();
							String expectedNamespaceURI = expected.getNamespaceURI();
							String expectedLocalName = expected.getLocalPart();
							if(((actualNamespaceURI == null && expectedNamespaceURI == null) || (actualNamespaceURI!=null && actualNamespaceURI.equals(expectedNamespaceURI)))
									&& actualLocalName.equals(expectedLocalName)) {
									//trovato l'elemento che ci interessa
									found = true;
							}
						}
					}
					if(!found) {
						SubErrorCodeSecurity subCodice = new SubErrorCodeSecurity();
						subCodice.setEncrypt(true);
						subCodice.setMsgErrore("Expected encryption part("+ expected +") not found");
						subCodice.setTipo(SecurityConstants.ENCRYPTION_PART_CONTENT);
						subCodice.setNamespace(expected.getNamespaceURI());
						subCodice.setName(expected.getLocalPart());
						listaErroriInElementi.add(subCodice);
					}
				}
			}
			
		}catch(Exception e){
			throw new SecurityException(e.getMessage(),e);
		}
	}

	public static Map<QName, QName> checkEncryptSignatureParts(MessageSecurityContext messageSecurityContext,List<Reference> elementsToClean, OpenSPCoop2SoapMessage message,
			List<SubErrorCodeSecurity> codiciErrore, QName qnameSecurity) throws SecurityException {
		
		Map<String, Object> properties = messageSecurityContext.getIncomingProperties();
		boolean addAttachmentIdBrackets = properties.containsKey(SecurityConstants.ADD_ATTACHMENT_ID_BRACKETS) ?
				properties.get(SecurityConstants.ADD_ATTACHMENT_ID_BRACKETS).equals(SecurityConstants.ADD_ATTACHMENT_ID_BRACKETS_TRUE) :
					SecurityConstants.ADD_ATTACHMENT_ID_BRACKETS_DEFAULT;
		try{
			int numAttachmentsInMsg = message.countAttachments();
			List<String> cidAttachments = new ArrayList<>();
			if(numAttachmentsInMsg>0){
				Iterator<?> itAttach = message.getAttachments();
				while (itAttach.hasNext()) {
					AttachmentPart ap = (AttachmentPart) itAttach.next();
					String cid = ap.getContentId();
					if (!addAttachmentIdBrackets) {
						cid = cid.replaceAll("(^<)|(>$)", "");
					}
					cidAttachments.add(cid);
				}
			}
		
			// *** ENCRYPT VERIFY ***
			Map<QName, QName> notResolvedMap = new HashMap<QName, QName>();
			Object encryptionPartsVerify = messageSecurityContext.getIncomingProperties().get(SecurityConstants.ENCRYPTION_PARTS_VERIFY);
			Object encryptionParts = messageSecurityContext.getIncomingProperties().get(SecurityConstants.ENCRYPTION_PARTS);
			if(encryptionPartsVerify!=null && "true".equalsIgnoreCase(((String)encryptionPartsVerify).trim()) &&
					encryptionParts==null){
				// Se forzo la proprietà verify allora devo indicare le encryption parts
				throw new SecurityException(SecurityConstants.ENCRYPTION_PARTS+" non indicate");
			}
			if( (
					encryptionPartsVerify==null || 
					"true".equalsIgnoreCase(((String)encryptionPartsVerify).trim())
				) &&
					(encryptionParts!=null) ){
				
				Map<String, Boolean> encryptionPartsMap = new HashMap<String, Boolean>();
				Map<Reference, Boolean> encryptionReferenceMap = new HashMap<Reference, Boolean>();
				boolean isAllAttachmentEncrypted = false;
				int numAttachmentsEncrypted = 0;
				if(encryptionParts!=null){
					String[]split = ((String)encryptionParts).split(";");
					int numElementsEncrypted = 0;
					for (int i = 0; i < split.length; i++) {
						boolean checked = false;
						String[]split2 = split[i].trim().split("}");
						String tipo = split2[0].trim().substring(1); // Element o Content
						if("".equals(tipo)){
							// caso speciale wss4j {}cid:Attachments
							tipo = SecurityConstants.PART_CONTENT;
						}
						String namespace = null;
						String nome = null;
						boolean attach = false;
						if(split2.length==3){
							namespace = split2[1].trim().substring(1); 
							nome = split2[2].trim();
							attach = SecurityConstants.ENCRYPTION_NAMESPACE_ATTACH.equals(namespace);
						}
						else{
							// caso speciale wss4j {}cid:Attachments ?
							if(SecurityConstants.CID_ATTACH_WSS4J.equalsIgnoreCase(split2[1].trim())){
								namespace = SecurityConstants.ENCRYPTION_NAMESPACE_ATTACH;
								nome = SecurityConstants.ATTACHMENT_INDEX_ALL;
								attach = true;
							}
							else{
								throw new Exception("Part ["+split[i]+"] with wrong format");
							}
						}
						if(SecurityConstants.ENCRYPTION_PART_ELEMENT.equals(tipo)) {
							// incremento solamente se non è un attachments
							if(!attach){
								numElementsEncrypted++;
							}
						}
						if(nome.startsWith("{"))
							nome = nome.substring(1);
						//System.out.println("CIFRO ["+tipo+"] ["+namespace+"] ["+nome+"]");	
						
						List<String> cidAttachmentsEncrypt = new ArrayList<>();
						if(cidAttachments!=null && cidAttachments.size()>0){
							cidAttachmentsEncrypt.addAll(cidAttachments);
						}
						
						for (Reference reference : elementsToClean) {
						
							if(reference instanceof AttachmentReference) {
								if(AttachmentReference.TYPE_ENCRYPT_ATTACHMENT==reference.getType()){
									if(SecurityConstants.ENCRYPTION_NAMESPACE_ATTACH.equals(namespace)){
										numAttachmentsEncrypted++;
										if(nome.equals(SecurityConstants.ATTACHMENT_INDEX_ALL)) {
											checked = true;
											isAllAttachmentEncrypted = true;
											encryptionReferenceMap.put(reference, true);
											encryptionPartsMap.put(split[i], checked);
											// break;
											// Vanno iterate tutte, dovendole contare
										}else{
											int position = -1;
											try{
												position = Integer.parseInt(nome);
											}catch(Exception e){
												// position non presente
											}
											if(position>0){
												int refPosition = -1;
												for (int j = 0; j < cidAttachments.size(); j++) {
													if(cidAttachments.get(j).equals(reference.getReference())){
														refPosition = j+1;
														break;
													}
												}
												//if(refPosition == position){ // Alcune implementazioni durante la spedizione modificano l'ordine degli attachments.
												// Non è possibile effettuare tale controllo sulla ricezione. Può essere usato solo per specificare quale attach firmare/cifrare in spedizione
												// verifico solamente che il cid sia presente
												if(refPosition>0){
													cidAttachmentsEncrypt.remove((refPosition-1));
													checked = true;
													encryptionReferenceMap.put(reference, true);
													encryptionPartsMap.put(split[i], checked);
													break;
												}
											}
											else{
												if(nome.equals(reference.getReference())) {
													checked = true;
													encryptionReferenceMap.put(reference, true);
													encryptionPartsMap.put(split[i], checked);
													break;
												}
											}
										}
									}
								}
							} else {
								ElementReference elementReference = ((ElementReference)reference);
								String localName = elementReference.getElement().getLocalName();
								String namespaceURI = elementReference.getElement().getNamespaceURI();
								if(ElementReference.TYPE_ENCRYPT_CONTENT==reference.getType()){
									// Check encrypt content
									if(nome.equals(localName) && namespace.equals(namespaceURI)) {
										checked = SecurityConstants.ENCRYPTION_PART_CONTENT.equals(tipo);
										encryptionReferenceMap.put(reference, checked);
										encryptionPartsMap.put(split[i], checked);
										if(checked){
											break;
										}
									}
								} else if(ElementReference.TYPE_ENCRYPT_ELEMENT==reference.getType() && SecurityConstants.ENCRYPTION_PART_ELEMENT.equals(tipo)) {
										// Segnamo l'elemento come checked, e lo aggiungiamo alla lista di elementi da verificare 
										// dopo la decifratura, visto che allo stato attuale e' imposibile verificarlo
										checked = true;
										encryptionPartsMap.put(split[i], checked);
										QName actual = new QName(namespaceURI, localName);
										QName expected = new QName(namespace, nome);
										boolean localPartSec = localName.equals(qnameSecurity.getLocalPart());
										boolean namespaceURISec = namespaceURI.equals(qnameSecurity.getNamespaceURI()); 
										if(!(localPartSec && namespaceURISec)) {
											if(!notResolvedMap.containsKey(expected)) {
												notResolvedMap.put(expected, actual);
											}
										}
								}
							}
						}
					}
					
					int numElementsExpectedToBeEncrypted = 0;
					for(Reference reference : elementsToClean) {
						if(reference.getType()==ElementReference.TYPE_ENCRYPT_ELEMENT) {
							numElementsExpectedToBeEncrypted++;
						}
					}
					
					if(numElementsExpectedToBeEncrypted != numElementsEncrypted) {
						SubErrorCodeSecurity subCodice = new SubErrorCodeSecurity();
						subCodice.setEncrypt(true);
						subCodice.setMsgErrore("Expected encryption {Element} " + numElementsExpectedToBeEncrypted + ", found " + notResolvedMap.size());
						codiciErrore.add(subCodice);
					}
				}
				if(encryptionParts!=null){
					String[]split = ((String)encryptionParts).split(";");
					for (int i = 0; i < split.length; i++) {
						String[]split2 = split[i].trim().split("}");
						String tipo = split2[0].trim().substring(1); // Element o Content
						if("".equals(tipo)){
							// caso speciale wss4j {}cid:Attachments
							tipo = SecurityConstants.PART_CONTENT;
						}
						String namespace = null;
						String nome = null;
						if(split2.length==3){
							namespace = split2[1].trim().substring(1); 
							nome = split2[2].trim();
						}
						else{
							// caso speciale wss4j {}cid:Attachments ?
							if(SecurityConstants.CID_ATTACH_WSS4J.equalsIgnoreCase(split2[1].trim())){
								namespace = SecurityConstants.ENCRYPTION_NAMESPACE_ATTACH;
								nome = SecurityConstants.ATTACHMENT_INDEX_ALL;
							}
							else{
								throw new Exception("Part ["+split[i]+"] with wrong format");
							}
						}
						if(nome.startsWith("{"))
							nome = nome.substring(1);
						if(SecurityConstants.ENCRYPTION_PART_CONTENT.equals(tipo)){
							if(!encryptionPartsMap.containsKey(split[i]) || !encryptionPartsMap.get(split[i])) {
								SubErrorCodeSecurity subCodice = new SubErrorCodeSecurity();
								subCodice.setEncrypt(true);
								subCodice.setMsgErrore("Expected encryption part("+ split[i] +") not found");
								subCodice.setTipo(tipo);
								subCodice.setNamespace(namespace);
								subCodice.setName(nome);
								codiciErrore.add(subCodice);
	//							throw new Exception("Expected encryption part("+ split[i] +") not found");
							}
						}
					}
				}
			
				if(isAllAttachmentEncrypted && numAttachmentsEncrypted != numAttachmentsInMsg) {
					SubErrorCodeSecurity subCodice = new SubErrorCodeSecurity();
					subCodice.setEncrypt(true);
					subCodice.setMsgErrore("All attachments in message (found:"+numAttachmentsInMsg+") must be encrypted, but only "+numAttachmentsEncrypted+" appear to be encrypted");
					subCodice.setTipo(SecurityConstants.ENCRYPTION_PART_CONTENT);
					subCodice.setNamespace(SecurityConstants.ENCRYPTION_NAMESPACE_ATTACH);
					subCodice.setName(SecurityConstants.ATTACHMENT_INDEX_ALL);
					codiciErrore.add(subCodice);
					//throw new Exception("All attachments in message (found:"+numAttachmentsInMsg+") must be encrypted, but only "+numAttachmentsEncrypted+" appear to be encrypted");
				}
				
				for (Reference reference: elementsToClean) {
					if(reference.getType()==ElementReference.TYPE_ENCRYPT_CONTENT || reference.getType()==AttachmentReference.TYPE_ENCRYPT_ATTACHMENT) {
						if(!encryptionReferenceMap.containsKey(reference) || !encryptionReferenceMap.get(reference)) {
							SubErrorCodeSecurity subCodice = new SubErrorCodeSecurity();
							subCodice.setEncrypt(true);
							subCodice.setMsgErrore("Found encryption part("+ reference+") not expected");
							if(reference.getType()==ElementReference.TYPE_ENCRYPT_CONTENT){
								ElementReference elReference = (ElementReference) reference;
								subCodice.setTipo(SecurityConstants.ENCRYPTION_PART_CONTENT);
								subCodice.setNamespace(elReference.getElement().getNamespaceURI());
								subCodice.setName(elReference.getElement().getLocalName());
							}
							else{
								subCodice.setTipo(SecurityConstants.ENCRYPTION_PART_CONTENT);
								subCodice.setNamespace(SecurityConstants.ENCRYPTION_NAMESPACE_ATTACH);
								subCodice.setName(reference.getReference());
							}
							codiciErrore.add(subCodice);
							//throw new Exception("Found encryption part("+ reference+") not expected");
						}
					}
				}
				
			}
					
			// *** SIGNATURE VERIFY ***
			Object signaturePartsVerify = messageSecurityContext.getIncomingProperties().get(SecurityConstants.SIGNATURE_PARTS_VERIFY);
			Object signatureParts = messageSecurityContext.getIncomingProperties().get(SecurityConstants.SIGNATURE_PARTS);
			if(signaturePartsVerify!=null && "true".equalsIgnoreCase(((String)signaturePartsVerify).trim()) &&
					signatureParts==null){
				// Se forzo la proprietà verify allora devo indicare le signature parts
				throw new SecurityException(SecurityConstants.SIGNATURE_PARTS+" non indicate");
			}
			if( (
					signaturePartsVerify==null || 
					"true".equalsIgnoreCase(((String)signaturePartsVerify).trim())
				) &&
					(signatureParts!=null) ){
					
				boolean isAllAttachmentSigned = false;
				int numAttachmentsSigned = 0;
				Map<String, Boolean> signaturePartsMap = new HashMap<String, Boolean>();
				Map<Reference, Boolean> referenceMap = new HashMap<Reference, Boolean>();
				if(signatureParts!=null){
					String[]split = ((String)signatureParts).split(";");
					for (int i = 0; i < split.length; i++) {
						boolean checked = false;
						String[]split2 = split[i].trim().split("}");
						String tipo = split2[0].trim().substring(1); // Element o Content
						if("".equals(tipo)){
							// caso speciale wss4j {}cid:Attachments
							tipo = SecurityConstants.PART_CONTENT;
						}
						String namespace = null;
						String nome = null;
						if(split2.length==3){
							namespace = split2[1].trim().substring(1); 
							nome = split2[2].trim();
						}
						else{
							// caso speciale wss4j {}cid:Attachments ?
							if(SecurityConstants.CID_ATTACH_WSS4J.equalsIgnoreCase(split2[1].trim())){
								namespace = SecurityConstants.SIGNATURE_NAMESPACE_ATTACH;
								nome = SecurityConstants.ATTACHMENT_INDEX_ALL;
							}
							else{
								throw new Exception("Part ["+split[i]+"] with wrong format");
							}
						}
						if(nome.startsWith("{"))
							nome = nome.substring(1);
						//System.out.println("CIFRO ["+tipo+"] ["+namespace+"] ["+nome+"]");	
						
						List<String> cidAttachmentsSignature = new ArrayList<>();
						if(cidAttachments!=null && cidAttachments.size()>0){
							cidAttachmentsSignature.addAll(cidAttachments);
						}
						
						for (Reference reference : elementsToClean) {
						
							if(reference instanceof AttachmentReference) {
								if(AttachmentReference.TYPE_SIGN_ATTACHMENT==reference.getType()){
									if(SecurityConstants.SIGNATURE_NAMESPACE_ATTACH.equals(namespace)){
										numAttachmentsSigned++;
										if(nome.equals(SecurityConstants.ATTACHMENT_INDEX_ALL)) {
											checked = true;
											isAllAttachmentSigned = true;
											referenceMap.put(reference, true);
											// break;
											// Vanno iterate tutte, dovendole contare
										}
										else{
											int position = -1;
											try{
												position = Integer.parseInt(nome);
											}catch(Exception e){
												// position non presente
											}
											if(position>0){
												int refPosition = -1;
												for (int j = 0; j < cidAttachments.size(); j++) {
													if(cidAttachments.get(j).equals(reference.getReference())){
														refPosition = j+1;
														break;
													}
												}
												//if(refPosition == position){ // Alcune implementazioni durante la spedizione modificano l'ordine degli attachments.
												// Non è possibile effettuare tale controllo sulla ricezione. Può essere usato solo per specificare quale attach firmare/cifrare in spedizione
												// verifico solamente che il cid sia presente
												if(refPosition>0){
													cidAttachmentsSignature.remove((refPosition-1));
													checked = true;
													referenceMap.put(reference, true);
													break;
												}
											}
											else{
												if(nome.equals(reference.getReference())) {
													checked = true;
													referenceMap.put(reference, true);
													break;
												}
											}
										}
									}
								}
							} else {
								ElementReference elementReference = ((ElementReference)reference);
								String localName = elementReference.getElement().getLocalName();
								String namespaceURI = elementReference.getElement().getNamespaceURI();
								if(ElementReference.TYPE_SIGNATURE==reference.getType() && nome.equals(localName) && namespace.equals(namespaceURI)) {
									checked = SecurityConstants.SIGNATURE_PART_COMPLETE.equals(tipo) ||
											SecurityConstants.SIGNATURE_PART_CONTENT.equals(tipo) || SecurityConstants.SIGNATURE_PART_ELEMENT.equals(tipo);
									referenceMap.put(reference, checked);
									if(checked){
										break;
									}
								}
							}
						}
						signaturePartsMap.put(split[i], checked);
					}
				}
				
				for (String part : signaturePartsMap.keySet()) {
					if(!signaturePartsMap.get(part)) {
						String[]split2 = part.trim().split("}");
						String tipo = split2[0].trim().substring(1); // Element o Content
						if("".equals(tipo)){
							// caso speciale wss4j {}cid:Attachments
							tipo = SecurityConstants.PART_CONTENT;
						}
						String namespace = null;
						String nome = null;
						if(split2.length==3){
							namespace = split2[1].trim().substring(1); 
							nome = split2[2].trim();
						}
						else{
							// caso speciale wss4j {}cid:Attachments ?
							if(SecurityConstants.CID_ATTACH_WSS4J.equalsIgnoreCase(split2[1].trim())){
								namespace = SecurityConstants.SIGNATURE_NAMESPACE_ATTACH;
								nome = SecurityConstants.ATTACHMENT_INDEX_ALL;
							}
							else{
								throw new Exception("Part ["+part+"] with wrong format");
							}
						}
						if(nome.startsWith("{"))
							nome = nome.substring(1);
						SubErrorCodeSecurity subCodice = new SubErrorCodeSecurity();
						subCodice.setEncrypt(false);
						subCodice.setMsgErrore("Expected signature part("+ part +") not found");
						subCodice.setTipo(tipo);
						subCodice.setNamespace(namespace);
						subCodice.setName(nome);
						codiciErrore.add(subCodice);
						//throw new Exception("Expected signature part("+ part +") not found");
					}
				}
				
		
				if(isAllAttachmentSigned && numAttachmentsSigned != numAttachmentsInMsg) {
					SubErrorCodeSecurity subCodice = new SubErrorCodeSecurity();
					subCodice.setEncrypt(false);
					subCodice.setMsgErrore("All attachments in message (found:"+numAttachmentsInMsg+") must be signed, but only "+numAttachmentsSigned+" appear to be signed");
					subCodice.setTipo(SecurityConstants.SIGNATURE_PART_CONTENT);
					subCodice.setNamespace(SecurityConstants.SIGNATURE_NAMESPACE_ATTACH);
					subCodice.setName(SecurityConstants.ATTACHMENT_INDEX_ALL);
					codiciErrore.add(subCodice);
					//throw new Exception("All attachments in message (found:"+numAttachmentsInMsg+") must be signed, but "+numAttachmentsSigned+" appear to be signed");
				}
				
				for (Reference reference: elementsToClean) {
					if(reference.getType()==ElementReference.TYPE_SIGNATURE || reference.getType()==AttachmentReference.TYPE_SIGN_ATTACHMENT) {
						if(!referenceMap.containsKey(reference) || !referenceMap.get(reference)) {
							SubErrorCodeSecurity subCodice = new SubErrorCodeSecurity();
							subCodice.setEncrypt(false);
							subCodice.setMsgErrore("Found signature part("+ reference+") not expected");
							if(reference.getType()==ElementReference.TYPE_SIGNATURE){
								ElementReference elReference = (ElementReference) reference;
								subCodice.setTipo("Signature"); // Non esiste Content/Element
								subCodice.setNamespace(elReference.getElement().getNamespaceURI());
								subCodice.setName(elReference.getElement().getLocalName());
							}
							else{
								subCodice.setTipo(SecurityConstants.SIGNATURE_PART_CONTENT);
								subCodice.setNamespace(SecurityConstants.ENCRYPTION_NAMESPACE_ATTACH);
								subCodice.setName(reference.getReference());
							}
							codiciErrore.add(subCodice);						
							//throw new Exception("Found signature part("+ reference+") not expected");
						}
					}
				}
			}
			
			return notResolvedMap;
			
		}catch(Exception e){
			throw new SecurityException(e.getMessage(),e);
		}
	}
	
}