/* * Copyright 2017 Graz University of Technology EAAF-Core Components has been developed in a * cooperation between EGIZ, A-SIT Plus, A-SIT, and Graz University of Technology. * * Licensed under the EUPL, Version 1.2 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: * https://joinup.ec.europa.eu/news/understanding-eupl-v12 * * 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.modules.pvp2.impl.utils; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.Provider; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; 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; import at.gv.egiz.eaaf.core.impl.data.Pair; import at.gv.egiz.eaaf.modules.pvp2.PvpConstants; import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; import at.gv.egiz.eaaf.modules.pvp2.api.utils.IPvp2CredentialProvider; import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.EaafKeyStoreX509CredentialAdapter; import org.apache.commons.lang3.StringUtils; import org.apache.xml.security.algorithms.JCEMapper; import org.opensaml.security.credential.UsageType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ResourceLoader; import lombok.extern.slf4j.Slf4j; @Slf4j public abstract class AbstractCredentialProvider implements IPvp2CredentialProvider { private static final String TRUSTED_CERTIFICATES_OPERATION = "Trusted Certificate Entries"; @Autowired protected ResourceLoader resourceLoader; @Autowired protected IConfiguration basicConfig; @Autowired private EaafKeyStoreFactory keyStoreFactory; private Pair keyStore = null; /** * Get a friendlyName for this keyStore implementation This friendlyName is used * for logging. * * @return keyStore friendlyName */ public final String getFriendlyName() { try { return getBasicKeyStoreConfig().getFriendlyName(); } catch (final EaafConfigurationException e) { return "No KeyStoreName"; } } /** * Get the basic KeyStore configuration object for this SAML2 credential. * * @return KeyStore configuration object * @throws EaafConfigurationException In case of a configuration error */ @Nonnull public abstract KeyStoreConfiguration getBasicKeyStoreConfig() throws EaafConfigurationException; /** * Get alias of key for metadata signing. * * @return key alias */ public abstract String getMetadataKeyAlias(); /** * Get password of key for metadata signing. * * @return key password */ public abstract String getMetadataKeyPassword(); /** * Get alias of key for request/response signing. * * @return key alias */ public abstract String getSignatureKeyAlias(); /** * Get password of key for request/response signing. * * @return key password */ public abstract String getSignatureKeyPassword(); /** * Get alias of key for IDP response encryption. * * @return key alias */ public abstract String getEncryptionKeyAlias(); /** * Get password of key for IDP response encryption. * * @return key password */ public abstract String getEncryptionKeyPassword(); /** * Get Credentials to sign metadata. * * @return Credentials * @throws CredentialsNotAvailableException In case of a credential error */ @Override public EaafX509Credential getMetaDataSigningCredential() throws CredentialsNotAvailableException { try { final EaafKeyStoreX509CredentialAdapter credentials = new EaafKeyStoreX509CredentialAdapter( keyStore.getFirst(), getMetadataKeyAlias(), getPassCharArrayOrNull(getMetadataKeyPassword()), getFriendlyName()); credentials.setUsageType(UsageType.SIGNING); credentials.setSignatureAlgorithmForSigning(selectSigningAlgorithm(credentials)); credentials.setKeyEncryptionAlgorithmForDataEncryption(selectKeyEncryptionAlgorithm(credentials)); return credentials; } catch (final Exception e) { throw new CredentialsNotAvailableException("internal.pvp.01", new Object[] { getFriendlyName(), getMetadataKeyAlias() }, e); } } /** * Get Credentials to sign SAML2 messages, like AuthnRequest, Response, * Assertions as some examples. * * @return Credentials * @throws CredentialsNotAvailableException In case of a credential error */ @Override public EaafX509Credential getMessageSigningCredential() throws CredentialsNotAvailableException { try { final EaafKeyStoreX509CredentialAdapter credentials = new EaafKeyStoreX509CredentialAdapter( keyStore.getFirst(), getSignatureKeyAlias(), getPassCharArrayOrNull(getSignatureKeyPassword()), getFriendlyName()); credentials.setUsageType(UsageType.SIGNING); credentials.setSignatureAlgorithmForSigning(selectSigningAlgorithm(credentials)); credentials.setKeyEncryptionAlgorithmForDataEncryption(selectKeyEncryptionAlgorithm(credentials)); return credentials; } catch (final Exception e) { throw new CredentialsNotAvailableException("internal.pvp.01", new Object[] { getFriendlyName(), getSignatureKeyAlias() }, e); } } /** * Get Credentials to encrypt messages, like Assertion as example. * * @return Credentials * @throws CredentialsNotAvailableException In case of a credential error */ @Override public EaafX509Credential getMessageEncryptionCredential() throws CredentialsNotAvailableException { // if no encryption key is configured return null if (StringUtils.isEmpty(getEncryptionKeyAlias())) { return null; } try { final EaafKeyStoreX509CredentialAdapter credentials = new EaafKeyStoreX509CredentialAdapter( keyStore.getFirst(), getEncryptionKeyAlias(), getPassCharArrayOrNull(getEncryptionKeyPassword()), getFriendlyName()); credentials.setUsageType(UsageType.ENCRYPTION); credentials.setSignatureAlgorithmForSigning(selectSigningAlgorithm(credentials)); credentials.setKeyEncryptionAlgorithmForDataEncryption(selectKeyEncryptionAlgorithm(credentials)); return credentials; } catch (final Exception e) { throw new CredentialsNotAvailableException("internal.pvp.01", new Object[] { getFriendlyName(), getEncryptionKeyAlias() }, e); } } /** * Get a List of trusted {@link X509Certificate} that are available in this * KeyStore. * * @return List of trusted {@link X509Certificate}, or an emptry {@link List} if * no certificates are available * @throws CredentialsNotAvailableException In case of a KeyStore error */ @Override @Nonnull public List getTrustedCertificates() throws CredentialsNotAvailableException { final List result = new ArrayList<>(); try { final Enumeration aliases = keyStore.getFirst().aliases(); while (aliases.hasMoreElements()) { final String el = aliases.nextElement(); log.trace("Process TrustStoreEntry: " + el); if (keyStore.getFirst().isCertificateEntry(el)) { final Certificate cert = keyStore.getFirst().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"); } } } } catch (final KeyStoreException e) { throw new CredentialsNotAvailableException("internal.pvp.01", new Object[] { getFriendlyName(), TRUSTED_CERTIFICATES_OPERATION }, e); } return Collections.unmodifiableList(result); } @PostConstruct private void initialize() throws Exception { try { final KeyStoreConfiguration keyStoreConfig = getBasicKeyStoreConfig(); keyStore = keyStoreFactory.buildNewKeyStore(keyStoreConfig); if (JCEMapper.getProviderId() != null && keyStore.getSecond() != null && !JCEMapper.getProviderId().equals(keyStore.getSecond().getName())) { log.error("OpenSAML3.x can ONLY use a single type of CryptoProvider in an application. " + "Can NOT set: {}, because {} was already set", keyStore.getSecond().getName(), JCEMapper.getProviderId()); throw new EaafConfigurationException(EaafKeyStoreFactory.ERRORCODE_06, new Object[] { keyStoreConfig.getFriendlyName(), "OpenSAML3.x can ONLY use a single type of CryptoProvider" }); } // Set JCEMapper only in case of HSM based KeyStores because Software KeyStores // can use // the default SecurityProvider system in OpenSAML3.x signing engine if (keyStore.getSecond() != null && JCEMapper.getProviderId() == null) { log.info("Register CryptoProvider: {} as defaut for OpenSAML3.x", keyStore.getSecond().getName()); JCEMapper.setProviderId(keyStore.getSecond().getName()); } } catch (final EaafException e) { log.error("Can not initialize KeyStore for eIDAS authentication client.", e); throw e; } } private String selectSigningAlgorithm(EaafKeyStoreX509CredentialAdapter credentials) throws SamlSigningException { return Saml2Utils.getKeyOperationAlgorithmFromCredential( credentials, basicConfig.getBasicConfiguration( PvpConstants.CONFIG_PROP_SEC_SIGNING_RSA_ALG, PvpConstants.DEFAULT_SIGNING_METHODE_RSA), basicConfig.getBasicConfiguration( PvpConstants.CONFIG_PROP_SEC_SIGNING_EC_ALG, PvpConstants.DEFAULT_SIGNING_METHODE_EC)); } private String selectKeyEncryptionAlgorithm(EaafKeyStoreX509CredentialAdapter credentials) throws SamlSigningException { return Saml2Utils.getKeyOperationAlgorithmFromCredential( credentials, basicConfig.getBasicConfiguration( PvpConstants.CONFIG_PROP_SEC_ENCRYPTION_KEY_RSA_ALG, PvpConstants.DEFAULT_ASYM_ENCRYPTION_METHODE_RSA), basicConfig.getBasicConfiguration( PvpConstants.CONFIG_PROP_SEC_ENCRYPTION_KEY_EC_ALG, PvpConstants.DEFAULT_ASYM_ENCRYPTION_METHODE_EC)); } private char[] getPassCharArrayOrNull(String metadataKeyPassword) { char[] keyPassChar = null; if (metadataKeyPassword != null) { keyPassChar = metadataKeyPassword.toCharArray(); } return keyPassChar; } }