HSMManager.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.hsm;

import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.certificate.KeyStore;
import org.openspcoop2.utils.properties.PropertiesReader;
import org.slf4j.Logger;

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

	private static HSMManager staticInstance;
	public static synchronized void init(File f, boolean throwNotExists, Logger log, boolean accessKeystore) throws UtilsException {
		if(staticInstance==null) {
			staticInstance = new HSMManager(f, throwNotExists, log, accessKeystore);
		}
	}
	public static HSMManager getInstance() {
		// spotbugs warning 'SING_SINGLETON_GETTER_NOT_SYNCHRONIZED': l'istanza viene creata allo startup
		if (staticInstance == null) {
	        synchronized (HSMManager.class) {
	            if (staticInstance == null) {
	                return null;
	            }
	        }
	    }
		return staticInstance;
	}
	
	/*
	 * Consente di inizializzare una serie di keystore hardware
	 * 
	 * La configurazione di ogni keystore deve essere definita nel file hsm.properties fornito come argomento dove la sintassi utilizzabile è la seguente
	 * 
	 * hsm.<idKeystore>.provider: [required su nodo run] classe del provider che deve essere stata registrata in JVM/conf/security/java.security o andrà aggiunta dinamicamente tramite opzione successiva
	 * hsm.<idKeystore>.provider.add: [optional, default false] indica se il provider fornito deve essere aggiunto (se non già presente)
	 * hsm.<idKeystore>.provider.configFile: [optional] se fornito verrà utilizzato per configurare il provider tramite l'istruzione 'configure(configFile)'
	 * hsm.<idKeystore>.provider.config: [optional] se fornito verrà utilizzato per configurare il provider tramite l'istruzione 'configure(config)'
	 * hsm.<idKeystore>.pin: [required su nodo run] pin per accedere al keystore
	 * hsm.<idKeystore>.keystoreType.label: [required su nodo run] label associata al keystore e visualizzata nelle console
	 * hsm.<idKeystore>.keystoreType: [required su nodo run] tipo associato al keystore ed utilizzato per istanziarlo tramite l'istruzione 'KeyStore.getInstance(keystoreType, provider)'
	 * hsm.<idKeystore>.usableAsTrustStore: [optional, default false] indica se il keystore è utilizzabile anche come truststore di certificati
	 * hsm.<idKeystore>.usableAsSecretKeyStore: [optional, default false] indica se il keystore è utilizzabile anche come repository di chiavi segrete
	 * 
	 * Un disponibile in HSM.example
	 * 
	 **/
	
	private HashMap<String, HSMKeystore> hsmKeystoreMapIDtoConfig = new HashMap<>();
	
	private HashMap<String, String> hsmKeystoreMapKeystoreTypeLabelToID = new HashMap<>();
	
	private HSMManager(File f, boolean throwNotExists, Logger log, boolean accessKeystore) throws UtilsException {
		String prefixFile = "File '"+f.getAbsolutePath()+"'";
		if(!f.exists()) {
			if(throwNotExists) {
				throw new UtilsException(prefixFile+" not exists");
			}
		}
		else {
			if(!f.canRead()) {
				throw new UtilsException(prefixFile+" cannot read");
			}
			Properties p = new Properties();
			try {
				try(FileInputStream fin = new FileInputStream(f)){
					p.load(fin);
				}
			}catch(Exception t) {
				throw new UtilsException(prefixFile+"; initialize error: "+t.getMessage(),t);
			}
			init(p, log, accessKeystore);
		}
	}
	/**private HSMManager(Properties p, Logger log, boolean accessKeystore) throws UtilsException {
		init(p, log, accessKeystore);
	}*/
	private void init(Properties p, Logger log, boolean accessKeystore) throws UtilsException {
		
		List<String> idKeystore = new ArrayList<>();
		
		if(p!=null && !p.isEmpty()) {
			init(p, idKeystore);
		}
		
		if(!idKeystore.isEmpty()) {
			for (String idK : idKeystore) {
				init(p, log, accessKeystore, idK);		
			}
		}
		else {
			log.warn("La configurazione fornita per HSM non contiene alcun keystore");
		}
	}
	private void init(Properties p, List<String> idKeystore) {
		Enumeration<?> enKeys = p.keys();
		while (enKeys.hasMoreElements()) {
			Object object = enKeys.nextElement();
			if(object instanceof String) {
				String key = (String) object;
				init(key, idKeystore);	
			}
		}
	}
	private void init(String key, List<String> idKeystore) {
		if(key.startsWith(HSMCostanti.PROPERTY_PREFIX) && key.length()>(HSMCostanti.PROPERTY_PREFIX.length())) {
			String tmp = key.substring(HSMCostanti.PROPERTY_PREFIX.length());
			if(tmp!=null && tmp.contains(".")) {
				int indeoOf = tmp.indexOf(".");
				if(indeoOf>0) {
					String idK = tmp.substring(0,indeoOf);
					if(!idKeystore.contains(idK)) {
						idKeystore.add(idK);
					}
				}
			}
		}
	}
	private void init(Properties p, Logger log, boolean accessKeystore, String idK) throws UtilsException {
		String prefix = HSMCostanti.PROPERTY_PREFIX + idK + ".";
		PropertiesReader pReader = new PropertiesReader(p, true);
		Properties pKeystore = pReader.readProperties_convertEnvProperties(prefix);
		HSMKeystore hsmKeystore = new HSMKeystore(idK, pKeystore, log, accessKeystore);
		
		boolean alreadyExists = false;
		for (String type : this.hsmKeystoreMapKeystoreTypeLabelToID.keySet()) {
			if(hsmKeystore.getKeystoreTypeLabel().equalsIgnoreCase(type)) {
				alreadyExists = true;
			}
		}
		if(alreadyExists) {
			throw new UtilsException("Same keystore type label found for keystore '"+this.hsmKeystoreMapKeystoreTypeLabelToID.get(hsmKeystore.getKeystoreTypeLabel())+"' e '"+idK+"'");
		}
		this.hsmKeystoreMapKeystoreTypeLabelToID.put(hsmKeystore.getKeystoreTypeLabel(), idK);
		
		this.hsmKeystoreMapIDtoConfig.put(idK, hsmKeystore);
		String d = "HSMKeystore "+idK+" registrato (keystoreType:"+hsmKeystore.getKeystoreTypeLabel()+")";
		log.info(d);	
	}
	
	public void providerInit(Logger log, boolean uniqueProviderInstance) throws UtilsException {
		if(this.hsmKeystoreMapIDtoConfig!=null && !this.hsmKeystoreMapIDtoConfig.isEmpty()) {
			for (Map.Entry<String,HSMKeystore> entry : this.hsmKeystoreMapIDtoConfig.entrySet()) {
				String idK = entry.getKey();
				HSMKeystore hsmKeystore = this.hsmKeystoreMapIDtoConfig.get(idK);
				hsmKeystore.init(log, uniqueProviderInstance);
			}
		}
	}
	
	private HSMKeystore getHSMKeystore(String keystoreTypeLabel) throws UtilsException {
		if(!this.hsmKeystoreMapKeystoreTypeLabelToID.containsKey(keystoreTypeLabel)) {
			throw new UtilsException("Keystore type '"+keystoreTypeLabel+"' unknown");
		}
		String idK = this.hsmKeystoreMapKeystoreTypeLabelToID.get(keystoreTypeLabel);
		if(!this.hsmKeystoreMapIDtoConfig.containsKey(idK)) {
			throw new UtilsException("Keystore config for type '"+keystoreTypeLabel+"' unknown ? (id:"+idK+")");
		}
		return this.hsmKeystoreMapIDtoConfig.get(idK);
	}
	
	public KeyStore getKeystore(String keystoreTypeLabel) throws UtilsException {
		HSMKeystore hsmKeystore = getHSMKeystore(keystoreTypeLabel);
		return hsmKeystore.getInstance();
	}
	
	public boolean isUsableAsTrustStore(String keystoreTypeLabel) throws UtilsException {
		HSMKeystore hsmKeystore = getHSMKeystore(keystoreTypeLabel);
		return hsmKeystore.isUsableAsTrustStore();
	}
	
	public boolean isUsableAsSecretKeyStore(String keystoreTypeLabel) throws UtilsException {
		HSMKeystore hsmKeystore = getHSMKeystore(keystoreTypeLabel);
		return hsmKeystore.isUsableAsSecretKeyStore();
	}
	
	public List<String> getKeystoreTypes() {
		List<String> l = new ArrayList<>();
		if(!this.hsmKeystoreMapKeystoreTypeLabelToID.isEmpty()) {
			for (String type : this.hsmKeystoreMapKeystoreTypeLabelToID.keySet()) {
				l.add(type);
			}
		}
		return l;
	}
	
	public boolean existsKeystoreType(String keystoreTypeLabel) {
		if(keystoreTypeLabel==null) {
			return false;
		}
		for (String type : this.hsmKeystoreMapKeystoreTypeLabelToID.keySet()) {
			if(keystoreTypeLabel.equalsIgnoreCase(type)) {
				return true;
			}
		}
		return false;
	}
}