diff options
Diffstat (limited to 'eaaf_core_utils/src/main')
24 files changed, 2474 insertions, 249 deletions
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/exception/EaafKeyUsageException.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/exception/EaafKeyUsageException.java new file mode 100644 index 00000000..8b4e68a4 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/exception/EaafKeyUsageException.java @@ -0,0 +1,21 @@ +package at.gv.egiz.eaaf.core.exception; + +import at.gv.egiz.eaaf.core.exceptions.EaafException; + +public class EaafKeyUsageException extends EaafException { + + private static final long serialVersionUID = -2641273589744430903L; + + public static final String ERROR_CODE_01 = "internal.key.01"; + + public EaafKeyUsageException(String errorCode, String... params) { + super(errorCode, new Object[] {params}); + + } + + public EaafKeyUsageException(String errorCode, Throwable e, String... params) { + super(errorCode, new Object[] {params}, e); + + } + +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/builder/BpkBuilder.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/builder/BpkBuilder.java new file mode 100644 index 00000000..903aa300 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/builder/BpkBuilder.java @@ -0,0 +1,446 @@ +/* + * Copyright 2014 Federal Chancellery Austria MOA-ID has been developed in a cooperation between + * BRZ, the Federal Chancellery Austria - ICT staff unit, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the European + * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + * + * This product combines work with different licenses. See the "NOTICE" text file for details on the + * various modules and licenses. The "NOTICE" text file is part of the distribution. Any derivative + * works that you distribute must include a readable copy of the "NOTICE" text file. +*/ + +package at.gv.egiz.eaaf.core.impl.builder; + +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map.Entry; + +import javax.annotation.Nonnull; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.Base64Utils; + +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.core.exceptions.EaafBuilderException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import lombok.extern.slf4j.Slf4j; + + +/** + * Builder for the bPK, as defined in + * <code>"Ableitung f¨r die bereichsspezifische Personenkennzeichnung"</code> + * version <code>1.0.1</code> from + * <code>"reference.e-government.gv.at"</code>. + * + */ +@Slf4j +public class BpkBuilder { + + private static final String ERROR_CODE_33 = "builder.33"; + + private static final String ERROR_MSG_WRONG_TARGET_FORMAT = "bPK-target format must be full URI"; + + + /** + * Calculates an area specific unique person-identifier from a baseID. + * + * @param baseID baseId from user but never null + * @param targetIdentifier target identifier for area specific identifier + * calculation but never null + * @return Pair consists of (unique person identifier for this target, + * targetArea) but never null + * @throws EaafBuilderException if some input data are not valid + */ + public static Pair<String, String> generateAreaSpecificPersonIdentifier(final String baseID, + final String targetIdentifier) throws EaafBuilderException { + return generateAreaSpecificPersonIdentifier(baseID, EaafConstants.URN_PREFIX_BASEID, + targetIdentifier); + + } + + /** + * Calculates an area specific unique person-identifier from an unique + * identifier with a specific type. + * + * @param baseID baseId from user but never null + * @param baseIdType Type of the baseID but never null + * @param targetIdentifier target identifier for area specific identifier + * calculation but never null + * @return Pair consists of (unique person identifier for this target, + * targetArea) but never null + * @throws EaafBuilderException if some input data are not valid + */ + public static Pair<String, String> generateAreaSpecificPersonIdentifier(final String baseID, + final String baseIdType, final String targetIdentifier) throws EaafBuilderException { + if (StringUtils.isEmpty(baseID)) { + throw new EaafBuilderException(ERROR_CODE_33, new Object[] { "baseID is empty or null" }, + "BaseId is empty or null"); + } + + if (StringUtils.isEmpty(baseIdType)) { + throw new EaafBuilderException(ERROR_CODE_33, + new Object[] { "the type of baseID is empty or null" }, "Type of baseId is empty or null"); + } + + if (StringUtils.isEmpty(targetIdentifier)) { + throw new EaafBuilderException(ERROR_CODE_33, + new Object[] { "SP specific target identifier is empty or null" }, + "SP specific target identifier is empty or null"); + } + + if (baseIdType.startsWith(EaafConstants.URN_PREFIX_BASEID)) { + log.trace("Find baseID. Starting unique identifier caluclation for this target"); + + if (targetIdentifier.startsWith(EaafConstants.URN_PREFIX_CDID)) { + log.trace("Calculate bPK identifier for target: " + targetIdentifier); + return Pair.newInstance(calculatebPKwbPK(baseID + "+" + targetIdentifier), + targetIdentifier); + + } else if (targetIdentifier.startsWith(EaafConstants.URN_PREFIX_WBPK)) { + log.trace("Calculate wbPK identifier for target: " + targetIdentifier); + String commonBpkTarget = normalizeBpkTargetIdentifierToCommonFormat(targetIdentifier); + return Pair.newInstance(calculatebPKwbPK( + baseID + "+" + normalizeBpkTargetIdentifierToBpkCalculationFormat(commonBpkTarget)), + commonBpkTarget); + + } else if (targetIdentifier.startsWith(EaafConstants.URN_PREFIX_EIDAS)) { + log.trace("Calculate eIDAS identifier for target: " + targetIdentifier); + final String[] splittedTarget = targetIdentifier.split("\\+"); + final String cititzenCountryCode = splittedTarget[1]; + final String eidasOutboundCountry = splittedTarget[2]; + + if (cititzenCountryCode.equalsIgnoreCase(eidasOutboundCountry)) { + log.warn("Suspect configuration FOUND!!! CitizenCountry equals DestinationCountry"); + + } + return buildEidasIdentifer(baseID, baseIdType, cititzenCountryCode, eidasOutboundCountry); + + } else { + throw new EaafBuilderException(ERROR_CODE_33, + new Object[] { "Target identifier: " + targetIdentifier + " is NOT allowed or unknown" }, + "Target identifier: " + targetIdentifier + " is NOT allowed or unknown"); + } + + } else { + log.trace("BaseID is not of type " + EaafConstants.URN_PREFIX_BASEID + + ". Check type against requested target ..."); + if (baseIdType.equals(targetIdentifier)) { + log.debug("Unique identifier is already area specific. Is nothing todo"); + return Pair.newInstance(baseID, targetIdentifier); + + } else { + log.warn("Get unique identifier for target: " + baseIdType + " but target: " + + targetIdentifier + " is required!"); + throw new EaafBuilderException(ERROR_CODE_33, + new Object[] { "Get unique identifier for target: " + baseIdType + " but target: " + + targetIdentifier + " is required" }, + "Get unique identifier for target: " + baseIdType + " but target: " + targetIdentifier + + " is required"); + + } + } + } + + + + /** + * Create an encrypted bPK. + * + * @param bpk unencrypted bPK + * @param target bPK target in full form + * @param publicKey Public-Key used for encryption + * @return encrypted bPK + * @throws EaafBuilderException In case of an error + */ + public static String encryptBpk(final String bpk, String target, final PublicKey publicKey) + throws EaafBuilderException { + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + if (!target.startsWith(EaafConstants.URN_PREFIX_WITH_COLON)) { + throw new EaafBuilderException("builder.32", + null, ERROR_MSG_WRONG_TARGET_FORMAT); + + } + + target = normalizeBpkTargetIdentifierToBpkCalculationFormat( + normalizeBpkTargetIdentifierToCommonFormat(target)); + + final String input = + "V1::" + target + "::" + bpk + "::" + sdf.format(new Date()); + // System.out.println(input); + byte[] result; + try { + final byte[] inputBytes = input.getBytes("ISO-8859-1"); + result = encrypt(inputBytes, publicKey); + return new String(Base64Utils.encode(result), "ISO-8859-1").replaceAll("\r\n", ""); + // return new String(Base64Utils.encode(result, + // "ISO-8859-1")).replaceAll("\r\n", ""); + + } catch (final Exception e) { + throw new EaafBuilderException("bPK encryption FAILED", null, e.getMessage(), e); + + } + } + + /** + * Decrypt an encrypted bPK. + * + * @param encryptedBpk encrypted bPK + * @param target bPK target in full form + * @param privateKey private-key for decryption + * @return bPK Pair consists of (unique person identifier for this target, + * targetArea) but never null + * @throws EaafBuilderException In case of an error + */ + public static Pair<String, String> decryptBpk(final String encryptedBpk, String target, + final PrivateKey privateKey) throws EaafBuilderException { + String decryptedString; + + if (!target.startsWith(EaafConstants.URN_PREFIX_WITH_COLON)) { + throw new EaafBuilderException("builder.32", + null, ERROR_MSG_WRONG_TARGET_FORMAT); + + } + + try { + final byte[] encryptedBytes = Base64Utils.decode(encryptedBpk.getBytes("ISO-8859-1")); + final byte[] decryptedBytes = decrypt(encryptedBytes, privateKey); + decryptedString = new String(decryptedBytes, "ISO-8859-1"); + + } catch (final Exception e) { + throw new EaafBuilderException("bPK decryption FAILED", null, e.getMessage(), e); + + } + + String[] parts = decryptedString.split("::"); + if (parts.length != 4) { + log.trace("Encrypted bPK has value: {}", decryptedString); + throw new EaafBuilderException("builder.31", new Object[] {parts.length}, + "encBpk has a suspect format"); + + } + + final String sector = parts[1]; + final String bPK = parts[2]; + + if (target.equals(normalizeBpkTargetIdentifierToCommonFormat(sector))) { + return Pair.newInstance(bPK, target); + + } else { + throw new EaafBuilderException("builder.30", new Object[] {sector, target}, + "Decrypted bPK-target does not match"); + + } + } + + /** + * Normalize wbPK target identifier for FN, ZVR, and ERSB to XFN, XZVR, and XERSB. + * + * <p>If the target is not of this types the target will be returned as it is</p> + * @param targetIdentifier bPK input target + * @return XFN, XZVR, XERSB, or targetIdentfier if no normalization is required + */ + @Nullable + public static String normalizeBpkTargetIdentifierToCommonFormat(@Nullable String targetIdentifier) { + if (targetIdentifier != null + && !targetIdentifier.startsWith(EaafConstants.URN_PREFIX_WBPK_TARGET_WITH_X)) { + for (Entry<String, String> mapper : EaafConstants.URN_WBPK_TARGET_X_TO_NONE_MAPPER.entrySet()) { + if (targetIdentifier.startsWith(mapper.getValue())) { + String wbpkTarget = mapper.getKey() + targetIdentifier.substring(mapper.getValue().length()); + log.trace("Normalize wbPK target: {} to {}", targetIdentifier, wbpkTarget); + return wbpkTarget; + + } + } + } + + return targetIdentifier; + } + + /** + * Normalize wbPK target identifier for XFN, XZVR, and XERSB to bPK non-X format like, FN, ZVR, and ERSB. + * + * <p>If the target is not of this types the target will be returned as it is</p> + * + * @param targetIdentifier bPK input target + * @return FN, ZVR, ERSB, or targetIdentfier if no normalization is required + */ + @Nullable + public static String normalizeBpkTargetIdentifierToNonXFormat(@Nullable String targetIdentifier) { + if (targetIdentifier != null && targetIdentifier.startsWith(EaafConstants.URN_PREFIX_WBPK)) { + for (Entry<String, String> mapper : EaafConstants.URN_WBPK_TARGET_X_TO_NONE_MAPPER.entrySet()) { + if (targetIdentifier.startsWith(mapper.getKey())) { + String wbpkTarget = mapper.getValue() + targetIdentifier.substring(mapper.getKey().length()); + log.trace("Find new wbPK target: {}. Replace it by: {}", targetIdentifier, wbpkTarget); + return wbpkTarget; + + } + } + } + + return targetIdentifier; + } + + /** + * Normalize wbPK target identifier for XFN, XZVR, and XERSB to bPK calculation format like, FN, VR, and ERJ. + * + * <p>If the target is not of this types the target will be returned as it is</p> + * + * @param targetIdentifier bPK input target + * @return FN, VR, ERJ, or targetIdentfier if no normalization is required + */ + @Nullable + public static String normalizeBpkTargetIdentifierToBpkCalculationFormat(@Nullable String targetIdentifier) { + if (targetIdentifier != null && targetIdentifier.startsWith(EaafConstants.URN_PREFIX_WBPK)) { + for (Entry<String, String> mapper : EaafConstants.URN_WBPK_TARGET_X_TO_CALC_TARGET_MAPPER.entrySet()) { + if (targetIdentifier.startsWith(mapper.getKey())) { + String wbpkTarget = mapper.getValue() + targetIdentifier.substring(mapper.getKey().length()); + log.trace("Find new wbPK target: {}. Replace it by: {}", targetIdentifier, wbpkTarget); + return wbpkTarget; + + } + } + } + + return targetIdentifier; + } + + /** + * Remove prefixes from bPK target identifier and get only the SP specific part. + * + * @param type full qualified bPK target with 'urn:publicid:gv.at:' prefix + * @return SP specific part, or full type if reduction is not supported + */ + @Nonnull + public static String removeBpkTypePrefix(@Nonnull final String type) { + Assert.isTrue(type != null, "bPKType is 'NULL'"); + if (type.startsWith(EaafConstants.URN_PREFIX_WBPK)) { + return type.substring(EaafConstants.URN_PREFIX_WBPK.length()); + + } else if (type.startsWith(EaafConstants.URN_PREFIX_CDID)) { + return type.substring(EaafConstants.URN_PREFIX_CDID.length()); + + } else if (type.startsWith(EaafConstants.URN_PREFIX_EIDAS)) { + return type.substring(EaafConstants.URN_PREFIX_EIDAS.length()); + + } else { + return type; + + } + } + + /** + * Builds the eIDAS from the given parameters. + * + * @param baseId baseID of the citizen + * @param baseIdType Type of the baseID + * @param sourceCountry CountryCode of that country, which build the eIDAs + * ID + * @param destinationCountry CountryCode of that country, which receives the + * eIDAs ID + * + * @return Pair eIDAs/bPKType in a BASE64 encoding + * @throws EaafBuilderException if some input data are not valid + */ + private static Pair<String, String> buildEidasIdentifer(final String baseId, + final String baseIdType, final String sourceCountry, final String destinationCountry) + throws EaafBuilderException { + String bpk = null; + String bpkType = null; + + // check if we have been called by public sector application + if (baseIdType.startsWith(EaafConstants.URN_PREFIX_BASEID)) { + bpkType = EaafConstants.URN_PREFIX_EIDAS + sourceCountry + "+" + destinationCountry; + log.debug("Building eIDAS identification from: [identValue]+" + bpkType); + bpk = calculatebPKwbPK(baseId + "+" + bpkType); + + } else { // if not, sector identification value is already calculated by BKU + log.debug("eIDAS eIdentifier already provided by BKU"); + bpk = baseId; + } + + if (StringUtils.isEmpty(bpk) || StringUtils.isEmpty(sourceCountry) + || StringUtils.isEmpty(destinationCountry)) { + throw new EaafBuilderException("builder.00", + new Object[] { "eIDAS-ID", + "Unvollständige Parameterangaben: identificationValue=" + bpk + ", Zielland=" + + destinationCountry + ", Ursprungsland=" + sourceCountry }, + "eIDAS-ID: Unvollständige Parameterangaben: identificationValue=" + bpk + ", Zielland=" + + destinationCountry + ", Ursprungsland=" + sourceCountry); + } + + log.trace("eIDAS pseudonym generation finished. "); + final String eIdentifier = sourceCountry + "/" + destinationCountry + "/" + bpk; + + return Pair.newInstance(eIdentifier, bpkType); + } + + private static String calculatebPKwbPK(final String basisbegriff) throws EaafBuilderException { + try { + final MessageDigest md = MessageDigest.getInstance("SHA-1"); + final byte[] hash = md.digest(basisbegriff.getBytes("ISO-8859-1")); + final String hashBase64 = + new String(Base64Utils.encode(hash), "ISO-8859-1").replaceAll("\r\n", ""); // Base64Utils.encode(hash); + return hashBase64; + + } catch (final Exception ex) { + throw new EaafBuilderException(ERROR_CODE_33, new Object[] {ex.toString() }, + ex.getMessage(), ex); + + } + + } + + private static byte[] encrypt(final byte[] inputBytes, final PublicKey publicKey) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, + IllegalBlockSizeException, BadPaddingException { + byte[] result; + Cipher cipher = null; + try { + cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle + + } catch (final NoSuchAlgorithmException e) { + cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider + } + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + result = cipher.doFinal(inputBytes); + + return result; + } + + private static byte[] decrypt(final byte[] encryptedBytes, final PrivateKey privateKey) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, + IllegalBlockSizeException, BadPaddingException { + byte[] result; + Cipher cipher = null; + try { + cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle + + } catch (final NoSuchAlgorithmException e) { + cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider + + } + cipher.init(Cipher.DECRYPT_MODE, privateKey); + result = cipher.doFinal(encryptedBytes); + return result; + + } +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreFactory.java index e60c326c..623e9d2c 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreFactory.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreFactory.java @@ -2,37 +2,46 @@ package at.gv.egiz.eaaf.core.impl.credential; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.security.Key; import java.security.KeyStore; +import java.security.KeyStore.LoadStoreParameter; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Provider; import java.security.Security; +import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.PostConstruct; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; -import at.asitplus.hsmfacade.provider.HsmFacadeProvider; -import at.asitplus.hsmfacade.provider.RemoteKeyStoreLoadParameter; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exception.EaafKeyAccessException; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.exceptions.EaafFactoryException; import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; +import at.gv.egiz.eaaf.core.impl.credential.SymmetricKeyConfiguration.SymmetricKeyType; import at.gv.egiz.eaaf.core.impl.data.Pair; import at.gv.egiz.eaaf.core.impl.utils.FileUtils; import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; - import lombok.extern.slf4j.Slf4j; @Slf4j @@ -43,6 +52,7 @@ public class EaafKeyStoreFactory { public static final String CONFIG_PROP_HSM_FACADE_SSLTRUST = "security.hsmfacade.trustedsslcert"; public static final String CONFIG_PROP_HSM_FACADE_CLIENT_USERNAME = "security.hsmfacade.username"; public static final String CONFIG_PROP_HSM_FACADE_CLIENT_PASSWORD = "security.hsmfacade.password"; + public static final String CONFIG_PROP_HSM_FACADE_GRPC_DEADLINE = "security.hsmfacade.grpc.deadline"; public static final String ERRORCODE_00 = "internal.keystore.00"; public static final String ERRORCODE_01 = "internal.keystore.01"; @@ -52,10 +62,26 @@ public class EaafKeyStoreFactory { public static final String ERRORCODE_05 = "internal.keystore.05"; public static final String ERRORCODE_06 = "internal.keystore.06"; public static final String ERRORCODE_07 = "internal.keystore.07"; - + public static final String ERRORCODE_10 = "internal.keystore.10"; + public static final String ERRORCODE_11 = "internal.keystore.11"; + + public static final String ERRORCODE_KEY_00 = "internal.key.00"; + + private static final String HSM_FACADE_PROVIDER_CLASS = "at.asitplus.hsmfacade.provider.HsmFacadeProvider"; + private static final String HSM_FACADE_KEYSTORELOADPARAMETERS_CLASS + = "at.asitplus.hsmfacade.provider.RemoteKeyStoreLoadParameter"; + private static final String HSM_FACADE_PROVIDER_METHOD_CONSTRUCT = "getInstance"; + private static final String HSM_FACADE_PROVIDER_METHOD_INIT = "init"; + private static final String HSM_FACADE_PROVIDER_METHOD_ISINITIALIZED = "isInitialized"; + private static final String HSM_FACADE_PROVIDER_METHOD_HEALTHCHECK = "healthcheck"; + private static final String HSM_FACADE_PROVIDER_INIT_ERROR_MSG + = "Has HSM-Facade class supported '{}' method: {}"; private static final String HSM_FACADE_PROVIDER = "HsmFacade"; private static final String HSM_FACADE_KEYSTORE_TYPE = "RemoteKeyStore"; - + private static final String HSM_FACADE_DEFAULT_DEADLINE = "30"; + + public enum HsmFacadeStatus { UP, DOWN, UNKNOWN } + @Autowired private IConfiguration basicConfig; @Autowired @@ -64,6 +90,43 @@ public class EaafKeyStoreFactory { private boolean isHsmFacadeInitialized = false; /** + * Get a new symmetric key based on a {@link SymmetricKeyConfiguration} object. + * + * @param config Symmetric key configuration + * @return {@link Pair} of a new {@link SecretKey} instance and an optional {@link Provider}. + * The {@link SecretKey} is {@link Nonnull}. If the {@link Provider} is not <code>null</code> + * this {@link SecretKey} requires a specific {@link Provider} for {@link Key} operations. + * @throws EaafException In case of a KeyStore initialization error + */ + @Nonnull + public Pair<SecretKey, Provider> buildNewSymmetricKey(SymmetricKeyConfiguration config) throws EaafException { + log.trace("Starting symmetric-key generation based on configuration object ... "); + if (SymmetricKeyType.PASSPHRASE.equals(config.getKeyType())) { + return generatePassPhraseBasedSymmetricKey(config); + + } else if (SymmetricKeyType.HSMFACADE.equals(config.getKeyType())) { + if (isHsmFacadeInitialized) { + return getSymmetricKeyFromHsmFacade(config); + + } else { + log.error("HSMFacade can NOT be used for symmetric Key: {} because {} is not initialized", + config.getFriendlyName()); + throw new EaafConfigurationException(ERRORCODE_00, + new Object[] { config.getFriendlyName() }); + + } + + } else { + log.warn("Symmetric KeyType: {} is unrecognized", config.getKeyType()); + throw new EaafConfigurationException(ERRORCODE_01, + new Object[] { config.getFriendlyName() }); + + } + + + } + + /** * Get a new KeyStore based on a KeyStore configuration-object. * * @param config KeyStore configuration @@ -113,45 +176,168 @@ public class EaafKeyStoreFactory { return isHsmFacadeInitialized; } + + /** + * Get the current status for HSM-Facade interaction. + * + * @return {@link HsmFacadeStatus} to indicate the current status. + */ + public HsmFacadeStatus checkHsmFacadeStatus() { + if (isHsmFacadeInitialized()) { + final Provider alreadyLoadedProvider = Security.getProvider(HSM_FACADE_PROVIDER); + if (alreadyLoadedProvider != null) { + try { + final Method healthCheck = + alreadyLoadedProvider.getClass().getMethod(HSM_FACADE_PROVIDER_METHOD_HEALTHCHECK, new Class[]{}); + boolean currentHealthStatus = (boolean) healthCheck.invoke(alreadyLoadedProvider); + HsmFacadeStatus status = currentHealthStatus ? HsmFacadeStatus.UP : HsmFacadeStatus.DOWN; + log.trace("Current HSM-Facade status is: {}", status); + return status; + + } catch (final Exception e) { + log.info("Can not determine state of alreay loaded HSM Facade: {} because HealthCheck not support", + alreadyLoadedProvider.getVersion()); + log.debug("Full HSM-Facade health-check exception", e); + return HsmFacadeStatus.UNKNOWN; + + } + + } else { + log.warn("HSM-Facade is marked as 'initialized', but not load as Security-Provider"); + return HsmFacadeStatus.DOWN; + } + + } else { + log.trace("HSM-Facade is not initialized. Set status do 'unknown'"); + return HsmFacadeStatus.UNKNOWN; + + } + } + @PostConstruct private void initialize() throws EaafException { + final Class<?> hsmProviderClazz = getHsmProviderClass(); + if (hsmProviderClazz != null) { + final String hsmFacadeHost = basicConfig.getBasicConfiguration(CONFIG_PROP_HSM_FACADE_HOST); + final Provider alreadyLoadedProvider = Security.getProvider(HSM_FACADE_PROVIDER); + if (alreadyLoadedProvider != null + && alreadyLoadedProvider.getClass().isAssignableFrom(hsmProviderClazz)) { + log.info("Find already initialized Java SecurityProvider: {}", alreadyLoadedProvider.getName()); + //mark it as initialized if the state can not be determined + boolean isAlreadyInitialized = true; + try { + final Method initializeCheck = + alreadyLoadedProvider.getClass().getMethod(HSM_FACADE_PROVIDER_METHOD_ISINITIALIZED, new Class[]{}); + isAlreadyInitialized = (boolean) initializeCheck.invoke(alreadyLoadedProvider); + + } catch (final Exception e) { + log.warn("Can not determine state of alreay loaded HSM Facade. Mark it as 'initialized'"); + log.debug("HSM Facade check error: {}", e.getMessage()); + + } + isHsmFacadeInitialized = isAlreadyInitialized; + + if (isHsmFacadeInitialized) { + log.info("HSM Facade is already initialized. {} can provide KeyStores based on remote HSM", + EaafKeyStoreFactory.class.getSimpleName()); + + } else { + log.info("HSM Facade is already loaded but not initialized. {} can NOT provide KeyStores based on remote HSM", + EaafKeyStoreFactory.class.getSimpleName()); + + } + + } else if (StringUtils.isNotEmpty(hsmFacadeHost)) { + log.debug("Find host for HSMFacade. Starting crypto provider initialization ... "); + initializeHsmFacadeSecurityProvider(hsmProviderClazz, hsmFacadeHost); - final String hsmFacadeHost = basicConfig.getBasicConfiguration(CONFIG_PROP_HSM_FACADE_HOST); - if (StringUtils.isNotEmpty(hsmFacadeHost)) { - log.debug("Find host for HSMFacade. Starting crypto provider initialization ... "); - try { - final int port = Integer.parseUnsignedInt( - getConfigurationParameter(CONFIG_PROP_HSM_FACADE_PORT)); - final String clientUsername = - getConfigurationParameter(CONFIG_PROP_HSM_FACADE_CLIENT_USERNAME); - final String clientPassword = - getConfigurationParameter(CONFIG_PROP_HSM_FACADE_CLIENT_PASSWORD); - - final HsmFacadeProvider provider = HsmFacadeProvider.Companion.getInstance(); - provider.init(getHsmFacadeTrustSslCertificate(), clientUsername, clientPassword, hsmFacadeHost, port); - //Security.addProvider(provider); - Security.insertProviderAt(provider, 0); - isHsmFacadeInitialized = true; - log.info("HSM Facade is initialized. {} can provide KeyStores based on remote HSM", + } else { + log.info("HSM Facade is on ClassPath but not configurated. {} can only provide software keystores", EaafKeyStoreFactory.class.getSimpleName()); - } catch (final EaafException e) { - throw e; - - } catch (final Exception e) { - log.error("HSM Facade initialization FAILED with an generic error.", e); - throw new EaafConfigurationException(ERRORCODE_03, new Object[] { e.getMessage() }, e); } } else { - log.info("HSM Facade is not configurated. {} can only provide software keystores", + log.info("HSM Facade is not on ClassPath. {} can only provide software keystores", EaafKeyStoreFactory.class.getSimpleName()); } } + private void initializeHsmFacadeSecurityProvider(Class<?> hsmProviderClazz, String hsmFacadeHost) + throws EaafException { + try { + final int port = Integer.parseUnsignedInt( + getConfigurationParameter(CONFIG_PROP_HSM_FACADE_PORT)); + final String clientUsername = + getConfigurationParameter(CONFIG_PROP_HSM_FACADE_CLIENT_USERNAME); + final String clientPassword = + getConfigurationParameter(CONFIG_PROP_HSM_FACADE_CLIENT_PASSWORD); + final long grpcDeadline = getConfigurationParameterLong(CONFIG_PROP_HSM_FACADE_GRPC_DEADLINE, + HSM_FACADE_DEFAULT_DEADLINE); + + + //initialize HSM-Facade by using JAVA Reflection, because in that case HSM-Facade + //has not be in ClassPath on every project + final Method constructor = hsmProviderClazz.getMethod(HSM_FACADE_PROVIDER_METHOD_CONSTRUCT, new Class[]{}); + final Method initMethod = hsmProviderClazz.getMethod(HSM_FACADE_PROVIDER_METHOD_INIT, + X509Certificate.class, String.class, String.class, String.class, int.class, long.class); + if (initMethod != null && constructor != null) { + final Object rawProvider = constructor.invoke(hsmProviderClazz); + initMethod.invoke( + rawProvider, getHsmFacadeTrustSslCertificate(), + clientUsername, clientPassword, hsmFacadeHost, port, grpcDeadline); + + if (rawProvider instanceof Provider) { + Security.addProvider((Provider) rawProvider); + + isHsmFacadeInitialized = true; + log.info("HSM Facade is initialized. {} can provide KeyStores based on remote HSM", + EaafKeyStoreFactory.class.getSimpleName()); + + } else { + log.warn("Is HSM-Facade class type of 'java.security.Provider': {}", + rawProvider instanceof Provider); + throw new EaafException(ERRORCODE_10, new Object[] {HSM_FACADE_PROVIDER_CLASS}); + + } + + } else { + log.warn(HSM_FACADE_PROVIDER_INIT_ERROR_MSG, + HSM_FACADE_PROVIDER_METHOD_CONSTRUCT, constructor != null); + log.warn(HSM_FACADE_PROVIDER_INIT_ERROR_MSG, + HSM_FACADE_PROVIDER_METHOD_INIT, initMethod != null); + throw new EaafException(ERRORCODE_10, new Object[] {HSM_FACADE_PROVIDER_CLASS}); + + } + + //final HsmFacadeProvider provider = HsmFacadeProvider.Companion.getInstance(); + //provider.init(getHsmFacadeTrustSslCertificate(), clientUsername, clientPassword, hsmFacadeHost, port); + + } catch (final EaafException e) { + throw e; + + } catch (final Exception e) { + log.error("HSM Facade initialization FAILED with an generic error.", e); + throw new EaafConfigurationException(ERRORCODE_03, new Object[] { e.getMessage() }, e); + + } + + } + + private Class<?> getHsmProviderClass() { + try { + return Class.forName(HSM_FACADE_PROVIDER_CLASS); + + } catch (final ClassNotFoundException e1) { + log.debug("No HSM-Facade implemenation in ClassPath. HSM-Facade will not be available"); + return null; + + } + } + @Nonnull private Pair<KeyStore, Provider> getKeyStoreFromFileSystem(KeyStoreConfiguration config) throws EaafConfigurationException, EaafFactoryException { @@ -162,28 +348,41 @@ public class EaafKeyStoreFactory { final String keyStorePassword = checkConfigurationParameter(config.getSoftKeyStorePassword(), ERRORCODE_06, config.getFriendlyName(), "Software-KeyStore missing Password for KeyStore"); - final String absKeyStorePath = FileUtils.makeAbsoluteUrl(keyStorePath, basicConfig - .getConfigurationRootDirectory()); - final Resource ressource = resourceLoader.getResource(absKeyStorePath); + Resource ressource; + if (config.isSkipMakeAbsolutPaths()) { + log.debug("Use filepath from config: {}", keyStorePath); + ressource = resourceLoader.getResource(keyStorePath); + + } else { + final String absKeyStorePath = FileUtils.makeAbsoluteUrl(keyStorePath, basicConfig + .getConfigurationRootDirectory()); + log.debug("Use filepath from config: {}", absKeyStorePath); + + ressource = resourceLoader.getResource(absKeyStorePath); + + } + if (!ressource.exists()) { - throw new EaafConfigurationException(ERRORCODE_05, + throw new EaafConfigurationException(ERRORCODE_06, new Object[] { config.getFriendlyName(), - "File not found at: " + absKeyStorePath }); + "RessourceLoader does NOT find File at: " + ressource.getURI() }); } final InputStream is = ressource.getInputStream(); - final KeyStore keyStore = KeyStoreUtils.loadKeyStore(is, keyStorePassword); + final KeyStore keyStore = KeyStoreUtils.loadKeyStore(is, keyStorePassword, config.getKeyStoreType()); is.close(); - if (keyStore == null) { - throw new EaafFactoryException(ERRORCODE_06, - new Object[] { config.getFriendlyName(), "KeyStore not valid or password wrong" }); - - } return Pair.newInstance(keyStore, null); - } catch (KeyStoreException | IOException e) { + } catch (final EaafException e) { + throw e; + + } catch (final IOException e) { + throw new EaafFactoryException(ERRORCODE_06, + new Object[] { config.getFriendlyName(), "KeyStore not valid or password wrong" }); + + } catch (final Exception e) { log.error("Software KeyStore initialization FAILED with an generic error.", e); throw new EaafConfigurationException(ERRORCODE_03, new Object[] { e.getMessage() }, e); @@ -193,24 +392,102 @@ public class EaafKeyStoreFactory { @Nonnull private Pair<KeyStore, Provider> getKeyStoreFromHsmFacade(KeyStoreConfiguration config) throws EaafFactoryException, EaafConfigurationException { - final String keyStoreName = checkConfigurationParameter(config.getKeyStoreName(), - ERRORCODE_06, config.getFriendlyName(), "KeyStoreName missing for HSM Facade"); + return getKeyStoreFromHsmFacade(config.getKeyStoreName(), config.getFriendlyName()); + + } + + @Nonnull + private Pair<KeyStore, Provider> getKeyStoreFromHsmFacade(String keyStoreName, String friendlyName) + throws EaafFactoryException, EaafConfigurationException { + final String validatedKeyStoreName = checkConfigurationParameter(keyStoreName, + ERRORCODE_06, friendlyName, "KeyStoreName missing for HSM Fac)ade"); try { final KeyStore keyStore = KeyStore.getInstance(HSM_FACADE_KEYSTORE_TYPE, HSM_FACADE_PROVIDER); - keyStore.load(new RemoteKeyStoreLoadParameter(keyStoreName)); + keyStore.load(getHsmFacadeKeyStoreParameter(validatedKeyStoreName)); return Pair.newInstance(keyStore, keyStore.getProvider()); } catch (NoSuchAlgorithmException | CertificateException | IOException | KeyStoreException - | NoSuchProviderException e) { + | NoSuchProviderException | EaafException e) { log.error("Can not initialize KeyStore: {} with reason: {}", - config.getFriendlyName(), e.getMessage()); + friendlyName, e.getMessage()); throw new EaafFactoryException(ERRORCODE_06, - new Object[] { config.getFriendlyName(), e.getMessage() }, e); + new Object[] {friendlyName, e.getMessage() }, e); } } + private KeyStore.LoadStoreParameter getHsmFacadeKeyStoreParameter(String keyStoreName) throws EaafException { + try { + final Class<?> clazz = Class.forName(HSM_FACADE_KEYSTORELOADPARAMETERS_CLASS); + final Constructor<?> constructor = clazz.getConstructor(String.class); + final Object keyStoreParams = constructor.newInstance(keyStoreName); + return (LoadStoreParameter) keyStoreParams; + + } catch (final Exception e) { + log.error("Can NOT build class: {} for HSM-Facade provider", HSM_FACADE_KEYSTORELOADPARAMETERS_CLASS, e); + throw new EaafException(ERRORCODE_10, new Object[] {HSM_FACADE_PROVIDER_CLASS}, e); + + } + + } + + @Nonnull + private Pair<SecretKey, Provider> generatePassPhraseBasedSymmetricKey(SymmetricKeyConfiguration config) + throws EaafConfigurationException { + checkConfigurationParameter(config.getSoftKeyPassphrase(), + ERRORCODE_KEY_00, config.getFriendlyName(), "passphrase missing"); + checkConfigurationParameter(config.getSoftKeySalt(), + ERRORCODE_KEY_00, config.getFriendlyName(), "salt missing"); + + try { + final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WITHHMACSHA256"); + final KeySpec spec = new PBEKeySpec( + config.getSoftKeyPassphrase().toCharArray(), + config.getSoftKeySalt().getBytes("UTF-8"), + 10000, 128); + return Pair.newInstance(keyFactory.generateSecret(spec), null); + + } catch (NoSuchAlgorithmException | InvalidKeySpecException | UnsupportedEncodingException e) { + log.error("Passphrase based symmetric-key generation FAILED", e); + throw new EaafConfigurationException(ERRORCODE_KEY_00, + new Object[] { config.getFriendlyName(), e.getMessage() }, + e); + + } + } + + @Nonnull + private Pair<SecretKey, Provider> getSymmetricKeyFromHsmFacade(SymmetricKeyConfiguration config) + throws EaafFactoryException, EaafConfigurationException, EaafKeyAccessException { + final Pair<KeyStore, Provider> keyStore = getKeyStoreFromHsmFacade( + config.getKeyStoreName(), config.getFriendlyName()); + + checkConfigurationParameter(config.getKeyAlias(), + ERRORCODE_KEY_00, config.getFriendlyName(), "keyAlias missing"); + + try { + final SecretKey secretKey = (SecretKey) keyStore.getFirst().getKey(config.getKeyAlias(), null); + if (secretKey == null) { + throw new EaafKeyAccessException(EaafKeyAccessException.ERROR_CODE_09, + config.getFriendlyName(), config.getKeyAlias(), "No SecretKey with Alias "); + + } + + return Pair.newInstance(secretKey, keyStore.getSecond()); + + } catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException e) { + throw new EaafKeyAccessException(EaafKeyAccessException.ERROR_CODE_09, e, + config.getFriendlyName(), config.getKeyAlias(), e.getMessage()); + + } catch (final ClassCastException e) { + throw new EaafKeyAccessException(EaafKeyAccessException.ERROR_CODE_09, + config.getFriendlyName(), config.getKeyAlias(), "Wrong SecretKey type "); + + } + + } + private X509Certificate getHsmFacadeTrustSslCertificate() throws EaafConfigurationException { try { final String certFilePath = getConfigurationParameter(CONFIG_PROP_HSM_FACADE_SSLTRUST); @@ -241,6 +518,19 @@ public class EaafKeyStoreFactory { } @Nonnull + private Long getConfigurationParameterLong(@Nonnull String configParamKey, String defaultValue) + throws EaafConfigurationException { + try { + return Long.valueOf(basicConfig.getBasicConfiguration(configParamKey, defaultValue)); + + } catch (NumberFormatException e) { + throw new EaafConfigurationException(ERRORCODE_05, new Object[] { configParamKey, e.getMessage()}); + + } + + } + + @Nonnull private String getConfigurationParameter(@Nonnull String configParamKey) throws EaafConfigurationException { return checkConfigurationParameter( diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreUtils.java index b4b44724..12541222 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreUtils.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/EaafKeyStoreUtils.java @@ -24,13 +24,13 @@ import lombok.extern.slf4j.Slf4j; public class EaafKeyStoreUtils { private static final String ERROR_MSG_REASON = "Maybe 'Alias' is not valid"; private static final String ERROR_MSG_1 = "Can NOT access key: {} in KeyStore: {}. Reason: {}"; - private static final String ERROR_MSG_2 = "Key: {} will be NOT available"; + private static final String ERROR_MSG_2 = "Key: {} will be NOT available"; /** * Read all certificates from a {@link KeyStore}. * * @param keyStore KeyStore with certificates - * @return {@link List} of {@link X509Certificate}, but never null + * @return Unmodifiable {@link List} of {@link X509Certificate}, but never null * @throws KeyStoreException In case of an error during KeyStore operations */ @Nonnull @@ -45,6 +45,7 @@ public class EaafKeyStoreUtils { final Certificate cert = keyStore.getCertificate(el); if (cert != null && cert instanceof X509Certificate) { result.add((X509Certificate) cert); + } else { log.info("Can not process entry: {}. Reason: {}", el, cert != null ? cert.getType() : "cert is null"); } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/KeyStoreConfiguration.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/KeyStoreConfiguration.java index 970efd22..c1a1d917 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/KeyStoreConfiguration.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/KeyStoreConfiguration.java @@ -4,10 +4,9 @@ import java.util.Map; import javax.annotation.Nonnull; -import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; - import org.apache.commons.lang3.StringUtils; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -53,6 +52,12 @@ public class KeyStoreConfiguration { */ private String softKeyStorePassword; + + /** + * Use filePaths as it is and does not make it absolut. + */ + private boolean skipMakeAbsolutPaths = false; + /** * Build a {@link KeyStoreConfiguration} from a configuration map. <br> * <p> diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/SymmetricKeyConfiguration.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/SymmetricKeyConfiguration.java new file mode 100644 index 00000000..9477789c --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/credential/SymmetricKeyConfiguration.java @@ -0,0 +1,221 @@ +package at.gv.egiz.eaaf.core.impl.credential; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.commons.lang3.StringUtils; + +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +@Setter +public class SymmetricKeyConfiguration { + + public static final String PROP_CONFIG_KEY_TYPE = + "key.type"; + + public static final String PROP_CONFIG_HSMFACADE_NAME = + "keystore.name"; + public static final String PROP_CONFIG_HSM_KEY_ALIAS = + "key.alias"; + + public static final String PROP_CONFIG_SOFTWARE_KEY_PASSPHRASE = + "key.passphrase"; + public static final String PROP_CONFIG_SOFTWARE_KEY_SALT = + "key.salt"; + + /** + * FriendlyName for this KeyStore. Mainly used for logging. + */ + private String friendlyName; + + /** + * General type of the KeyStore that should be generated. + */ + private SymmetricKeyType keyType; + + /** + * Name of the KeyStore in HSM Facade. + */ + private String keyStoreName; + + /** + * Alias of the Key in HSM Facade keystore. + */ + private String keyAlias; + + /** + * Software key passphrase. + */ + private String softKeyPassphrase; + + /** + * Software key salt. + */ + private String softKeySalt; + + /** + * Build a {@link SymmetricKeyConfiguration} from a configuration map. <br> + * <p> + * The configuration parameters defined in this class are used to load the + * configuration. + * </p> + * + * @param config Configuration + * @param friendlyName FriendlyName for this KeyStore + * @return Configuration object for {@link EaafKeyStoreFactory} + * @throws EaafConfigurationException In case of a configuration error. + */ + public static SymmetricKeyConfiguration buildFromConfigurationMap(Map<String, String> config, + String friendlyName) throws EaafConfigurationException { + + final SymmetricKeyConfiguration internalConfig = new SymmetricKeyConfiguration(); + internalConfig.setFriendlyName(friendlyName); + + final SymmetricKeyType internalKeyStoreType = SymmetricKeyType.fromString( + getConfigurationParameter(config, PROP_CONFIG_KEY_TYPE)); + if (internalKeyStoreType != null) { + internalConfig.setKeyType(internalKeyStoreType); + + } else { + log.error("Symmetric Key-configuration: {} sets an unknown Keytype: {}", + friendlyName, getConfigurationParameter(config, PROP_CONFIG_KEY_TYPE)); + throw new EaafConfigurationException(EaafKeyStoreFactory.ERRORCODE_01, + new Object[] { friendlyName }); + + } + + if (internalKeyStoreType.equals(SymmetricKeyType.HSMFACADE)) { + log.trace("Set-up HSM-Facade Symmentric-Key ... "); + internalConfig.setKeyStoreName(getConfigurationParameter(config, PROP_CONFIG_HSMFACADE_NAME)); + internalConfig.setKeyAlias(getConfigurationParameter(config, PROP_CONFIG_HSM_KEY_ALIAS)); + + } else { + log.trace("Set-up software passphrase based symmetric key ... "); + internalConfig.setSoftKeyPassphrase(getConfigurationParameter(config, PROP_CONFIG_SOFTWARE_KEY_PASSPHRASE)); + internalConfig.setSoftKeySalt(getConfigurationParameter(config, PROP_CONFIG_SOFTWARE_KEY_SALT)); + + } + + return internalConfig; + } + + /** + * Set the Type of the symmetric key based on String identifier. + * + * @param keyType String based KeyStore type + * @throws EaafConfigurationException In case of an unknown KeyStore type + */ + public void setKeyType(@Nonnull String keyType) throws EaafConfigurationException { + final SymmetricKeyType internalKeyStoreType = SymmetricKeyType.fromString(keyType); + if (internalKeyStoreType != null) { + setKeyType(internalKeyStoreType); + + } else { + log.error("KeyStore: {} sets an unknown KeyStore type: {}", + friendlyName, keyType); + throw new EaafConfigurationException(EaafKeyStoreFactory.ERRORCODE_01, + new Object[] { friendlyName }); + + } + + } + + /** + * Set the Type of the symmetric Key based on String identifier. + * + * @param type of tke symmetric key + */ + public void setKeyType(@Nonnull SymmetricKeyType type) { + this.keyType = type; + + } + + /** + * Validate the internal state of this configuration object. + * + * @throws EaafConfigurationException In case of a configuration error + */ + public void validate() throws EaafConfigurationException { + if (SymmetricKeyType.HSMFACADE.equals(keyType)) { + log.trace("Validate HSM-Facade symmetric key ... "); + checkConfigurationValue(keyStoreName, EaafKeyStoreFactory.ERRORCODE_07, + friendlyName, "Missing 'KeyStoreName' for HSM-Facade"); + checkConfigurationValue(keyAlias, EaafKeyStoreFactory.ERRORCODE_07, + friendlyName, "Missing 'KeyAlias' for HSM-Facade"); + + } else { + log.trace("Validate passphrase based symmetric key ... "); + checkConfigurationValue(softKeyPassphrase, EaafKeyStoreFactory.ERRORCODE_07, + friendlyName, "Missing 'passphrase' for symmetric-key generation"); + checkConfigurationValue(softKeySalt, EaafKeyStoreFactory.ERRORCODE_07, + friendlyName, "Missing 'salt' for symmetric-key generation"); + + } + } + + public enum SymmetricKeyType { + PASSPHRASE("passphrase"), HSMFACADE("hsmfacade"); + + private final String keyType; + + SymmetricKeyType(final String keyStoreType) { + this.keyType = keyStoreType; + } + + /** + * Get Type of this Key. + * + * @return + */ + public String getKeyType() { + return this.keyType; + } + + /** + * Get KeyType from String representation. + * + * @param s Config parameter + * @return + */ + public static SymmetricKeyType fromString(final String s) { + try { + return SymmetricKeyType.valueOf(s.toUpperCase()); + + } catch (IllegalArgumentException | NullPointerException e) { + return null; + } + } + + @Override + public String toString() { + return getKeyType(); + + } + } + + @Nonnull + private static String getConfigurationParameter(@Nonnull Map<String, String> config, + @Nonnull String configParamKey) + throws EaafConfigurationException { + final String configValue = config.get(configParamKey); + checkConfigurationValue(configValue, EaafKeyStoreFactory.ERRORCODE_04, configParamKey); + return configValue; + + } + + private static void checkConfigurationValue(String configValue, String errorCode, String... params) + throws EaafConfigurationException { + if (StringUtils.isEmpty(configValue)) { + throw new EaafConfigurationException(errorCode, + params); + + } + + } +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java new file mode 100644 index 00000000..3aa908e8 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafHttpRequestRetryHandler.java @@ -0,0 +1,33 @@ +package at.gv.egiz.eaaf.core.impl.http; + +import java.net.UnknownHostException; +import java.util.Arrays; + +import javax.net.ssl.SSLException; + +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; + +public class EaafHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler implements + HttpRequestRetryHandler { + + /** + * Create the request retry handler using the following list of non-retriable. + * IOException classes: <br> + * <ul> + * <li>UnknownHostException</li> + * <li>SSLException</li> + * </ul> + * + * @param retryCount how many times to retry; 0 means no retries + * @param requestSentRetryEnabled true if it's OK to retry non-idempotent + * requests that have been sent + */ + public EaafHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) { + super(retryCount, requestSentRetryEnabled, Arrays.asList( + UnknownHostException.class, + SSLException.class)); + + } + +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslContextBuilder.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslContextBuilder.java new file mode 100644 index 00000000..1cd739de --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslContextBuilder.java @@ -0,0 +1,433 @@ +package at.gv.egiz.eaaf.core.impl.http; + +import java.net.Socket; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.Security; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.http.ssl.PrivateKeyDetails; +import org.apache.http.ssl.PrivateKeyStrategy; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.TrustStrategy; +import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; + +/** + * Fork of {@link SSLContextBuilder} that uses JSSE provider to get TrustManager. + * + * <p>This implementation fix an incompatibility between {@link BouncyCastleJsseProvider} and JAVA JDK >= v9</p> + * + * @author tlenz + * + */ +public class EaafSslContextBuilder { + + static final String TLS = "TLS"; + + private String protocol; + private final Set<KeyManager> keyManagers; + private String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); + private String keyStoreType = KeyStore.getDefaultType(); + private final Set<TrustManager> trustManagers; + private String trustManagerFactoryAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + private SecureRandom secureRandom; + private Provider provider; + + public static EaafSslContextBuilder create() { + return new EaafSslContextBuilder(); + } + + /** + * Get a new SSLContext builder object. + */ + public EaafSslContextBuilder() { + super(); + this.keyManagers = new LinkedHashSet<>(); + this.trustManagers = new LinkedHashSet<>(); + } + + /** + * Sets the SSLContext protocol algorithm name. + * + * @param protocol the SSLContext protocol algorithm name of the requested + * protocol. See the SSLContext section in the <a href= + * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">Java + * Cryptography Architecture Standard Algorithm Name + * Documentation</a> for more information. + * @return this builder + * @see <a href= + * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">Java + * Cryptography Architecture Standard Algorithm Name Documentation</a> + * @deprecated Use {@link #setProtocol(String)}. + */ + @Deprecated + public EaafSslContextBuilder useProtocol(final String protocol) { + this.protocol = protocol; + return this; + } + + /** + * Sets the SSLContext protocol algorithm name. + * + * @param protocol the SSLContext protocol algorithm name of the requested + * protocol. See the SSLContext section in the <a href= + * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">Java + * Cryptography Architecture Standard Algorithm Name + * Documentation</a> for more information. + * @return this builder + * @see <a href= + * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext">Java + * Cryptography Architecture Standard Algorithm Name Documentation</a> + * @since 4.4.7 + */ + public EaafSslContextBuilder setProtocol(final String protocol) { + this.protocol = protocol; + return this; + } + + public EaafSslContextBuilder setSecureRandom(final SecureRandom secureRandom) { + this.secureRandom = secureRandom; + return this; + } + + public EaafSslContextBuilder setProvider(final Provider provider) { + this.provider = provider; + return this; + } + + public EaafSslContextBuilder setProvider(final String name) { + this.provider = Security.getProvider(name); + return this; + } + + /** + * Sets the key store type. + * + * @param keyStoreType the SSLkey store type. See the KeyStore section in the + * <a href= + * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore">Java + * Cryptography Architecture Standard Algorithm Name + * Documentation</a> for more information. + * @return this builder + * @see <a href= + * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore">Java + * Cryptography Architecture Standard Algorithm Name Documentation</a> + * @since 4.4.7 + */ + public EaafSslContextBuilder setKeyStoreType(final String keyStoreType) { + this.keyStoreType = keyStoreType; + return this; + } + + /** + * Sets the key manager factory algorithm name. + * + * @param keyManagerFactoryAlgorithm the key manager factory algorithm name of + * the requested protocol. See the + * KeyManagerFactory section in the <a href= + * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyManagerFactory">Java + * Cryptography Architecture Standard + * Algorithm Name Documentation</a> for more + * information. + * @return this builder + * @see <a href= + * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyManagerFactory">Java + * Cryptography Architecture Standard Algorithm Name Documentation</a> + * @since 4.4.7 + */ + public EaafSslContextBuilder setKeyManagerFactoryAlgorithm(final String keyManagerFactoryAlgorithm) { + this.keyManagerFactoryAlgorithm = keyManagerFactoryAlgorithm; + return this; + } + + /** + * Sets the trust manager factory algorithm name. + * + * @param trustManagerFactoryAlgorithm the trust manager algorithm name of the + * requested protocol. See the + * TrustManagerFactory section in the + * <a href= + * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#TrustManagerFactory">Java + * Cryptography Architecture Standard + * Algorithm Name Documentation</a> for more + * information. + * @return this builder + * @see <a href= + * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#TrustManagerFactory">Java + * Cryptography Architecture Standard Algorithm Name Documentation</a> + * @since 4.4.7 + */ + public EaafSslContextBuilder setTrustManagerFactoryAlgorithm(final String trustManagerFactoryAlgorithm) { + this.trustManagerFactoryAlgorithm = trustManagerFactoryAlgorithm; + return this; + } + + /** + * Load custom truststore. + * + * @param truststore {@link KeyStore} if trusted certificates + * @param trustStrategy Trust validation strategy + * @return {@link EaafSslContextBuilder} + * @throws NoSuchAlgorithmException In case of an invalid TrustManager algorithm + * @throws KeyStoreException In case of an invalid KeyStore + */ + public EaafSslContextBuilder loadTrustMaterial( + final KeyStore truststore, + final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException { + + final String alg = trustManagerFactoryAlgorithm == null + ? TrustManagerFactory.getDefaultAlgorithm() + : trustManagerFactoryAlgorithm; + + final TrustManagerFactory tmfactory = provider != null + ? TrustManagerFactory.getInstance(alg, provider) + : TrustManagerFactory.getInstance(alg); + tmfactory.init(truststore); + final TrustManager[] tms = tmfactory.getTrustManagers(); + if (tms != null) { + if (trustStrategy != null) { + for (int i = 0; i < tms.length; i++) { + final TrustManager tm = tms[i]; + if (tm instanceof X509TrustManager) { + tms[i] = new TrustManagerDelegate((X509TrustManager) tm, trustStrategy); + } + } + } + Collections.addAll(this.trustManagers, tms); + } + return this; + } + + public EaafSslContextBuilder loadTrustMaterial( + final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException { + return loadTrustMaterial(null, trustStrategy); + } + + + /** + * Load SSL client-authentication key-material into SSL context. + * + * @param keystore {@link KeyStore} for SSL client-authentication + * @param keyPassword Password for this keystore + * @param aliasStrategy Stategy to select keys by alias + * @return {@link EaafSslContextBuilder} + * @throws NoSuchAlgorithmException In case of an invalid KeyManagerFactory algorithm + * @throws KeyStoreException In case of an invalid KeyStore + * @throws UnrecoverableKeyException In case of a invalid Key in this KeyStore + */ + public EaafSslContextBuilder loadKeyMaterial( + final KeyStore keystore, + final char[] keyPassword, + final PrivateKeyStrategy aliasStrategy) + throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { + final KeyManagerFactory kmfactory = KeyManagerFactory + .getInstance(keyManagerFactoryAlgorithm == null ? KeyManagerFactory.getDefaultAlgorithm() + : keyManagerFactoryAlgorithm); + kmfactory.init(keystore, keyPassword); + final KeyManager[] kms = kmfactory.getKeyManagers(); + if (kms != null) { + if (aliasStrategy != null) { + for (int i = 0; i < kms.length; i++) { + final KeyManager km = kms[i]; + if (km instanceof X509ExtendedKeyManager) { + kms[i] = new KeyManagerDelegate((X509ExtendedKeyManager) km, aliasStrategy); + } + } + } + Collections.addAll(keyManagers, kms); + } + return this; + } + + public EaafSslContextBuilder loadKeyMaterial( + final KeyStore keystore, + final char[] keyPassword) throws NoSuchAlgorithmException, KeyStoreException, + UnrecoverableKeyException { + return loadKeyMaterial(keystore, keyPassword, null); + } + + protected void initSslContext( + final SSLContext sslContext, + final Collection<KeyManager> keyManagers, + final Collection<TrustManager> trustManagers, + final SecureRandom secureRandom) throws KeyManagementException { + sslContext.init( + !keyManagers.isEmpty() ? keyManagers.toArray(new KeyManager[keyManagers.size()]) : null, + !trustManagers.isEmpty() ? trustManagers.toArray(new TrustManager[trustManagers.size()]) : null, + secureRandom); + } + + /** + * Build a {@link SSLContext} from this builder. + * + * @return new {@link SSLContext} + * @throws NoSuchAlgorithmException In case of an unknown SSL protocol + * @throws KeyManagementException In case of a key-access error + */ + public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + final SSLContext sslContext; + final String protocolStr = this.protocol != null ? this.protocol : TLS; + if (this.provider != null) { + sslContext = SSLContext.getInstance(protocolStr, this.provider); + } else { + sslContext = SSLContext.getInstance(protocolStr); + } + initSslContext(sslContext, keyManagers, trustManagers, secureRandom); + return sslContext; + } + + static class TrustManagerDelegate implements X509TrustManager { + + private final X509TrustManager trustManager; + private final TrustStrategy trustStrategy; + + TrustManagerDelegate(final X509TrustManager trustManager, final TrustStrategy trustStrategy) { + super(); + this.trustManager = trustManager; + this.trustStrategy = trustStrategy; + } + + @Override + public void checkClientTrusted( + final X509Certificate[] chain, final String authType) throws CertificateException { + this.trustManager.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted( + final X509Certificate[] chain, final String authType) throws CertificateException { + if (!this.trustStrategy.isTrusted(chain, authType)) { + this.trustManager.checkServerTrusted(chain, authType); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return this.trustManager.getAcceptedIssuers(); + } + + } + + static class KeyManagerDelegate extends X509ExtendedKeyManager { + + private final X509ExtendedKeyManager keyManager; + private final PrivateKeyStrategy aliasStrategy; + + KeyManagerDelegate(final X509ExtendedKeyManager keyManager, final PrivateKeyStrategy aliasStrategy) { + super(); + this.keyManager = keyManager; + this.aliasStrategy = aliasStrategy; + } + + @Override + public String[] getClientAliases( + final String keyType, final Principal[] issuers) { + return this.keyManager.getClientAliases(keyType, issuers); + } + + public Map<String, PrivateKeyDetails> getClientAliasMap( + final String[] keyTypes, final Principal[] issuers) { + final Map<String, PrivateKeyDetails> validAliases = new HashMap<>(); + for (final String keyType : keyTypes) { + final String[] aliases = this.keyManager.getClientAliases(keyType, issuers); + if (aliases != null) { + for (final String alias : aliases) { + validAliases.put(alias, + new PrivateKeyDetails(keyType, this.keyManager.getCertificateChain(alias))); + } + } + } + return validAliases; + } + + public Map<String, PrivateKeyDetails> getServerAliasMap( + final String keyType, final Principal[] issuers) { + final Map<String, PrivateKeyDetails> validAliases = new HashMap<>(); + final String[] aliases = this.keyManager.getServerAliases(keyType, issuers); + if (aliases != null) { + for (final String alias : aliases) { + validAliases.put(alias, + new PrivateKeyDetails(keyType, this.keyManager.getCertificateChain(alias))); + } + } + return validAliases; + } + + @Override + public String chooseClientAlias( + final String[] keyTypes, final Principal[] issuers, final Socket socket) { + final Map<String, PrivateKeyDetails> validAliases = getClientAliasMap(keyTypes, issuers); + return this.aliasStrategy.chooseAlias(validAliases, socket); + } + + @Override + public String[] getServerAliases( + final String keyType, final Principal[] issuers) { + return this.keyManager.getServerAliases(keyType, issuers); + } + + @Override + public String chooseServerAlias( + final String keyType, final Principal[] issuers, final Socket socket) { + final Map<String, PrivateKeyDetails> validAliases = getServerAliasMap(keyType, issuers); + return this.aliasStrategy.chooseAlias(validAliases, socket); + } + + @Override + public X509Certificate[] getCertificateChain(final String alias) { + return this.keyManager.getCertificateChain(alias); + } + + @Override + public PrivateKey getPrivateKey(final String alias) { + return this.keyManager.getPrivateKey(alias); + } + + @Override + public String chooseEngineClientAlias( + final String[] keyTypes, final Principal[] issuers, final SSLEngine sslEngine) { + final Map<String, PrivateKeyDetails> validAliases = getClientAliasMap(keyTypes, issuers); + return this.aliasStrategy.chooseAlias(validAliases, null); + } + + @Override + public String chooseEngineServerAlias( + final String keyType, final Principal[] issuers, final SSLEngine sslEngine) { + final Map<String, PrivateKeyDetails> validAliases = getServerAliasMap(keyType, issuers); + return this.aliasStrategy.chooseAlias(validAliases, null); + } + + } + + @Override + public String toString() { + return "[provider=" + provider + ", protocol=" + protocol + ", keyStoreType=" + keyStoreType + + ", keyManagerFactoryAlgorithm=" + keyManagerFactoryAlgorithm + ", keyManagers=" + keyManagers + + ", trustManagerFactoryAlgorithm=" + trustManagerFactoryAlgorithm + ", trustManagers=" + + trustManagers + + ", secureRandom=" + secureRandom + "]"; + } +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslKeySelectionStrategy.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslKeySelectionStrategy.java index 1e1e2137..d2377d69 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslKeySelectionStrategy.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/EaafSslKeySelectionStrategy.java @@ -33,18 +33,23 @@ public class EaafSslKeySelectionStrategy implements PrivateKeyStrategy { @Override public String chooseAlias(Map<String, PrivateKeyDetails> aliases, Socket socket) { log.trace("Selection SSL client-auth key for alias: {}", keyAlias); + if (aliases.keySet().isEmpty()) { + log.debug("No Key with Alias: {} in empty KeyStore", keyAlias); + return null; + + } + final PrivateKeyDetails selected = aliases.get(keyAlias); if (selected != null) { log.trace("Select SL client-auth key with type:", selected.getType()); return keyAlias; - } else { + } else { log.warn("KeyStore contains NO key with alias: {}. Using first key from keystore", keyAlias); log.info("Available aliases: {}", StringUtils.join(aliases.keySet(), ", ")); return aliases.keySet().iterator().next(); - + } - } } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java index 582ad545..9239d0c5 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java @@ -5,11 +5,12 @@ import java.util.UUID; import javax.annotation.Nonnull; -import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; -import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; - import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.ServiceUnavailableRetryStrategy; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -56,7 +57,17 @@ public class HttpClientConfiguration { @Setter private boolean followHttpRedirects = true; + + @Setter + private int httpErrorRetryCount = 3; + + @Setter + private boolean httpErrorRetryPost = false; + @Setter + private ServiceUnavailableRetryStrategy serviceUnavailStrategy = null; + + /** * Get a new HTTP-client configuration object. * @@ -117,7 +128,9 @@ public class HttpClientConfiguration { } - if (StringUtils.isEmpty(this.sslKeyPassword)) { + if (StringUtils.isEmpty(this.sslKeyPassword) + && (KeyStoreType.JKS.equals(keyStoreConfig.getKeyStoreType()) + || KeyStoreType.PKCS12.equals(keyStoreConfig.getKeyStoreType()))) { throw new EaafConfigurationException(ERROR_02, new Object[] { this.friendlyName, this.keyStoreConfig.getFriendlyName()}); @@ -187,5 +200,4 @@ public class HttpClientConfiguration { } } - } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java index 00d5891a..07522b56 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java @@ -1,8 +1,11 @@ package at.gv.egiz.eaaf.core.impl.http; import java.security.KeyStore; +import java.security.Provider; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; @@ -22,6 +25,7 @@ import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.config.SocketConfig; +import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.LayeredConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; @@ -32,16 +36,19 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.BasicHttpClientConnectionManager; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContexts; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; +import at.gv.egiz.eaaf.core.impl.data.Pair; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -64,6 +71,10 @@ public class HttpClientFactory implements IHttpClientFactory { "client.http.connection.timeout.connection"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST = "client.http.connection.timeout.request"; + public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT = + "client.http.connection.retry.count"; + public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST = + "client.http.connection.retry.post"; public static final String PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL = "client.http.ssl.hostnameverifier.trustall"; @@ -89,9 +100,16 @@ public class HttpClientFactory implements IHttpClientFactory { public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST = "30"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL = "500"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE = "100"; + public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT = "3"; + public static final String DEFAUTL_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST = String.valueOf(false); + public static final int DEFAULT_CLEANUP_RUNNER_TIME = 30000; + public static final int DEFAULT_CLEANUP_IDLE_TIME = 60; + + private String defaultConfigurationId = null; - private final Map<String, HttpClientBuilder> availableBuilders = new HashMap<>(); + private final Map<String, Pair<HttpClientBuilder, HttpClientConnectionManager>> + availableBuilders = new HashMap<>(); /* * (non-Javadoc) @@ -106,7 +124,7 @@ public class HttpClientFactory implements IHttpClientFactory { @Override public CloseableHttpClient getHttpClient(final boolean followRedirects) { - return availableBuilders.get(defaultConfigurationId).setRedirectStrategy( + return availableBuilders.get(defaultConfigurationId).getFirst().setRedirectStrategy( buildRedirectStrategy(followRedirects)).build(); } @@ -116,27 +134,31 @@ public class HttpClientFactory implements IHttpClientFactory { log.trace("Build http client for: {}", config.getFriendlyName()); HttpClientBuilder builder = null; if (availableBuilders.containsKey(config.getUuid())) { - builder = availableBuilders.get(config.getUuid()); + builder = availableBuilders.get(config.getUuid()).getFirst(); } else { log.debug("Initialize new http-client builder for: {}", config.getFriendlyName()); - //validate configuration object + // validate configuration object config.validate(); builder = HttpClients.custom(); + + // inject request configuration builder.setDefaultRequestConfig(buildDefaultRequestConfig()); + injectInternalRetryHandler(builder, config); - //inject basic authentication infos + // inject basic authentication infos injectBasicAuthenticationIfRequired(builder, config); - //inject authentication if required + // inject authentication if required final LayeredConnectionSocketFactory sslConnectionFactory = getSslContext(config); // set pool connection if required - injectDefaultConnectionPoolIfRequired(builder, sslConnectionFactory); + HttpClientConnectionManager connectionManager + = injectConnectionManager(builder, sslConnectionFactory); - availableBuilders.put(config.getUuid(), builder); + availableBuilders.put(config.getUuid(), Pair.newInstance(builder, connectionManager)); } @@ -145,6 +167,47 @@ public class HttpClientFactory implements IHttpClientFactory { } + /** + * Worker that closes expired connections or connections that in idle + * for more than DEFAULT_CLEANUP_IDLE_TIME seconds. + * + */ + @Scheduled(fixedDelay = DEFAULT_CLEANUP_RUNNER_TIME) + private void httpConnectionPoolCleaner() { + log.trace("Starting http connection-pool eviction policy ... "); + for (final Entry<String, Pair<HttpClientBuilder, HttpClientConnectionManager>> el + : availableBuilders.entrySet()) { + log.trace("Checking connections of http-client: {}", el.getKey()); + el.getValue().getSecond().closeExpiredConnections(); + el.getValue().getSecond().closeIdleConnections(DEFAULT_CLEANUP_IDLE_TIME, TimeUnit.SECONDS); + + } + + } + + private void injectInternalRetryHandler(HttpClientBuilder builder, HttpClientConfiguration config) { + if (config.getHttpErrorRetryCount() > 0) { + log.info("Set HTTP error-retry to {} for http-client: {}", + config.getHttpErrorRetryCount(), config.getFriendlyName()); + builder.setRetryHandler(new EaafHttpRequestRetryHandler( + config.getHttpErrorRetryCount(), + config.isHttpErrorRetryPost())); + + if (config.getServiceUnavailStrategy() != null) { + log.debug("HttpClient configuration: {} set custom ServiceUnavailableRetryStrategy: {}", + config.getFriendlyName(), config.getServiceUnavailStrategy().getClass().getName()); + builder.setServiceUnavailableRetryStrategy(config.getServiceUnavailStrategy()); + + } + + } else { + log.info("Disable HTTP error-retry for http-client: {}", config.getFriendlyName()); + builder.disableAutomaticRetries(); + + } + + } + @PostConstruct private void initalize() throws EaafException { final HttpClientConfiguration defaultHttpClientConfig = buildDefaultHttpClientConfiguration(); @@ -155,8 +218,9 @@ public class HttpClientFactory implements IHttpClientFactory { // set default request configuration defaultHttpClientBuilder.setDefaultRequestConfig(buildDefaultRequestConfig()); + injectInternalRetryHandler(defaultHttpClientBuilder, defaultHttpClientConfig); - //inject http basic authentication + // inject http basic authentication injectBasicAuthenticationIfRequired(defaultHttpClientBuilder, defaultHttpClientConfig); // inject authentication if required @@ -164,11 +228,13 @@ public class HttpClientFactory implements IHttpClientFactory { getSslContext(defaultHttpClientConfig); // set pool connection if required - injectDefaultConnectionPoolIfRequired(defaultHttpClientBuilder, sslConnectionFactory); + HttpClientConnectionManager connectionManager + = injectConnectionManager(defaultHttpClientBuilder, sslConnectionFactory); - //set default http client builder + // set default http client builder defaultConfigurationId = defaultHttpClientConfig.getUuid(); - availableBuilders.put(defaultConfigurationId, defaultHttpClientBuilder); + availableBuilders.put(defaultConfigurationId, + Pair.newInstance(defaultHttpClientBuilder, connectionManager)); } @@ -203,6 +269,13 @@ public class HttpClientFactory implements IHttpClientFactory { config.setDisableHostnameValidation(basicConfig.getBasicConfigurationBoolean( PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false)); + config.setHttpErrorRetryCount(Integer.parseInt(basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT, + DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_COUNT))); + config.setHttpErrorRetryPost(Boolean.parseBoolean(basicConfig.getBasicConfiguration( + PROP_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST, + DEFAUTL_CONFIG_CLIENT_HTTP_CONNECTION_RETRY_POST))); + // validate configuration object config.validate(); @@ -237,8 +310,8 @@ public class HttpClientFactory implements IHttpClientFactory { SSLContext sslContext = null; if (httpClientConfig.getAuthMode().equals(HttpClientConfiguration.ClientAuthMode.SSL)) { log.debug("Open keyStore with type: {}", httpClientConfig.getKeyStoreConfig().getKeyStoreType()); - final KeyStore keyStore = keyStoreFactory.buildNewKeyStore(httpClientConfig.getKeyStoreConfig()) - .getFirst(); + final Pair<KeyStore, Provider> keyStore = keyStoreFactory.buildNewKeyStore(httpClientConfig + .getKeyStoreConfig()); log.trace("Injecting SSL client-authentication into http client ... "); sslContext = HttpUtils.buildSslContextWithSslClientAuthentication(keyStore, @@ -248,7 +321,7 @@ public class HttpClientFactory implements IHttpClientFactory { } else { log.trace("Initializing default SSL Context ... "); sslContext = SSLContexts.createDefault(); - + } // set hostname verifier @@ -266,48 +339,37 @@ public class HttpClientFactory implements IHttpClientFactory { } - private void injectDefaultConnectionPoolIfRequired( + @Nonnull + private HttpClientConnectionManager injectConnectionManager( HttpClientBuilder builder, final LayeredConnectionSocketFactory sslConnectionFactory) { if (basicConfig.getBasicConfigurationBoolean(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE, true)) { - PoolingHttpClientConnectionManager pool; - - // set socketFactoryRegistry if SSLConnectionFactory is Set - if (sslConnectionFactory != null) { - final Registry<ConnectionSocketFactory> socketFactoryRegistry = - RegistryBuilder.<ConnectionSocketFactory>create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", sslConnectionFactory).build(); - log.trace("Inject SSLSocketFactory into pooled connection"); - pool = new PoolingHttpClientConnectionManager(socketFactoryRegistry); - - } else { - pool = new PoolingHttpClientConnectionManager(); - - } - - pool.setDefaultMaxPerRoute(Integer.parseInt( + PoolingHttpClientConnectionManager connectionPool + = new PoolingHttpClientConnectionManager(getDefaultRegistry(sslConnectionFactory)); + connectionPool.setDefaultMaxPerRoute(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE))); - pool.setMaxTotal(Integer.parseInt( + connectionPool.setMaxTotal(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL))); - - pool.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(Integer.parseInt( + connectionPool.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)) * 1000).build()); + builder.setConnectionManager(connectionPool); + log.debug("Initalize http-client pool with, maxTotal: {} maxPerRoute: {}", + connectionPool.getMaxTotal(), connectionPool.getDefaultMaxPerRoute()); + return connectionPool; + + } else { + log.debug("Building http-client without Connection-Pool ... "); + final BasicHttpClientConnectionManager basicPool = new BasicHttpClientConnectionManager( + getDefaultRegistry(sslConnectionFactory)); + builder.setConnectionManager(basicPool); + return basicPool; - builder.setConnectionManager(pool); - log.debug("Initalize http-client pool with, maxTotal: {} maxPerRoute: {}", pool.getMaxTotal(), - pool.getDefaultMaxPerRoute()); - - } else if (sslConnectionFactory != null) { - log.trace("Inject SSLSocketFactory without connection pool"); - builder.setSSLSocketFactory(sslConnectionFactory); - } - + } private RequestConfig buildDefaultRequestConfig() { @@ -323,7 +385,7 @@ public class HttpClientFactory implements IHttpClientFactory { .setSocketTimeout(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)) - * 1000) + * 1000) .build(); return requestConfig; @@ -350,5 +412,25 @@ public class HttpClientFactory implements IHttpClientFactory { return redirectStrategy; } + + private static Registry<ConnectionSocketFactory> getDefaultRegistry( + final LayeredConnectionSocketFactory sslConnectionFactory) { + final RegistryBuilder<ConnectionSocketFactory> builder = + RegistryBuilder.<ConnectionSocketFactory>create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()); + + if (sslConnectionFactory != null) { + log.trace("Inject own SSLSocketFactory into pooled connection"); + builder.register("https", sslConnectionFactory); + + } else { + log.trace("Inject default SSLSocketFactory into pooled connection"); + builder.register("https", SSLConnectionSocketFactory.getSocketFactory()); + + } + + return builder.build(); + + } } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java index 2d514912..dd6f69ee 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpUtils.java @@ -18,10 +18,14 @@ package at.gv.egiz.eaaf.core.impl.http; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.Provider; import java.security.UnrecoverableKeyException; import javax.annotation.Nonnull; @@ -29,22 +33,67 @@ import javax.annotation.Nullable; import javax.net.ssl.SSLContext; import javax.servlet.http.HttpServletRequest; -import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; -import at.gv.egiz.eaaf.core.exceptions.EaafFactoryException; - import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.ResponseHandler; import org.apache.http.conn.ssl.TrustAllStrategy; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.SSLContexts; +import org.apache.http.entity.ContentType; import org.apache.http.ssl.TrustStrategy; +import org.apache.http.util.EntityUtils; +import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafFactoryException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.data.Triple; +import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @Slf4j public class HttpUtils { private static final String ERROR_03 = "internal.httpclient.03"; + + /** + * Simple Http response-handler that only give http status-code as result. + * + * @return Status-Code of http response + */ + public static ResponseHandler<StatusLine> simpleStatusCodeResponseHandler() { + return new ResponseHandler<StatusLine>() { + @Override + public StatusLine handleResponse(HttpResponse response) throws ClientProtocolException, IOException { + EntityUtils.consumeQuietly(response.getEntity()); + return response.getStatusLine(); + } + }; + } + + /** + * Http response-handler that gives a pair of http status-code, + * a copy of the full http-body as {@link InputStream} and the response {@link ContentType}. + * + * @return {@link Triple} of http response {@link StatusLine}, http body as {@link InputStream}, + * and {@link ContentType} + */ + public static ResponseHandler<Triple<StatusLine, ByteArrayInputStream, ContentType>> + bodyStatusCodeResponseHandler() { + return new ResponseHandler<Triple<StatusLine, ByteArrayInputStream, ContentType>>() { + @Override + public Triple<StatusLine, ByteArrayInputStream, ContentType> handleResponse(HttpResponse response) + throws ClientProtocolException, IOException { + byte[] bodyBytes = EntityUtils.toByteArray(response.getEntity()); + return Triple.newInstance(response.getStatusLine(), new ByteArrayInputStream(bodyBytes), + ContentType.getOrDefault(response.getEntity())); + + } + }; + } + /** * Helper method to retrieve server URL including context path. * @@ -124,7 +173,7 @@ public class HttpUtils { * @param url URL * @param paramname Name of the parameter. * @param paramvalue Value of the parameter. - * @return + * @return Url with parameter */ public static String addUrlParameter(final String url, final String paramname, final String paramvalue) { @@ -137,6 +186,23 @@ public class HttpUtils { } /** + * Inject HTTP header into http request. + * + * <p>The header is only set if HeaderValue is not null</p> + * + * @param req Http request object + * @param headerName HeaderName + * @param headerValue HeaderValue + */ + public static void addHeaderIfNotEmpty(@NonNull HttpRequest req, @NonNull String headerName, + @Nullable String headerValue) { + if (StringUtils.isNotEmpty(headerValue)) { + req.addHeader(headerName, headerValue); + + } + } + + /** * Initialize a {@link SSLContext} with a {@link KeyStore} that uses X509 Client * authentication. * @@ -155,40 +221,114 @@ public class HttpUtils { * @throws EaafFactoryException In case of a {@link SSLContext} * initialization error */ - public static SSLContext buildSslContextWithSslClientAuthentication(@Nonnull final KeyStore keyStore, + public static SSLContext buildSslContextWithSslClientAuthentication(@Nonnull final Pair<KeyStore, Provider> keyStore, @Nullable String keyAlias, @Nullable String keyPasswordString, boolean trustAllServerCertificates, @Nonnull String friendlyName) throws EaafConfigurationException, EaafFactoryException { try { - log.trace("Open SSL Client-Auth keystore with password: {}", keyPasswordString); - final char[] keyPassword = keyPasswordString == null ? StringUtils.EMPTY.toCharArray() - : keyPasswordString.toCharArray(); - - SSLContextBuilder sslContextBuilder = SSLContexts.custom(); - if (StringUtils.isNotEmpty(keyAlias)) { - sslContextBuilder = sslContextBuilder - .loadKeyMaterial(keyStore, keyPassword, new EaafSslKeySelectionStrategy(keyAlias)); - - } else { - sslContextBuilder = sslContextBuilder - .loadKeyMaterial(keyStore, keyPassword); - - } - - if (trustAllServerCertificates) { - log.warn("Http-client:{} trusts ALL TLS server-certificates!"); - final TrustStrategy trustStrategy = new TrustAllStrategy(); - sslContextBuilder = sslContextBuilder.loadTrustMaterial(trustStrategy); + EaafSslContextBuilder sslContextBuilder = EaafSslContextBuilder.create(); + + injectKeyStore(sslContextBuilder, keyStore, keyAlias, keyPasswordString, friendlyName); + + injectTrustStore(sslContextBuilder, null, trustAllServerCertificates, friendlyName); + + return sslContextBuilder.build(); - } + } catch (NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException + | KeyStoreException e) { + throw new EaafFactoryException(ERROR_03, new Object[] { friendlyName, e.getMessage() }, e); + } + } + + /** + * Initialize a {@link SSLContext} with a {@link KeyStore} that uses X509 Client + * authentication and a custom TrustStore as {@link KeyStore}. + * + * @param keyStore KeyStore with private keys that should be + * used + * @param keyAlias Alias of the key that should be used. If + * the alias is null, than the first key that + * is found will be selected. + * @param keyPasswordString Password of the Key in this keystore + * @param trustStore TrustStore with trusted SSL certificates + * @param trustAllServerCertificates Deactivate SSL server-certificate + * validation + * @param friendlyName FriendlyName of the http client for logging + * purposes + * @return {@link SSLContext} with X509 client authentication + * @throws EaafConfigurationException In case of a configuration error + * @throws EaafFactoryException In case of a {@link SSLContext} + * initialization error + */ + public static SSLContext buildSslContextWithSslClientAuthentication(@Nonnull final Pair<KeyStore, Provider> keyStore, + @Nullable String keyAlias, @Nullable String keyPasswordString, + @Nullable final Pair<KeyStore, Provider> trustStore, boolean trustAllServerCertificates, + @Nonnull String friendlyName) + throws EaafConfigurationException, EaafFactoryException { + try { + EaafSslContextBuilder sslContextBuilder = EaafSslContextBuilder.create(); + + injectKeyStore(sslContextBuilder, keyStore, keyAlias, keyPasswordString, friendlyName); + + injectTrustStore(sslContextBuilder, trustStore, trustAllServerCertificates, friendlyName); + return sslContextBuilder.build(); } catch (NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException | KeyStoreException e) { throw new EaafFactoryException(ERROR_03, new Object[] { friendlyName, e.getMessage() }, e); + } + } + + private static void injectTrustStore(EaafSslContextBuilder sslContextBuilder, + Pair<KeyStore, Provider> trustStore, boolean trustAllServerCertificates, String friendlyName) + throws NoSuchAlgorithmException, KeyStoreException { + + TrustStrategy trustStrategy = null; + if (trustAllServerCertificates) { + log.warn("Http-client:{} trusts ALL TLS server-certificates!", friendlyName); + trustStrategy = new TrustAllStrategy(); + } + + KeyStore trustStoreImpl = null; + if (trustStore != null) { + log.info("Http-client: {} uses custom TrustStore.", friendlyName); + trustStoreImpl = trustStore.getFirst(); + + } + + sslContextBuilder.loadTrustMaterial(trustStoreImpl, trustStrategy); + } + private static void injectKeyStore(EaafSslContextBuilder sslContextBuilder, Pair<KeyStore, Provider> keyStore, + String keyAlias, String keyPasswordString, String friendlyName) + throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { + + Provider provider; + if (keyStore.getSecond() != null) { + provider = new BouncyCastleJsseProvider(keyStore.getSecond()); + log.debug("KeyStore: {} provide special security-provider. Inject: {} into SSLContext", + friendlyName, provider.getName()); + sslContextBuilder.setProvider(provider); + + } + + log.trace("Open SSL Client-Auth keystore with password: {}", keyPasswordString); + final char[] keyPassword = keyPasswordString == null ? StringUtils.EMPTY.toCharArray() + : keyPasswordString.toCharArray(); + + if (StringUtils.isNotEmpty(keyAlias)) { + sslContextBuilder + .loadKeyMaterial(keyStore.getFirst(), keyPassword, new EaafSslKeySelectionStrategy(keyAlias)); + + } else { + sslContextBuilder.loadKeyMaterial(keyStore.getFirst(), keyPassword); + + } + + } } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java index 7ec58d46..4e8374e1 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/IHttpClientFactory.java @@ -2,10 +2,10 @@ package at.gv.egiz.eaaf.core.impl.http; import javax.annotation.Nonnull; -import at.gv.egiz.eaaf.core.exceptions.EaafException; - import org.apache.http.impl.client.CloseableHttpClient; +import at.gv.egiz.eaaf.core.exceptions.EaafException; + public interface IHttpClientFactory { /** diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/pvp/PvpRProfileHttpHeaders.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/pvp/PvpRProfileHttpHeaders.java new file mode 100644 index 00000000..cd6d7404 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/pvp/PvpRProfileHttpHeaders.java @@ -0,0 +1,86 @@ +package at.gv.egiz.eaaf.core.impl.http.pvp; + +/** + * PVP2 R-Profile HTTP-Header definitions. + * + * @author tlenz + * + */ +public class PvpRProfileHttpHeaders { + + //PVP 1.x headers + public static final String PVP_1X_VALUE_VERSION_PREFIX = "1."; + + public static final String PVP_1X_PREFIX = "X-"; + public static final String PVP_1X_VERSION_NAME = "Version"; + public static final String PVP_1X_USERID_NAME = "AUTHENTICATE-UserID"; + public static final String PVP_1X_GID_NAME = "AUTHENTICATE-GVGID"; + public static final String PVP_1X_PARTICIPANT_ID_NAME = "AUTHENTICATE-PARTICIPANTID"; + public static final String PVP_1X_GV_OU_ID_NAME = "AUTHENTICATE-GVOUID"; + public static final String PVP_1X_OU_NAME = "AUTHENTICATE-OU"; + public static final String PVP_1X_FUNCTION_NAME = "AUTHENTICATE-GVFUNCTION"; + public static final String PVP_1X_SECCLASS_NAME = "AUTHENTICATE-gvSecClass"; + public static final String PVP_1X_CN_NAME = "AUTHENTICATE-cn"; + public static final String PVP_1X_COST_CENTER_ID_NAME = "ACCOUNTING-CostCenterId"; + public static final String PVP_1X_INVOICE_RECPT_ID_NAME = "ACCOUNTING-InvoiceRecptId"; + public static final String PVP_1X_ROLES_NAME = "AUTHORIZE-ROLES"; + public static final String PVP_1X_GV_OU_OKZ_NAME = "AUTHENTICATE-GVOUOKZ"; + public static final String PVP_1X_VERSION = PVP_1X_PREFIX + PVP_1X_VERSION_NAME; + public static final String PVP_1X_USERID = PVP_1X_PREFIX + PVP_1X_USERID_NAME; + public static final String PVP_1X_GID = PVP_1X_PREFIX + PVP_1X_GID_NAME; + public static final String PVP_1X_PARTICIPANT_ID = PVP_1X_PREFIX + PVP_1X_PARTICIPANT_ID_NAME; + public static final String PVP_1X_GV_OU_ID = PVP_1X_PREFIX + PVP_1X_GV_OU_ID_NAME; + public static final String PVP_1X_OU = PVP_1X_PREFIX + PVP_1X_OU_NAME; + public static final String PVP_1X_FUNCTION = PVP_1X_PREFIX + PVP_1X_FUNCTION_NAME; + public static final String PVP_1X_SECCLASS = PVP_1X_PREFIX + PVP_1X_SECCLASS_NAME; + public static final String PVP_1X_CN = PVP_1X_PREFIX + PVP_1X_CN_NAME; + public static final String PVP_1X_COST_CENTER_ID = PVP_1X_PREFIX + PVP_1X_COST_CENTER_ID_NAME; + public static final String PVP_1X_INVOICE_RECPT_ID = PVP_1X_PREFIX + PVP_1X_INVOICE_RECPT_ID_NAME; + public static final String PVP_1X_ROLES = PVP_1X_PREFIX + PVP_1X_ROLES_NAME; + public static final String PVP_1X_GV_OU_OKZ = PVP_1X_PREFIX + PVP_1X_GV_OU_OKZ_NAME; + + + //PVP 2.x headers + public static final String PVP_2X_VALUE_VERSION_PREFIX = "2."; + + public static final String PVP_2X_VERSION = "X-PVP-VERSION"; + public static final String PVP_2X_USERID = "X-PVP-USERID"; + public static final String PVP_2X_GID = "X-PVP-GID"; + public static final String PVP_2X_PARTICIPANT_ID = "X-PVP-PARTICIPANT-ID"; + public static final String PVP_2X_GV_OU_ID = "X-PVP-OU-GV-OU-ID"; + public static final String PVP_2X_OU = "X-PVP-OU"; + public static final String PVP_2X_FUNCTION = "X-PVP-FUNCTION"; + public static final String PVP_2X_SECCLASS = "X-PVP-SECCLASS"; + public static final String PVP_2X_PRINCIPAL_NAME = "X-PVP-PRINCIPAL-NAME"; + public static final String PVP_2X_BINDING = "X-PVP-BINDING"; + public static final String PVP_2X_OU_OKZ = "X-PVP-OU-OKZ"; + public static final String PVP_2X_COST_CENTER_ID = "X-PVP-COST-CENTER-ID"; + public static final String PVP_2X_INVOICE_RECPT_ID = "X-PVP-INVOICE-RECPT-ID"; + public static final String PVP_2X_ROLES = "X-PVP-ROLES"; + + public static final String PVP_ERROR_440_CODE = "440"; + public static final String PVP_ERROR_440_MSG = "Mandatory PVP-Header {0} fehlt"; + public static final String PVP_ERROR_441_CODE = "441"; + public static final String PVP_ERROR_441_MSG = "Werte in X-PVP-ROLES haben ungültiges Format"; + public static final String PVP_ERROR_442_CODE = "442"; + public static final String PVP_ERROR_442_MSG = "Kein zulässiges Recht in X-PVP-ROLES"; + public static final String PVP_ERROR_443_CODE = "443"; + public static final String PVP_ERROR_443_MSG = "Die UserId ist am Anwendungsportal gesperrt"; + public static final String PVP_ERROR_444_CODE = "444"; + public static final String PVP_ERROR_444_MSG = + "Stammportal ist für Anfragen des angegebenen Participants nicht berechtigt"; + public static final String PVP_ERROR_445_CODE = "445"; + public static final String PVP_ERROR_445_MSG = "Participant am Anwendungsportal nicht registriert"; + public static final String PVP_ERROR_490_CODE = "490"; + public static final String PVP_ERROR_490_MSG = "Zertifikatsüberprüfung fehlgeschlagen. Grund: {0}"; + public static final String PVP_ERROR_493_CODE = "493"; + public static final String PVP_ERROR_493_MSG = "Keine Berechtigung für diese Anwendung im Stammportal"; + public static final String PVP_ERROR_494_CODE = "494"; + public static final String PVP_ERROR_494_MSG = "Die Authentifizierung des Stammportals ist fehlgeschlagen"; + public static final String PVP_ERROR_511_CODE = "511"; + public static final String PVP_ERROR_511_MSG = "PVP Version nicht unterstützt"; + + private PvpRProfileHttpHeaders() { + + } +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/SpConfigurationImpl.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/SpConfigurationImpl.java index de54d103..2f4e18fa 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/SpConfigurationImpl.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/SpConfigurationImpl.java @@ -20,8 +20,10 @@ package at.gv.egiz.eaaf.core.impl.idp.conf; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,8 +40,8 @@ public class SpConfigurationImpl implements ISpConfiguration { private static final Logger log = LoggerFactory.getLogger(SpConfigurationImpl.class); private final Map<String, String> spConfiguration; - private final List<String> targetAreasWithNoInteralBaseIdRestriction; - private final List<String> targetAreasWithNoBaseIdTransmissionRestriction; + private final Set<String> targetAreasWithNoInteralBaseIdRestriction; + private final Set<String> targetAreasWithNoBaseIdTransmissionRestriction; /** * Service-provider configuration holder. @@ -52,21 +54,19 @@ public class SpConfigurationImpl implements ISpConfiguration { // set oa specific restrictions targetAreasWithNoInteralBaseIdRestriction = Collections - .unmodifiableList(KeyValueUtils.getListOfCsvValues(authConfig.getBasicConfiguration( - CONFIG_KEY_RESTRICTIONS_BASEID_INTERNAL, EaafConstants.URN_PREFIX_CDID))); + .unmodifiableSet(new HashSet<String>(KeyValueUtils.getListOfCsvValues(authConfig.getBasicConfiguration( + CONFIG_KEY_RESTRICTIONS_BASEID_INTERNAL, EaafConstants.URN_PREFIX_CDID)))); targetAreasWithNoBaseIdTransmissionRestriction = Collections - .unmodifiableList(KeyValueUtils.getListOfCsvValues(authConfig.getBasicConfiguration( - CONFIG_KEY_RESTRICTIONS_BASEID_TRANSMISSION, EaafConstants.URN_PREFIX_CDID))); + .unmodifiableSet(new HashSet<String>(KeyValueUtils.getListOfCsvValues(authConfig.getBasicConfiguration( + CONFIG_KEY_RESTRICTIONS_BASEID_TRANSMISSION, EaafConstants.URN_PREFIX_CDID)))); if (log.isTraceEnabled()) { log.trace("Internal policy for OA: " + getUniqueIdentifier()); - for (final String el : targetAreasWithNoInteralBaseIdRestriction) { - log.trace(" Allow baseID processing for prefix " + el); - } - for (final String el : targetAreasWithNoBaseIdTransmissionRestriction) { - log.trace(" Allow baseID transfer for prefix " + el); - } + targetAreasWithNoInteralBaseIdRestriction.stream() + .forEach(el -> log.trace(" Allow baseID processing for prefix " + el)); + targetAreasWithNoBaseIdTransmissionRestriction.stream() + .forEach(el -> log.trace(" Allow baseID transfer for prefix " + el)); } } @@ -143,12 +143,12 @@ public class SpConfigurationImpl implements ISpConfiguration { } @Override - public final List<String> getTargetsWithNoBaseIdInternalProcessingRestriction() { + public final Set<String> getTargetsWithNoBaseIdInternalProcessingRestriction() { return this.targetAreasWithNoInteralBaseIdRestriction; } @Override - public final List<String> getTargetsWithNoBaseIdTransferRestriction() { + public final Set<String> getTargetsWithNoBaseIdTransferRestriction() { return this.targetAreasWithNoBaseIdTransmissionRestriction; } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/AuthenticatedEncryptionPendingRequestIdGenerationStrategy.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/AuthenticatedEncryptionPendingRequestIdGenerationStrategy.java new file mode 100644 index 00000000..ca1db67d --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/AuthenticatedEncryptionPendingRequestIdGenerationStrategy.java @@ -0,0 +1,280 @@ +package at.gv.egiz.eaaf.core.impl.utils; + +import java.nio.charset.StandardCharsets; +import java.security.Provider; +import java.util.Base64; + +import javax.annotation.Nonnull; +import javax.annotation.PostConstruct; +import javax.crypto.SecretKey; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.joda.time.DurationFieldType; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.jose4j.jca.ProviderContext; +import org.jose4j.jwa.AlgorithmConstraints; +import org.jose4j.jwa.AlgorithmConstraints.ConstraintType; +import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers; +import org.jose4j.jwe.JsonWebEncryption; +import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers; +import org.jose4j.lang.JoseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.utils.IPendingRequestIdGenerationStrategy; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.SymmetricKeyConfiguration; +import at.gv.egiz.eaaf.core.impl.credential.SymmetricKeyConfiguration.SymmetricKeyType; +import at.gv.egiz.eaaf.core.impl.data.Pair; + +/** + * PendingRequestId generation strategy based on signed tokens that facilitates + * extended token validation. + * + * @author tlenz + * + */ +public class AuthenticatedEncryptionPendingRequestIdGenerationStrategy + implements IPendingRequestIdGenerationStrategy { + private static final Logger log = + LoggerFactory.getLogger(AuthenticatedEncryptionPendingRequestIdGenerationStrategy.class); + + @Autowired(required = true) IConfiguration baseConfig; + @Autowired EaafKeyStoreFactory keyStoreFactory; + + private static final String FRIENDLYNAME = "pendingRequestId key"; + + public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_TYPE = + "core.pendingrequestid.digist.type"; + public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET = + "core.pendingrequestid.digist.secret"; + + public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_KEYSTORE = + "core.pendingrequestid.digist.keystore.name"; + public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_ALIAS = + "core.pendingrequestid.digist.key.alias"; + + public static final String CONFIG_PROP_PENDINGREQUESTID_MAX_LIFETIME = + "core.pendingrequestid.maxlifetime"; + + public static final String DEFAULT_PENDINGREQUESTID_DIGIST_ALGORITHM = "HmacSHA256"; + public static final String DEFAULT_PENDINGREQUESTID_MAX_LIFETIME = "300"; + + private static final int ENCODED_TOKEN_PARTS = 2; + private static final String TOKEN_SEPARATOR = "|"; + private static final DateTimeFormatter TOKEN_TEXTUAL_DATE_FORMAT = + DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss SSS ZZ").withZoneUTC(); + + private int maxPendingRequestIdLifeTime = 300; + private final int maxPendingReqIdSize = 1024; + private Pair<SecretKey, Provider> key = null; + private final String salt = "notRequiredInThisScenario"; + + @Override + public String generateExternalPendingRequestId() throws EaafException { + try { + final String toSign = buildInternalToken(Random.nextLongRandom(), DateTime.now()); + JsonWebEncryption encToken = new JsonWebEncryption(); + encToken.setAlgorithmHeaderValue(selectKeyWrappingAlgorithm(key.getFirst())); + encToken.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_GCM); + encToken.setKey(key.getFirst()); + encToken.setPayload(toSign); + + + + if (key.getSecond() != null) { + final ProviderContext providerCtx = new ProviderContext(); + providerCtx.getSuppliedKeyProviderContext().setSignatureProvider( + key.getSecond().getName()); + encToken.setProviderContext(providerCtx); + + } + + return Base64.getUrlEncoder() + .encodeToString(encToken.getCompactSerialization().getBytes(StandardCharsets.UTF_8)); + + } catch (final JoseException e) { + throw new EaafException("internal.pendingreqid.02", new Object[] { e.getMessage() }, e); + + } + + } + + @Override + public String getPendingRequestIdWithOutChecks(final String externalPendingReqId) + throws PendingReqIdValidationException { + try { + String stringToken = getDecryptedExternalPendingRequestId(externalPendingReqId); + log.debug("Token decryption successful"); + + if (!(StringUtils.countMatches(stringToken, TOKEN_SEPARATOR) == ENCODED_TOKEN_PARTS - 1)) { + log.warn("PendingRequestId has an unvalid format"); + log.debug("PendingRequestId: {}", stringToken); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.01"); + + } + + final String[] tokenElements = + StringUtils.split(stringToken, TOKEN_SEPARATOR, ENCODED_TOKEN_PARTS); + return tokenElements[1]; + + } catch (JoseException e) { + log.warn("Token is NOT a valid String. Msg: {}", e.getMessage()); + log.debug("TokenValue: {}", externalPendingReqId); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.05", e); + + } + } + + @Override + public String validateAndGetPendingRequestId(final String externalPendingReqId) + throws PendingReqIdValidationException { + try { + String stringToken = getDecryptedExternalPendingRequestId(externalPendingReqId); + log.debug("Token decryption successful"); + + if (!(StringUtils.countMatches(stringToken, TOKEN_SEPARATOR) == ENCODED_TOKEN_PARTS - 1)) { + log.info("PendingRequestId: {}", stringToken); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.01"); + + } + + final String[] tokenElements = + StringUtils.split(stringToken, TOKEN_SEPARATOR, ENCODED_TOKEN_PARTS); + final String internalPendingReqId = tokenElements[1]; + final DateTime timeStamp = TOKEN_TEXTUAL_DATE_FORMAT.parseDateTime(tokenElements[0]); + + log.trace("Checking valid period ... "); + final DateTime now = DateTime.now(); + if (timeStamp.withFieldAdded(DurationFieldType.seconds(), maxPendingRequestIdLifeTime) + .isBefore(now)) { + log.info("Token exceeds the valid period. Token: {} | Now: {}", timeStamp, now); + throw new PendingReqIdValidationException(internalPendingReqId, + "internal.pendingreqid.06"); + + } + log.debug("Token valid-period check successful"); + + return internalPendingReqId; + + } catch (JoseException e) { + log.warn("Token is NOT a valid encrypt. Msg: {}", e.getMessage()); + log.debug("TokenValue: {}", externalPendingReqId); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.04", e); + + } catch (final IllegalArgumentException e) { + log.warn("Token is NOT a valid String. Msg: {}", e.getMessage()); + log.debug("TokenValue: {}", externalPendingReqId); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.05", e); + + } + } + + @Nonnull + private String getDecryptedExternalPendingRequestId(String externalPendingReqId) + throws JoseException, PendingReqIdValidationException { + if (StringUtils.isEmpty(externalPendingReqId)) { + log.info("PendingReqId is 'null' or empty"); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.00"); + + } + + log.trace("RAW external pendingReqId: {}", externalPendingReqId); + final byte[] externalPendingReqIdBytes = Base64.getUrlDecoder().decode(externalPendingReqId); + + if (externalPendingReqIdBytes.length > maxPendingReqIdSize) { + log.warn("pendingReqId size exceeds {}", maxPendingReqIdSize); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.03"); + + } + + + JsonWebEncryption encToken = new JsonWebEncryption(); + encToken.setContentEncryptionAlgorithmConstraints(new AlgorithmConstraints( + ConstraintType.WHITELIST, ContentEncryptionAlgorithmIdentifiers.AES_128_GCM)); + encToken.setAlgorithmConstraints(new AlgorithmConstraints( + ConstraintType.WHITELIST, + KeyManagementAlgorithmIdentifiers.DIRECT, + KeyManagementAlgorithmIdentifiers.A128GCMKW + )); + encToken.setKey(key.getFirst()); + + if (key.getSecond() != null) { + final ProviderContext providerCtx = new ProviderContext(); + providerCtx.getSuppliedKeyProviderContext().setSignatureProvider( + key.getSecond().getName()); + encToken.setProviderContext(providerCtx); + + } + + encToken.setCompactSerialization(new String(externalPendingReqIdBytes, StandardCharsets.UTF_8)); + return encToken.getPayload(); + + } + + private String selectKeyWrappingAlgorithm(SecretKey first) { + if ("AES".equals(first.getAlgorithm())) { + return KeyManagementAlgorithmIdentifiers.A128GCMKW; + + } else { + return KeyManagementAlgorithmIdentifiers.DIRECT; + + } + } + + @PostConstruct + private void initialize() throws EaafConfigurationException { + log.debug("Initializing " + this.getClass().getName() + " ... "); + + maxPendingRequestIdLifeTime = + Integer.parseInt(baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_MAX_LIFETIME, + DEFAULT_PENDINGREQUESTID_MAX_LIFETIME)); + + + SymmetricKeyConfiguration secretKeyConfig = new SymmetricKeyConfiguration(); + secretKeyConfig.setFriendlyName(FRIENDLYNAME); + secretKeyConfig.setKeyType( + baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_TYPE, + SymmetricKeyType.PASSPHRASE.name())); + + secretKeyConfig.setSoftKeyPassphrase( + baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET)); + secretKeyConfig.setSoftKeySalt(salt); + + secretKeyConfig.setKeyStoreName( + baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_KEYSTORE)); + secretKeyConfig.setKeyAlias( + baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_ALIAS)); + + //validate symmetric-key configuration + secretKeyConfig.validate(); + + try { + key = keyStoreFactory.buildNewSymmetricKey(secretKeyConfig); + + } catch (EaafException e) { + log.error("Can NOT initialize TokenService with configuration object", e); + throw new EaafConfigurationException("config.09", + new Object[] { CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET, "Can NOT generate HMAC key" }, + e); + + } + + log.info(this.getClass().getName() + " initialized with Alg: {} and maxLifeTime: {}", + ContentEncryptionAlgorithmIdentifiers.AES_128_GCM, maxPendingRequestIdLifeTime); + + } + + private String buildInternalToken(final String internalPendingReqId, final DateTime now) { + return new StringBuilder().append(TOKEN_TEXTUAL_DATE_FORMAT.print(now)).append(TOKEN_SEPARATOR) + .append(internalPendingReqId).toString(); + } + +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafObjectInputStream.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafObjectInputStream.java new file mode 100644 index 00000000..e15c7a37 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafObjectInputStream.java @@ -0,0 +1,39 @@ +package at.gv.egiz.eaaf.core.impl.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InvalidClassException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.util.List; + +import javax.annotation.Nonnull; + +public class EaafObjectInputStream extends ObjectInputStream { + + private List<String> allowedClassNames; + + /** + * Object input-stream with internal class validation. + * + * @param is Inputstream to deserialize. + * @param classNames Whitelisted classnames + * @throws IOException In case of an error + */ + public EaafObjectInputStream(@Nonnull InputStream is, @Nonnull List<String> classNames) throws IOException { + super(is); + this.allowedClassNames = classNames; + + } + + //Only deserialize instances of our expected class + @Override + protected Class<?> resolveClass(ObjectStreamClass desc) + throws IOException, ClassNotFoundException { + if (!allowedClassNames.contains(desc.getName())) { + throw new InvalidClassException("Unauthorized deserialization attempt: {}",desc.getName()); + + } + return super.resolveClass(desc); + } +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafSerializationUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafSerializationUtils.java new file mode 100644 index 00000000..e15c6800 --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/EaafSerializationUtils.java @@ -0,0 +1,69 @@ +package at.gv.egiz.eaaf.core.impl.utils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.List; + +import org.springframework.lang.Nullable; + +public class EaafSerializationUtils { + + private EaafSerializationUtils() { + + } + + /** + * Serialize a given Java object into a byte array. + * + * @param object Java object to serialize. + * @return Serialized Java object + */ + @Nullable + public static byte[] serialize(@Nullable Object object) { + if (object == null) { + return null; + + } + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(object); + oos.flush(); + + } catch (final IOException ex) { + throw new IllegalArgumentException("Failed to serialize object of type: " + object.getClass(), ex); + + } + + return baos.toByteArray(); + } + + /** + * Deserialize the byte array into an object. + * + * @param bytes a serialized object + * @param allowedClassName List of classnames that are allowed for deserialization + * @return the result of deserializing the bytes + */ + @Nullable + public static Object deserialize(@Nullable byte[] bytes, List<String> allowedClassName) { + if (bytes == null) { + return null; + + } + + try (ObjectInputStream ois = new EaafObjectInputStream(new ByteArrayInputStream(bytes), allowedClassName)) { + return ois.readObject(); + + } catch (final IOException ex) { + throw new IllegalArgumentException("Failed to deserialize object", ex); + + } catch (final ClassNotFoundException ex) { + throw new IllegalStateException("Failed to deserialize object type", ex); + + } + } +} diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/KeyStoreUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/KeyStoreUtils.java index 99b87819..be51426c 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/KeyStoreUtils.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/KeyStoreUtils.java @@ -30,12 +30,16 @@ import java.security.KeyStoreException; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; +import lombok.extern.slf4j.Slf4j; + /** * Utility for creating and loading key stores. * * @author Paul Ivancsics * @version $Id$ */ +@Slf4j public class KeyStoreUtils { /** @@ -110,6 +114,32 @@ public class KeyStoreUtils { } /** + * Loads a keyStore with known keyStore type. + * + * @param is input stream + * @param password Password protecting the keyStore + * @param keyStoreType Type of the KeyStore + * @return loaded KeyStore + * @throws IOException In case of a general error + * @throws GeneralSecurityException In case of a KeyStore access error + */ + public static KeyStore loadKeyStore(final InputStream is, final String password, KeyStoreType keyStoreType) + throws IOException, GeneralSecurityException { + String internalType = KEYSTORE_TYPE_PKCS12; + if (keyStoreType.equals(KeyStoreType.JKS)) { + internalType = KEYSTORE_TYPE_JKS; + + } else if (keyStoreType.equals(KeyStoreType.PKCS12)) { + internalType = KEYSTORE_TYPE_PKCS12; + + } + + return loadKeyStore(internalType, is, password); + + } + + + /** * Loads a keyStore without knowing the keyStore type. * * @param is input stream @@ -125,14 +155,18 @@ public class KeyStoreUtils { try { try { ks = loadKeyStore(KEYSTORE_TYPE_PKCS12, is, password); + } catch (final IOException e2) { is.reset(); ks = loadKeyStore(KEYSTORE_TYPE_JKS, is, password); + } + } catch (final Exception e) { - e.printStackTrace(); - + log.warn("Can not load keystore", e); + } + return ks; } diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/KeyValueUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/KeyValueUtils.java index 0c5eeb40..b0a91e74 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/KeyValueUtils.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/KeyValueUtils.java @@ -28,13 +28,12 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Utils to operate on Key/Value based configurations. @@ -43,8 +42,7 @@ import org.slf4j.LoggerFactory; * */ public class KeyValueUtils { - private static final Logger log = LoggerFactory.getLogger(KeyValueUtils.class); - + public static final String KEY_DELIMITER = "."; public static final String CSV_DELIMITER = ","; public static final String KEYVVALUEDELIMITER = "="; @@ -154,18 +152,14 @@ public class KeyValueUtils { * null */ public static Map<String, String> removePrefixFromKeys(final Map<String, String> keys, - final String prefix) { - final Map<String, String> result = new HashMap<>(); - final Iterator<Entry<String, String>> interator = keys.entrySet().iterator(); - while (interator.hasNext()) { - final Entry<String, String> el = interator.next(); - final String newKey = removePrefixFromKey(el.getKey(), prefix); - if (StringUtils.isNotEmpty(newKey)) { - result.put(newKey, el.getValue()); - } - } - - return result; + final String prefix) { + return keys.entrySet().stream() + .filter(el -> StringUtils.isNotEmpty(removePrefixFromKey(el.getKey(), prefix))) + .collect(Collectors.toMap( + el -> removePrefixFromKey(el.getKey(), prefix), + el -> el.getValue())); + + } /** @@ -351,19 +345,13 @@ public class KeyValueUtils { * @return Map of Key / Value pairs, but never null */ public static Map<String, String> convertListToMap(final List<String> elements) { - final Map<String, String> map = new HashMap<>(); - for (final String el : elements) { - if (el.contains(KEYVVALUEDELIMITER)) { - final String[] split = el.split(KEYVVALUEDELIMITER); - map.put(split[0], split[1]); - - } else { - log.debug("Key/Value Mapper: '" + el + "' contains NO '='. Ignore it."); - } - - } + return elements.stream() + .filter(el -> el.contains(KEYVVALUEDELIMITER)) + .map(el -> el.split(KEYVVALUEDELIMITER)) + .collect(Collectors.toMap( + el -> el[0], + el -> el[1])); - return map; } /** diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java index bc770a8c..5cac4cb0 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java @@ -1,19 +1,14 @@ package at.gv.egiz.eaaf.core.impl.utils; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; -import java.util.Arrays; import java.util.Base64; import javax.annotation.PostConstruct; import javax.crypto.Mac; import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; @@ -32,6 +27,9 @@ import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.exceptions.EaafIllegalStateException; import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.SymmetricKeyConfiguration; +import at.gv.egiz.eaaf.core.impl.credential.SymmetricKeyConfiguration.SymmetricKeyType; /** * PendingRequestId generation strategy based on signed tokens that facilitates @@ -45,11 +43,22 @@ public class SecurePendingRequestIdGenerationStrategy private static final Logger log = LoggerFactory.getLogger(SecurePendingRequestIdGenerationStrategy.class); - @Autowired(required = true) - IConfiguration baseConfig; + @Autowired(required = true) IConfiguration baseConfig; + @Autowired EaafKeyStoreFactory keyStoreFactory; + private static final String FRIENDLYNAME = "pendingRequestId key"; + + public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_TYPE = + "core.pendingrequestid.digist.type"; public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET = "core.pendingrequestid.digist.secret"; + + public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_KEYSTORE = + "core.pendingrequestid.digist.keystore.name"; + public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_ALIAS = + "core.pendingrequestid.digist.key.alias"; + + public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_ALGORITHM = "core.pendingrequestid.digist.algorithm"; public static final String CONFIG_PROP_PENDINGREQUESTID_MAX_LIFETIME = @@ -61,43 +70,32 @@ public class SecurePendingRequestIdGenerationStrategy private static final int ENCODED_TOKEN_PARTS = 3; private static final String TOKEN_SEPARATOR = "|"; private static final DateTimeFormatter TOKEN_TEXTUAL_DATE_FORMAT = - DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss SSS"); + DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss SSS ZZ").withZoneUTC(); private int maxPendingRequestIdLifeTime = 300; private final int maxPendingReqIdSize = 1024; private String digistAlgorithm = null; private SecretKey key = null; - private final byte[] salt = "notRequiredInThisScenario".getBytes(Charset.defaultCharset()); + private final String salt = "notRequiredInThisScenario"; @Override public String generateExternalPendingRequestId() throws EaafException { - try { - final String toSign = buildInternalToken(Random.nextLongRandom(), DateTime.now()); - final StringBuilder externalPendingRequestId = new StringBuilder(); - externalPendingRequestId.append(toSign); - externalPendingRequestId.append(TOKEN_SEPARATOR); - externalPendingRequestId.append(Base64.getEncoder().encodeToString(calculateHmac(toSign))); - return Base64.getUrlEncoder() - .encodeToString(externalPendingRequestId.toString().getBytes("UTF-8")); - - } catch (final UnsupportedEncodingException e) { - throw new EaafException("internal.99", new Object[] { e.getMessage() }, e); - - } + final String toSign = buildInternalToken(Random.nextLongRandom(), DateTime.now()); + final StringBuilder externalPendingRequestId = new StringBuilder(); + externalPendingRequestId.append(toSign); + externalPendingRequestId.append(TOKEN_SEPARATOR); + externalPendingRequestId.append(Base64.getEncoder().encodeToString(calculateHmac(toSign))); + return Base64.getUrlEncoder() + .encodeToString(externalPendingRequestId.toString().getBytes(StandardCharsets.UTF_8)); } @Override public String getPendingRequestIdWithOutChecks(final String externalPendingReqId) throws PendingReqIdValidationException { - try { - final String[] tokenElements = extractTokens(externalPendingReqId); - return tokenElements[1]; - - } catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - - } + final String[] tokenElements = extractTokens(externalPendingReqId); + return tokenElements[1]; + } @Override @@ -111,11 +109,11 @@ public class SecurePendingRequestIdGenerationStrategy log.trace("Checking HMAC from externalPendingReqId ... "); final byte[] tokenDigest = Base64.getDecoder().decode(tokenElements[2]); final byte[] refDigist = calculateHmac(buildInternalToken(internalPendingReqId, timeStamp)); - if (!Arrays.equals(tokenDigest, refDigist)) { + + if (!MessageDigest.isEqual(refDigist,tokenDigest)) { log.warn("Digest of Token does NOT match"); log.debug("Token: {} | Ref: {}", tokenDigest, refDigist); - throw new PendingReqIdValidationException(null, - "Digest of pendingRequestId does NOT match"); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.04"); } log.debug("PendingRequestId HMAC digest check successful"); @@ -126,8 +124,7 @@ public class SecurePendingRequestIdGenerationStrategy .isBefore(now)) { log.warn("Token exceeds the valid period"); log.debug("Token: {} | Now: {}", timeStamp, now); - throw new PendingReqIdValidationException(internalPendingReqId, - "PendingRequestId exceeds the valid period"); + throw new PendingReqIdValidationException(internalPendingReqId, "internal.pendingreqid.06"); } log.debug("Token valid-period check successful"); @@ -137,20 +134,17 @@ public class SecurePendingRequestIdGenerationStrategy } catch (final IllegalArgumentException | EaafIllegalStateException e) { log.warn("Token is NOT a valid String. Msg: {}", e.getMessage()); log.debug("TokenValue: {}", externalPendingReqId); - throw new PendingReqIdValidationException(null, "PendingReqId is NOT a valid String", e); - - } catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.06", e); } } @NonNull private String[] extractTokens(@Nullable final String externalPendingReqId) - throws PendingReqIdValidationException, UnsupportedEncodingException { + throws PendingReqIdValidationException { if (StringUtils.isEmpty(externalPendingReqId)) { log.info("PendingReqId is 'null' or empty"); - throw new PendingReqIdValidationException(null, "PendingReqId is 'null' or empty"); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.00"); } @@ -159,12 +153,11 @@ public class SecurePendingRequestIdGenerationStrategy if (externalPendingReqIdBytes.length > maxPendingReqIdSize) { log.warn("pendingReqId size exceeds {}", maxPendingReqIdSize); - throw new PendingReqIdValidationException(null, - "pendingReqId exceeds max.size: " + maxPendingReqIdSize); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.03"); } - final String stringToken = new String(externalPendingReqIdBytes, "UTF-8"); + final String stringToken = new String(externalPendingReqIdBytes, StandardCharsets.UTF_8); if (StringUtils.countMatches(stringToken, TOKEN_SEPARATOR) == ENCODED_TOKEN_PARTS - 1) { final String[] tokenElements = StringUtils.split(stringToken, TOKEN_SEPARATOR, ENCODED_TOKEN_PARTS); @@ -173,7 +166,7 @@ public class SecurePendingRequestIdGenerationStrategy } else { log.warn("PendingRequestId has an unvalid format"); log.debug("PendingRequestId: {}", stringToken); - throw new PendingReqIdValidationException(null, "PendingReqId has an unvalid format"); + throw new PendingReqIdValidationException(null, "internal.pendingreqid.01"); } @@ -183,13 +176,6 @@ public class SecurePendingRequestIdGenerationStrategy private void initialize() throws EaafConfigurationException { log.debug("Initializing " + this.getClass().getName() + " ... "); - final String pendingReqIdDigistSecret = - baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET); - if (StringUtils.isEmpty(pendingReqIdDigistSecret)) { - throw new EaafConfigurationException("config.08", - new Object[] { CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET }); - } - digistAlgorithm = baseConfig.getBasicConfiguration( CONFIG_PROP_PENDINGREQUESTID_DIGIST_ALGORITHM, DEFAULT_PENDINGREQUESTID_DIGIST_ALGORITHM); @@ -197,12 +183,29 @@ public class SecurePendingRequestIdGenerationStrategy Integer.parseInt(baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_MAX_LIFETIME, DEFAULT_PENDINGREQUESTID_MAX_LIFETIME)); + + SymmetricKeyConfiguration secretKeyConfig = new SymmetricKeyConfiguration(); + secretKeyConfig.setFriendlyName(FRIENDLYNAME); + secretKeyConfig.setKeyType( + baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_TYPE, + SymmetricKeyType.PASSPHRASE.name())); + + secretKeyConfig.setSoftKeyPassphrase( + baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET)); + secretKeyConfig.setSoftKeySalt(salt); + + secretKeyConfig.setKeyStoreName( + baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_KEYSTORE)); + secretKeyConfig.setKeyAlias( + baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_ALIAS)); + + //validate symmetric-key configuration + secretKeyConfig.validate(); + try { - final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WITHHMACSHA256"); - final KeySpec spec = new PBEKeySpec(pendingReqIdDigistSecret.toCharArray(), salt, 10000, 128); - key = keyFactory.generateSecret(spec); + key = keyStoreFactory.buildNewSymmetricKey(secretKeyConfig).getFirst(); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + } catch (EaafException e) { log.error("Can NOT initialize TokenService with configuration object", e); throw new EaafConfigurationException("config.09", new Object[] { CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET, "Can NOT generate HMAC key" }, @@ -224,9 +227,9 @@ public class SecurePendingRequestIdGenerationStrategy try { final Mac mac = Mac.getInstance(digistAlgorithm); mac.init(key); - return mac.doFinal(toSign.getBytes("UTF-8")); + return mac.doFinal(toSign.getBytes(StandardCharsets.UTF_8)); - } catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeyException e) { + } catch (NoSuchAlgorithmException | InvalidKeyException e) { log.error("Can NOT generate secure pendingRequestId", e); throw new EaafIllegalStateException( new Object[] { "Can NOT caluclate digist for secure pendingRequestId" }, e); diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/TransactionIdUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/TransactionIdUtils.java index 4c1601c0..212460d7 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/TransactionIdUtils.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/TransactionIdUtils.java @@ -21,7 +21,10 @@ package at.gv.egiz.eaaf.core.impl.utils; import java.util.UUID; +import javax.annotation.Nullable; + import at.gv.egiz.eaaf.core.api.IRequest; +import lombok.extern.slf4j.Slf4j; /** * Transaction Identifier Utils. @@ -29,6 +32,7 @@ import at.gv.egiz.eaaf.core.api.IRequest; * @author tlenz * */ +@Slf4j public class TransactionIdUtils { /** @@ -58,11 +62,16 @@ public class TransactionIdUtils { * * @param pendingRequest Http request object */ - public static void setAllLoggingVariables(final IRequest pendingRequest) { - setTransactionId(pendingRequest.getUniqueTransactionIdentifier()); - setSessionId(pendingRequest.getUniqueSessionIdentifier()); - setServiceProviderId(pendingRequest.getServiceProviderConfiguration().getUniqueIdentifier()); - + public static void setAllLoggingVariables(@Nullable final IRequest pendingRequest) { + if (pendingRequest != null) { + setTransactionId(pendingRequest.getUniqueTransactionIdentifier()); + setSessionId(pendingRequest.getUniqueSessionIdentifier()); + setServiceProviderId(pendingRequest.getServiceProviderConfiguration().getUniqueIdentifier()); + + } else { + log.info("Can NOT set MDC variables from pendingRequest because it is 'null'"); + + } } /** diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/X509Utils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/X509Utils.java index 72c183bf..4d872ebe 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/X509Utils.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/X509Utils.java @@ -1,6 +1,7 @@ package at.gv.egiz.eaaf.core.impl.utils; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.List; import javax.security.auth.x500.X500Principal; @@ -11,6 +12,18 @@ public class X509Utils { * Sorts the Certificate Chain by IssuerDN and SubjectDN. The [0]-Element should * be the Hostname, the last Element should be the Root Certificate. * + * @param certChain The first element must be the correct one. + * @return sorted Certificate Chain + */ + public static List<X509Certificate> sortCertificates(X509Certificate[] certChain) { + return sortCertificates(Arrays.asList(certChain)); + + } + + /** + * Sorts the Certificate Chain by IssuerDN and SubjectDN. The [0]-Element should + * be the Hostname, the last Element should be the Root Certificate. + * * @param certs The first element must be the correct one. * @return sorted Certificate Chain */ @@ -48,4 +61,5 @@ public class X509Utils { return certs; } + } diff --git a/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties b/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties index b20c5f63..79f82af8 100644 --- a/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties +++ b/eaaf_core_utils/src/main/resources/messages/eaaf_utils_message.properties @@ -11,8 +11,22 @@ internal.keystore.06=KeyStore: {0} initialization failed. Reason: {1} internal.keystore.07=Validation of KeyStore: {0} failed. Reason: {1} internal.keystore.08=Can not access Key: {1} in KeyStore: {0} internal.keystore.09=Can not access Key: {1} in KeyStore: {0} Reason: {2} +internal.keystore.10=HSM-Facade NOT INITIALIZED. Find HSM-Facade class: {0} put that looks WRONG. +internal.keystore.11=KeyStore: {0} has a wrong configuration. Property: {0} Reason:{1} + +internal.key.00=Can not generate passphrase based symmetric-key: {0} Reason: {1} +internal.key.01=Can not use key from Keystore: {0} Reason: {1} internal.httpclient.00=HttpClient:{0} uses http Basic-Auth, but 'Username' is NOT set internal.httpclient.01=HttpClient:{0} uses X509 client-auth, but 'KeyStoreConfig' is NOT set internal.httpclient.02=HttpClient:{0} uses KeyStore:{1}, but 'keyPassword' is NOT set -internal.httpclient.03=Can not initialize SSLContext for HttpClient:{0} Reason:{1}
\ No newline at end of file +internal.httpclient.03=Can not initialize SSLContext for HttpClient:{0} Reason:{1} + +internal.pendingreqid.00=Process Token is 'null' or 'empty' +internal.pendingreqid.01=Process Token is NOT valid because it has an invalid format +internal.pendingreqid.02=Can not create process Token +internal.pendingreqid.03=Process Token is NOT valid because it reached maximum size +internal.pendingreqid.04=Process Token is NOT valid because it is cryptographically invalid +internal.pendingreqid.05=Process Token is NOT valid because it has an invalid encoding +internal.pendingreqid.06=Process Token is NOT valid because it exceeds the valid period + |