RemoteStoreProviderDriver.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.keystore;
import java.io.ByteArrayOutputStream;
import java.security.PublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.openspcoop2.core.config.driver.db.DriverConfigurazioneDB;
import org.openspcoop2.core.eventi.Evento;
import org.openspcoop2.pdd.config.ConfigurazionePdDReader;
import org.openspcoop2.pdd.config.OpenSPCoop2Properties;
import org.openspcoop2.pdd.config.PDNDConfigUtilities;
import org.openspcoop2.pdd.core.eventi.GestoreEventi;
import org.openspcoop2.pdd.timers.TimerException;
import org.openspcoop2.pdd.timers.pdnd.TimerGestoreChiaviPDNDEvent;
import org.openspcoop2.pdd.timers.pdnd.TimerGestoreChiaviPDNDLib;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.certificate.ArchiveLoader;
import org.openspcoop2.utils.certificate.Certificate;
import org.openspcoop2.utils.certificate.JWK;
import org.openspcoop2.utils.certificate.KeyUtils;
import org.openspcoop2.utils.certificate.remote.IRemoteStoreProvider;
import org.openspcoop2.utils.certificate.remote.RemoteKeyType;
import org.openspcoop2.utils.certificate.remote.RemoteStoreClientInfo;
import org.openspcoop2.utils.certificate.remote.RemoteStoreConfig;
import org.openspcoop2.utils.certificate.remote.RemoteStoreUtils;
import org.openspcoop2.utils.date.DateManager;
import org.openspcoop2.utils.date.DateUtils;
import org.slf4j.Logger;
/**
* RemoteStoreProviderDriver
*
* @author Poli Andrea (apoli@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class RemoteStoreProviderDriver implements IRemoteStoreProvider {
private static int keyMaxLifeMinutes = -1; // infinito
public static int getKeyMaxLifeMinutes() {
return keyMaxLifeMinutes;
}
public static void setKeyMaxLifeMinutes(int keyMaxLifeMinutes) {
RemoteStoreProviderDriver.keyMaxLifeMinutes = keyMaxLifeMinutes;
}
private static int clientDetailsMaxLifeMinutes = -1; // infinito
public static int getClientDetailsMaxLifeMinutes() {
return clientDetailsMaxLifeMinutes;
}
public static void setClientDetailsMaxLifeMinutes(int clientDetailsMaxLifeMinutes) {
RemoteStoreProviderDriver.clientDetailsMaxLifeMinutes = clientDetailsMaxLifeMinutes;
}
private static final Map<String, RemoteStoreProviderDriver> _providerStore = new HashMap<>();
public static synchronized void initialize(Logger log, RemoteStoreConfig remoteStoreConfig) throws KeystoreException {
String storeConfigName = getRemoteStoreConfigName(remoteStoreConfig);
RemoteStoreProviderDriver s = new RemoteStoreProviderDriver(log, remoteStoreConfig);
_providerStore.put(storeConfigName, s);
}
static RemoteStoreProviderDriver getProviderStore(String storeConfigName) throws KeystoreException{
RemoteStoreProviderDriver s = _providerStore.get(storeConfigName);
if(s==null) {
throw new KeystoreException("ProviderStore '"+storeConfigName+"' not exists");
}
return s;
}
private static final Map<String, org.openspcoop2.utils.Semaphore> _lockStore = new HashMap<>();
private static synchronized org.openspcoop2.utils.Semaphore initLockStore(String nomeRemoteStore){
org.openspcoop2.utils.Semaphore s = _lockStore.get(nomeRemoteStore);
if(s==null) {
Integer permits = OpenSPCoop2Properties.getInstance().getGestioneTokenValidazioneJWTLockPermits();
if(permits!=null && permits.intValue()>1) {
s = new org.openspcoop2.utils.Semaphore("GestoreTokenValidazioneJWT_"+nomeRemoteStore, permits);
}
else {
s = new org.openspcoop2.utils.Semaphore("GestoreTokenValidazioneJWT_"+nomeRemoteStore);
}
_lockStore.put(nomeRemoteStore, s);
}
return s;
}
private static org.openspcoop2.utils.Semaphore getLockStore(String nomeRemoteStore){
org.openspcoop2.utils.Semaphore s = _lockStore.get(nomeRemoteStore);
if(s==null) {
s = initLockStore(nomeRemoteStore);
}
return s;
}
static String getRemoteStoreConfigName(RemoteStoreConfig remoteStoreConfig) throws KeystoreException {
if(remoteStoreConfig==null) {
throw new KeystoreException("RemoteStoreConfig undefined");
}
String remoteStoreName = remoteStoreConfig.getStoreName();
if(remoteStoreName==null) {
throw new KeystoreException("RemoteStoreConfig name undefined");
}
return remoteStoreName;
}
private DriverConfigurazioneDB driverConfigurazioneDB = null;
private Logger log;
private RemoteStoreConfig remoteStoreConfig;
private String keyAlgorithm;
private long remoteStoreId;
private RemoteStoreProviderDriver(Logger log, RemoteStoreConfig remoteStoreConfig) throws KeystoreException {
this.log = log;
getRemoteStoreConfigName(remoteStoreConfig);
this.remoteStoreConfig = remoteStoreConfig;
this.keyAlgorithm = remoteStoreConfig.getKeyAlgorithm();
if(this.keyAlgorithm==null) {
this.keyAlgorithm = KeyUtils.ALGO_RSA;
}
Object oConfig = ConfigurazionePdDReader.getDriverConfigurazionePdD();
if(oConfig instanceof DriverConfigurazioneDB) {
this.driverConfigurazioneDB = (DriverConfigurazioneDB) oConfig;
}
else {
throw new KeystoreException("RemoteStoreProvider utilizzabile solamente con una configurazione su database");
}
this.remoteStoreId = RemoteStoreProviderDriverUtils.registerIfNotExistsRemoteStore(this.driverConfigurazioneDB, this.remoteStoreConfig);
}
private String getPrefixKid(String keyId) {
return "Chiave con kid '"+keyId+"'";
}
private Object readKey(RemoteKeyType remoteStoreKeyType, String keyId, ByteArrayOutputStream bout, RemoteStoreConfig remoteConfig) throws KeystoreException {
RemoteStoreKey key = null;
try {
key = RemoteStoreProviderDriverUtils.getRemoteStoreKey(this.driverConfigurazioneDB, this.remoteStoreId, keyId);
}catch(KeystoreNotFoundException notFound) {
String msg = getPrefixKid(keyId)+" non presente su database";
this.log.debug(msg);
// ignore
}
boolean updateRequired = isUpdateRequired(key, keyId);
if(!updateRequired && key!=null && key.getKey()!=null) {
try {
switch (remoteStoreKeyType) {
case JWK:
return new JWK(new String(key.getKey()));
case PUBLIC_KEY:
return KeyUtils.getInstance(this.keyAlgorithm).getPublicKey(key.getKey());
case X509:
return ArchiveLoader.load(key.getKey());
}
}catch(Exception e) {
throw new KeystoreException(e.getMessage(),e);
}
}
org.openspcoop2.utils.Semaphore semaphore = getLockStore(this.remoteStoreConfig.getStoreName());
try {
semaphore.acquire("readKey");
}catch(Exception e) {
throw new KeystoreException(e.getMessage(),e);
}
try (ByteArrayOutputStream boutInternal = new ByteArrayOutputStream()){
ByteArrayOutputStream b = bout!=null ? bout : boutInternal;
RemoteStoreConfig remoteConfigUse = (remoteConfig!=null && remoteConfig.isMultitenant()) ? remoteConfig : this.remoteStoreConfig;
Object objectKey = null;
switch (remoteStoreKeyType) {
case JWK:
objectKey = RemoteStoreUtils.readJWK(keyId, remoteConfigUse, b);
break;
case PUBLIC_KEY:
objectKey = RemoteStoreUtils.readPublicKey(keyId, remoteConfigUse, b);
break;
case X509:
objectKey = RemoteStoreUtils.readX509(keyId, remoteConfigUse, b);
break;
}
saveKey(updateRequired, keyId, b);
return objectKey;
}catch(Exception e) {
throw new KeystoreException(e.getMessage(),e);
}finally {
semaphore.release("readKey");
}
}
private void saveKey(boolean updateRequired, String keyId, ByteArrayOutputStream b) throws KeystoreException, TimerException {
if(updateRequired) {
String msg = getPrefixKid(keyId)+" ottenuta da remote store config, aggiornamento entry sul db ...";
this.log.debug(msg);
int rows = RemoteStoreProviderDriverUtils.updateRemoteStoreKey(this.driverConfigurazioneDB, this.remoteStoreId, keyId, b.toByteArray());
msg = getPrefixKid(keyId)+" ottenuta da remote store config, aggiornata entry sul db (updateRows:"+rows+")";
this.log.debug(msg);
}
else {
String msg = getPrefixKid(keyId)+" ottenuta da remote store config, registrazione sul db ...";
this.log.debug(msg);
int rows = RemoteStoreProviderDriverUtils.addRemoteStoreKey(this.driverConfigurazioneDB, this.remoteStoreId, keyId, b.toByteArray());
msg = getPrefixKid(keyId)+" ottenuta da remote store config, registrata entry sul db (updateRows:"+rows+")";
this.log.debug(msg);
if(OpenSPCoop2Properties.getInstance().isGestoreChiaviPDNDEventiAdd()) {
Evento evento = TimerGestoreChiaviPDNDLib.buildEvento(TimerGestoreChiaviPDNDEvent.EVENT_TYPE_ADDED, keyId, "La chiave è stata aggiunta al repository locale");
registerEvent(evento, keyId);
}
}
}
private void registerEvent(Evento evento, String keyId) {
try {
GestoreEventi.getInstance().log(evento);
}catch(Exception e) {
String msgError = "Registrazione evento per kid '"+keyId+"' (eventType:"+TimerGestoreChiaviPDNDEvent.EVENT_TYPE_ADDED+") non riuscita: "+e.getMessage();
this.log.error(msgError,e);
}
}
private boolean isUpdateRequired(RemoteStoreKey key, String keyId) {
if(key!=null && key.isInvalid()) {
String msg = getPrefixKid(keyId)+" non è valida";
this.log.debug(msg);
return true;
}
if(key!=null && keyMaxLifeMinutes>0 && key.getDataAggiornamento()!=null) {
long maxLifeSeconds = keyMaxLifeMinutes * 60l;
long maxLifeMs = maxLifeSeconds * 1000l;
Date tooOld = new Date(DateManager.getTimeMillis()-maxLifeMs);
if(key.getDataAggiornamento().before(tooOld)) {
String msg = getPrefixKid(keyId)+" è più vecchia di "+keyMaxLifeMinutes+" minuti (data aggiornamento: "+DateUtils.getSimpleDateFormatMs().format(key.getDataAggiornamento())+")";
this.log.debug(msg);
return true;
}
}
return false;
}
@Override
public JWK readJWK(String keyId, RemoteStoreConfig remoteConfig) throws UtilsException {
try {
return (JWK) readKey(RemoteKeyType.JWK, keyId, null, remoteConfig);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public JWK readJWK(String keyId, RemoteStoreConfig remoteConfig, ByteArrayOutputStream bout) throws UtilsException {
try {
return (JWK) readKey(RemoteKeyType.JWK, keyId, bout, remoteConfig);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public Certificate readX509(String keyId, RemoteStoreConfig remoteConfig) throws UtilsException {
try {
return (Certificate) readKey(RemoteKeyType.X509, keyId, null, remoteConfig);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public Certificate readX509(String keyId, RemoteStoreConfig remoteConfig, ByteArrayOutputStream bout)
throws UtilsException {
try {
return (Certificate) readKey(RemoteKeyType.X509, keyId, bout, remoteConfig);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public PublicKey readPublicKey(String keyId, RemoteStoreConfig remoteConfig) throws UtilsException {
try {
return (PublicKey) readKey(RemoteKeyType.PUBLIC_KEY, keyId, null, remoteConfig);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public PublicKey readPublicKey(String keyId, RemoteStoreConfig remoteConfig, ByteArrayOutputStream bout)
throws UtilsException {
try {
return (PublicKey) readKey(RemoteKeyType.PUBLIC_KEY, keyId, bout, remoteConfig);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public RemoteStoreClientInfo readClientInfo(String keyId, String clientId, RemoteStoreConfig remoteConfig, org.openspcoop2.utils.Map<Object> context)
throws UtilsException {
try {
return readClientInfoEngine(keyId, clientId, remoteConfig, context);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
private static boolean createEntryIfNotExists = true;
public static void setCreateEntryIfNotExists(boolean createEntryIfNotExists) {
RemoteStoreProviderDriver.createEntryIfNotExists = createEntryIfNotExists;
}
private RemoteStoreClientInfo readClientInfoEngine(String keyId, String clientId, RemoteStoreConfig remoteConfig, org.openspcoop2.utils.Map<Object> context) throws KeystoreException {
RemoteStoreClientDetails clientDetails = null;
try {
clientDetails = RemoteStoreProviderDriverUtils.getRemoteStoreClientDetails(this.driverConfigurazioneDB, this.remoteStoreId, keyId, this.log, createEntryIfNotExists);
if(clientDetails==null) {
throw new KeystoreNotFoundException("Client details not found");
}
}catch(KeystoreNotFoundException notFound) {
String msg = getPrefixKidClientDetails(keyId)+" non presente su database";
this.log.error(msg);
return null;
}
boolean updateRequired = isUpdateRequired(clientDetails, keyId, clientId);
if(!updateRequired) {
return clientDetails.getClientInfo();
}
org.openspcoop2.utils.Semaphore semaphore = getLockStore(this.remoteStoreConfig.getStoreName());
try {
semaphore.acquire("readClientDetails");
}catch(Exception e) {
throw new KeystoreException(e.getMessage(),e);
}
try {
OpenSPCoop2Properties propertiesReader = OpenSPCoop2Properties.getInstance();
RemoteStoreConfig remoteConfigUse = (remoteConfig!=null && remoteConfig.isMultitenant()) ? remoteConfig : this.remoteStoreConfig;
String clientJson = PDNDConfigUtilities.readClientDetails(remoteConfigUse, propertiesReader, context, clientId, this.log);
String organizationId = null;
String organizationJson = null;
if(clientJson!=null) {
organizationId = PDNDConfigUtilities.readOrganizationId(propertiesReader, context, clientJson, this.log);
if(organizationId!=null) {
organizationJson = PDNDConfigUtilities.readOrganizationDetails(remoteConfigUse, propertiesReader, context, organizationId, this.log);
}
}
clientDetails.setDataAggiornamento(DateManager.getDate());
if(clientDetails.getClientInfo()==null) {
clientDetails.setClientInfo(new RemoteStoreClientInfo());
}
clientDetails.getClientInfo().setClientId(clientId);
clientDetails.getClientInfo().setClientDetails(clientJson);
clientDetails.getClientInfo().setOrganizationId(organizationId);
clientDetails.getClientInfo().setOrganizationDetails(organizationJson);
String msg = getPrefixKidClientDetails(keyId)+" ottenuta da remote store config, aggiornamento entry sul db ...";
this.log.debug(msg);
int rows = RemoteStoreProviderDriverUtils.updateRemoteStoreClientDetails(this.driverConfigurazioneDB, this.remoteStoreId, keyId, clientDetails);
msg = getPrefixKidClientDetails(keyId)+" ottenuta da remote store config, aggiornata entry sul db (updateRows:"+rows+")";
this.log.debug(msg);
return clientDetails.getClientInfo();
}catch(Exception e) {
throw new KeystoreException(e.getMessage(),e);
}finally {
semaphore.release("readClientDetails");
}
}
private String getPrefixKidClientDetails(String keyId) {
return "ClientDetails con kid '"+keyId+"'";
}
private boolean isUpdateRequired(RemoteStoreClientDetails clientDetails, String keyId, String clientId) {
if(clientDetails!=null && clientDetails.isInvalid()) {
String msg = getPrefixKidClientDetails(keyId)+" non è valida";
this.log.debug(msg);
return true;
}
if(clientDetails!=null && clientDetails.getClientInfo()==null) {
String msg = getPrefixKidClientDetails(keyId)+" client info non presente";
this.log.debug(msg);
return true;
}
if(clientDetails!=null && clientDetails.getClientInfo().getClientId()==null) {
String msg = getPrefixKidClientDetails(keyId)+" client id non presente";
this.log.debug(msg);
return true;
}
if(clientDetails!=null && !clientDetails.getClientInfo().getClientId().equals(clientId)) {
String msg = getPrefixKidClientDetails(keyId)+" client id differente da quello presente su database";
this.log.debug(msg);
return true;
}
if(clientDetails!=null && clientDetailsMaxLifeMinutes>0 && clientDetails.getDataAggiornamento()!=null) {
long maxLifeSeconds = clientDetailsMaxLifeMinutes * 60l;
long maxLifeMs = maxLifeSeconds * 1000l;
Date tooOld = new Date(DateManager.getTimeMillis()-maxLifeMs);
if(clientDetails.getDataAggiornamento().before(tooOld)) {
String msg = getPrefixKidClientDetails(keyId)+" è più vecchia di "+clientDetailsMaxLifeMinutes+" minuti (data aggiornamento: "+DateUtils.getSimpleDateFormatMs().format(clientDetails.getDataAggiornamento())+")";
this.log.debug(msg);
return true;
}
}
return false;
}
}