Package org.opensaml.saml2.metadata.provider

Source Code of org.opensaml.saml2.metadata.provider.SignatureValidationFilter

/*
* Licensed to the University Corporation for Advanced Internet Development,
* Inc. (UCAID) under one or more contributor license agreements.  See the
* NOTICE file distributed with this work for additional information regarding
* copyright ownership. The UCAID licenses this file to You 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 org.opensaml.saml2.metadata.provider;

import java.util.HashSet;
import java.util.Iterator;

import org.opensaml.saml2.metadata.AffiliationDescriptor;
import org.opensaml.saml2.metadata.EntitiesDescriptor;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.RoleDescriptor;
import org.opensaml.security.SAMLSignatureProfileValidator;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.security.CriteriaSet;
import org.opensaml.xml.security.SecurityException;
import org.opensaml.xml.security.credential.UsageType;
import org.opensaml.xml.security.criteria.UsageCriteria;
import org.opensaml.xml.signature.SignableXMLObject;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.SignatureTrustEngine;
import org.opensaml.xml.validation.ValidationException;
import org.opensaml.xml.validation.Validator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A metadata filter that validates XML signatures.
*/
public class SignatureValidationFilter implements MetadataFilter {
   
    /** Class logger. */
    private final Logger log = LoggerFactory.getLogger(SignatureValidationFilter.class);

    /** Trust engine used to validate a signature. */
    private SignatureTrustEngine signatureTrustEngine;

    /** Indicates whether signed metadata is required. */
    private boolean requireSignature;
   
    /** Set of externally specified default criteria for input to the trust engine. */
    private CriteriaSet defaultCriteria;
   
    /** Pre-validator for XML Signature instances. */
    private Validator<Signature> sigValidator;

    /**
     * Constructor.
     *
     * @param engine the trust engine used to validate signatures on incoming metadata.
     */
    public SignatureValidationFilter(SignatureTrustEngine engine) {
        if (engine == null) {
            throw new IllegalArgumentException("Signature trust engine may not be null");
        }

        signatureTrustEngine = engine;
        sigValidator = new SAMLSignatureProfileValidator();
    }
   
    /**
     * Constructor.
     *
     * @param engine the trust engine used to validate signatures on incoming metadata.
     * @param signatureValidator optional pre-validator used to validate Signature elements prior to the actual
     *            cryptographic validation operation
     */
    public SignatureValidationFilter(SignatureTrustEngine engine, Validator<Signature> signatureValidator) {
        if (engine == null) {
            throw new IllegalArgumentException("Signature trust engine may not be null");
        }

        signatureTrustEngine = engine;
        sigValidator = signatureValidator;
    }

    /**
     * Gets the trust engine used to validate signatures on incoming metadata.
     *
     * @return trust engine used to validate signatures on incoming metadata
     */
    public SignatureTrustEngine getSignatureTrustEngine() {
        return signatureTrustEngine;
    }
   
    /**
     * Get the validator used to perform pre-validation on Signature tokens.
     *
     * @return the configured Signature validator, or null
     */
    public Validator<Signature> getSignaturePrevalidator() {
        return sigValidator;
    }

    /**
     * Gets whether incoming metadata's root element is required to be signed.
     *
     * @return whether incoming metadata is required to be signed
     */
    public boolean getRequireSignature() {
        return requireSignature;
    }

    /**
     * Sets whether incoming metadata's root element is required to be signed.
     *
     * @param require whether incoming metadata is required to be signed
     */
    public void setRequireSignature(boolean require) {
        requireSignature = require;
    }
    /**
     * Get the set of default criteria used as input to the trust engine.
     *
     * @return the criteria set
     */
    public CriteriaSet getDefaultCriteria() {
        return defaultCriteria;
    }
   
    /**
     * Set the set of default criteria used as input to the trust engine.
     *
     * @param newCriteria the new criteria set to use
     */
    public void setDefaultCriteria(CriteriaSet newCriteria) {
        defaultCriteria = newCriteria;
    }

    /** {@inheritDoc} */
    public void doFilter(XMLObject metadata) throws FilterException {
        SignableXMLObject signableMetadata = (SignableXMLObject) metadata;

        if (!signableMetadata.isSigned()){
            if (getRequireSignature()) {
                throw new FilterException("Metadata root element was unsigned and signatures are required.");
            }
        }
       
        if (signableMetadata instanceof EntityDescriptor) {
            processEntityDescriptor((EntityDescriptor) signableMetadata);
        } else if (signableMetadata instanceof EntitiesDescriptor) {
            processEntityGroup((EntitiesDescriptor) signableMetadata);
        } else {
            log.error("Internal error, metadata object was of an unsupported type: {}", metadata.getClass().getName());
        }
    }
   
    /**
     * Process the signatures on the specified EntityDescriptor and any signed children.
     *
     * If signature verification fails on a child, it will be removed from the entity descriptor.
     *
     * @param entityDescriptor the EntityDescriptor to be processed
     * @throws FilterException thrown if an error occurs during the signature verification process
     *                          on the root EntityDescriptor specified
     */
    protected void processEntityDescriptor(EntityDescriptor entityDescriptor) throws FilterException {
        String entityID = entityDescriptor.getEntityID();
        log.trace("Processing EntityDescriptor: {}", entityID);
       
        if (entityDescriptor.isSigned()) {
            verifySignature(entityDescriptor, entityID, false);
        }
       
        Iterator<RoleDescriptor> roleIter = entityDescriptor.getRoleDescriptors().iterator();
        while (roleIter.hasNext()) {
           RoleDescriptor roleChild = roleIter.next();
            if (!roleChild.isSigned()) {
                log.trace("RoleDescriptor member '{}' was not signed, skipping signature processing...",
                        roleChild.getElementQName());
                continue;
            } else {
                log.trace("Processing signed RoleDescriptor member: {}", roleChild.getElementQName());
            }
           
            try {
                String roleID = getRoleIDToken(entityID, roleChild);
                verifySignature(roleChild, roleID, false);
            } catch (FilterException e) {
                log.error("RoleDescriptor '{}' subordinate to entity '{}' failed signature verification, "
                       + "removing from metadata provider",
                       roleChild.getElementQName(), entityID);
                // Note that this is ok since we're iterating over an IndexedXMLObjectChildrenList directly,
                // rather than a sublist like in processEntityGroup, and iterator remove() is supported there.
               roleIter.remove();
            }
        }
       
        if (entityDescriptor.getAffiliationDescriptor() != null) {
            AffiliationDescriptor affiliationDescriptor = entityDescriptor.getAffiliationDescriptor();
            if (!affiliationDescriptor.isSigned()) {
                log.trace("AffiliationDescriptor member was not signed, skipping signature processing...");
            } else {
                log.trace("Processing signed AffiliationDescriptor member with owner ID: {}",
                        affiliationDescriptor.getOwnerID());
               
                try {
                    verifySignature(affiliationDescriptor, affiliationDescriptor.getOwnerID(), false);
                } catch (FilterException e) {
                    log.error("AffiliationDescriptor with owner ID '{}' subordinate to entity '{}' " +
                            "failed signature verification, removing from metadata provider",
                            affiliationDescriptor.getOwnerID(), entityID);
                    entityDescriptor.setAffiliationDescriptor(null);
                }
               
            }
        }
    }
   
    /**
     * Process the signatures on the specified EntitiesDescriptor and any signed children.
     *
     * If signature verification fails on a child, it will be removed from the entities descriptor group.
     *
     * @param entitiesDescriptor the EntitiesDescriptor to be processed
     * @throws FilterException thrown if an error occurs during the signature verification process
     *                          on the root EntitiesDescriptor specified
     */
    protected void processEntityGroup(EntitiesDescriptor entitiesDescriptor) throws FilterException {
        log.trace("Processing EntitiesDescriptor group: {}", entitiesDescriptor.getName());
       
        if (entitiesDescriptor.isSigned()) {
            verifySignature(entitiesDescriptor, entitiesDescriptor.getName(), true);
        }
       
        // Can't use IndexedXMLObjectChildrenList sublist iterator remove() to remove members,
        // so just note them in a set and then remove after iteration has completed.
        HashSet<XMLObject> toRemove = new HashSet<XMLObject>();
       
        Iterator<EntityDescriptor> entityIter = entitiesDescriptor.getEntityDescriptors().iterator();
        while (entityIter.hasNext()) {
            EntityDescriptor entityChild = entityIter.next();
            if (!entityChild.isSigned()) {
                log.trace("EntityDescriptor member '{}' was not signed, skipping signature processing...",
                        entityChild.getEntityID());
                continue;
            } else {
                log.trace("Processing signed EntityDescriptor member: {}", entityChild.getEntityID());
            }
           
            try {
                processEntityDescriptor(entityChild);
            } catch (FilterException e) {
               log.error("EntityDescriptor '{}' failed signature verification, removing from metadata provider",
                       entityChild.getEntityID());
               toRemove.add(entityChild);
            }
        }
       
        if (!toRemove.isEmpty()) {
            entitiesDescriptor.getEntityDescriptors().removeAll(toRemove);
            toRemove.clear();
        }
       
        Iterator<EntitiesDescriptor> entitiesIter = entitiesDescriptor.getEntitiesDescriptors().iterator();
        while(entitiesIter.hasNext()) {
            EntitiesDescriptor entitiesChild = entitiesIter.next();
            log.trace("Processing EntitiesDescriptor member: {}", entitiesChild.getName());
            try {
                processEntityGroup(entitiesChild);
            } catch (FilterException e) {
               log.error("EntitiesDescriptor '{}' failed signature verification, removing from metadata provider",
                       entitiesChild.getName());
               toRemove.add(entitiesChild);
            }
        }
       
        if (!toRemove.isEmpty()) {
            entitiesDescriptor.getEntitiesDescriptors().removeAll(toRemove);
        }
    }

    /**
     * Evaluate the signature on the signed metadata instance.
     *
     * @param signedMetadata the metadata object whose signature is to be verified
     * @param metadataEntryName the EntityDescriptor entityID, EntitiesDescriptor Name,
     *                          AffiliationDescriptor affiliationOwnerID,
     *                          or RoleDescriptor {@link #getRoleIDToken(String, RoleDescriptor)}
     *                          corresponding to the element whose signature is being evaluated.
     *                          This is used exclusively for logging/debugging purposes and
     *                          should not be used operationally (e.g. for building a criteria set).
     * @param isEntityGroup flag indicating whether the signed object is a metadata group (EntitiesDescriptor),
     *                      primarily useful for constructing a criteria set for the trust engine
     * @throws FilterException thrown if the metadata entry's signature can not be established as trusted,
     *                         or if an error occurs during the signature verification process
     */
    protected void verifySignature(SignableXMLObject signedMetadata, String metadataEntryName,
            boolean isEntityGroup) throws FilterException {
       
        log.debug("Verifying signature on metadata entry: {}", metadataEntryName);
       
        Signature signature = signedMetadata.getSignature();
        if (signature == null) {
            // We shouldn't ever be calling this on things that aren't actually signed, but just to be safe...
            log.warn("Signature was null, skipping processing on metadata entry: {}", metadataEntryName);
            return;
        }
       
        performPreValidation(signature, metadataEntryName);
       
        CriteriaSet criteriaSet = buildCriteriaSet(signedMetadata, metadataEntryName, isEntityGroup);
       
        try {
            if ( getSignatureTrustEngine().validate(signature, criteriaSet) ) {
                log.trace("Signature trust establishment succeeded for metadata entry {}", metadataEntryName);
                return;
            } else {
                log.error("Signature trust establishment failed for metadata entry {}", metadataEntryName);
                throw new FilterException("Signature trust establishment failed for metadata entry");
            }
        } catch (SecurityException e) {
            // Treat evaluation errors as fatal
            log.error("Error processing signature verification for metadata entry '{}': {} ",
                    metadataEntryName, e.getMessage());
            throw new FilterException("Error processing signature verification for metadata entry", e);
        }
    }

    /**
     * Perform pre-validation on the Signature token.
     *
     * @param signature the signature to evaluate
     * @param metadataEntryName the EntityDescriptor entityID, EntitiesDescriptor Name,
     *                          AffiliationDescriptor affiliationOwnerID,
     *                          or RoleDescriptor {@link #getRoleIDToken(String, RoleDescriptor)}
     *                          corresponding to the element whose signature is being evaluated.
     *                          This is used exclusively for logging/debugging purposes and
     *                          should not be used operationally (e.g. for building a criteria set).
     * @throws FilterException thrown if the signature element fails pre-validation
     */
    protected void performPreValidation(Signature signature, String metadataEntryName) throws FilterException {
        if (getSignaturePrevalidator() != null) {
            try {
                getSignaturePrevalidator().validate(signature);
            } catch (ValidationException e) {
                log.error("Signature on metadata entry '{}' failed signature pre-validation", metadataEntryName);
                throw new FilterException("Metadata instance signature failed signature pre-validation", e);
            }
        }
    }
   
    /**
     * Build the criteria set which will be used as input to the configured trust engine.
     *
     * @param signedMetadata the metadata element whose signature is being verified
     * @param metadataEntryName the EntityDescriptor entityID, EntitiesDescriptor Name,
     *                          AffiliationDescriptor affiliationOwnerID,
     *                          or RoleDescriptor {@link #getRoleIDToken(String, RoleDescriptor)}
     *                          corresponding to the element whose signature is being evaluated.
     *                          This is used exclusively for logging/debugging purposes and
     *                          should not be used operationally (e.g. for building the criteria set).
     * @param isEntityGroup flag indicating whether the signed object is a metadata group (EntitiesDescriptor)
     * @return the newly constructed criteria set
     */
    protected CriteriaSet buildCriteriaSet(SignableXMLObject signedMetadata,
            String metadataEntryName, boolean isEntityGroup) {
       
        CriteriaSet newCriteriaSet = new CriteriaSet();
       
        if (getDefaultCriteria() != null) {
            newCriteriaSet.addAll( getDefaultCriteria() );
        }
       
        if (!newCriteriaSet.contains(UsageCriteria.class)) {
            newCriteriaSet.add( new UsageCriteria(UsageType.SIGNING) );
        }
       
        // TODO how to handle adding dynamic entity ID and/or other criteria for trust engine consumption?
        //
        // Have 4 signed metadata types:
        // 1) EntitiesDescriptor
        // 2) EntityDescriptor
        // 3) RoleDescriptor
        // 4) AffiliationDescriptor
        //
        // Logic will likely vary for how to specify criteria to trust engine for different types + specific use cases,
        // e.g. for federation metadata publishers of EntitiesDescriptors vs. "self-signed" EntityDescriptors.
        // May need to delegate to more specialized subclasses.
       
        return newCriteriaSet;
    }
   
    /**
     * Get a string token for logging/debugging purposes that contains role information and containing entityID.
     *
     * @param entityID the containing entityID
     * @param role the role descriptor
     *
     * @return the constructed role ID token.
     */
    protected String getRoleIDToken(String entityID, RoleDescriptor role) {
        String roleName = role.getElementQName().getLocalPart();
        return "[Role: " + entityID + "::" + roleName + "]";
    }
}
TOP

Related Classes of org.opensaml.saml2.metadata.provider.SignatureValidationFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.