Select Git revision
TokenCheckConfiguration.java 7.93 KiB
/*
* Copyright (c) 2024.
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
package de.ozgcloud.token;
import static de.ozgcloud.token.saml.SamlTokenUtils.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import jakarta.annotation.PostConstruct;
import org.opensaml.core.config.ConfigurationService;
import org.opensaml.core.config.InitializationException;
import org.opensaml.core.config.InitializationService;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
import org.opensaml.saml.criterion.ProtocolCriterion;
import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion;
import org.opensaml.saml.saml2.encryption.Decrypter;
import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.CredentialResolver;
import org.opensaml.security.credential.CredentialSupport;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion;
import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion;
import org.opensaml.security.credential.impl.CollectionCredentialResolver;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.security.x509.BasicX509Credential;
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver;
import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver;
import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver;
import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver;
import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver;
import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.stereotype.Component;
import de.ozgcloud.common.errorhandling.TechnicalException;
import de.ozgcloud.token.saml.ConfigurationEntity;
import de.ozgcloud.token.saml.SamlSetting;
import de.ozgcloud.token.saml.SamlSettingsRegistry;
import de.ozgcloud.token.saml.SamlTokenUtils;
import lombok.RequiredArgsConstructor;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.xml.ParserPool;
import net.shibboleth.utilities.java.support.xml.XMLParserException;
@Component
@Configuration
@RequiredArgsConstructor
public class TokenCheckConfiguration {
private XMLObjectProviderRegistry registry;
private final TokenCheckProperties tokenCheckerProperties;
private final ParserPool parserPool;
private final SamlSettingsRegistry samlSettingsRegistry;
private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver(
Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(),
new SimpleRetrievalMethodEncryptedKeyResolver()));
@PostConstruct
public void initOpenSAML() {
try {
registry = new XMLObjectProviderRegistry();
ConfigurationService.register(XMLObjectProviderRegistry.class, registry);
registry.setParserPool(parserPool);
InitializationService.initialize();
tokenCheckerProperties.getEntities().forEach(this::initSamlSetting);
} catch (InitializationException e) {
throw new TechnicalException("Initialization failed", e);
}
}
private void initSamlSetting(final ConfigurationEntity entity) {
var trustEngine = initTrustEngine(entity.getMetadata(), entity.getIdpEntityId());
var verificationCriteria = getVerificationCriteria(entity.getIdpEntityId());
var decrypter = initDecrypter(entity);
var samlSetting = SamlSetting.builder()
.trustEngine(trustEngine)
.criteriaSet(verificationCriteria)
.decrypter(decrypter)
.mappings(entity.getMappings())
.idIsPostfachId(entity.getUseIdAsPostfachId())
.build();
samlSettingsRegistry.addSetting(entity.getIdpEntityId(), samlSetting);
}
private SignatureTrustEngine initTrustEngine(Resource metadata, String idpEntityId) {
Set<Credential> credentials = new HashSet<>();
Collection<Saml2X509Credential> keys = getCertificatesFromMetadata(metadata);
for (Saml2X509Credential key : keys) {
var cred = new BasicX509Credential(key.getCertificate());
cred.setUsageType(UsageType.SIGNING);
cred.setEntityId(idpEntityId);
credentials.add(cred);
}
CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);
return new ExplicitKeySignatureTrustEngine(credentialsResolver,
DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
}
private CriteriaSet getVerificationCriteria(String idpEntityId) {
var criteria = new CriteriaSet();
criteria.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(idpEntityId)));
criteria.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion("urn:oasis:names:tc:SAML:2.0:protocol")));
criteria.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING)));
return criteria;
}
private List<Saml2X509Credential> getCertificatesFromMetadata(Resource metadataResource) {
try (var metadata = metadataResource.getInputStream()) {
var xmlObject = xmlObject(metadata);
var descriptorOptional = findEntityDescriptor(xmlObject);
if (descriptorOptional.isPresent()) {
return SamlTokenUtils.getVerificationCertificates(descriptorOptional.get());
}
} catch (IOException e) {
throw new Saml2Exception("Error reading idp metadata.", e);
} catch (XMLParserException e) {
throw new Saml2Exception("Error initializing parser pool.", e);
}
throw new Saml2Exception("No IDPSSO Descriptors found");
}
private XMLObject xmlObject(InputStream inputStream) throws XMLParserException {
var document = parserPool.parse(inputStream);
var element = document.getDocumentElement();
var unmarshaller = registry.getUnmarshallerFactory().getUnmarshaller(element);
if (unmarshaller == null) {
throw new Saml2Exception("Unsupported element of type " + element.getTagName());
}
try {
return unmarshaller.unmarshall(element);
} catch (Exception ex) {
throw new Saml2Exception(ex);
}
}
private Decrypter initDecrypter(ConfigurationEntity entity) {
var credentials = new ArrayList<Credential>();
var decryptionX509Credential = SamlTokenUtils.getDecryptionCredential(entity.getKey(), entity.getCertificate());
var cred = CredentialSupport.getSimpleCredential(decryptionX509Credential.getCertificate(), decryptionX509Credential.getPrivateKey());
credentials.add(cred);
var resolver = new CollectionKeyInfoCredentialResolver(credentials);
var setupDecrypter = new Decrypter(null, resolver, encryptedKeyResolver);
setupDecrypter.setRootInNewDocument(true);
return setupDecrypter;
}
}