diff --git a/token-checker-server/src/main/java/de/ozgcloud/token/TokenCheckConfiguration.java b/token-checker-server/src/main/java/de/ozgcloud/token/TokenCheckConfiguration.java deleted file mode 100644 index df1d3f971d9fe526538bce3bfb109ab449b79945..0000000000000000000000000000000000000000 --- a/token-checker-server/src/main/java/de/ozgcloud/token/TokenCheckConfiguration.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * 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; - } -} diff --git a/token-checker-server/src/main/java/de/ozgcloud/token/TokenCheckerConfiguration.java b/token-checker-server/src/main/java/de/ozgcloud/token/TokenCheckerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..00bce6c3068da62d39b1fa3682d1ce00d3631cd6 --- /dev/null +++ b/token-checker-server/src/main/java/de/ozgcloud/token/TokenCheckerConfiguration.java @@ -0,0 +1,76 @@ +/* + * 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 java.util.Map; + +import org.opensaml.core.config.ConfigurationService; +import org.opensaml.core.config.InitializationException; +import org.opensaml.core.config.InitializationService; +import org.opensaml.core.xml.config.XMLObjectProviderRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.xml.BasicParserPool; +import net.shibboleth.utilities.java.support.xml.ParserPool; + +@Component +@Configuration +@RequiredArgsConstructor +public class TokenCheckerConfiguration { + + public static final String FEATURES_EXTERNAL_GENERAL_ENTITIES = "http://xml.org/sax/features/external-general-entities"; + public static final String FEATURES_EXTERNAL_PARAMETER_ENTITIES = "http://xml.org/sax/features/external-parameter-entities"; + public static final String FEATURES_DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl"; + public static final String VALIDATION_SCHEMA_NORMALIZED_VALUE = "http://apache.org/xml/features/validation/schema/normalized-value"; + public static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing"; + + @Bean + ParserPool parserPool() throws ComponentInitializationException { + var localParserPool = new BasicParserPool(); + localParserPool.setBuilderFeatures(createFeatureMap()); + localParserPool.initialize(); + + return localParserPool; + } + + Map<String, Boolean> createFeatureMap() { + return Map.of( + FEATURES_EXTERNAL_GENERAL_ENTITIES, Boolean.FALSE, + FEATURES_EXTERNAL_PARAMETER_ENTITIES, Boolean.FALSE, + FEATURES_DISALLOW_DOCTYPE_DECL, Boolean.TRUE, + VALIDATION_SCHEMA_NORMALIZED_VALUE, Boolean.FALSE, + FEATURE_SECURE_PROCESSING, Boolean.TRUE); + } + + @Bean + XMLObjectProviderRegistry xmlObjectProviderRegistry(ParserPool parserPool) throws InitializationException { + var registry = new XMLObjectProviderRegistry(); + registry.setParserPool(parserPool); + ConfigurationService.register(XMLObjectProviderRegistry.class, registry); + InitializationService.initialize(); + return registry; + } + +} diff --git a/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlConfiguration.java b/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlConfiguration.java index ebbec99d8289c722135a1b46e788a414211c682b..765249041fe488427e299d744c89f1db216f5422 100644 --- a/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlConfiguration.java +++ b/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlConfiguration.java @@ -1,17 +1,35 @@ package de.ozgcloud.token.saml; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.saml.criterion.ProtocolCriterion; +import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; import org.opensaml.saml.saml2.core.Response; import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller; import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; +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.criteria.UsageCriterion; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import de.ozgcloud.token.TokenValidationProperties; +import lombok.RequiredArgsConstructor; +import net.shibboleth.utilities.java.support.resolver.CriteriaSet; + @Configuration +@RequiredArgsConstructor public class SamlConfiguration { + private final SamlTrustEngineFactory samlTrustEngineFactory; + private final SamlDecrypterFactory samlDecrypterFactory; + @Bean - SAMLSignatureProfileValidator sAMLSignatureProfileValidator() { + SAMLSignatureProfileValidator samlSignatureProfileValidator() { return new SAMLSignatureProfileValidator(); } @@ -19,4 +37,31 @@ public class SamlConfiguration { ResponseUnmarshaller responseUnmarshaller() { return (ResponseUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory().getUnmarshaller(Response.DEFAULT_ELEMENT_NAME); } + + @Bean + SamlServiceRegistry samSettingsRegistry(TokenValidationProperties tokenValidationProperties) { + var registryBuilder = SamlServiceRegistry.builder(); + for (var tokenEntity : tokenValidationProperties.getEntities()) { + registryBuilder.samlService(tokenEntity.getIdpEntityId(), samlTokenService(tokenEntity)); + } + return registryBuilder.build(); + } + + SamlTokenService samlTokenService(TokenValidationProperties.TokenValidationProperty tokenValidationProperty) { + return SamlTokenService.builder() + .signatureTrustEngine(samlTrustEngineFactory.buildSamlTrustEngine(tokenValidationProperty)) + .decrypter(samlDecrypterFactory.buildDecrypter(tokenValidationProperty)) + .verificationCriteria(buildVerificationCriteria(tokenValidationProperty.getIdpEntityId())) + .userIdAsPostfachId(tokenValidationProperty.isUserIdAsPostfachId()) + .build(); + } + + CriteriaSet buildVerificationCriteria(String idpEntityId) { + return Stream.of( + new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(idpEntityId)), + new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion("urn:oasis:names:tc:SAML:2.0:protocol")), + new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING)) + ).collect(Collectors.toCollection(CriteriaSet::new)); + } + }