/**
* DSS - Digital Signature Services
* Copyright (C) 2015 European Commission, provided under the CEF programme
*
* This file is part of the "DSS - Digital Signature Services" project.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package eu.europa.esig.dss.tsl.service;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.europa.esig.dss.DSSException;
import eu.europa.esig.dss.client.http.DataLoader;
import eu.europa.esig.dss.tsl.TSLLoaderResult;
import eu.europa.esig.dss.tsl.TSLParserResult;
import eu.europa.esig.dss.tsl.TSLPointer;
import eu.europa.esig.dss.tsl.TSLValidationModel;
import eu.europa.esig.dss.tsl.TSLValidationResult;
import eu.europa.esig.dss.utils.Utils;
import eu.europa.esig.dss.x509.CertificateToken;
import eu.europa.esig.dss.x509.KeyStoreCertificateSource;
/**
* This class is job class which allows to launch TSL loading/parsing/validation. An instance of this class can be
* injected in a Spring quartz job.
*/
public class TSLValidationJob {
private static final Logger logger = LoggerFactory.getLogger(TSLValidationJob.class);
private ExecutorService executorService = Executors.newCachedThreadPool();
private DataLoader dataLoader;
private TSLRepository repository;
private String lotlCode;
private String lotlUrl;
/*
* Official journal URL where the allowed certificates can be found. This URL is present in the LOTL
* (SchemeInformationURI)
*/
private String ojUrl;
private KeyStoreCertificateSource dssKeyStore;
private boolean checkLOTLSignature = true;
private boolean checkTSLSignatures = true;
private List<String> filterTerritories;
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
public void setDataLoader(DataLoader dataLoader) {
this.dataLoader = dataLoader;
}
public void setRepository(TSLRepository repository) {
this.repository = repository;
}
public void setLotlCode(String lotlCode) {
this.lotlCode = lotlCode;
}
public void setLotlUrl(String lotlUrl) {
this.lotlUrl = lotlUrl;
}
public void setOjUrl(String ojUrl) {
this.ojUrl = ojUrl;
}
public void setDssKeyStore(KeyStoreCertificateSource dssKeyStore) {
this.dssKeyStore = dssKeyStore;
}
public void setCheckLOTLSignature(boolean checkLOTLSignature) {
this.checkLOTLSignature = checkLOTLSignature;
}
public void setCheckTSLSignatures(boolean checkTSLSignatures) {
this.checkTSLSignatures = checkTSLSignatures;
}
public void setFilterTerritories(List<String> filterTerritories) {
this.filterTerritories = filterTerritories;
}
public void initRepository() {
logger.info("Initialization of the TSL repository ...");
int loadedTSL = 0;
List<File> cachedFiles = repository.getStoredFiles();
if (Utils.isCollectionNotEmpty(cachedFiles)) {
List<Future<TSLParserResult>> futureParseResults = new ArrayList<Future<TSLParserResult>>();
for (File file : cachedFiles) {
try {
FileInputStream fis = new FileInputStream(file);
futureParseResults.add(executorService.submit(new TSLParser(fis)));
} catch (Exception e) {
logger.error("Unable to parse file '" + file.getAbsolutePath() + "' : " + e.getMessage(), e);
}
}
for (Future<TSLParserResult> futureParseResult : futureParseResults) {
try {
TSLParserResult tslParserResult = futureParseResult.get();
repository.addParsedResultFromCacheToMap(tslParserResult);
loadedTSL++;
} catch (Exception e) {
logger.error("Unable to get parsing result : " + e.getMessage(), e);
}
}
TSLValidationModel europeanModel = repository.getByCountry(lotlCode);
if (checkLOTLSignature && (europeanModel != null)) {
try {
TSLValidationResult europeanValidationResult = validateLOTL(europeanModel);
europeanModel.setValidationResult(europeanValidationResult);
} catch (Exception e) {
logger.error("Unable to validate the LOTL : " + e.getMessage(), e);
}
}
if (checkTSLSignatures && ((europeanModel != null) && (europeanModel.getParseResult() != null))) {
List<TSLPointer> pointers = europeanModel.getParseResult().getPointers();
List<Future<TSLValidationResult>> futureValidationResults = new ArrayList<Future<TSLValidationResult>>();
Map<String, TSLValidationModel> map = repository.getAllMapTSLValidationModels();
for (Entry<String, TSLValidationModel> entry : map.entrySet()) {
String countryCode = entry.getKey();
if (!lotlCode.equals(countryCode)) {
TSLValidationModel countryModel = entry.getValue();
TSLValidator tslValidator = new TSLValidator(new File(countryModel.getFilepath()), countryCode,
getPotentialSigners(pointers, countryCode));
futureValidationResults.add(executorService.submit(tslValidator));
}
}
storeValidationResults(futureValidationResults);
}
repository.synchronize();
}
logger.info(loadedTSL + " loaded TSL from cached files in the repository");
}
public void refresh() {
logger.debug("TSL Validation Job is starting ...");
TSLLoaderResult resultLoaderLOTL = null;
Future<TSLLoaderResult> result = executorService.submit(new TSLLoader(dataLoader, lotlCode, lotlUrl));
try {
resultLoaderLOTL = result.get();
} catch (Exception e) {
logger.error("Unable to load the LOTL : " + e.getMessage(), e);
throw new DSSException("Unable to load the LOTL : " + e.getMessage());
}
if (resultLoaderLOTL.getContent() == null) {
logger.error("Unable to load the LOTL: content is empty");
throw new DSSException("Unable to load the LOTL: content is empty");
}
TSLValidationModel europeanModel = null;
boolean newLotl = !repository.isLastVersion(resultLoaderLOTL);
if (newLotl) {
europeanModel = repository.storeInCache(resultLoaderLOTL);
} else {
europeanModel = repository.getByCountry(resultLoaderLOTL.getCountryCode());
}
TSLParserResult parseResult = europeanModel.getParseResult();
if (parseResult == null) {
try {
parseResult = parseLOTL(europeanModel);
europeanModel.setParseResult(parseResult);
} catch (Exception e) {
logger.error("Unable to parse the LOTL : " + e.getMessage(), e);
return;
}
}
if (!isLatestDssKeystore(parseResult)) {
logger.warn("DSS keystore is out-dated !");
}
if (checkLOTLSignature && (europeanModel.getValidationResult() == null)) {
try {
TSLValidationResult validationResult = validateLOTL(europeanModel);
europeanModel.setValidationResult(validationResult);
} catch (Exception e) {
logger.error("Unable to validate the LOTL : " + e.getMessage(), e);
}
}
analyzeCountryPointers(parseResult.getPointers(), newLotl);
repository.synchronize();
logger.debug("TSL Validation Job is finishing ...");
}
/**
* This method checks if the OJ url is still correct. If not, the DSS keystore is outdated.
*
* @param parseResult
*
* @return
*/
private boolean isLatestDssKeystore(TSLParserResult parseResult) {
List<String> englishSchemeInformationURIs = parseResult.getEnglishSchemeInformationURIs();
return englishSchemeInformationURIs.contains(ojUrl);
}
private void analyzeCountryPointers(List<TSLPointer> pointers, boolean newLotl) {
List<Future<TSLLoaderResult>> futureLoaderResults = new ArrayList<Future<TSLLoaderResult>>();
for (TSLPointer tslPointer : pointers) {
if (Utils.isCollectionEmpty(filterTerritories) || filterTerritories.contains(tslPointer.getTerritory())) {
TSLLoader tslLoader = new TSLLoader(dataLoader, tslPointer.getTerritory(), tslPointer.getUrl());
futureLoaderResults.add(executorService.submit(tslLoader));
}
}
List<Future<TSLParserResult>> futureParseResults = new ArrayList<Future<TSLParserResult>>();
List<Future<TSLValidationResult>> futureValidationResults = new ArrayList<Future<TSLValidationResult>>();
for (Future<TSLLoaderResult> futureLoaderResult : futureLoaderResults) {
try {
TSLLoaderResult loaderResult = futureLoaderResult.get();
if (loaderResult != null && loaderResult.getContent() != null) {
TSLValidationModel countryModel = null;
if (!repository.isLastVersion(loaderResult)) {
countryModel = repository.storeInCache(loaderResult);
} else {
countryModel = repository.getByCountry(loaderResult.getCountryCode());
}
TSLParserResult countryParseResult = countryModel.getParseResult();
if (countryParseResult == null) {
FileInputStream fis = new FileInputStream(countryModel.getFilepath());
futureParseResults.add(executorService.submit(new TSLParser(fis)));
}
if (checkTSLSignatures && (countryModel.getValidationResult() == null || newLotl)) {
TSLValidator tslValidator = new TSLValidator(new File(countryModel.getFilepath()), loaderResult.getCountryCode(),
getPotentialSigners(pointers, loaderResult.getCountryCode()));
futureValidationResults.add(executorService.submit(tslValidator));
}
}
} catch (Exception e) {
logger.error("Unable to load/parse TSL : " + e.getMessage(), e);
}
}
for (Future<TSLParserResult> futureParseResult : futureParseResults) {
try {
TSLParserResult tslParserResult = futureParseResult.get();
repository.updateParseResult(tslParserResult);
} catch (Exception e) {
logger.error("Unable to get parsing result : " + e.getMessage(), e);
}
}
storeValidationResults(futureValidationResults);
}
private void storeValidationResults(List<Future<TSLValidationResult>> futureValidationResults) {
for (Future<TSLValidationResult> futureValidationResult : futureValidationResults) {
try {
TSLValidationResult tslValidationResult = futureValidationResult.get();
repository.updateValidationResult(tslValidationResult);
} catch (Exception e) {
logger.error("Unable to get validation result : " + e.getMessage(), e);
}
}
}
private List<CertificateToken> getPotentialSigners(List<TSLPointer> pointers, String countryCode) {
if (Utils.isCollectionNotEmpty(pointers)) {
for (TSLPointer tslPointer : pointers) {
if (Utils.areStringsEqual(countryCode, tslPointer.getTerritory())) {
return tslPointer.getPotentialSigners();
}
}
}
return Collections.emptyList();
}
private TSLValidationResult validateLOTL(TSLValidationModel validationModel) throws Exception {
validationModel.setLotl(true);
List<CertificateToken> certsFromKeystore = Collections.emptyList();
if (dssKeyStore != null) {
certsFromKeystore = dssKeyStore.getCertificatesFromKeyStore();
}
TSLValidator tslValidator = new TSLValidator(new File(validationModel.getFilepath()), lotlCode, certsFromKeystore);
Future<TSLValidationResult> future = executorService.submit(tslValidator);
return future.get();
}
private TSLParserResult parseLOTL(TSLValidationModel validationModel) throws Exception {
FileInputStream fis = new FileInputStream(validationModel.getFilepath());
Future<TSLParserResult> future = executorService.submit(new TSLParser(fis));
return future.get();
}
}