/* * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.] * * 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 org.opensaml.saml2.metadata.provider; import java.util.Iterator; import org.opensaml.saml2.metadata.EntitiesDescriptor; import org.opensaml.saml2.metadata.EntityDescriptor; 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 is required to be signed. * * @return whether incoming metadata is required to be signed */ public boolean getRequireSignature() { return requireSignature; } /** * Sets whether incoming metadata 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 was unsigned and signatures are required."); } else { if (signableMetadata instanceof EntityDescriptor) { log.trace("Root EntityDescriptor was not signed, filter processing terminated"); return; } else { log.trace("Root EntitiesDescriptor was not signed, continuing filter processing of children"); } } } if (signableMetadata instanceof EntityDescriptor) { EntityDescriptor entityDescriptor = (EntityDescriptor) signableMetadata; verifySignature(entityDescriptor, entityDescriptor.getEntityID(), false); } 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 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); } 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 { verifySignature(entityChild, entityChild.getEntityID(), false); } catch (FilterException e) { log.error("EntityDescriptor '{}' failed signature verification, removing from metadata provider", entityChild.getEntityID()); entityIter.remove(); } } 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()); entityIter.remove(); } } } /** * Evaluate the signature on the signed metadata instance. * * @param signedMetadata the metadata object whose signature is to be verified * @param metadataEntryName the EntityDescriptor entityID or EntitiesDescriptor Name * of the signature being evaluated * @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 or EntitiesDescriptor Name * of the signature being evaluated * @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 or EntitiesDescriptor Name * of the signature being evaluated * @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() ); } //TODO how to handle adding dynamic entity ID (or other) criteria (if at all?), if (!newCriteriaSet.contains(UsageCriteria.class)) { newCriteriaSet.add( new UsageCriteria(UsageType.SIGNING) ); } return newCriteriaSet; } }