CertificateUtils.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.utils.certificate;

import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.security.auth.x500.X500Principal;

import org.apache.xml.security.utils.RFC2253Parser;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.UtilsMultiException;
import org.openspcoop2.utils.io.Base64Utilities;
import org.openspcoop2.utils.io.HexBinaryUtilities;
import org.openspcoop2.utils.resources.Charset;
import org.slf4j.Logger;
import org.springframework.web.util.UriUtils;

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

	public static void printCertificate(StringBuilder bf,List<java.security.cert.X509Certificate> certs){
		printCertificate(bf, certs, false);
	}
	public static void printCertificate(StringBuilder bf,List<java.security.cert.X509Certificate> certs, boolean addPrefix){
		if(!certs.isEmpty()) {
			java.security.cert.X509Certificate[] certsArray = certs.toArray(new java.security.cert.X509Certificate[1]);
			printCertificate(bf, certsArray, addPrefix);
		}
		else {
			bf.append("X509Certificates: "+0+"\n");
		}
	}
	
	public static void printCertificate(StringBuilder bf,java.security.cert.X509Certificate[] certs){
		printCertificate(bf, certs, false);
	}
	public static void printCertificate(StringBuilder bf,java.security.cert.X509Certificate[] certs, boolean addPrefix){
		bf.append("X509Certificates: "+certs.length+"\n");
		for (int i = 0; i < certs.length; i++) {
			java.security.cert.X509Certificate cert = certs[i];
			printCertificate(bf, cert, i+"", addPrefix);
		}
	}
	
	public static void printCertificate(StringBuilder bf,java.security.cert.X509Certificate cert, String name){
		printCertificate(bf, cert, name, false);
	}
	public static void printCertificate(StringBuilder bf,java.security.cert.X509Certificate cert, String name, boolean addPrefix){
		String prefix = "";
		if(addPrefix) {
			prefix = "Cert["+name+"].";
		}
		bf.append("#### X509Certificate["+name+"]\n");
		bf.append("\t"+prefix+"toString()="+cert.toString()+"\n");
		bf.append("\t"+prefix+"getType()="+cert.getType()+"\n");
		bf.append("\t"+prefix+"getVersion()="+cert.getVersion()+"\n");
		
		if(cert.getIssuerDN()!=null){
			bf.append("\t"+prefix+"cert.getIssuerDN().toString()="+cert.getIssuerDN().toString()+"\n");
			bf.append("\t"+prefix+"cert.getIssuerDN().getName()="+cert.getIssuerDN().getName()+"\n");
		}
		else{
			bf.append("\t"+prefix+"cert.getIssuerDN() is null"+"\n");
		}
		
		if(cert.getIssuerX500Principal()!=null){
			bf.append("\t"+prefix+"getIssuerX500Principal().toString()="+cert.getIssuerX500Principal().toString()+"\n");
			bf.append("\t"+prefix+"getIssuerX500Principal().getName()="+cert.getIssuerX500Principal().getName()+"\n");
			bf.append("\t"+prefix+"getIssuerX500Principal().getName(X500Principal.CANONICAL)="+cert.getIssuerX500Principal().getName(X500Principal.CANONICAL)+"\n");
			bf.append("\t"+prefix+"getIssuerX500Principal().getName(X500Principal.RFC1779)="+cert.getIssuerX500Principal().getName(X500Principal.RFC1779)+"\n");
			bf.append("\t"+prefix+"getIssuerX500Principal().getName(X500Principal.RFC2253)="+cert.getIssuerX500Principal().getName(X500Principal.RFC2253)+"\n");
			/**	Map<String,String> oidMapCanonical = new HashMap<>();
				bf.append("\t"+prefix+"getIssuerX500Principal().getName(X500Principal.CANONICAL,oidMapCanonical)="+
						cert.getIssuerX500Principal().getName(X500Principal.CANONICAL,oidMapCanonical));
				if(oidMapCanonical!=null && oidMapCanonical.size()>0){
					Iterator<String> it = oidMapCanonical.keySet().iterator();
					while (it.hasNext()) {
						String key = (String) it.next();
						String value = oidMapCanonical.get(key);
						bf.append("\t"+prefix+"getIssuerX500Principal() ["+key+"]=["+value+"]"+"\n");
					}
				}*/
		}
		else{
			bf.append("\t"+prefix+"cert.getIssuerX500Principal() is null"+"\n");
		}
		
		if(cert.getSubjectDN()!=null){
			bf.append("\t"+prefix+"getSubjectDN().toString()="+cert.getSubjectDN().toString()+"\n");
			bf.append("\t"+prefix+"getSubjectDN().getName()="+cert.getSubjectDN().getName()+"\n");
		}
		else{
			bf.append("\t"+prefix+"cert.getSubjectDN() is null"+"\n");
		}
		
		bf.append("\t"+prefix+"getSerialNumber()="+cert.getSerialNumber()+"\n");
		bf.append("\t"+prefix+"getNotAfter()="+cert.getNotAfter()+"\n");
		bf.append("\t"+prefix+"getNotBefore()="+cert.getNotBefore()+"\n");
		
		if(cert.getSubjectX500Principal()!=null){
			bf.append("\t"+prefix+"getSubjectX500Principal().toString()="+cert.getSubjectX500Principal().toString()+"\n");
			bf.append("\t"+prefix+"getSubjectX500Principal().getName()="+cert.getSubjectX500Principal().getName()+"\n");
			bf.append("\t"+prefix+"getSubjectX500Principal().getName(X500Principal.CANONICAL)="+cert.getSubjectX500Principal().getName(X500Principal.CANONICAL)+"\n");
			bf.append("\t"+prefix+"getSubjectX500Principal().getName(X500Principal.RFC1779)="+cert.getSubjectX500Principal().getName(X500Principal.RFC1779)+"\n");
			bf.append("\t"+prefix+"getSubjectX500Principal().getName(X500Principal.RFC2253)="+cert.getSubjectX500Principal().getName(X500Principal.RFC2253)+"\n");
			/**	Map<String,String> oidMapCanonical = new HashMap<>();
				bf.append("\t"+prefix+"getSubjectX500Principal().getName(X500Principal.CANONICAL,oidMapCanonical)="+
						cert.getSubjectX500Principal().getName(X500Principal.CANONICAL,oidMapCanonical));
				if(oidMapCanonical!=null && oidMapCanonical.size()>0){
					Iterator<String> it = oidMapCanonical.keySet().iterator();
					while (it.hasNext()) {
						String key = (String) it.next();
						String value = oidMapCanonical.get(key);
						bf.append("\t"+prefix+"getSubjectX500Principal() ["+key+"]=["+value+"]"+"\n");
					}
				}*/
		}
		else{
			bf.append("\t"+prefix+"cert.getSubjectX500Principal() is null"+"\n");
		}
	}
	
	
	
	
	
	
	/* UTILITY SSL */
	
	private static final boolean TRIM_VALUE_BEFORE_SAVE_DB = true;
	
	private static void debug(Logger log, String msg) {
		if(log!=null) {
			log.debug(msg);
		}
	}
	private static String getAnalisiTypePrefixString(String principal, PrincipalType type) {
		return "("+principal+") Analisi "+type+" ";
	}
	
	public static boolean sslVerify(String principalPresenteNellaConfigurazione, String principalArrivatoConnessioneSSL, PrincipalType type, Logger log) throws UtilsException{

		if(log!=null) {
			debug(log, "SSL VERIFY CONF["+principalPresenteNellaConfigurazione+"] SSL["+principalArrivatoConnessioneSSL+"]");
		}
		/** System.out.println("SSL VERIFY CONF["+principalPresenteNellaConfigurazione+"] SSL["+principalArrivatoConnessioneSSL+"]"); */

		// Costruzione key=value
		Map<String, List<String>> hashPrincipalArrivatoConnessioneSSL = CertificateUtils.getPrincipalIntoMap(principalArrivatoConnessioneSSL, type);
		Map<String, List<String>> hashPrincipalPresenteNellaConfigurazione = CertificateUtils.getPrincipalIntoMap(principalPresenteNellaConfigurazione, type);

		if(!sslVerifyCheckSize(principalPresenteNellaConfigurazione, principalArrivatoConnessioneSSL, type, 
				hashPrincipalArrivatoConnessioneSSL, hashPrincipalPresenteNellaConfigurazione, log)) {
			return false;
		}

		for (Map.Entry<String,List<String>> entry : hashPrincipalArrivatoConnessioneSSL.entrySet()) {

			String key = entry.getKey();
			
			if(!hashPrincipalPresenteNellaConfigurazione.containsKey(key)){
				/** System.out.println("KEY ["+key+"] non presente"); */
				if(log!=null) {
					List<String> lKeys = new ArrayList<>();
					lKeys.addAll(hashPrincipalPresenteNellaConfigurazione.keySet());
					debug(log, "sslVerify key["+key+"] non trovata in "+type+" "+"Configurazione["+principalPresenteNellaConfigurazione+"]"+", key riscontrate: "+
							lKeys);
				}
				return false;
			}

			// Prendo valori
			List<String> connessioneSSLValueList = hashPrincipalArrivatoConnessioneSSL.get(key);
			List<String> configurazioneInternaValueList = hashPrincipalPresenteNellaConfigurazione.get(key);
			if(connessioneSSLValueList.size() != configurazioneInternaValueList.size()){
				/** System.out.println("LUNGHEZZA DIVERSA KEY ["+key+"]"); */
				if(log!=null) {
					debug(log, "sslVerify "+type+" key["+key+"] trovata in Configurazione["+principalPresenteNellaConfigurazione+"]("+configurazioneInternaValueList.size()+
						") SSL["+principalArrivatoConnessioneSSL+"]("+connessioneSSLValueList.size()+"): lunghezza differente");
				}
				return false;
			}
			
			// Ordino Valori
			Collections.sort(connessioneSSLValueList);
			Collections.sort(configurazioneInternaValueList);
			
			// confronto valori
			if(!sslVerifyCheckValues(key, type, connessioneSSLValueList, configurazioneInternaValueList, log)) {
				return false;
			}
			
		}

		/** System.out.println("SSL RETURN TRUE"); */
		return true;

	}
	private static boolean sslVerifyCheckSize(String principalPresenteNellaConfigurazione, String principalArrivatoConnessioneSSL, PrincipalType type, 
			Map<String, List<String>> hashPrincipalArrivatoConnessioneSSL, Map<String, List<String>> hashPrincipalPresenteNellaConfigurazione, Logger log) {
		if(hashPrincipalArrivatoConnessioneSSL.size() != hashPrincipalPresenteNellaConfigurazione.size()){
			/** System.out.println("LUNGHEZZA DIVERSA"); */
			if(log!=null) {
				debug(log, "sslVerify "+type+" "+"Configurazione["+principalPresenteNellaConfigurazione+"]"+"("+hashPrincipalPresenteNellaConfigurazione.size()+
					") SSL["+principalArrivatoConnessioneSSL+"]("+hashPrincipalArrivatoConnessioneSSL.size()+"): lunghezza differente");
			}
			return false;
		}
		return true;
	}
	private static boolean sslVerifyCheckValues(String key, PrincipalType type, List<String> connessioneSSLValueList, List<String> configurazioneInternaValueList, Logger log) {
		for (int i = 0; i < connessioneSSLValueList.size(); i++) {
			String connessioneSSLValue = connessioneSSLValueList.get(i);
			String configurazioneInternaValue = configurazioneInternaValueList.get(i);
			
			// Normalizzo caratteri escape
			while(connessioneSSLValue.contains("\\/")){
				connessioneSSLValue = connessioneSSLValue.replace("\\/", "/");
			}
			while(connessioneSSLValue.contains("\\,")){
				connessioneSSLValue = connessioneSSLValue.replace("\\,", ",");
			}
			while(configurazioneInternaValue.contains("\\/")){
				configurazioneInternaValue = configurazioneInternaValue.replace("\\/", "/");
			}
			while(configurazioneInternaValue.contains("\\,")){
				configurazioneInternaValue = configurazioneInternaValue.replace("\\,", ",");
			}
			
			if(!connessioneSSLValue.equals(configurazioneInternaValue)){
				if(log!=null) {
					debug(log, "sslVerify key["+key+"] "+type+" Configurazione["+configurazioneInternaValue+"] SSL["+connessioneSSLValue+"] not match");
				}
				/** System.out.println("VALUE SSL["+connessioneSSLValue+"]=CONF["+configurazioneInternaValue+"] non match"); */
				return false;
			}
		}
		return true;
	}


	public static String formatPrincipal(String principal, PrincipalType type) throws UtilsException{

		/** System.out.println("PRIMA ["+principal+"]"); */
		
		// Autenticazione SSL deve essere LIKE
		Map<String, List<String>> hashPrincipal = CertificateUtils.getPrincipalIntoMap(principal, type);
		StringBuilder bf = new StringBuilder();
		bf.append("/");
		for (Map.Entry<String,List<String>> entry : hashPrincipal.entrySet()) {
			
			String key = entry.getKey();
			
			List<String> listValues = hashPrincipal.get(key);
			for (String value : listValues) {
				bf.append(CertificateUtils.formatKeyPrincipal(key));
				bf.append("=");
				bf.append(CertificateUtils.formatValuePrincipal(value));
				bf.append("/");
			}
			
		}
		/** System.out.println("DOPO ["+bf.toString()+"]"); */
		return bf.toString();
	}
	
	public static Map<String, String> formatPrincipalToMap(String principal, PrincipalType type) throws UtilsException{
		Map<String, String> returnMap = new HashMap<>();
		Map<String, List<String>> hashPrincipal = CertificateUtils.getPrincipalIntoMap(principal, type);
		for (Map.Entry<String,List<String>> entry : hashPrincipal.entrySet()) {
			
			String key = entry.getKey();
			
			List<String> listValues = hashPrincipal.get(key);
			for (String value : listValues) {
				String keyFormat = CertificateUtils.formatKeyPrincipal(key);
				String valueFormat = CertificateUtils.formatValuePrincipal(value);
				returnMap.put(keyFormat, valueFormat);
			}
			
		}
		return returnMap;
	}

	public static void validaPrincipal(String principalParam, PrincipalType type) throws UtilsException{
		
		/** System.out.println("PRIMA VALIDAZIONE ["+principalParam+"]"); */
		
		String principal = principalParam;
		UtilsException normalizedException = null;
		try{
			String tmp = normalizePrincipal(principalParam);
			principal = tmp;
		}catch(UtilsException e){
			/** System.out.println("ERRORE: "+e.getMessage());
			// non voglio rilanciare l'eccezione, verra' segnalata l'eccezione puntuale.
			// Se cosi' non fosse solo in fondo viene sollevata l'eccezione. */
			normalizedException = e;
		}
		
		/** System.out.println("DOPOP VALIDAZIONE ["+principal+"]"); */
		
		boolean commaFound = contains(principal, ",");
		boolean slashFound = contains(principal, "/");
		if(commaFound && slashFound){
			throw new UtilsException("("+principal+") Non possono coesistere i separatore \",\" e \"/\", solo uno dei due tipi deve essere utilizzato come delimitatore (usare eventualmente come carattere di escape '\\')");
		}
		if(!commaFound && !slashFound && !principal.contains("=")){
			throw new UtilsException("("+principal+") "+type+" non valido, nemmeno una coppia nome=valore trovata");
		}
		String [] valoriPrincipal = CertificateUtils.getValoriPrincipal(principal, type);
		validaPrincipal(valoriPrincipal, principal, type);
		
		if(normalizedException!=null){
			throw normalizedException;
		}
	}
	private static void validaPrincipal(String [] valoriPrincipal, String principal, PrincipalType type) throws UtilsException {
		boolean campoObbligatorioCN = false;
		boolean campoObbligatorioOU = false;
		boolean campoObbligatorioO = false;
		boolean campoObbligatorioL = false;
		boolean campoObbligatorioST = false;
		boolean campoObbligatorioC = false;
		boolean campoObbligatorioE = false;
		for(int i=0; i<valoriPrincipal.length; i++){
			
			String [] keyValue = getKeyValuePairEngine(valoriPrincipal[i], principal, type);

			if(keyValue[0].trim().contains(" ")){
				throw new UtilsException(getAnalisiTypePrefixString(principal, type)+"fallita: il campo ["+valoriPrincipal[i]+"] contiene spazi nella chiave identificativa ["+keyValue[0].trim()+"]");
			}
			
			if(CertificateUtils.formatKeyPrincipal(keyValue[0]).equalsIgnoreCase("CN")){
				campoObbligatorioCN = true;
			}
			else if(CertificateUtils.formatKeyPrincipal(keyValue[0]).equalsIgnoreCase("OU")){
				campoObbligatorioOU = true;
			}
			else if(CertificateUtils.formatKeyPrincipal(keyValue[0]).equalsIgnoreCase("O")){
				campoObbligatorioO = true;
			}
			else if(CertificateUtils.formatKeyPrincipal(keyValue[0]).equalsIgnoreCase("L")){
				campoObbligatorioL = true;
			}
			else if(CertificateUtils.formatKeyPrincipal(keyValue[0]).equalsIgnoreCase("ST")){
				campoObbligatorioST = true;
			}
			else if(CertificateUtils.formatKeyPrincipal(keyValue[0]).equalsIgnoreCase("C")){
				campoObbligatorioC = true;
			}
			else if(CertificateUtils.formatKeyPrincipal(keyValue[0]).equalsIgnoreCase("E")){
				campoObbligatorioE = true;
			}
		}
		if(!campoObbligatorioCN && !campoObbligatorioOU && !campoObbligatorioO && !campoObbligatorioL && !campoObbligatorioST && !campoObbligatorioC && !campoObbligatorioE){
			throw new UtilsException("("+principal+") Almeno un attributo di certificato tra 'CN', 'OU', 'O', 'L', 'ST', 'C' e 'E' deve essere valorizzato.");
		}
	}

	
	public static String normalizePrincipal(String principalParam) throws UtilsException{
		
		/*
		 *  The principal extract from class represents an X.500 Principal. 
		 *  X500Principals are represented by distinguished names such as "CN=Duke, OU=JavaSoft, O=Sun Microsystems, C=US".
		 *  This class can be instantiated by using a string representation of the distinguished name, or by using the ASN.1 DER encoded byte representation 
		 *  of the distinguished name. 
		 *  The current specification for the string representation of a distinguished name is defined in RFC 2253: 
		 *  Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names. 
		 *  This class, however, accepts string formats from both RFC 2253 and RFC 1779: A String Representation of Distinguished Names, 
		 *  and also recognizes attribute type keywords whose OIDs (Object Identifiers) are defined in RFC 3280:
		 *  Internet X.509 Public Key Infrastructure Certificate and CRL Profile. 
		 */
		
		try{
			// Normalizza da un eventuale formato human readable o da un formato RFC 1779 nel formato RFC 2253 gestito poi nel seguito del codice.
			// Alcuni esempi dove il carattere '/' viene usato internamente come valore del CN
			// Esempio di formato "human readable": /C=IT/ST=Italiy/OU=PROVA di Bari, di Como/CN=SPC/SOGGETTO
			// Esempio di formato "RFC 1779": CN=SPC/SOGGETTO, OU="PROVA di Bari, di Como", ST=Italiy, C=IT
			// Esempio di formato "RFC 2253": CN=SPC/SOGGETTO,OU=PROVA di Bari\, di Como,ST=Italiy,C=IT
			return RFC2253Parser.normalize(principalParam);
			/**String principal = RFC2253Parser.normalize(principalParam);
			System.out.println(" ORIGINALE ["+principalParam+"]  NORMALIZZATO ["+principal+"]");
			return principal;*/
		}catch(Exception e){
			throw new UtilsException("("+principalParam+") Normalizzazione RFC2253 non riuscita: "+e.getMessage(),e);
		}
	}

	public static String [] getValoriPrincipal(String principalParam, PrincipalType type) throws UtilsException{
		try{
			/** System.out.println("PRINCIPAL getValoriPrincipal["+principalParam+"]"); */
			String principal = normalizePrincipal(principalParam);
			/** System.out.println("PRINCIPAL dopo normalize getValoriPrincipal["+principal+"]"); */
			
			return getValoriPrincipalEngine(principal, type);
			
		}catch(Exception e){
			
			/** System.out.println("PRINCIPAL getValoriPrincipal["+principalParam+"] errore: "+e.getMessage()); */
			
			try{
			
				javax.naming.ldap.LdapName prova = new javax.naming.ldap.LdapName(principalParam);
				Enumeration<String> ens = prova.getAll();
				List<String> values = new ArrayList<>();
				while (ens.hasMoreElements()) {
					String name = ens.nextElement();
					values.add(name);
				}
				
				if(!values.isEmpty()){
					return values.toArray(new String[1]);
				}
				else{
					throw new UtilsException("Coppie nome/valore non trovate");
				}
				
			}catch(Exception e2Level){
				/**e2Level.printStackTrace(System.out); */
				throw new UtilsException("("+principalParam+") javax.naming.ldap.LdapName reader failed: "+e2Level.getMessage()+". \nFirst method error: "+e.getMessage(),e);
			}
				
		}
	}
		
	private static String [] getValoriPrincipalEngine(String principal, PrincipalType type) throws UtilsException{
			
		String [] valori;
		boolean commaFound = contains(principal, ",");
		boolean slashFound = contains(principal, "/");
		/**System.out.println("PRINCIPAL _getValoriPrincipal commaFound["+commaFound+"] slashFound["+slashFound+"]"); */
		if(commaFound){
			if(principal.startsWith(",")){
				principal = principal.substring(1);
			}
			if(principal.endsWith(",")){
				principal = principal.substring(0,principal.length()-1);
			}
			/**System.out.println("PRINCIPAL _getValoriPrincipal preSplit , ["+principal+"] ..");*/
			valori = Utilities.split(principal, ',');
		}else{
			valori = getValoriPrincipalEngine(principal, type, slashFound);
		}
		if(valori==null || valori.length<1){
			throw new UtilsException(getAnalisiTypePrefixString(principal, type)+"interno alla configurazione di OpenSPCoop non riuscita: null??");
		}
		
		// validazione
		for(int i=0; i<valori.length; i++){
			getKeyValuePairEngine(valori[i], principal, type);
		}
				
		return valori;
	}
	private static String [] getValoriPrincipalEngine(String principal, PrincipalType type, boolean slashFound) throws UtilsException {
		/**System.out.println("PRINCIPAL _getValoriPrincipal comma not found ["+principal+"] .."); */
		String [] valori = null;
		if(!slashFound){
			/** System.out.println("PRINCIPAL _getValoriPrincipal slash not found ["+principal+"] .."); */
			int indexOf = principal.indexOf("=");
			if(indexOf<=0){
				throw new UtilsException("("+principal+") Separatore validi per il "+type+" interno alla configurazione di OpenSPCoop non trovati:  \",\" o \"/\" e carattere \"=\" non presente");
			}
			if(principal.indexOf("=",indexOf+1)>=0){
				throw new UtilsException("("+principal+") Separatore validi per il "+type+" interno alla configurazione di OpenSPCoop non trovati:  \",\" o \"/\"");
			}
			valori =  new String[1];
			valori[0] = principal;
		}else{
			valori = getValoriSlashPrincipalEngine(principal);
		}
		return valori;
	}
	private static String [] getValoriSlashPrincipalEngine(String principal) throws UtilsException {
		if(principal.startsWith("/")){
			principal = principal.substring(1);
		}
		if(principal.endsWith("/")){
			principal = principal.substring(0,principal.length()-1);
		}
		/**System.out.println("PRINCIPAL _getValoriPrincipal preSplit / ["+principal+"] .."); */
		String [] tmpValori = Utilities.split(principal, '/');
		
		// Bug Fix OP-670 certificato formato come:
		// C=IT/ST= /O=Esempio di Agenzia/OU=Servizi Informatici/CN=Ministero dell'Interno/prova/23234234554/DEMO
		List<String> normalize = new ArrayList<>();
		StringBuilder bf = new StringBuilder();
		for (String tmp : tmpValori) {
			if(tmp.contains("=")) {
				if(bf.length()>0) {
					normalize.add(bf.toString());
					bf.delete(0, bf.length());
				}
				bf.append(tmp);
			}
			else {
				bf.append("/").append(tmp);
			}
		}
		if(bf.length()>0) {
			normalize.add(bf.toString());
			bf.delete(0, bf.length());
		}
		return normalize.toArray(new String[1]);
	}
	
	public static Map<String, List<String>> getPrincipalIntoMap(String principal, PrincipalType type) throws UtilsException{
		Map<String, List<String>> hashPrincipal = new HashMap<>();
		String [] valoriPrincipal = CertificateUtils.getValoriPrincipal(principal, type);
		for(int i=0; i<valoriPrincipal.length; i++){
			
			// override eccezione in caso '=' non rpesente
			if(!valoriPrincipal[i].contains("=")){
				String fallita = "fallita"+": ["+valoriPrincipal[i]+"] ";
				throw new UtilsException(getAnalisiTypePrefixString(principal, type)+fallita+"non separata dal carattere \"=\"");
			}
			String [] keyValue = getKeyValuePairEngine(valoriPrincipal[i], principal, type);
			
			/**System.out.println("CONF INTERNA ["+Utilities.formatKeyPrincipal(keyValue[0])+"] ["+Utilities.formatValuePrincipal(keyValue[1])+"]");*/
			String formatKey = CertificateUtils.formatKeyPrincipal(keyValue[0]);
			String formatValue = CertificateUtils.formatValuePrincipal(keyValue[1]);
			List<String> listValue = null;
			if(hashPrincipal.containsKey(formatKey)) {
				listValue = hashPrincipal.get(formatKey);
			}
			else {
				listValue = new ArrayList<>();
				hashPrincipal.put(formatKey, listValue);
			}
			listValue.add(formatValue);
		}
		return hashPrincipal;
	}

	public static String formatKeyPrincipal(String keyPrincipal){
		return keyPrincipal.trim().toLowerCase();
	}
	public static String formatValuePrincipal(String valuePrincipal){
		// siccome uso il carattere '/' come separatore, un eventuale '/' deve essere escaped.
		StringBuilder bf = new StringBuilder();
		for (int i = 0; i < valuePrincipal.length(); i++) {
			if(valuePrincipal.charAt(i)=='/'){
				// escape
				if(i==0){
					bf.append('\\');
				}
				else{
					// verifico se non ho gia' effettuato l'escape
					if(valuePrincipal.charAt((i-1))!='\\'){
						bf.append('\\');
					}
				}
			}
			bf.append(valuePrincipal.charAt(i));
		}
		String value = bf.toString();
		if(TRIM_VALUE_BEFORE_SAVE_DB) {
			value = value.trim();
		}
		return value;
	}

	private static String[] getKeyValuePairEngine(String keyValue, String principal, PrincipalType type) throws UtilsException {
		
		if(!keyValue.contains("=")){
			throw new UtilsException(getAnalisiTypePrefixString(principal, type)+"fallita: ["+keyValue+"] non separata dal carattere \"=\". Verificare che non esistano coppie che possiedono valori che contengono il carattere separatore (usare eventualmente come carattere di escape '\\')");
		}
		
		// Bug Fix OP-664
		// Per non bruciare gli spazi presenti nei valori della chiave che sono ammessi. Ad esempio e' capitato un "C=IT,ST= ,CN=XXESEMPIOXX"
		/**String [] keyValue = keyValue.trim().split("=");*/
		String [] keyValueReturn = keyValue.split("="); // lo spazio è ammesso nel valore.		
		if(keyValueReturn.length<2){
			if(TRIM_VALUE_BEFORE_SAVE_DB && keyValueReturn.length==1) {
				// Serve per i valori inseriti che poi vengono comunque normalizzati con il trim. Se il valore era ' ' viene normalizzato a ''
				// una volta riletto poi si ottiene l'errore.
				// Che ci sia l'uguale e' garantito dai controlli sopra. In pratica keyValue.endsWith("=")
				String key = keyValueReturn[0];
				keyValueReturn = new String[2];
				keyValueReturn[0] = key;
				keyValueReturn[1] = "";
			}
			else {
				throw new UtilsException(getAnalisiTypePrefixString(principal, type)+"fallita: ["+keyValue+"] non contiene un valore? Verificare che non esistano coppie che possiedono valori che contengono il carattere separatore (usare eventualmente come carattere di escape '\\')");
			}
		}
		
		// Lo spazio e' ammesso nel valore, ma non nella chiave
		keyValueReturn[0] = keyValueReturn[0].trim();
		/**System.out.println("KEY["+keyValueReturn[0]+"]=VALUE["+keyValueReturn[1]+"]");*/
		
		// Questo controllo non deve essere fatto poiche' il valore puo' contenere il '='.
/**		if(keyValue.length!=2){
//			throw new UtilsException(getAnalisiTypePrefixString(principal, type)+"fallita: ["+keyValue+"] contiene piu' di un carattere \"=\"");
//		}*/
		if(keyValueReturn.length==2) {
			return keyValueReturn;
		}
		else {
			String[] keyValueReturnNormalized = new String[2];
			keyValueReturnNormalized[0] = keyValueReturn[0];
			keyValueReturnNormalized[1] = extractValueFromKeyPairEngine(keyValue);
			return keyValueReturnNormalized;
		}
		
	}
	private static String extractValueFromKeyPairEngine(String keyValue) throws UtilsException {
		// Questo metodo server poiche' il valore puo' contenere il '=' ed il carattere ' ' anche all'inizio o alla fine.
		// Quindi uno split con il carattere '=' non puo' essere usato.
		// Deve quindi essere estratto dopo il primo uguale
		int indexOf = keyValue.indexOf("=");
		if(indexOf<=0) {
			throw new UtilsException("Carattere '=' non presente in ["+keyValue+"]");
		}
		return keyValue.substring(indexOf+1);
		/**String valoreEstratto = keyValue.substring(indexOf+1);
		System.out.println("VALORE ESTRATTO ["+valoreEstratto+"]");
		return valoreEstratto;*/
	}
	
	
	private static boolean contains(String value,String separator){
		int indexOf = value.indexOf(separator);
		boolean found = false;
		if(indexOf==0){
			found = true;
		}
		else{
			boolean itera = true;
			while(indexOf>0 && itera){
				char precedente = value.charAt(indexOf-1);
				if(precedente == '\\'){
					if(indexOf+1>value.length()){
						itera=false;
					}
					else{
						indexOf = value.indexOf(separator,indexOf+1);
					}
				}
				else{
					found = true;
					itera=false;
				}
			}
		}
		return found;
	}
	
	public static Certificate readCertificate(CertificateDecodeConfig config, String certificateParam) throws UtilsException {
		return readCertificateEngine(config, certificateParam, Charset.UTF_8.getValue());
	}
	public static Certificate readCertificate(CertificateDecodeConfig config, String certificateParam, String charset) throws UtilsException {
		return readCertificateEngine(config, certificateParam, charset);
	}
	
	private static Certificate readCertificateEngine(CertificateDecodeConfig config, String certificateParam, String charset) throws UtilsException {
		if(config.isUrlDecodeOrBase64Decode() || config.isUrlDecodeOrBase64DecodeOrHexDecode()) {
			
			Throwable tUrlDecode = null;
			try {
				config.setUrlDecode(true);
				config.setBase64Decode(false);
				config.setHexDecode(false);
				return readCertificateEngineByConfig(config, certificateParam, charset);
			}catch(Exception t) {
				tUrlDecode = t;
			}
			
			Throwable tBase64Decode = null;
			try {
				config.setUrlDecode(false);
				config.setBase64Decode(true);
				config.setHexDecode(false);
				return readCertificateEngineByConfig(config, certificateParam, charset);
			}catch(Exception t) {
				tBase64Decode = t;
			}
			
			Throwable tHexDecode = null;
			if(config.isUrlDecodeOrBase64DecodeOrHexDecode()) {
				try {
					config.setUrlDecode(false);
					config.setBase64Decode(false);
					config.setHexDecode(true);
					return readCertificateEngineByConfig(config, certificateParam, charset);
				}catch(Exception t) {
					tHexDecode = t;
				}
			}
			
			UtilsMultiException uMulti = config.isUrlDecodeOrBase64DecodeOrHexDecode() ?
				new UtilsMultiException("Decodifica non riuscita", tUrlDecode, tBase64Decode, tHexDecode)	
					:
				new UtilsMultiException("Decodifica non riuscita", tUrlDecode, tBase64Decode);
			throw new UtilsException(uMulti.getMessage(),uMulti);
		}
		else {
			return readCertificateEngineByConfig(config, certificateParam, charset);
		}
	}
	private static Certificate readCertificateEngineByConfig(CertificateDecodeConfig config, String certificateParam, String charset) throws UtilsException {
		
		if(certificateParam==null || "".equals(certificateParam)){
			throw new UtilsException("Certificate non fornito");
		}
		
		try {
		
			String certificate = certificateParam;
			
			if(config.isUrlDecode()) {
				certificate = UriUtils.decode(certificate, charset);
			}
			
			boolean forceEnrichPEMBeginEnd = false;
			if(config.isReplace()) {
				StringBuilder sbNewCertificate = new StringBuilder();
				forceEnrichPEMBeginEnd = replaceCharacters(config, certificate, sbNewCertificate);
				certificate = sbNewCertificate.toString();
			}
			
			if(config.isEnrichPEMBeginEnd() || forceEnrichPEMBeginEnd) {
				certificate = addPEMDeclaration(certificate, forceEnrichPEMBeginEnd);
			}
			
			byte [] certBytes = null;
			if(config.isBase64Decode()) {
				certBytes = Base64Utilities.decode(certificate);
			}
			else if(config.isHexDecode()) {
				certBytes = HexBinaryUtilities.decode(certificate);
			}
			else {
				certBytes = certificate.getBytes(charset);
			}
			
			// Per adesso l'utility di load gestisce solo il tipo DER. La decodifica in base64 è quindi essenziale, a meno che non sia un DER.
			
			return ArchiveLoader.load(certBytes);
			
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(), e);
		}
		
	} 
	
	private static boolean replaceCharacters(CertificateDecodeConfig config, String certificate, StringBuilder sbNewCertificate) {
		boolean forceEnrichPEMBeginEnd = false;
		// per evitare di sovrascrivere 'BEGIN' e 'END'
		if(certificate.startsWith(PEMReader.X509_BEGIN) && certificate.length()>PEMReader.X509_BEGIN.length()) {
			certificate = certificate.substring(PEMReader.X509_BEGIN.length());
			forceEnrichPEMBeginEnd = true;
		}
		if(certificate.endsWith(PEMReader.X509_END) && certificate.length()>PEMReader.X509_END.length()) {
			certificate = certificate.substring(0, certificate.length()-PEMReader.X509_END.length());
		}
		
		int index = 0; // per evitare bug di cicli infiniti
		while(certificate.contains(config.getReplaceSource()) && index<10000) {
			certificate = certificate.replace(config.getReplaceSource(), config.getReplaceDest());
			index++;
		}
		sbNewCertificate.append(certificate);
		return forceEnrichPEMBeginEnd;
	}
	
	private static String addPEMDeclaration(String certificate, boolean forceEnrichPEMBeginEnd) {
		if(!certificate.startsWith(PEMReader.X509_BEGIN)) {
			certificate = PEMReader.X509_BEGIN+ (forceEnrichPEMBeginEnd?"":"\n")+ certificate;
		}
		if(!certificate.endsWith(PEMReader.X509_END)) {
			certificate = certificate+ (forceEnrichPEMBeginEnd?"":"\n") +PEMReader.X509_END;
		}
		return certificate;
	}
	
	public static String toPEM(java.security.cert.X509Certificate cert) throws UtilsException {
		try {
			java.io.StringWriter sw = new java.io.StringWriter();
		    try (org.bouncycastle.openssl.jcajce.JcaPEMWriter pw = new org.bouncycastle.openssl.jcajce.JcaPEMWriter(sw)) {
		        pw.writeObject(cert);
		    }
		    sw.flush();
		    sw.close();
		    return sw.toString();
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(), e);
		}
	}
	public static String toPEM(PrivateKey privateKey) throws UtilsException {
		try {
			java.io.StringWriter sw = new java.io.StringWriter();
		    try (org.bouncycastle.openssl.jcajce.JcaPEMWriter pw = new org.bouncycastle.openssl.jcajce.JcaPEMWriter(sw)) {
		        pw.writeObject(privateKey);
		    }
		    sw.flush();
		    sw.close();
		    return sw.toString();
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(), e);
		}
	}
}