MerlinProvider.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.keystore;

import java.io.IOException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Properties;

import org.apache.wss4j.common.crypto.PasswordEncryptor;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.common.ext.WSSecurityException.ErrorCode;
import org.openspcoop2.core.config.MessageSecurity;
import org.openspcoop2.core.config.MessageSecurityFlow;
import org.openspcoop2.core.config.MessageSecurityFlowParameter;
import org.openspcoop2.protocol.sdk.state.RequestInfo;
import org.openspcoop2.security.keystore.cache.GestoreKeystoreCache;
import org.openspcoop2.utils.certificate.KeystoreType;
import org.openspcoop2.utils.certificate.byok.BYOKProvider;
import org.openspcoop2.utils.certificate.byok.BYOKRequestParams;

/**
 * Implementazione che estente l'implementazione di default e permette di caricare keystore utilizzando la cache o il binario passato direttamente.
 *
 * @author Poli Andrea (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class MerlinProvider extends org.apache.wss4j.common.crypto.Merlin {

	private static boolean useBouncyCastleProvider = false;
	public static boolean isUseBouncyCastleProvider() {
		return useBouncyCastleProvider;
	}
	public static void setUseBouncyCastleProvider(boolean useBouncyCastleProvider) {
		MerlinProvider.useBouncyCastleProvider = useBouncyCastleProvider;
	}
	
	public MerlinProvider() {
		super();
	}

	public MerlinProvider(Properties properties, ClassLoader loader, PasswordEncryptor passwordEncryptor)
			throws WSSecurityException, IOException {
		super(properties, loader, passwordEncryptor);
	}

	private org.openspcoop2.utils.certificate.KeyStore op2KeyStore;
	private org.openspcoop2.utils.certificate.KeyStore op2TrustStore;
	public org.openspcoop2.utils.certificate.KeyStore getOp2KeyStore() {
		return this.op2KeyStore;
	}
	public org.openspcoop2.utils.certificate.KeyStore getOp2TrustStore() {
		return this.op2TrustStore;
	}

	private Boolean useBouncyCastleProviderDirective = null;
	
	public static String readPrefix(Properties properties) {
		for (Object key : properties.keySet()) {
			if (key instanceof String) {
				String propKey = (String)key;
				if (propKey.startsWith(PREFIX)) {
					return PREFIX;
				} else if (propKey.startsWith(OLD_PREFIX)) {
					return OLD_PREFIX;
				}
			}
		}
		return PREFIX;
	}
	
	private static final String TRUST_STORE_REF = "truststore";
	private static final String KEY_STORE_REF = "keystore";
	private String getError(String tipoStore, String location) {
		if(location!=null) {
			return "Accesso al "+tipoStore+" '"+location+"' non riuscito";
		}
		else {
			return "Accesso al "+tipoStore+" non riuscito";
		}
	}
	
    public static final String SUFFIX_BYOK = ".byok";
	
    public static final String X509_CRL_FILE_VALIDATE_ONLY_END_ENTITY = X509_CRL_FILE + ".validateOnlyEndEntity";
    
    private static final String TRUE = "true";
    private static final String FALSE = "false";
    
	@Override
	public void loadProperties(Properties properties, ClassLoader loader, PasswordEncryptor passwordEncryptor)
			throws WSSecurityException, IOException {

		if (properties == null) {
			return;
		}
		this.properties = properties;
		this.passwordEncryptor = passwordEncryptor;

		String prefix = readPrefix(properties);

		
		//
		// Load the RequestInfo
		//
		RequestInfo requestInfo = null;
		Object requestInfoObject = properties.get(KeystoreConstants.PROPERTY_REQUEST_INFO);
		if(requestInfoObject instanceof RequestInfo) {
			requestInfo = (RequestInfo) requestInfoObject;
		}
		

		//
		// Load the KeyStore
		//
		String keyStoreLocation = properties.getProperty(prefix + KEYSTORE_FILE);
		if (keyStoreLocation == null) {
			keyStoreLocation = properties.getProperty(prefix + OLD_KEYSTORE_FILE);
		}
		Object keyStoreArchiveObject = properties.get(prefix + KeystoreConstants.KEYSTORE);
		byte [] keyStoreArchive = null;
		if(keyStoreArchiveObject instanceof byte[]) {
			keyStoreArchive = (byte[]) keyStoreArchiveObject;
		}
		
		String keyStorePassword = properties.getProperty(prefix + KEYSTORE_PASSWORD, "security");
		if (keyStorePassword != null) {
			keyStorePassword = keyStorePassword.trim();
			keyStorePassword = decryptPassword(keyStorePassword, passwordEncryptor);
		}
		String keyStoreType = properties.getProperty(prefix + KEYSTORE_TYPE, KeyStore.getDefaultType());
		if (keyStoreType != null) {
			keyStoreType = keyStoreType.trim();
		}
				
		if (keyStoreLocation != null) {
		
			String keyStoreByokPolicy = properties.getProperty(prefix + KEYSTORE_FILE+SUFFIX_BYOK);
			BYOKRequestParams byokParams = null;
			if (keyStoreByokPolicy == null) {
				keyStoreByokPolicy = properties.getProperty(prefix + OLD_KEYSTORE_FILE+SUFFIX_BYOK);
			}
			if (keyStoreByokPolicy != null) {
				keyStoreByokPolicy = keyStoreByokPolicy.trim();
				if(BYOKProvider.isPolicyDefined(keyStoreByokPolicy)){
					try {
						byokParams = BYOKProvider.getBYOKRequestParamsByUnwrapBYOKPolicy(keyStoreByokPolicy, 
								requestInfo!=null && requestInfo.getDynamicMap()!=null ? requestInfo.getDynamicMap() : new HashMap<>() );
					}catch(Exception e) {
						throw new IOException(e.getMessage(),e);
					}
				}
			}
			
			// rimuovo la proprietà per non farla trovare quando chiamo il super.loadProperties
			this.properties.remove(prefix + KEYSTORE_FILE);
			this.properties.remove(prefix + OLD_KEYSTORE_FILE);
		
			// il set 'privatePasswordSet' e' stato riportato poichè eliminando sopra le due proprietà, questo codice non verra utilizzato nel super.loadProperties
			String privatePasswd = properties.getProperty(prefix + KEYSTORE_PRIVATE_PASSWORD);
			if (privatePasswd != null) {
				this.privatePasswordSet = true;
			}
			
			
			keyStoreLocation = keyStoreLocation.trim();

			try {
				MerlinKeystore merlinKs = GestoreKeystoreCache.getMerlinKeystore(requestInfo, keyStoreLocation, keyStoreType, 
						keyStorePassword, byokParams);
				if(merlinKs==null) {
					throw new IOException(getError(KEY_STORE_REF,keyStoreLocation));
				}
				if(merlinKs.getKeyStore()==null) {
					throw new IOException(getError(KEY_STORE_REF,keyStoreLocation));
				}
				this.op2KeyStore = merlinKs.getKeyStore();
				this.keystore = this.op2KeyStore.getKeystore();
			}catch(Exception e) {
				throw new IOException("[Keystore-File] '"+keyStoreLocation+"' "+e.getMessage(),e);
			}

		}
		else if (keyStoreArchive != null) {
			try {
				MerlinKeystore merlinKs = GestoreKeystoreCache.getMerlinKeystore(requestInfo, keyStoreArchive, keyStoreType, 
						keyStorePassword);
				if(merlinKs==null) {
					throw new IOException(getError(KEY_STORE_REF,null));
				}
				if(merlinKs.getKeyStore()==null) {
					throw new IOException(getError(KEY_STORE_REF,null));
				}
				this.op2KeyStore = merlinKs.getKeyStore();
				this.keystore = this.op2KeyStore.getKeystore();
			}catch(Exception e) {
				throw new IOException("[Keystore-Archive] "+e.getMessage(),e);
			}
		}
		

		//
		// Load the TrustStore
		//
		
		String trustStoreLocation = properties.getProperty(prefix + TRUSTSTORE_FILE);
		Object trustStoreArchiveObject = properties.get(prefix + KeystoreConstants.TRUSTSTORE);
		byte [] trustStoreArchive = null;
		if(trustStoreArchiveObject instanceof byte[]) {
			trustStoreArchive = (byte[]) trustStoreArchiveObject;
		}
		
		String trustStorePassword = properties.getProperty(prefix + TRUSTSTORE_PASSWORD, "security");
		if (trustStorePassword != null) {
			trustStorePassword = trustStorePassword.trim();
			trustStorePassword = decryptPassword(trustStorePassword, passwordEncryptor);
		}
		String trustStoreType = properties.getProperty(prefix + TRUSTSTORE_TYPE, KeyStore.getDefaultType());
		if (trustStoreType != null) {
			trustStoreType = trustStoreType.trim();
		}
		
		if (trustStoreLocation != null) {
			
			// rimuovo la proprietà per non farla trovare quando chiamo il super.loadProperties
			this.properties.remove(prefix + TRUSTSTORE_FILE);
		
			// il set 'loadCACerts' e' stato riportato poichè eliminando sopra le due proprietà, questo codice non verra utilizzato nel super.loadProperties
			this.loadCACerts = false;
			
			trustStoreLocation = trustStoreLocation.trim();

			try {
				MerlinTruststore merlinTs = GestoreKeystoreCache.getMerlinTruststore(requestInfo, trustStoreLocation, trustStoreType, 
						trustStorePassword);
				if(merlinTs==null) {
					throw new IOException(getError(TRUST_STORE_REF,trustStoreLocation));
				}
				if(merlinTs.getTrustStore()==null) {
					throw new IOException(getError(TRUST_STORE_REF,trustStoreLocation));
				}
				this.op2TrustStore = merlinTs.getTrustStore();
				this.truststore = this.op2TrustStore.getKeystore();
			}catch(Exception e) {
				throw new IOException("[Truststore-File] '"+trustStoreLocation+"' "+e.getMessage(),e);
			}

		}
		else if (trustStoreArchive != null) {
			try {
				MerlinTruststore merlinTs = GestoreKeystoreCache.getMerlinTruststore(requestInfo, trustStoreArchive, trustStoreType, 
						trustStorePassword);
				if(merlinTs==null) {
					throw new IOException(getError(TRUST_STORE_REF,null));
				}
				if(merlinTs.getTrustStore()==null) {
					throw new IOException(getError(TRUST_STORE_REF,null));
				}
				this.op2TrustStore = merlinTs.getTrustStore();
				this.truststore = this.op2TrustStore.getKeystore();
			}catch(Exception e) {
				throw new IOException("[Truststore-Archive] "+e.getMessage(),e);
			}
		}
		
		if(this.truststore!=null) {
			
			// Devo evitare che venga eseguito il loadCerts quando invoco super.loadProperties
			
			properties.remove(prefix + LOAD_CA_CERTS);
			properties.setProperty(prefix + LOAD_CA_CERTS, FALSE);
			
		}
		


		//
		// Load the CRL file(s)
		//
		String crlLocations = properties.getProperty(prefix + X509_CRL_FILE);
		if (crlLocations != null) {
			
			// rimuovo la proprietà per non farla trovare quando chiamo il super.loadProperties
			this.properties.remove(prefix + X509_CRL_FILE);
			
			CRLCertstore crlCertstore = null;
			try {
				crlCertstore = GestoreKeystoreCache.getCRLCertstore(requestInfo, crlLocations);
				if(crlCertstore==null) {
					throw new IOException("Accesso alle crl '"+crlLocations+"' non riuscito");
				}
				this.crlCertStore = crlCertstore.getCertStore();
			}catch(Exception e) {
				throw new IOException("[CRLCertstore] "+e.getMessage(),e);
			}

			String useBouncyCastleProviderProperty = properties.getProperty(prefix + "useBouncyCastleProvider");
			if (useBouncyCastleProviderProperty != null) {
				if(TRUE.equalsIgnoreCase(useBouncyCastleProviderProperty.trim())) {
					this.useBouncyCastleProviderDirective = true;
				}
				else if(FALSE.equalsIgnoreCase(useBouncyCastleProviderProperty.trim())) {
					this.useBouncyCastleProviderDirective = false;
				}
			}
			
			String validateOnlyEndEntity = properties.getProperty(prefix + X509_CRL_FILE_VALIDATE_ONLY_END_ENTITY);
			if (validateOnlyEndEntity != null) {
				if(TRUE.equalsIgnoreCase(validateOnlyEndEntity.trim())) {
					this.setValidateOnlyEndEntity(true);
				}
				else if(FALSE.equalsIgnoreCase(validateOnlyEndEntity.trim())) {
					this.setValidateOnlyEndEntity(false);
				}
			}
			else if(crlCertstore!=null && crlCertstore.getWrappedCRLCertStore()!=null && crlCertstore.getWrappedCRLCertStore().countCrls()==1) {
				// per default si assume che venga fornito solo il file CRL del certificato finale
				this.setValidateOnlyEndEntity(true);
			}
			
		}



		//
		// Super
		//
		super.loadProperties(properties, loader, passwordEncryptor);
	}

	/**@Override
	public PrivateKey getPrivateKey(PublicKey publicKey, CallbackHandler callbackHandler) throws WSSecurityException {
		System.out.println("@@@ getPrivateKey PUBLIC KEY, CALLBACK");
		return super.getPrivateKey(publicKey, callbackHandler);
	}*/

	@Override
	public PrivateKey getPrivateKey(String alias, String password) throws WSSecurityException {
		/**System.out.println("@@@ getPrivateKey arg0["+alias+"] arg1["+password+"]");*/
		if(this.op2KeyStore!=null) {
			try {
				return this.op2KeyStore.getPrivateKey(alias, password);
			}catch(Exception e) {
				throw new WSSecurityException(ErrorCode.SECURITY_ERROR,e);
			}
		}
		return super.getPrivateKey(alias, password);
	}

	/**@Override
	public PrivateKey getPrivateKey(X509Certificate x509, CallbackHandler callbackHandler) throws WSSecurityException {
		System.out.println("@@@ getPrivateKey X509Certificatw, CALLBACK");
		return super.getPrivateKey(x509, callbackHandler);
	}*/
	
	@Override
	public String getCryptoProvider() {
		boolean useBC = (this.useBouncyCastleProviderDirective!=null && this.useBouncyCastleProviderDirective) 
				||
				(MerlinProvider.useBouncyCastleProvider);
		if(useBC) {
			if(this.truststore!=null && this.truststore.getType()!=null && this.truststore.getType().equalsIgnoreCase(KeystoreType.PKCS11.getNome())) {
				useBC=false;
			}
			if(this.keystore!=null && this.keystore.getType()!=null && this.keystore.getType().equalsIgnoreCase(KeystoreType.PKCS11.getNome())) {
				useBC=false;
			}
		}
		if(useBC) {
			return org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
		}
		else {
			return super.getCryptoProvider();
		}
	}
	
	public static void correctProviderName(MessageSecurity messageSecurity){
		if(messageSecurity!=null) {
			MessageSecurityFlow request = messageSecurity.getRequestFlow();
			correctProviderNameRequestFlow(request);
			
			MessageSecurityFlow response = messageSecurity.getResponseFlow();
			correctProviderNameResponseFlow(response);
		}
	}
	private static void correctProviderNameRequestFlow(MessageSecurityFlow request){
		if(request!=null && request.sizeParameterList()>0) {
			for (MessageSecurityFlowParameter param : request.getParameterList()) {
				if(param.getNome()!=null && param.getNome().trim().endsWith(KeystoreConstants.PROPERTY_PROVIDER) && 
						param.getValore()!=null && KeystoreConstants.OLD_PROVIDER_GOVWAY.equals(param.getValore().trim()) ) {
					param.setValore(KeystoreConstants.PROVIDER_GOVWAY);
				}
			}
		}
	}
	private static void correctProviderNameResponseFlow(MessageSecurityFlow response){
		if(response!=null && response.sizeParameterList()>0) {
			for (MessageSecurityFlowParameter param : response.getParameterList()) {
				if(param.getNome()!=null && param.getNome().trim().endsWith(KeystoreConstants.PROPERTY_PROVIDER) && 
						param.getValore()!=null && KeystoreConstants.OLD_PROVIDER_GOVWAY.equals(param.getValore().trim()) ) {
					param.setValore(KeystoreConstants.PROVIDER_GOVWAY);
				}
			}
		}
	}
}