/**
* 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.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import javax.xml.bind.DatatypeConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.europa.esig.dss.DSSException;
import eu.europa.esig.dss.DSSUtils;
import eu.europa.esig.dss.DigestAlgorithm;
import eu.europa.esig.dss.tsl.Condition;
import eu.europa.esig.dss.tsl.ServiceInfo;
import eu.europa.esig.dss.tsl.ServiceInfoStatus;
import eu.europa.esig.dss.tsl.TLInfo;
import eu.europa.esig.dss.tsl.TSLConditionsForQualifiers;
import eu.europa.esig.dss.tsl.TSLLoaderResult;
import eu.europa.esig.dss.tsl.TSLParserResult;
import eu.europa.esig.dss.tsl.TSLService;
import eu.europa.esig.dss.tsl.TSLServiceProvider;
import eu.europa.esig.dss.tsl.TSLServiceStatusAndInformationExtensions;
import eu.europa.esig.dss.tsl.TSLValidationModel;
import eu.europa.esig.dss.tsl.TSLValidationResult;
import eu.europa.esig.dss.tsl.TrustedListsCertificateSource;
import eu.europa.esig.dss.util.MutableTimeDependentValues;
import eu.europa.esig.dss.util.TimeDependentValues;
import eu.europa.esig.dss.utils.Utils;
import eu.europa.esig.dss.x509.CertificateToken;
/**
* This class is a repository which allows to store TSL loading/parsing/validation results.
*
*/
public class TSLRepository {
private static final Logger logger = LoggerFactory.getLogger(TSLRepository.class);
private String cacheDirectoryPath = System.getProperty("java.io.tmpdir") + File.separator + "dss-cache-tsl" + File.separator;
private Map<String, TSLValidationModel> tsls = new HashMap<String, TSLValidationModel>();
private TrustedListsCertificateSource trustedListsCertificateSource;
public void setCacheDirectoryPath(String cacheDirectoryPath) {
this.cacheDirectoryPath = cacheDirectoryPath;
}
public void setTrustedListsCertificateSource(TrustedListsCertificateSource trustedListsCertificateSource) {
this.trustedListsCertificateSource = trustedListsCertificateSource;
}
public TSLValidationModel getByCountry(String countryIsoCode) {
return tsls.get(countryIsoCode);
}
public Map<String, TSLValidationModel> getAllMapTSLValidationModels() {
return Collections.unmodifiableMap(new TreeMap<String, TSLValidationModel>(tsls));
}
public void clearRepository() {
try {
Utils.cleanDirectory(new File(cacheDirectoryPath));
tsls.clear();
} catch (IOException e) {
logger.error("Unable to clean cache directory : " + e.getMessage(), e);
}
}
boolean isLastVersion(TSLLoaderResult resultLoader) {
TSLValidationModel validationModel = getByCountry(resultLoader.getCountryCode());
if (validationModel == null) {
return false;
} else {
// TODO Best place ? Download didn't work, we use previous version
if (Utils.isArrayEmpty(resultLoader.getContent())) {
return true;
}
validationModel.setUrl(resultLoader.getUrl());
validationModel.setLoadedDate(new Date());
String lastSha256 = getSHA256(resultLoader.getContent());
return Utils.areStringsEqual(lastSha256, validationModel.getSha256FileContent());
}
}
void updateParseResult(TSLParserResult tslParserResult) {
TSLValidationModel validationModel = getByCountry(tslParserResult.getTerritory());
if (validationModel != null) {
validationModel.setParseResult(tslParserResult);
}
}
void updateValidationResult(TSLValidationResult tslValidationResult) {
TSLValidationModel validationModel = getByCountry(tslValidationResult.getCountryCode());
if (validationModel != null) {
validationModel.setValidationResult(tslValidationResult);
}
}
TSLValidationModel storeInCache(TSLLoaderResult resultLoader) {
TSLValidationModel validationModel = new TSLValidationModel();
validationModel.setUrl(resultLoader.getUrl());
validationModel.setSha256FileContent(getSHA256(resultLoader.getContent()));
validationModel.setFilepath(storeOnFileSystem(resultLoader.getCountryCode(), resultLoader));
validationModel.setLoadedDate(new Date());
validationModel.setCertificateSourceSynchronized(false);
add(resultLoader.getCountryCode(), validationModel);
logger.info("New version of " + resultLoader.getCountryCode() + " TSL is stored in cache");
return validationModel;
}
void addParsedResultFromCacheToMap(TSLParserResult tslParserResult) {
TSLValidationModel validationModel = new TSLValidationModel();
String countryCode = tslParserResult.getTerritory();
String filePath = getFilePath(countryCode);
validationModel.setFilepath(filePath);
FileInputStream fis = null;
try {
fis = new FileInputStream(filePath);
byte[] data = Utils.toByteArray(fis);
validationModel.setSha256FileContent(getSHA256(data));
} catch (Exception e) {
logger.error("Unable to read '" + filePath + "' : " + e.getMessage());
} finally {
Utils.closeQuietly(fis);
}
validationModel.setParseResult(tslParserResult);
validationModel.setCertificateSourceSynchronized(false);
add(countryCode, validationModel);
}
private void add(String countryCode, TSLValidationModel tsl) {
tsls.put(countryCode, tsl);
}
private String storeOnFileSystem(String countryCode, TSLLoaderResult resultLoader) {
ensureCacheDirectoryExists();
String filePath = getFilePath(countryCode);
File fileToCreate = new File(filePath);
OutputStream os = null;
try {
os = new FileOutputStream(fileToCreate);
Utils.write(resultLoader.getContent(), os);
} catch (Exception e) {
throw new DSSException("Cannot create file in cache : " + e.getMessage(), e);
} finally {
Utils.closeQuietly(os);
}
return filePath;
}
private void ensureCacheDirectoryExists() {
File cacheDir = new File(cacheDirectoryPath);
if (!cacheDir.exists() || !cacheDir.isDirectory()) {
cacheDir.mkdirs();
}
}
private String getFilePath(String countryCode) {
return cacheDirectoryPath + countryCode + ".xml";
}
private String getSHA256(byte[] data) {
return DatatypeConverter.printBase64Binary(DSSUtils.digest(DigestAlgorithm.SHA256, data));
}
List<File> getStoredFiles() {
ensureCacheDirectoryExists();
File cacheDir = new File(cacheDirectoryPath);
File[] listFiles = cacheDir.listFiles();
return Arrays.asList(listFiles);
}
void synchronize() {
if (trustedListsCertificateSource != null) {
Map<String, TSLValidationModel> allMapTSLValidationModels = getAllMapTSLValidationModels();
for (Entry<String, TSLValidationModel> entry : allMapTSLValidationModels.entrySet()) {
String countryCode = entry.getKey();
TSLValidationModel model = entry.getValue();
// Synchronize certpool
if (!model.isCertificateSourceSynchronized()) {
TSLParserResult parseResult = model.getParseResult();
if (parseResult != null) {
List<TSLServiceProvider> serviceProviders = parseResult.getServiceProviders();
for (TSLServiceProvider serviceProvider : serviceProviders) {
for (TSLService service : serviceProvider.getServices()) {
for (CertificateToken certificate : service.getCertificates()) {
// Update info
trustedListsCertificateSource.removeCertificate(certificate);
trustedListsCertificateSource.addCertificate(certificate, getServiceInfo(serviceProvider, service, countryCode));
}
}
}
}
model.setCertificateSourceSynchronized(true);
}
// Synchronize tlInfos
trustedListsCertificateSource.updateTlInfo(countryCode, getTlInfo(countryCode, model));
}
logger.info("Nb of loaded trusted lists : " + allMapTSLValidationModels.size());
logger.info("Nb of trusted certificates : " + trustedListsCertificateSource.getNumberOfTrustedCertificates());
}
}
private TLInfo getTlInfo(String countryCode, TSLValidationModel model) {
TLInfo info = new TLInfo();
info.setCountryCode(countryCode);
info.setUrl(model.getUrl());
info.setLastLoading(model.getLoadedDate());
info.setLotl(model.isLotl());
TSLParserResult parseResult = model.getParseResult();
if (parseResult != null) {
info.setIssueDate(parseResult.getIssueDate());
info.setNextUpdate(parseResult.getNextUpdateDate());
info.setSequenceNumber(parseResult.getSequenceNumber());
info.setVersion(parseResult.getVersion());
int nbServiceProviders = 0;
int nbServices = 0;
int nbCertificates = 0;
List<TSLServiceProvider> serviceProviders = parseResult.getServiceProviders();
if (serviceProviders != null) {
nbServiceProviders = serviceProviders.size();
for (TSLServiceProvider tslServiceProvider : serviceProviders) {
List<TSLService> services = tslServiceProvider.getServices();
if (services != null) {
nbServices += services.size();
for (TSLService tslService : services) {
List<CertificateToken> certificates = tslService.getCertificates();
nbCertificates += Utils.collectionSize(certificates);
}
}
}
}
info.setNbServiceProviders(nbServiceProviders);
info.setNbServices(nbServices);
info.setNbCertificates(nbCertificates);
}
TSLValidationResult validationResult = model.getValidationResult();
if (validationResult != null) {
info.setWellSigned(validationResult.isValid());
}
return info;
}
private ServiceInfo getServiceInfo(TSLServiceProvider serviceProvider, TSLService service, String countryCode) {
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setTspName(serviceProvider.getName());
serviceInfo.setTspTradeName(serviceProvider.getTradeName());
serviceInfo.setTspPostalAddress(serviceProvider.getPostalAddress());
serviceInfo.setTspElectronicAddress(serviceProvider.getElectronicAddress());
serviceInfo.setServiceName(service.getName());
final MutableTimeDependentValues<ServiceInfoStatus> status = new MutableTimeDependentValues<ServiceInfoStatus>();
final TimeDependentValues<TSLServiceStatusAndInformationExtensions> serviceStatus = service.getStatusAndInformationExtensions();
if (serviceStatus != null) {
for (TSLServiceStatusAndInformationExtensions tslServiceStatus : serviceStatus) {
final Map<String, List<Condition>> qualifiersAndConditions = getMapConditionsByQualifier(tslServiceStatus);
final ServiceInfoStatus s = new ServiceInfoStatus(tslServiceStatus.getType(), tslServiceStatus.getStatus(), qualifiersAndConditions,
tslServiceStatus.getAdditionalServiceInfoUris(), tslServiceStatus.getExpiredCertsRevocationInfo(), tslServiceStatus.getStartDate(),
tslServiceStatus.getEndDate());
status.addOldest(s);
}
}
serviceInfo.setStatus(status);
serviceInfo.setTlCountryCode(countryCode);
return serviceInfo;
}
private Map<String, List<Condition>> getMapConditionsByQualifier(TSLServiceStatusAndInformationExtensions tslServiceStatus) {
List<TSLConditionsForQualifiers> conditionsForQualifiers = tslServiceStatus.getConditionsForQualifiers();
final Map<String, List<Condition>> qualifiersAndConditions = new HashMap<String, List<Condition>>();
if (conditionsForQualifiers != null) {
for (TSLConditionsForQualifiers tslConditionsForQualifiers : conditionsForQualifiers) {
Condition condition = tslConditionsForQualifiers.getCondition();
for (String qualifier : tslConditionsForQualifiers.getQualifiers()) {
List<Condition> conditionsForQualif = qualifiersAndConditions.get(qualifier);
if (conditionsForQualif == null) {
conditionsForQualif = new ArrayList<Condition>();
qualifiersAndConditions.put(qualifier, conditionsForQualif);
}
conditionsForQualif.add(condition);
}
}
}
return qualifiersAndConditions;
}
public Map<String, TLInfo> getSummary() {
if (trustedListsCertificateSource != null) {
return Collections.unmodifiableMap(new TreeMap<String, TLInfo>(trustedListsCertificateSource.getSummary()));
} else {
return Collections.emptyMap();
}
}
}