DriverBYOK.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.pdd.core.byok;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.openspcoop2.core.byok.BYOKUtilities;
import org.openspcoop2.core.byok.BYOKWrappedValue;
import org.openspcoop2.core.byok.IDriverBYOK;
import org.openspcoop2.pdd.core.dynamic.DynamicInfo;
import org.openspcoop2.pdd.core.dynamic.DynamicMapBuilderUtils;
import org.openspcoop2.pdd.core.dynamic.DynamicUtils;
import org.openspcoop2.pdd.core.jmx.JMXUtils;
import org.openspcoop2.protocol.sdk.Busta;
import org.openspcoop2.protocol.sdk.Context;
import org.openspcoop2.protocol.sdk.state.RequestInfo;
import org.openspcoop2.security.SecurityException;
import org.openspcoop2.security.keystore.BYOKLocalEncrypt;
import org.openspcoop2.utils.LoggerWrapperFactory;
import org.openspcoop2.utils.Semaphore;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.certificate.byok.BYOKCostanti;
import org.openspcoop2.utils.certificate.byok.BYOKInstance;
import org.openspcoop2.utils.certificate.byok.BYOKManager;
import org.openspcoop2.utils.certificate.byok.BYOKMode;
import org.openspcoop2.utils.certificate.byok.BYOKProvider;
import org.openspcoop2.utils.certificate.byok.BYOKRemoteUtils;
import org.openspcoop2.utils.certificate.byok.BYOKRequestParams;
import org.openspcoop2.utils.certificate.byok.BYOKSecurityConfig;
import org.openspcoop2.utils.certificate.byok.BYOKSecurityConfigParameter;
import org.openspcoop2.utils.transport.http.HttpResponse;
import org.openspcoop2.utils.transport.http.HttpUtilities;
import org.slf4j.Logger;

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

	private Logger log;
	private String securityPolicy;
	private String securityRemotePolicy;
	private Map<String, Map<String, Object>> dynamicMapForSecurityPolicy; // per supportare l'unwrap di secrets codificati con security policy differenti da quelle di default
	private boolean checkJmxPrefixOperazioneNonRiuscita;
	
	public DriverBYOK(Logger log, String securityPolicy, String securityRemotePolicy) {
		this(log, securityPolicy, securityRemotePolicy, buildDynamicMap(log), false);
	}
	public static Map<String, Object> buildDynamicMap(Logger log){
		Map<String, Object> dynamicMap = new HashMap<>();
		DynamicInfo dynamicInfo = new  DynamicInfo();
		DynamicUtils.fillDynamicMap(log, dynamicMap, dynamicInfo);
		return dynamicMap;
	}
	DriverBYOK(Logger log, String securityPolicy, String securityRemotePolicy, Map<String, Object> dynamicMapParam, boolean checkJmxPrefixOperazioneNonRiuscita) {
		this.log = log;
		if(securityPolicy!=null && StringUtils.isNotEmpty(securityPolicy)) {
			this.securityPolicy = securityPolicy;
		}
		if(securityRemotePolicy!=null && StringUtils.isNotEmpty(securityRemotePolicy)) {
			this.securityRemotePolicy = securityRemotePolicy;
		}
		
		this.dynamicMapForSecurityPolicy = new HashMap<>();
		Map<String, Object> defaultPolicy = dynamicMapParam==null ? new HashMap<>() : dynamicMapParam;
		this.dynamicMapForSecurityPolicy.put(this.securityRemotePolicy!=null ? this.securityRemotePolicy : this.securityPolicy, defaultPolicy);
		this.checkJmxPrefixOperazioneNonRiuscita = checkJmxPrefixOperazioneNonRiuscita;
	}
	
	private Semaphore semaphoreDynamicMap = new Semaphore("dynamicMap");
	private Map<String, Object> getDynamicMap(String securityPolicy) throws UtilsException{
		if(!this.dynamicMapForSecurityPolicy.containsKey(securityPolicy)) {
			this.initDynamicMap(securityPolicy);
		}
		return this.dynamicMapForSecurityPolicy.get(securityPolicy);
	}
	private void initDynamicMap(String securityPolicy) throws UtilsException {
		this.semaphoreDynamicMap.acquire("initDynamicMap");
		try {
			if(!this.dynamicMapForSecurityPolicy.containsKey(securityPolicy)) {
				Map<String, Object> mDefault = this.dynamicMapForSecurityPolicy.get(this.securityRemotePolicy!=null ? this.securityRemotePolicy : this.securityPolicy);
				Map<String, Object> mNew = new HashMap<>();
				for (Map.Entry<String,Object> entry : mDefault.entrySet()) {
					if(!BYOKCostanti.VARIABILE_KSM.equals(entry.getKey())){ // devo escludere ksm object contenente le variabili di un'altra security policy
						mNew.put(entry.getKey(), entry.getValue());
					}
				}
				this.dynamicMapForSecurityPolicy.put(securityPolicy, mNew);
			}
		}finally {
			this.semaphoreDynamicMap.release("initDynamicMap");
		}
	}
	
	@Override
	public BYOKWrappedValue wrap(String value) throws UtilsException {
		if(value==null) {
			throw new UtilsException("Value undefined");
		}
		if(BYOKUtilities.isWrappedValue(value)) {
			return new BYOKWrappedValue(value, BYOKUtilities.extractPrefixWrappedValue(value));
		}
		
		if(this.securityPolicy==null || StringUtils.isEmpty(value)) {
			return null;
		}
		
		BYOKRequestParams p = getBYOKRequestParams(true, this.securityPolicy);
		String prefix = BYOKUtilities.newPrefixWrappedValue((this.securityRemotePolicy!=null ? this.securityRemotePolicy : this.securityPolicy));
		byte[]wrapped = process(getBYOKInstance(this.log,value.getBytes(),p));
		
		String wrappedValue = new String(wrapped);
		if(!wrappedValue.startsWith(prefix)){
			wrappedValue = prefix + wrappedValue;
		}
		
		return new BYOKWrappedValue(wrappedValue, 
				prefix.substring(0, prefix.length()-1) // elimino il punto finale
				);
	}
	@Override
	public BYOKWrappedValue wrap(byte[] value) throws UtilsException {
		if(value==null) {
			throw new UtilsException("Value undefined");
		}
		if(BYOKUtilities.isWrappedValue(value)) {
			return new BYOKWrappedValue(value, BYOKUtilities.extractPrefixWrappedValue(value));
		}
		
		if(this.securityPolicy==null) {
			return null;
		}
		
		BYOKRequestParams p = getBYOKRequestParams(true, this.securityPolicy);
		String prefix = BYOKUtilities.newPrefixWrappedValue((this.securityRemotePolicy!=null ? this.securityRemotePolicy : this.securityPolicy));
		byte[]wrapped = process(getBYOKInstance(this.log,value,p));
		
		String wrappedValue = new String(wrapped);
		if(!wrappedValue.startsWith(prefix)){
			wrappedValue = prefix + wrappedValue;
		}
		
		return new BYOKWrappedValue(wrappedValue, 
				prefix.substring(0, prefix.length()-1) // elimino il punto finale
				);
	}

	public boolean isAlreadyWrappedBySecPolicy(String check) throws UtilsException {
		// Serve per evitare di effettuare un ulteriore livello di wrap ad una informazione già cifrata
		// nei metodi wrap non viene usato poichè viene già verificato più in generale con 'isWrappedValue'
		// si lascia come utility generica il metodo
		try {
			return getSecPolicyIdForUnwrap(check)!=null;
		}catch(Exception e) {
			return false;
		}
	}
	private String getSecPolicyIdForUnwrap(String check) throws UtilsException {
		// Serve per decodificare valori cifrati con security policy differente da quella impostata per la gestione della cifratura
		// Il comportamento serve a supportare eventuali valori cifrati dopo un cambio della policy senza un aggiornamento delle informazioni sensibili
		String secPolicy = this.securityPolicy;
		String wrapPolicy = BYOKUtilities.getPolicy(check);
		if(!secPolicy.equals(wrapPolicy)) {
			// dato cifrato con altra policy
			// verifico che comunque esista
			if(BYOKManager.getInstance().existsSecurityEngineByType(wrapPolicy)){
				secPolicy = wrapPolicy;
			}
			else {
				throw new UtilsException("Security policy '"+wrapPolicy+"' unknown");
			}
		}
		return secPolicy;
	}
	
	@Override
	public byte[] unwrap(byte[] value) throws UtilsException {
		if(value==null || value.length<=0) {
			return value;
		}
		String check = new String(value);
		if(BYOKUtilities.isWrappedValue(check)) {
			if(this.securityPolicy==null) {
				return value;
			}
			
			String rawWrappedValue =  BYOKUtilities.getRawWrappedValue(check);
			
			BYOKRequestParams p = getBYOKRequestParams(false, getSecPolicyIdForUnwrap(check));
			return process(getBYOKInstance(this.log,rawWrappedValue.getBytes(),p));
		}
		return value;
	}
	@Override
	public byte[] unwrap(String value) throws UtilsException {
		return unwrap(value, null, false);
	}
	public byte[] unwrap(String value, boolean checkAppendPrefix) throws UtilsException{
		return unwrap(value, 
				this.securityRemotePolicy!=null ? this.securityRemotePolicy : this.securityPolicy, 
				checkAppendPrefix);
	}
	public byte[] unwrap(String value, String securityPolicy, boolean checkAppendPrefix) throws UtilsException {
		if(BYOKUtilities.isWrappedValue(value)) {
			if(this.securityPolicy==null) {
				return value.getBytes();
			}
			
			String rawWrappedValue =  BYOKUtilities.getRawWrappedValue(value);
			BYOKRequestParams p = getBYOKRequestParams(false, getSecPolicyIdForUnwrap(value));
			return process(getBYOKInstance(this.log,rawWrappedValue.getBytes(),p));
		}
		else if(checkAppendPrefix){
			String newWrappedValue = BYOKUtilities.newPrefixWrappedValue(securityPolicy)+value;
			return this.unwrap(newWrappedValue);
		}
		else {
			return value.getBytes();
		}
	}
	
	public String unwrapAsString(String value, boolean checkAppendPrefix) throws UtilsException{
		return unwrapAsString(value, 
				this.securityRemotePolicy!=null ? this.securityRemotePolicy : this.securityPolicy, 
				checkAppendPrefix);
	}
	public String unwrapAsString(String value, String securityPolicy, boolean checkAppendPrefix) throws UtilsException{
		if(BYOKUtilities.isWrappedValue(value)) {
			return this.unwrapAsString(value);
		}
		else if(checkAppendPrefix){
			String newWrappedValue = BYOKUtilities.newPrefixWrappedValue(securityPolicy)+value;
			String unwrappedValue = this.unwrapAsString(newWrappedValue);
			if(newWrappedValue.equals(unwrappedValue)) {
				// decodifica non riuscita
				return value;
			}
			else {
				return unwrappedValue;
			}
		}
		else {
			return value;
		}
	}
	
	private BYOKInstance getBYOKInstance(Logger log,byte[] key, BYOKRequestParams p) throws UtilsException {
		return BYOKInstance.newInstance(log, p, key);
	}
	private BYOKRequestParams getBYOKRequestParams(boolean wrap, String securityPolicy) throws UtilsException {
		return getBYOKRequestParamsBySecurityPolicy(wrap, securityPolicy, this. getDynamicMap(securityPolicy));
	}
	public static BYOKRequestParams getBYOKRequestParamsBySecurityPolicy(boolean wrap, String securityPolicy, Map<String, Object> dynamicMap) throws UtilsException {
		
		if(securityPolicy==null) {
			return null;
		}
		
		BYOKManager manager = BYOKManager.getInstance();
		if(manager==null) {
			throw new UtilsException("BYOKManager not initialized");
		}
		
		BYOKSecurityConfig secConfig = manager.getKSMSecurityConfig(securityPolicy); 
		
		String ksmId = wrap ? secConfig.getWrapId() : secConfig.getUnwrapId();
		if(ksmId==null) {
			throw new UtilsException("BYOK security configuration '"+securityPolicy+"' without "+(wrap ?  "wrap" : "unwrap")+" ksm id");
		}
				
		Map<String, String> inputMap = new HashMap<>();
		if(secConfig.getInputParameters()!=null && !secConfig.getInputParameters().isEmpty()) {
			for (BYOKSecurityConfigParameter sec : secConfig.getInputParameters()) {
				inputMap.put(sec.getName(), sec.getValue());
			}
		}
		
		return getBYOKRequestParamsByKsmId(ksmId, manager, 
				inputMap, dynamicMap);
	}
	
	public static BYOKRequestParams getBYOKRequestParamsByUnwrapBYOKPolicy(String ksmId,
			Busta busta, RequestInfo requestInfo, Context context, Logger log) throws UtilsException {
		if(BYOKProvider.isPolicyDefined(ksmId)){
			Map<String,Object> dynamicMap = DynamicMapBuilderUtils.buildDynamicMap(busta, requestInfo, context, log);
			return BYOKProvider.getBYOKRequestParamsByUnwrapBYOKPolicy(ksmId, dynamicMap);
		}
		return null;
	}
	
	public static BYOKRequestParams getBYOKRequestParamsByKsmId(String ksmId, 
			Map<String, String> inputMap, Map<String, Object> dynamicMap) throws UtilsException {
		return BYOKRequestParams.getBYOKRequestParamsByKsmId(ksmId, 
				inputMap,dynamicMap);
	}
	public static BYOKRequestParams getBYOKRequestParamsByKsmId(String ksmId, BYOKManager manager, 
			Map<String, String> inputMap, Map<String, Object> dynamicMap) throws UtilsException {
		return BYOKRequestParams.getBYOKRequestParamsByKsmId(ksmId, manager, 
				inputMap, dynamicMap);
	}
	
	private byte[] process(BYOKInstance instance) throws UtilsException{
		return processInstance(instance, this.checkJmxPrefixOperazioneNonRiuscita);
	}
	public static byte[] processInstance(BYOKInstance instance, boolean checkJmxPrefixOperazioneNonRiuscita) throws UtilsException{
		
		try{
			if(instance==null){
				throw new SecurityException("Instance non fornita");
			}
			
			if(instance.getHttpRequest()!=null) {
				return remoteProcess(instance, checkJmxPrefixOperazioneNonRiuscita);
			}
			else {
				BYOKLocalEncrypt localEncrypt = new BYOKLocalEncrypt();
				if(BYOKMode.WRAP.equals(instance.getConfig().getMode())) {
					return localEncrypt.wrap(instance.getLocalConfigResolved(), instance.getLocalKey()).getBytes();
				}
				else {
					return localEncrypt.unwrap(instance.getLocalConfigResolved(), instance.getLocalKey());
				}
			}

		}catch(Exception e){
			throw new UtilsException(e.getMessage(),e);
		}
		
	}
	private static byte[] remoteProcess(BYOKInstance instance, boolean checkJmxPrefixOperazioneNonRiuscita) throws UtilsException{
		String debugUrl = "'"+instance.getConfig().getLabel()+"' (endpoint:"+instance.getHttpRequest().getUrl()+")";
		
		HttpResponse httpResponse = HttpUtilities.httpInvoke(instance.getHttpRequest());
		if(httpResponse==null || httpResponse.getContent()==null) {
			throw new UtilsException("Store "+debugUrl+" unavailable");
		}
		if(httpResponse.getResultHTTPOperation()!=200) {
			throw new UtilsException("Retrieve store "+debugUrl+" failed (returnCode:"+httpResponse.getResultHTTPOperation()+")");
		}
		byte [] content = null;
		if(checkJmxPrefixOperazioneNonRiuscita) {
			byte[] b = httpResponse.getContent();
			if(b==null || b.length<=0) {
				throw new UtilsException("Store "+debugUrl+" empty response");
			}
			String check = new String(b);
			if(check.startsWith(JMXUtils.MSG_OPERAZIONE_NON_EFFETTUATA)) {
				throw new UtilsException("Retrieve store "+debugUrl+" failed (returnCode:"+httpResponse.getResultHTTPOperation()+"): "+check);
			}
			content = b;
		}
		else {
			content = httpResponse.getContent();
		}
		if(content!=null && content.length>0) {
			content = BYOKRemoteUtils.normalizeResponse(instance, content, LoggerWrapperFactory.getLogger(DriverBYOK.class));
		}
		return content;
	}
	
	public boolean isWrappedWithInternalPolicy(byte[] value) {
		if(value==null || value.length<=0) {
			return false;
		}
		return isWrappedWithInternalPolicy(new String(value));
	}
	public boolean isWrappedWithInternalPolicy(String value) {
		String policy = this.securityRemotePolicy!=null ? this.securityRemotePolicy : this.securityPolicy;
		return DriverBYOKUtilities.isWrappedWithPolicy(this.log, value, policy);
	}
	
}