/* DigiDoc4J library
*
* This software is released under either the GNU Library General Public
* License (see LICENSE.LGPL).
*
* Note that the only valid version of the LGPL license as far as this
* project is concerned is the original GNU Library General Public License
* Version 2.1, February 1999
*/
package org.digidoc4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.digidoc4j.exceptions.ConfigurationException;
import org.digidoc4j.exceptions.DigiDoc4JException;
import org.digidoc4j.impl.ConfigurationSingeltonHolder;
import org.digidoc4j.impl.bdoc.tsl.TslManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ExecutorService;
import static java.util.Arrays.asList;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.apache.commons.lang.StringUtils.isNotEmpty;
import eu.europa.esig.dss.client.http.Protocol;
/**
* Possibility to create custom configurations for {@link Container} implementations.
* <p>
* It is possible to get the default Configuration object used in all containers by using
* {@link Configuration#getInstance()}. This will return a singelton Configuration object used by default
* if no configuration is provided.
* </p>
* <p/>
* You can specify the configuration mode, either {@link Configuration.Mode#TEST} or {@link Configuration.Mode#PROD}
* configuration. Default is {@link Configuration.Mode#PROD}.
* <p/>
* <p>
* It is a good idea to use only a single configuration object for all the containers so the operation times would be faster.
* </p>
* It is also possible to set the mode using the System property. Setting the property "digidoc4j.mode" to "TEST" forces
* the default mode to {@link Configuration.Mode#TEST} mode
* <p/>
* Configurations will be loaded from a file. The file must be in yaml format.<p/>
* <p/>
* <H3>Required entries of the configuration file:</H3>
* The configuration file must contain one or more Certificate Authorities under the heading DIGIDOC_CAS
* similar to following format (values are examples only):<br>
* <p>
* <pre>
* DIGIDOC_CAS:
* - DIGIDOC_CA:
* NAME: CA name
* TRADENAME: Tradename
* CERTS:
* - jar://certs/cert1.crt
* - jar://certs/cert2.crt
* OCSPS:
* </pre>
* <p/>
* Each DIGIDOC_CA entry must contain one or more OCSP certificates under the heading "OCSPS"
* similar to following format (values are examples only):<br>
* <p>
* <pre>
* - OCSP:
* CA_CN: your certificate authority common name
* CA_CERT: jar://your ca_cn.crt
* CN: your common name
* CERTS:
* - jar://certs/Your first OCSP Certifications file.crt
* - jar://certs/Your second OCSP Certifications file.crt
* URL: http://ocsp.test.test
* </pre>
* <p>All entries must exist and be valid. Under CERTS must be at least one entry.</p>
* <p/>
* <p/>
* <H3>Optional entries of the configuration file:</H3>
* <ul>
* <li>CANONICALIZATION_FACTORY_IMPL: Canonicalization factory implementation.<br>
* Default value: {@value #DEFAULT_FACTORY_IMPLEMENTATION}</li>
* <li>CONNECTION_TIMEOUT: TSL HTTP Connection timeout (milliseconds).<br>
* Default value: 1000 </li>
* <li>DIGIDOC_FACTORY_IMPL: Factory implementation.<br>
* Default value: {@value #DEFAULT_FACTORY_IMPLEMENTATION}</li>
* <li>DATAFILE_HASHCODE_MODE: Is the datafile containing only a hash (not the actual file)?
* Allowed values: true, false.<br>
* Default value: {@value #DEFAULT_DATAFILE_HASHCODE_MODE}</li>
* <li>DIGIDOC_DF_CACHE_DIR: Temporary directory to use. Default: uses system's default temporary directory</li>
* <li>DIGIDOC_MAX_DATAFILE_CACHED: Maximum datafile size that will be cached in MB.
* Must be numeric. Set to -1 to cache all files. Set to 0 to prevent caching for all files<br>
* Default value: {@value #DEFAULT_MAX_DATAFILE_CACHED}</li>
* <li>DIGIDOC_NOTARY_IMPL: Notary implementation.<br>
* Default value: {@value #DEFAULT_NOTARY_IMPLEMENTATION}</li>
* <li>DIGIDOC_OCSP_SIGN_CERT_SERIAL: OCSP Signing certificate serial number</li>
* <li>DIGIDOC_SECURITY_PROVIDER: Security provider.<br>
* Default value: {@value #DEFAULT_SECURITY_PROVIDER}</li>
* <li>DIGIDOC_SECURITY_PROVIDER_NAME: Name of the security provider.<br>
* Default value: {@value #DEFAULT_SECURITY_PROVIDER_NAME}</li>
* <li>DIGIDOC_TSLFAC_IMPL: TSL Factory implementation.<br>
* Default value: {@value #DEFAULT_TSL_FACTORY_IMPLEMENTATION}</li>
* <li>DIGIDOC_USE_LOCAL_TSL: Use local TSL? Allowed values: true, false<br>
* Default value: {@value #DEFAULT_USE_LOCAL_TSL}</li>
* <li>KEY_USAGE_CHECK: Should key usage be checked? Allowed values: true, false.<br>
* Default value: {@value #DEFAULT_KEY_USAGE_CHECK}</li>
* <li>DIGIDOC_PKCS12_CONTAINER: OCSP access certificate file</li>
* <li>DIGIDOC_PKCS12_PASSWD: OCSP access certificate password</li>
* <li>OCSP_SOURCE: Online Certificate Service Protocol source</li>
* <li>SIGN_OCSP_REQUESTS: Should OCSP requests be signed? Allowed values: true, false</li>
* <li>TSL_LOCATION: TSL Location</li>
* <li>TSP_SOURCE: Time Stamp Protocol source address</li>
* <li>VALIDATION_POLICY: Validation policy source file</li>
* <li>TSL_KEYSTORE_LOCATION: keystore location for tsl signing certificates</li>
* <li>TSL_KEYSTORE_PASSWORD: keystore password for the keystore in TSL_KEYSTORE_LOCATION</li>
* <li>TSL_CACHE_EXPIRATION_TIME: TSL cache expiration time in milliseconds</li>
* <li>TRUSTED_TERRITORIES: list of countries and territories to trust and load TSL certificates (for example, EE, LV, FR)</li>
* <li>HTTP_PROXY_HOST: network proxy host name</li>
* <li>HTTP_PROXY_PORT: network proxy port</li>
* <li>HTTP_PROXY_USER: network proxy user (for basic auth proxy)</li>
* <li>HTTP_PROXY_PASSWORD: network proxy password (for basic auth proxy)</li>
* <li>SSL_KEYSTORE_PATH: SSL KeyStore path</li>
* <li>SSL_KEYSTORE_TYPE: SSL KeyStore type (default is "jks")</li>
* <li>SSL_KEYSTORE_PASSWORD: SSL KeyStore password (default is an empty string)</li>
* <li>SSL_TRUSTSTORE_PATH: SSL TrustStore path</li>
* <li>SSL_TRUSTSTORE_TYPE: SSL TrustStore type (default is "jks")</li>
* <li>SSL_TRUSTSTORE_PASSWORD: SSL TrustStore password (default is an empty string)</li>
* </ul>
*/
public class Configuration implements Serializable {
private static final Logger logger = LoggerFactory.getLogger(Configuration.class);
private static final int ONE_SECOND = 1000;
private static final long ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
private static final int ONE_DAY_IN_MINUTES = 24 * 60;
public static final long ONE_MB_IN_BYTES = 1048576;
public static final String DEFAULT_CANONICALIZATION_FACTORY_IMPLEMENTATION
= "ee.sk.digidoc.c14n.TinyXMLCanonicalizer";
public static final String DEFAULT_SECURITY_PROVIDER = "org.bouncycastle.jce.provider.BouncyCastleProvider";
public static final String DEFAULT_SECURITY_PROVIDER_NAME = "BC";
public static final String DEFAULT_NOTARY_IMPLEMENTATION = "ee.sk.digidoc.factory.BouncyCastleNotaryFactory";
public static final String DEFAULT_TSL_FACTORY_IMPLEMENTATION = "ee.sk.digidoc.tsl.DigiDocTrustServiceFactory";
public static final String DEFAULT_FACTORY_IMPLEMENTATION = "ee.sk.digidoc.factory.SAXDigiDocFactory";
public static final String DEFAULT_KEY_USAGE_CHECK = "false";
public static final String DEFAULT_DATAFILE_HASHCODE_MODE = "false";
public static final String DEFAULT_USE_LOCAL_TSL = "true";
public static final String DEFAULT_MAX_DATAFILE_CACHED = "-1";
public static final String DEFAULT_TSL_KEYSTORE_LOCATION = "keystore/keystore.jks";
public static final List<String> DEFAULT_TRUESTED_TERRITORIES = Arrays.asList("AT", "BE", "BG", "CY", "CZ",/*"DE",*/"DK", "EE", "ES", "FI", "FR", "GR", "HU",/*"HR",*/"IE", "IS", "IT", "LT", "LU", "LV", "LI", "MT","NO","NL", "PL", "PT", "RO", "SE", "SI", "SK", "UK");
public static final long CACHE_ALL_DATA_FILES = -1;
public static final long CACHE_NO_DATA_FILES = 0;
public static final String TEST_OCSP_URL = "http://demo.sk.ee/ocsp";
public static final String PROD_OCSP_URL = "http://ocsp.sk.ee/";
private static final String SIGN_OCSP_REQUESTS = "SIGN_OCSP_REQUESTS";
private static final String OCSP_PKCS_12_CONTAINER = "DIGIDOC_PKCS12_CONTAINER";
private static final String OCSP_PKCS_12_PASSWD = "DIGIDOC_PKCS12_PASSWD";
private final Mode mode;
private LinkedHashMap configurationFromFile;
private String configurationInputSourceName;
private Hashtable<String, String> jDigiDocConfiguration = new Hashtable<>();
private ArrayList<String> inputSourceParseErrors = new ArrayList<>();
private TslManager tslManager;
Map<String, String> configuration = new HashMap<>();
private String httpProxyHost;
private Integer httpProxyPort;
private String httpProxyUser;
private String httpProxyPassword;
private List<String> trustedTerritories;
private String sslKeystorePath;
private String sslKeystoreType;
private String sslKeystorePassword;
private String sslTruststorePath;
private String sslTruststoreType;
private String sslTruststorePassword;
private transient ExecutorService threadExecutor;
/**
* Application mode
*/
public enum Mode {
TEST,
PROD
}
/**
* Getting the default Configuration object. <br/>
*
* The default configuration object is a singelton, meaning that all the containers will use the same configuration object.
* It is a good idea to use only a single configuration object for all the containers so the operation times would be faster.
*
* @return default configuration.
*/
public static Configuration getInstance() {
return ConfigurationSingeltonHolder.getInstance();
}
private void initDefaultValues() {
logger.debug("");
tslManager = new TslManager(this);
configuration.put("connectionTimeout", String.valueOf(ONE_SECOND));
configuration.put("socketTimeout", String.valueOf(ONE_SECOND));
configuration.put("tslKeyStorePassword", "digidoc4j-password");
configuration.put("revocationAndTimestampDeltaInMinutes", String.valueOf(ONE_DAY_IN_MINUTES));
configuration.put("tslCacheExpirationTime", String.valueOf(ONE_DAY_IN_MILLISECONDS));
if (mode == Mode.TEST) {
configuration.put("tspSource", "http://demo.sk.ee/tsa");
configuration.put("tslLocation", "https://demo.sk.ee/TSL/tl-mp-test-EE.xml");
configuration.put("tslKeyStoreLocation", "keystore/test-keystore.jks");
configuration.put("validationPolicy", "conf/test_constraint.xml");
configuration.put("ocspSource", TEST_OCSP_URL);
configuration.put(SIGN_OCSP_REQUESTS, "false");
jDigiDocConfiguration.put(SIGN_OCSP_REQUESTS, "false");
} else {
configuration.put("tspSource", "http://tsa.sk.ee");
configuration.put("tslLocation",
"https://ec.europa.eu/information_society/policy/esignature/trusted-list/tl-mp.xml");
configuration.put("tslKeyStoreLocation", DEFAULT_TSL_KEYSTORE_LOCATION);
configuration.put("validationPolicy", "conf/constraint.xml");
configuration.put("ocspSource", PROD_OCSP_URL);
configuration.put(SIGN_OCSP_REQUESTS, "false");
jDigiDocConfiguration.put(SIGN_OCSP_REQUESTS, "false");
trustedTerritories = DEFAULT_TRUESTED_TERRITORIES;
}
logger.debug(mode + "configuration:\n" + configuration);
loadInitialConfigurationValues();
}
/**
* Are requirements met for signing OCSP certificate?
*
* @return value indicating if requirements are met
*/
public boolean isOCSPSigningConfigurationAvailable() {
boolean available = isNotEmpty(getOCSPAccessCertificateFileName()) && getOCSPAccessCertificatePassword().length != 0;
logger.debug("Is OCSP signing configuration available: " + available);
return available;
}
/**
* Get OCSP access certificate filename
*
* @return filename for the OCSP access certificate
*/
public String getOCSPAccessCertificateFileName() {
logger.debug("Loading OCSPAccessCertificateFile");
String ocspAccessCertificateFile = getConfigurationParameter("OCSPAccessCertificateFile");
logger.debug("OCSPAccessCertificateFile " + ocspAccessCertificateFile + " loaded");
return ocspAccessCertificateFile;
}
/**
* Get OSCP access certificate password
*
* @return password
*/
public char[] getOCSPAccessCertificatePassword() {
logger.debug("Loading OCSPAccessCertificatePassword");
char[] result = {};
String password = getConfigurationParameter("OCSPAccessCertificatePassword");
if (isNotEmpty(password)) {
result = password.toCharArray();
}
logger.debug("OCSPAccessCertificatePassword loaded");
return result;
}
/**
* Set OCSP access certificate filename
*
* @param fileName filename for the OCSP access certficate
*/
public void setOCSPAccessCertificateFileName(String fileName) {
logger.debug("Setting OCSPAccessCertificateFileName: " + fileName);
setConfigurationParameter("OCSPAccessCertificateFile", fileName);
jDigiDocConfiguration.put(OCSP_PKCS_12_CONTAINER, fileName);
logger.debug("OCSPAccessCertificateFile is set");
}
/**
* Set OCSP access certificate password
*
* @param password password to set
*/
public void setOCSPAccessCertificatePassword(char[] password) {
logger.debug("Setting OCSPAccessCertificatePassword: ");
String value = String.valueOf(password);
setConfigurationParameter("OCSPAccessCertificatePassword", value);
jDigiDocConfiguration.put(OCSP_PKCS_12_PASSWD, value);
logger.debug("OCSPAccessCertificatePassword is set");
}
public void setSignOCSPRequests(boolean shouldSignOcspRequests) {
logger.debug("Should sign OCSP requests: " + shouldSignOcspRequests);
String valueToSet = String.valueOf(shouldSignOcspRequests);
setConfigurationParameter(SIGN_OCSP_REQUESTS, valueToSet);
jDigiDocConfiguration.put(SIGN_OCSP_REQUESTS, valueToSet);
}
/**
* Create new configuration
*/
public Configuration() {
mode = ("TEST".equalsIgnoreCase(System.getProperty("digidoc4j.mode")) ? Mode.TEST : Mode.PROD);
loadConfiguration("digidoc4j.yaml");
initDefaultValues();
logger.info("Configuration loaded for " + mode + " mode");
}
/**
* Create new configuration for application mode specified
*
* @param mode Application mode
*/
public Configuration(Mode mode) {
logger.debug("Mode: " + mode);
this.mode = mode;
loadConfiguration("digidoc4j.yaml");
initDefaultValues();
logger.info("Configuration loaded for " + mode + " mode");
}
/**
* Add configuration settings from a stream. After loading closes stream.
*
* @param stream Input stream
* @return configuration hashtable
*/
public Hashtable<String, String> loadConfiguration(InputStream stream) {
configurationInputSourceName = "stream";
return loadConfigurationSettings(stream);
}
/**
* Add configuration settings from a file
*
* @param file File name
* @return configuration hashtable
*/
public Hashtable<String, String> loadConfiguration(String file) {
logger.info("Loading configuration from file " + file);
configurationInputSourceName = file;
InputStream resourceAsStream = null;
try {
resourceAsStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
logger.info("Configuration file " + file + " not found. Trying to search from jar file.");
}
if (resourceAsStream == null) {
resourceAsStream = getResourceAsStream(file);
}
return loadConfigurationSettings(resourceAsStream);
}
private Hashtable<String, String> loadConfigurationSettings(InputStream stream) {
configurationFromFile = new LinkedHashMap();
Yaml yaml = new Yaml();
try {
configurationFromFile = (LinkedHashMap) yaml.load(stream);
} catch (Exception e) {
ConfigurationException exception = new ConfigurationException("Configuration from "
+ configurationInputSourceName + " is not correctly formatted");
logger.error(exception.getMessage());
throw exception;
}
IOUtils.closeQuietly(stream);
return mapToJDigiDocConfiguration();
}
private InputStream getResourceAsStream(String certFile) {
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(certFile);
if (resourceAsStream == null) {
String message = "File " + certFile + " not found in classpath.";
logger.error(message);
throw new ConfigurationException(message);
}
return resourceAsStream;
}
/**
* Returns configuration needed for JDigiDoc library
*
* @return configuration values
*/
public Hashtable<String, String> getJDigiDocConfiguration() {
loadCertificateAuthoritiesAndCertificates();
reportFileParseErrors();
return jDigiDocConfiguration;
}
/**
* Gives back all configuration parameters needed for jDigiDoc
*
* @return Hashtable containing jDigiDoc configuration parameters
*/
private Hashtable<String, String> mapToJDigiDocConfiguration() {
logger.debug("loading JDigiDoc configuration");
inputSourceParseErrors = new ArrayList<>();
loadInitialConfigurationValues();
reportFileParseErrors();
return jDigiDocConfiguration;
}
private void loadCertificateAuthoritiesAndCertificates() {
logger.debug("");
@SuppressWarnings("unchecked")
ArrayList<LinkedHashMap> digiDocCAs = (ArrayList<LinkedHashMap>) configurationFromFile.get("DIGIDOC_CAS");
if (digiDocCAs == null) {
String errorMessage = "Empty or no DIGIDOC_CAS entry";
logError(errorMessage);
return;
}
int numberOfDigiDocCAs = digiDocCAs.size();
jDigiDocConfiguration.put("DIGIDOC_CAS", String.valueOf(numberOfDigiDocCAs));
for (int i = 0; i < numberOfDigiDocCAs; i++) {
String caPrefix = "DIGIDOC_CA_" + (i + 1);
LinkedHashMap digiDocCA = (LinkedHashMap) digiDocCAs.get(i).get("DIGIDOC_CA");
if (digiDocCA == null) {
String errorMessage = "Empty or no DIGIDOC_CA for entry " + (i + 1);
logError(errorMessage);
} else {
loadCertificateAuthorityCerts(digiDocCA, caPrefix);
loadOCSPCertificates(digiDocCA, caPrefix);
}
}
}
private void logError(String errorMessage) {
logger.error(errorMessage);
inputSourceParseErrors.add(errorMessage);
}
private void reportFileParseErrors() {
logger.debug("");
if (inputSourceParseErrors.size() > 0) {
StringBuilder errorMessage = new StringBuilder();
errorMessage.append("Configuration from ");
errorMessage.append(configurationInputSourceName);
errorMessage.append(" contains error(s):\n");
for (String message : inputSourceParseErrors) {
errorMessage.append(message);
}
throw new ConfigurationException(errorMessage.toString());
}
}
private void loadInitialConfigurationValues() {
logger.debug("");
setJDigiDocConfigurationValue("DIGIDOC_SECURITY_PROVIDER", DEFAULT_SECURITY_PROVIDER);
setJDigiDocConfigurationValue("DIGIDOC_SECURITY_PROVIDER_NAME", DEFAULT_SECURITY_PROVIDER_NAME);
setJDigiDocConfigurationValue("KEY_USAGE_CHECK", DEFAULT_KEY_USAGE_CHECK);
setJDigiDocConfigurationValue("DIGIDOC_OCSP_SIGN_CERT_SERIAL", "");
setJDigiDocConfigurationValue("DATAFILE_HASHCODE_MODE", DEFAULT_DATAFILE_HASHCODE_MODE);
setJDigiDocConfigurationValue("CANONICALIZATION_FACTORY_IMPL", DEFAULT_CANONICALIZATION_FACTORY_IMPLEMENTATION);
setJDigiDocConfigurationValue("DIGIDOC_MAX_DATAFILE_CACHED", DEFAULT_MAX_DATAFILE_CACHED);
setJDigiDocConfigurationValue("DIGIDOC_USE_LOCAL_TSL", DEFAULT_USE_LOCAL_TSL);
setJDigiDocConfigurationValue("DIGIDOC_NOTARY_IMPL", DEFAULT_NOTARY_IMPLEMENTATION);
setJDigiDocConfigurationValue("DIGIDOC_TSLFAC_IMPL", DEFAULT_TSL_FACTORY_IMPLEMENTATION);
setJDigiDocConfigurationValue("DIGIDOC_OCSP_RESPONDER_URL", getOcspSource());
setJDigiDocConfigurationValue("DIGIDOC_FACTORY_IMPL", DEFAULT_FACTORY_IMPLEMENTATION);
setJDigiDocConfigurationValue("DIGIDOC_DF_CACHE_DIR", null);
setConfigurationValue("TSL_LOCATION", "tslLocation");
setConfigurationValue("TSP_SOURCE", "tspSource");
setConfigurationValue("VALIDATION_POLICY", "validationPolicy");
setConfigurationValue("OCSP_SOURCE", "ocspSource");
setConfigurationValue(OCSP_PKCS_12_CONTAINER, "OCSPAccessCertificateFile");
setConfigurationValue(OCSP_PKCS_12_PASSWD, "OCSPAccessCertificatePassword");
setConfigurationValue("CONNECTION_TIMEOUT", "connectionTimeout");
setConfigurationValue("SOCKET_TIMEOUT", "socketTimeout");
setConfigurationValue(SIGN_OCSP_REQUESTS, SIGN_OCSP_REQUESTS);
setConfigurationValue("TSL_KEYSTORE_LOCATION", "tslKeyStoreLocation");
setConfigurationValue("TSL_KEYSTORE_PASSWORD", "tslKeyStorePassword");
setConfigurationValue("TSL_CACHE_EXPIRATION_TIME", "tslCacheExpirationTime");
setConfigurationValue("REVOCATION_AND_TIMESTAMP_DELTA_IN_MINUTES", "revocationAndTimestampDeltaInMinutes");
setJDigiDocConfigurationValue(SIGN_OCSP_REQUESTS, Boolean.toString(hasToBeOCSPRequestSigned()));
setJDigiDocConfigurationValue(OCSP_PKCS_12_CONTAINER, getOCSPAccessCertificateFileName());
initOcspAccessCertPasswordForJDigidoc();
httpProxyHost = getParameterFromFile("HTTP_PROXY_HOST");
httpProxyPort = getIntParameterFromFile("HTTP_PROXY_PORT");
httpProxyUser = getParameterFromFile("HTTP_PROXY_USER");
httpProxyPassword = getParameterFromFile("HTTP_PROXY_PASSWORD");
sslKeystorePath = getParameterFromFile("SSL_KEYSTORE_PATH");
sslKeystoreType = getParameterFromFile("SSL_KEYSTORE_TYPE");
sslKeystorePassword = getParameterFromFile("SSL_KEYSTORE_PASSWORD");
sslTruststorePath = getParameterFromFile("SSL_TRUSTSTORE_PATH");
sslTruststoreType = getParameterFromFile("SSL_TRUSTSTORE_TYPE");
sslTruststorePassword = getParameterFromFile("SSL_TRUSTSTORE_PASSWORD");
updateTrustedTerritories();
}
private void updateTrustedTerritories() {
List<String> territories = getStringListParameterFromFile("TRUSTED_TERRITORIES");
if(territories != null) {
trustedTerritories = territories;
}
}
private String getParameterFromFile(String key) {
if (configurationFromFile == null) {
return null;
}
Object fileValue = configurationFromFile.get(key);
if (fileValue == null) {
return null;
}
String value = fileValue.toString();
if(valueIsAllowed(key, value)) {
return value;
}
return null;
}
private Integer getIntParameterFromFile(String key) {
String value = getParameterFromFile(key);
if(value == null) {
return null;
}
return new Integer(value);
}
private List<String> getStringListParameterFromFile(String key) {
String value = getParameterFromFile(key);
if(value == null) {
return null;
}
return Arrays.asList(value.split("\\s*,\\s*")); //Split by comma and trim whitespace
}
private void setConfigurationValue(String fileKey, String configurationKey) {
if (configurationFromFile == null) return;
Object fileValue = configurationFromFile.get(fileKey);
if (fileValue != null) {
configuration.put(configurationKey, fileValue.toString());
}
}
private void setJDigiDocConfigurationValue(String key, String defaultValue) {
String value = defaultIfNull(key, defaultValue);
if (value != null) {
jDigiDocConfiguration.put(key, value);
}
}
/**
* Enables big files support. Sets limit in MB when handling files are creating temporary file for streaming in
* container creation and adding data files.
* <p/>
* Used by DigiDoc4J and by JDigiDoc.
*
* @param maxFileSizeCachedInMB Maximum size in MB.
* @deprecated obnoxious naming. Use {@link Configuration#setMaxFileSizeCachedInMemoryInMB(long)} instead.
*/
@Deprecated
public void enableBigFilesSupport(long maxFileSizeCachedInMB) {
logger.debug("Set maximum datafile cached to: " + maxFileSizeCachedInMB);
String value = Long.toString(maxFileSizeCachedInMB);
if (isValidIntegerParameter("DIGIDOC_MAX_DATAFILE_CACHED", value)) {
jDigiDocConfiguration.put("DIGIDOC_MAX_DATAFILE_CACHED", value);
}
}
/**
* Sets limit in MB when handling files are creating temporary file for streaming in
* container creation and adding data files.
* <p/>
* Used by DigiDoc4J and by JDigiDoc.
*
* @param maxFileSizeCachedInMB maximum data file size in MB stored in memory.
*/
public void setMaxFileSizeCachedInMemoryInMB(long maxFileSizeCachedInMB) {
enableBigFilesSupport(maxFileSizeCachedInMB);
}
/**
* @return is big file support enabled
* @deprecated obnoxious naming. Use {@link Configuration#storeDataFilesOnlyInMemory()} instead.
*/
@Deprecated
public boolean isBigFilesSupportEnabled() {
return getMaxDataFileCachedInMB() >= 0;
}
/**
* If all the data files should be stored in memory. Default is true (data files are temporarily stored only in memory).
* @return true if everything is stored in memory, and false if data is temporarily stored on disk.
*/
public boolean storeDataFilesOnlyInMemory() {
long maxDataFileCachedInMB = getMaxDataFileCachedInMB();
return maxDataFileCachedInMB == -1 || maxDataFileCachedInMB == Long.MAX_VALUE;
}
/**
* Returns configuration item must be OCSP request signed. Reads it from configuration parameter SIGN_OCSP_REQUESTS.
* Default value is false for {@link Configuration.Mode#PROD} and false for {@link Configuration.Mode#TEST}
*
* @return must be OCSP request signed
*/
public boolean hasToBeOCSPRequestSigned() {
String signOcspRequests = getConfigurationParameter(SIGN_OCSP_REQUESTS);
return StringUtils.equalsIgnoreCase("true", signOcspRequests);
}
/**
* Get the maximum size of data files to be cached. Used by DigiDoc4J and by JDigiDoc.
*
* @return Size in MB. if size < 0 no caching is used
*/
public long getMaxDataFileCachedInMB() {
String maxDataFileCached = jDigiDocConfiguration.get("DIGIDOC_MAX_DATAFILE_CACHED");
logger.debug("Maximum datafile cached in MB: " + maxDataFileCached);
if (maxDataFileCached == null) return CACHE_ALL_DATA_FILES;
return Long.parseLong(maxDataFileCached);
}
/**
* Get the maximum size of data files to be cached. Used by DigiDoc4J and by JDigiDoc.
*
* @return Size in MB. if size < 0 no caching is used
*/
public long getMaxDataFileCachedInBytes() {
long maxDataFileCachedInMB = getMaxDataFileCachedInMB();
if (maxDataFileCachedInMB == CACHE_ALL_DATA_FILES) {
return CACHE_ALL_DATA_FILES;
} else {
return (maxDataFileCachedInMB * ONE_MB_IN_BYTES);
}
}
private String defaultIfNull(String configParameter, String defaultValue) {
logger.debug("Parameter: " + configParameter);
if (configurationFromFile == null) return defaultValue;
Object value = configurationFromFile.get(configParameter);
if (value != null) {
return valueIsAllowed(configParameter, value.toString()) ? value.toString() : "";
}
String configuredValue = jDigiDocConfiguration.get(configParameter);
return configuredValue != null ? configuredValue : defaultValue;
}
private boolean valueIsAllowed(String configParameter, String value) {
logger.debug("Parameter: " + configParameter + ", value: " + value);
List<String> mustBeBooleans =
asList(SIGN_OCSP_REQUESTS, "KEY_USAGE_CHECK", "DATAFILE_HASHCODE_MODE", "DIGIDOC_USE_LOCAL_TSL");
List<String> mustBeIntegers =
asList("DIGIDOC_MAX_DATAFILE_CACHED", "HTTP_PROXY_PORT");
boolean errorFound = false;
if (mustBeBooleans.contains(configParameter)) {
errorFound = !(isValidBooleanParameter(configParameter, value));
}
if (mustBeIntegers.contains(configParameter)) {
errorFound = !(isValidIntegerParameter(configParameter, value)) || errorFound;
}
return (!errorFound);
}
private boolean isValidBooleanParameter(String configParameter, String value) {
if (!("true".equals(value.toLowerCase()) || "false".equals(value.toLowerCase()))) {
String errorMessage = "Configuration parameter " + configParameter + " should be set to true or false"
+ " but the actual value is: " + value + ".";
logError(errorMessage);
return false;
}
return true;
}
private boolean isValidIntegerParameter(String configParameter, String value) {
Integer parameterValue;
try {
parameterValue = Integer.parseInt(value);
} catch (Exception e) {
String errorMessage = "Configuration parameter " + configParameter + " should have an integer value"
+ " but the actual value is: " + value + ".";
logError(errorMessage);
return false;
}
if (configParameter.equals("DIGIDOC_MAX_DATAFILE_CACHED") && parameterValue < -1) {
String errorMessage = "Configuration parameter " + configParameter + " should be greater or equal -1"
+ " but the actual value is: " + value + ".";
logError(errorMessage);
return false;
}
return true;
}
private void loadOCSPCertificates(LinkedHashMap digiDocCA, String caPrefix) {
logger.debug("");
String errorMessage;
@SuppressWarnings("unchecked")
ArrayList<LinkedHashMap> ocsps = (ArrayList<LinkedHashMap>) digiDocCA.get("OCSPS");
if (ocsps == null) {
errorMessage = "No OCSPS entry found or OCSPS entry is empty. Configuration from: "
+ configurationInputSourceName;
logError(errorMessage);
return;
}
int numberOfOCSPCertificates = ocsps.size();
jDigiDocConfiguration.put(caPrefix + "_OCSPS", String.valueOf(numberOfOCSPCertificates));
for (int i = 1; i <= numberOfOCSPCertificates; i++) {
String prefix = caPrefix + "_OCSP" + i;
LinkedHashMap ocsp = ocsps.get(i - 1);
List<String> entries = asList("CA_CN", "CA_CERT", "CN", "URL");
for (String entry : entries) {
if (!loadOCSPCertificateEntry(entry, ocsp, prefix)) {
errorMessage = "OCSPS list entry " + i + " does not have an entry for " + entry
+ " or the entry is empty\n";
logError(errorMessage);
}
}
if (!getOCSPCertificates(prefix, ocsp)) {
errorMessage = "OCSPS list entry " + i + " does not have an entry for CERTS or the entry is empty\n";
logError(errorMessage);
}
}
}
private boolean loadOCSPCertificateEntry(String ocspsEntryName, LinkedHashMap ocsp, String prefix) {
Object ocspEntry = ocsp.get(ocspsEntryName);
if (ocspEntry == null) return false;
jDigiDocConfiguration.put(prefix + "_" + ocspsEntryName, ocspEntry.toString());
return true;
}
@SuppressWarnings("unchecked")
private boolean getOCSPCertificates(String prefix, LinkedHashMap ocsp) {
ArrayList<String> certificates = (ArrayList<String>) ocsp.get("CERTS");
if (certificates == null) return false;
for (int j = 0; j < certificates.size(); j++) {
if (j == 0) {
jDigiDocConfiguration.put(prefix + "_CERT", certificates.get(0));
} else {
jDigiDocConfiguration.put(prefix + "_CERT_" + j, certificates.get(j));
}
}
return true;
}
private void loadCertificateAuthorityCerts(LinkedHashMap digiDocCA, String caPrefix) {
logger.debug("");
ArrayList<String> certificateAuthorityCerts = getCACertsAsArray(digiDocCA);
jDigiDocConfiguration.put(caPrefix + "_NAME", digiDocCA.get("NAME").toString());
jDigiDocConfiguration.put(caPrefix + "_TRADENAME", digiDocCA.get("TRADENAME").toString());
int numberOfCACertificates = certificateAuthorityCerts.size();
jDigiDocConfiguration.put(caPrefix + "_CERTS", String.valueOf(numberOfCACertificates));
for (int i = 0; i < numberOfCACertificates; i++) {
String certFile = certificateAuthorityCerts.get(i);
jDigiDocConfiguration.put(caPrefix + "_CERT" + (i + 1), certFile);
}
}
@SuppressWarnings("unchecked")
private ArrayList<String> getCACertsAsArray(LinkedHashMap digiDocCa) {
return (ArrayList<String>) digiDocCa.get("CERTS");
}
public String getTslLocation() {
String urlString = getConfigurationParameter("tslLocation");
if (!Protocol.isFileUrl(urlString)) return urlString;
try {
String filePath = new URL(urlString).getPath();
if (!new File(filePath).exists()) {
URL resource = getClass().getClassLoader().getResource(filePath);
if (resource != null)
urlString = resource.toString();
}
} catch (MalformedURLException e) {
logger.warn(e.getMessage());
}
return urlString;
}
/**
* Set the TSL certificate source.
*
* @param certificateSource TSL certificate source
* When certificateSource equals null then getTSL() will load the TSL according to the TSL
* location specified .
*/
public void setTSL(TSLCertificateSource certificateSource) {
tslManager.setTsl(certificateSource);
}
/**
* Loads TSL certificates
* If configuration mode is TEST then TSL signature is not checked.
*
* @return TSL source
*/
public TSLCertificateSource getTSL() {
return tslManager.getTsl();
}
public boolean shouldValidateTslSignature() {
return mode != Mode.TEST;
}
/**
* Set the TSL location.
* TSL can be loaded from file (file://) or from web (http://). If file protocol is used then
* first try is to locate file from this location if file does not exist then it tries to load
* relatively from classpath.
* <p/>
* Setting new location clears old values
* <p/>
* Windows wants it in file:DRIVE:/directories/tsl-file.xml format
*
* @param tslLocation TSL Location to be used
*/
public void setTslLocation(String tslLocation) {
logger.debug("Set TSL location: " + tslLocation);
setConfigurationParameter("tslLocation", tslLocation);
tslManager.setTsl(null);
}
/**
* Get the TSP Source
*
* @return TSP Source
*/
public String getTspSource() {
String tspSource = getConfigurationParameter("tspSource");
logger.debug("TSP Source: " + tspSource);
return tspSource;
}
/**
* Set HTTP connection timeout
* @param connectionTimeout connection timeout in milliseconds
*/
public void setConnectionTimeout(int connectionTimeout) {
logger.debug("Set connection timeout to " + connectionTimeout + " ms");
setConfigurationParameter("connectionTimeout", String.valueOf(connectionTimeout));
}
/**
* Set HTTP socket timeout
* @param socketTimeoutMilliseconds socket timeout in milliseconds
*/
public void setSocketTimeout(int socketTimeoutMilliseconds) {
logger.debug("Set socket timeout to " + socketTimeoutMilliseconds + " ms");
setConfigurationParameter("socketTimeout", String.valueOf(socketTimeoutMilliseconds));
}
/**
* Get HTTP connection timeout
*
* @return connection timeout in milliseconds
*/
public int getConnectionTimeout() {
return Integer.parseInt(getConfigurationParameter("connectionTimeout"));
}
/**
* Get HTTP socket timeout
*
* @return socket timeout in milliseconds
*/
public int getSocketTimeout() {
return Integer.parseInt(getConfigurationParameter("socketTimeout"));
}
/**
* Set the TSP Source
*
* @param tspSource TSPSource to be used
*/
public void setTspSource(String tspSource) {
logger.debug("Set TSP source: " + tspSource);
setConfigurationParameter("tspSource", tspSource);
}
/**
* Get the OCSP Source
*
* @return OCSP Source
*/
public String getOcspSource() {
String ocspSource = getConfigurationParameter("ocspSource");
logger.debug("OCSP source: " + ocspSource);
return ocspSource;
}
/**
* Set the KeyStore Location that holds potential TSL Signing certificates
* @param tslKeyStoreLocation KeyStore location to use
*/
public void setTslKeyStoreLocation(String tslKeyStoreLocation) {
logger.debug("Set tsl KeyStore Location: " + tslKeyStoreLocation);
setConfigurationParameter("tslKeyStoreLocation", tslKeyStoreLocation);
}
/**
* Get the Location to Keystore that holds potential TSL Signing certificates
* @return KeyStore Location
*/
public String getTslKeyStoreLocation() {
String keystoreLocation = getConfigurationParameter("tslKeyStoreLocation");
logger.debug("tsl KeyStore Location: " + keystoreLocation);
return keystoreLocation;
}
/**
* Set the password for Keystore that holds potential TSL Signing certificates
* @param tslKeyStorePassword Keystore password
*/
public void setTslKeyStorePassword(String tslKeyStorePassword) {
logger.debug("Set tsl KeyStore Password: " + tslKeyStorePassword);
setConfigurationParameter("tslKeyStorePassword", tslKeyStorePassword);
}
/**
* Get the password for Keystore that holds potential TSL Signing certificates
* @return Tsl Keystore password
*/
public String getTslKeyStorePassword() {
String keystorePassword = getConfigurationParameter("tslKeyStorePassword");
logger.debug("tsl KeyStore Password: " + keystorePassword);
return keystorePassword;
}
/**
* Sets the expiration time for TSL cache in milliseconds.
* If more time has passed from the cache's creation time time, then a fresh TSL is downloaded and cached,
* otherwise a cached copy is used.
*
* @param cacheExpirationTimeInMilliseconds cache expiration time in milliseconds
*/
public void setTslCacheExpirationTime(long cacheExpirationTimeInMilliseconds) {
logger.debug("Setting TSL cache expiration time in milliseconds: " + cacheExpirationTimeInMilliseconds);
setConfigurationParameter("tslCacheExpirationTime", String.valueOf(cacheExpirationTimeInMilliseconds));
}
/**
* Returns TSL cache expiration time in milliseconds.
*
* @return TSL cache expiration time in milliseconds.
*/
public long getTslCacheExpirationTime() {
String tslCacheExpirationTime = getConfigurationParameter("tslCacheExpirationTime");
logger.debug("TSL cache expiration time in milliseconds: " + tslCacheExpirationTime);
return Long.parseLong(tslCacheExpirationTime);
}
/**
* Set the OCSP source
*
* @param ocspSource OCSP Source to be used
*/
public void setOcspSource(String ocspSource) {
logger.debug("Set OCSP source: " + ocspSource);
setConfigurationParameter("ocspSource", ocspSource);
}
/**
* Get the validation policy
*
* @return Validation policy
*/
public String getValidationPolicy() {
String validationPolicy = getConfigurationParameter("validationPolicy");
logger.debug("Validation policy: " + validationPolicy);
return validationPolicy;
}
/**
* Set the validation policy
*
* @param validationPolicy Policy to be used
*/
public void setValidationPolicy(String validationPolicy) {
logger.debug("Set validation policy: " + validationPolicy);
setConfigurationParameter("validationPolicy", validationPolicy);
}
public int getRevocationAndTimestampDeltaInMinutes() {
String timeDelta = getConfigurationParameter("revocationAndTimestampDeltaInMinutes");
logger.debug("Revocation and timestamp delta in minutes: " + timeDelta);
return Integer.parseInt(timeDelta);
}
public void setRevocationAndTimestampDeltaInMinutes(int timeInMinutes) {
logger.debug("Set revocation and timestamp delta in minutes: " + timeInMinutes);
setConfigurationParameter("revocationAndTimestampDeltaInMinutes", String.valueOf(timeInMinutes));
}
public String getHttpProxyHost() {
return httpProxyHost;
}
/**
* Set HTTP network proxy host.
* @param httpProxyHost http proxy host.
*/
public void setHttpProxyHost(String httpProxyHost) {
this.httpProxyHost = httpProxyHost;
}
public Integer getHttpProxyPort() {
return httpProxyPort;
}
/**
* Set HTTP network proxy port.
*/
public void setHttpProxyPort(int httpProxyPort) {
this.httpProxyPort = httpProxyPort;
}
/**
* Set HTTP network proxy user name.
* @param httpProxyUser username.
*/
public void setHttpProxyUser(String httpProxyUser) {
this.httpProxyUser = httpProxyUser;
}
public String getHttpProxyUser() {
return httpProxyUser;
}
/**
* Set HTTP network proxy password.
* @param httpProxyPassword password.
*/
public void setHttpProxyPassword(String httpProxyPassword) {
this.httpProxyPassword = httpProxyPassword;
}
public String getHttpProxyPassword() {
return httpProxyPassword;
}
public boolean isNetworkProxyEnabled() {
return httpProxyPort != null && isNotBlank(httpProxyHost);
}
public boolean isSslConfigurationEnabled() {
return sslKeystorePath != null && isNotBlank(sslKeystorePath);
}
/**
* Set SSL KeyStore path.
* @param sslKeystorePath path to a file
*/
public void setSslKeystorePath(String sslKeystorePath) {
this.sslKeystorePath = sslKeystorePath;
}
/**
* Get SSL KeyStore path.
* @return path to a file
*/
public String getSslKeystorePath() {
return sslKeystorePath;
}
/**
* Set SSL KeyStore type. Default is "jks".
* @param sslKeystoreType type.
*/
public void setSslKeystoreType(String sslKeystoreType) {
this.sslKeystoreType = sslKeystoreType;
}
/**
* Get SSL KeyStore type.
* @return type.
*/
public String getSslKeystoreType() {
return sslKeystoreType;
}
/**
* Set SSL KeyStore password. Default is an empty string.
* @param sslKeystorePassword password.
*/
public void setSslKeystorePassword(String sslKeystorePassword) {
this.sslKeystorePassword = sslKeystorePassword;
}
public String getSslKeystorePassword() {
return sslKeystorePassword;
}
/**
* Set SSL TrustStore path.
* @param sslTruststorePath path to a file.
*/
public void setSslTruststorePath(String sslTruststorePath) {
this.sslTruststorePath = sslTruststorePath;
}
/**
* Get SSL TrustStore path.
* @return path to a file.
*/
public String getSslTruststorePath() {
return sslTruststorePath;
}
/**
* Set SSL TrustStore type. Default is "jks".
* @param sslTruststoreType type.
*/
public void setSslTruststoreType(String sslTruststoreType) {
this.sslTruststoreType = sslTruststoreType;
}
/**
* Get SSL TrustStore type.
* @return type.
*/
public String getSslTruststoreType() {
return sslTruststoreType;
}
/**
* Set SSL TrustStore password. Default is an empty string.
* @param sslTruststorePassword password.
*/
public void setSslTruststorePassword(String sslTruststorePassword) {
this.sslTruststorePassword = sslTruststorePassword;
}
public String getSslTruststorePassword() {
return sslTruststorePassword;
}
public void setThreadExecutor(ExecutorService threadExecutor) {
this.threadExecutor = threadExecutor;
}
public ExecutorService getThreadExecutor() {
return threadExecutor;
}
/**
* Set countries and territories (2 letter country codes) whom to trust and accept certificates.
* <p/>
* It is possible accept signatures (and certificates) only from particular countries by filtering
* trusted territories. Only the TSL (and certificates) from those countries are then downloaded and
* others are skipped.
* <p/>
* For example, it is possible to trust signatures only from these three countries: Estonia, Latvia and France,
* and skip all other countries: "EE", "LV", "FR".
*
* @param trustedTerritories list of 2 letter country codes.
*/
public void setTrustedTerritories(String... trustedTerritories) {
this.trustedTerritories = Arrays.asList(trustedTerritories);
}
public List<String> getTrustedTerritories() {
return trustedTerritories;
}
private void setConfigurationParameter(String key, String value) {
logger.debug("Key: " + key + ", value: " + value);
configuration.put(key, value);
}
private String getConfigurationParameter(String key) {
logger.debug("Key: " + key);
String value = configuration.get(key);
logger.debug("Value: " + value);
return value;
}
/**
* @return true when configuration is Configuration.Mode.TEST
* @see Configuration.Mode#TEST
*/
public boolean isTest() {
boolean isTest = mode == Mode.TEST;
logger.debug("Is test: " + isTest);
return isTest;
}
/**
* Clones configuration
*
* @return new configuration object
*/
public Configuration copy() {
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
Configuration copyConfiguration = null;
// deep copy
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
oos.flush();
ByteArrayInputStream bin =
new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bin);
copyConfiguration = (Configuration) ois.readObject();
} catch (Exception e) {
throw new DigiDoc4JException(e);
} finally {
IOUtils.closeQuietly(oos);
IOUtils.closeQuietly(ois);
IOUtils.closeQuietly(bos);
}
return copyConfiguration;
}
private void initOcspAccessCertPasswordForJDigidoc() {
char[] ocspAccessCertificatePassword = getOCSPAccessCertificatePassword();
if(ocspAccessCertificatePassword != null && ocspAccessCertificatePassword.length > 0) {
setJDigiDocConfigurationValue(OCSP_PKCS_12_PASSWD, String.valueOf(ocspAccessCertificatePassword));
}
}
}