
se.swedenconnect.opensaml.xmlsec.encryption.support.SAMLObjectEncrypter Maven / Gradle / Ivy
/*
* Copyright 2019-2024 Sweden Connect
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package se.swedenconnect.opensaml.xmlsec.encryption.support;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.resolver.CriteriaSet;
import net.shibboleth.shared.resolver.ResolverException;
import org.apache.xml.security.encryption.EncryptedKey;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.criterion.RoleDescriptorCriterion;
import org.opensaml.saml.metadata.resolver.MetadataResolver;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.SSODescriptor;
import org.opensaml.saml.security.impl.MetadataCredentialResolver;
import org.opensaml.saml.security.impl.SAMLMetadataEncryptionParametersResolver;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.xmlsec.EncryptionConfiguration;
import org.opensaml.xmlsec.EncryptionParameters;
import org.opensaml.xmlsec.SecurityConfigurationSupport;
import org.opensaml.xmlsec.algorithm.AlgorithmRegistry;
import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
import org.opensaml.xmlsec.criterion.EncryptionConfigurationCriterion;
import org.opensaml.xmlsec.criterion.EncryptionOptionalCriterion;
import org.opensaml.xmlsec.encryption.EncryptedData;
import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters;
import org.opensaml.xmlsec.encryption.support.Encrypter;
import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
import org.opensaml.xmlsec.encryption.support.EncryptionException;
import org.opensaml.xmlsec.encryption.support.KeyEncryptionParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import se.swedenconnect.opensaml.xmlsec.config.ExtendedDefaultSecurityConfigurationBootstrap;
import javax.annotation.Nonnull;
import java.security.Key;
import java.util.Collections;
import java.util.Objects;
/**
* Utility class for encrypting an element for a SAML entity.
*
* @author Martin Lindström ([email protected])
*/
public class SAMLObjectEncrypter {
/** Logger instance. */
private static final Logger log = LoggerFactory.getLogger(SAMLObjectEncrypter.class);
/** Resolver for finding the peer credentials. */
private MetadataResolver metadataResolver;
/** Resolver for finding encryption keys and parameters from SAML metadata. */
private final SAMLMetadataEncryptionParametersResolver encryptionParameterResolver;
/** The default encryption configuration. */
private EncryptionConfiguration defaultEncryptionConfiguration;
/** The encrypter to use. */
private Encrypter encrypter = new CustomEncrypter();
/**
* Sets up the object encrypter without a metadata provider. This means that the peer metadata has to be supplied in
* calls to {@link #encrypt(XMLObject, Peer)} and {@link #encrypt(XMLObject, Peer, EncryptionConfiguration)}.
*
* @throws ComponentInitializationException for init errors
*/
public SAMLObjectEncrypter() throws ComponentInitializationException {
this(null);
}
/**
* Sets up the object encrypter with a metadata resolver from where we find the peer credentials.
*
* @param metadataResolver the metadata resolver
* @throws ComponentInitializationException for init errors
*/
public SAMLObjectEncrypter(final MetadataResolver metadataResolver) throws ComponentInitializationException {
if (metadataResolver != null) {
this.metadataResolver = metadataResolver;
}
this.defaultEncryptionConfiguration = SecurityConfigurationSupport.getGlobalEncryptionConfiguration();
if (this.defaultEncryptionConfiguration == null) {
this.defaultEncryptionConfiguration =
ExtendedDefaultSecurityConfigurationBootstrap.buildDefaultEncryptionConfiguration();
}
final MetadataCredentialResolver credentialResolver = new MetadataCredentialResolver();
credentialResolver.setKeyInfoCredentialResolver(
DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
credentialResolver.initialize();
this.encryptionParameterResolver = new SAMLMetadataEncryptionParametersResolver(credentialResolver);
}
/**
* Maps to {@link #encrypt(XMLObject, Peer, EncryptionConfiguration)} where the default encryption configuration is
* supplied.
*
* @param xmlObject the object to encrypt
* @param peer the peer to whom we encrypt for
* @return an {@code EncryptedData} object
* @throws EncryptionException for encryption errors
*/
public EncryptedData encrypt(final XMLObject xmlObject, final Peer peer) throws EncryptionException {
return this.encrypt(xmlObject, peer, this.defaultEncryptionConfiguration);
}
/**
* Encrypts the supplied XML object by locating the peer encryption credentials and using the supplied configuration.
*
* @param xmlObject the object to encrypt
* @param peer the peer to whom we encrypt for
* @param configuration the encryption configuration
* @return an {@code EncryptedData} object
* @throws EncryptionException for encryption errors
*/
public EncryptedData encrypt(final XMLObject xmlObject, final Peer peer, final EncryptionConfiguration configuration)
throws EncryptionException {
Constraint.isNotNull(xmlObject, "xmlObject must not be null");
Constraint.isNotNull(peer, "peer must not be null");
// Locate the peer metadata ...
//
final EntityDescriptor peerMetadata = this.getPeerMetadata(peer);
// Get hold of the peer credentials ...
//
final EncryptionParameters parameters = this.getEncryptionParameters(peerMetadata,
configuration != null ? configuration : this.defaultEncryptionConfiguration);
if (parameters == null) {
throw new EncryptionException(String.format("No encryption credentials found for '%s'", peer.getEntityID()));
}
// Let's encrypt!
//
final DataEncryptionParameters dataEncryptionParameters = new DataEncryptionParameters(parameters);
final KeyEncryptionParameters kekParams = new KeyEncryptionParameters(parameters, peer.getEntityID());
if (kekParams.getAlgorithm() != null) {
return this.encrypter.encryptElement(xmlObject, dataEncryptionParameters, kekParams);
}
else {
return this.encrypter.encryptElement(xmlObject, dataEncryptionParameters, Collections.emptyList());
}
}
/**
* Retrives the peer metadata entry.
*
* @param peer the peer metadata
* @return the entity descriptor
* @throws EncryptionException if no metadata is found
*/
private EntityDescriptor getPeerMetadata(final Peer peer) throws EncryptionException {
EntityDescriptor peerMetadata = peer.getMetadata();
if (peerMetadata == null) {
if (this.metadataResolver == null) {
throw new EncryptionException("Peer metadata is not available - no metadataResolver has been configured");
}
try {
final CriteriaSet criteria = new CriteriaSet();
criteria.add(new EntityIdCriterion(peer.getEntityID()));
peerMetadata = this.metadataResolver.resolveSingle(criteria);
}
catch (final ResolverException e) {
throw new EncryptionException("Failed to locate peer metadata", e);
}
}
if (peerMetadata == null) {
throw new EncryptionException(String.format("Metadata for '%s' could not be found", peer.getEntityID()));
}
return peerMetadata;
}
/**
* Given the peer metadata and the encryption configuration, the method returns the encryption parameters to use for
* encryption.
*
* @param metadata the peer metadata
* @param configuration the encryption configuration
* @return the encryption parameters to use for encrypt, or {@code null} if no match is found
* @throws EncryptionException for errors
*/
private EncryptionParameters getEncryptionParameters(final EntityDescriptor metadata,
final EncryptionConfiguration configuration) throws EncryptionException {
final SSODescriptor descriptor = getSSODescriptor(metadata);
if (descriptor == null) {
throw new EncryptionException("Bad peer metadata - no SSO descriptor available");
}
final CriteriaSet criteriaSet = new CriteriaSet();
criteriaSet.add(new RoleDescriptorCriterion(descriptor));
criteriaSet.add(new UsageCriterion(UsageType.ENCRYPTION));
criteriaSet.add(new EncryptionConfigurationCriterion(configuration));
criteriaSet.add(new EncryptionOptionalCriterion(false));
try {
return this.encryptionParameterResolver.resolveSingle(criteriaSet);
}
catch (final ResolverException e) {
log.error("Error during resolve of encryption parameters", e);
throw new EncryptionException("Error during resolve of encryption parameters", e);
}
}
/**
* Returns the SSODescriptor for the supplied SP or IdP entity descriptor.
*
* @param ed the entity descriptor
* @return the SSODescriptor
*/
private static SSODescriptor getSSODescriptor(final EntityDescriptor ed) {
if (ed.getIDPSSODescriptor(SAMLConstants.SAML20P_NS) != null) {
return ed.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
}
else {
return ed.getSPSSODescriptor(SAMLConstants.SAML20P_NS);
}
}
/**
* The encrypter to use.
*
* @param encrypter the encrypter
*/
public void setEncrypter(final Encrypter encrypter) {
if (encrypter != null) {
this.encrypter = encrypter;
}
}
/**
* Sets the default encryption configuration to use.
*
* If not assigned, the system defaults will be used.
*
*
* @param encryptionConfiguration default encryption configuration
*/
public void setDefaultEncryptionConfiguration(final EncryptionConfiguration encryptionConfiguration) {
if (encryptionConfiguration != null) {
this.defaultEncryptionConfiguration = encryptionConfiguration;
}
}
/**
* Sets the {@link AlgorithmRegistry} instance used when resolving algorithm URIs. Defaults to the registry resolved
* via {@link AlgorithmSupport#getGlobalAlgorithmRegistry()}.
*
* @param algorithmRegistry the new algorithm registry instance
*/
public void setAlgorithmRegistry(final AlgorithmRegistry algorithmRegistry) {
if (algorithmRegistry != null) {
this.encryptionParameterResolver.setAlgorithmRegistry(algorithmRegistry);
}
}
/**
* Represents the peer when performing encryption.
*/
public static class Peer {
/** Peer SAML entityID. */
private final String entityID;
/** Peer SAML metadata entry. */
private EntityDescriptor metadata;
/**
* Constructor setting the entityID of the peer.
*
* @param entityID peer entityID
*/
public Peer(final String entityID) {
Constraint.isNotEmpty(entityID, "entityID must be set");
this.entityID = entityID;
}
/**
* Constructor setting the peer metadata.
*
* @param metadata peer metadata
*/
public Peer(final EntityDescriptor metadata) {
Constraint.isNotNull(metadata, "metadata must not be null");
this.metadata = metadata;
this.entityID = metadata.getEntityID();
}
/**
* Gets the peer entityID.
*
* @return the peer entityID
*/
public String getEntityID() {
return this.entityID;
}
/**
* Gets the peer metadata.
*
* @return the peer metadata
*/
public EntityDescriptor getMetadata() {
return this.metadata;
}
}
/**
* Some implementations have problems handling the {@code xenc11:MGF} element under {@code xenc:EncryptionMethod} when
* the {@code http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p} algorithm is used. If it appears and its value is
* {@code http://www.w3.org/2009/xmlenc11#mgf1sha1}, the element will be excluded.
*/
private static class CustomEncrypter extends Encrypter {
@Override
protected void postProcessApacheEncryptedKey(@Nonnull final EncryptedKey apacheEncryptedKey,
@Nonnull final Key targetKey, @Nonnull final Key encryptionKey, @Nonnull final String encryptionAlgorithmURI,
@Nonnull final Document containingDocument) throws EncryptionException {
super.postProcessApacheEncryptedKey(apacheEncryptedKey, targetKey, encryptionKey, encryptionAlgorithmURI,
containingDocument);
if (AlgorithmSupport.isRSAOAEP(encryptionAlgorithmURI)) {
if (EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSAOAEP.equals(encryptionAlgorithmURI)) {
final org.apache.xml.security.encryption.EncryptionMethod apacheEncryptionMethod =
apacheEncryptedKey.getEncryptionMethod();
if (Objects.equals(apacheEncryptionMethod.getMGFAlgorithm(), EncryptionConstants.ALGO_ID_MGF1_SHA1)) {
apacheEncryptionMethod.setMGFAlgorithm(null);
}
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy