All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensaml.saml.security.impl.SAMLSignatureProfileValidator Maven / Gradle / Ivy

The newest version!
/*
 * 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.saml.security.impl;

import javax.annotation.Nonnull;

import net.shibboleth.utilities.java.support.logic.Constraint;

import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.Reference;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transform;
import org.apache.xml.security.transforms.TransformationException;
import org.apache.xml.security.transforms.Transforms;
import org.opensaml.saml.common.SignableSAMLObject;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.impl.SignatureImpl;
import org.opensaml.xmlsec.signature.support.SignatureException;
import org.opensaml.xmlsec.signature.support.SignaturePrevalidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.google.common.base.Strings;

/**
 * A validator for instances of {@link Signature}, which validates that the signature meets security-related
 * requirements indicated by the SAML profile of XML Signature.
 */
public class SAMLSignatureProfileValidator implements SignaturePrevalidator {

    /** Class logger. */
    private final Logger log = LoggerFactory.getLogger(SAMLSignatureProfileValidator.class);

    /** {@inheritDoc} */
    @Override
    public void validate(@Nonnull final Signature signature) throws SignatureException {
        Constraint.isNotNull(signature, "Signature was null");

        if (!(signature instanceof SignatureImpl)) {
            log.info("Signature was not an instance of SignatureImpl, was {} validation not supported", signature
                    .getClass().getName());
            return;
        }

        validateSignatureImpl((SignatureImpl) signature);
    }

    /**
     * Validate an instance of {@link SignatureImpl}, which is in turn based on underlying Apache XML Security
     * XMLSignature instance.
     * 
     * @param sigImpl the signature implementation object to validate
     * @throws SignatureException thrown if the signature is not valid with respect to the profile
     */
    protected void validateSignatureImpl(final SignatureImpl sigImpl) throws SignatureException {

        if (sigImpl.getXMLSignature() == null) {
            log.error("SignatureImpl did not contain the an Apache XMLSignature child");
            throw new SignatureException("Apache XMLSignature does not exist on SignatureImpl");
        }
        final XMLSignature apacheSig = sigImpl.getXMLSignature();

        if (!(sigImpl.getParent() instanceof SignableSAMLObject)) {
            log.error("Signature is not an immedidate child of a SignableSAMLObject");
            throw new SignatureException("Signature is not an immediate child of a SignableSAMLObject.");
        }
        final SignableSAMLObject signableObject = (SignableSAMLObject) sigImpl.getParent();

        final Reference ref = validateReference(apacheSig);

        validateReferenceURI(ref.getURI(), signableObject);

        validateTransforms(ref);
        
        validateObjectChildren(apacheSig);
    }

    /**
     * Validate the Signature's SignedInfo Reference.
     * 
     * The SignedInfo must contain exactly 1 Reference.
     * 
     * @param apacheSig the Apache XML Signature instance
     * @return the valid Reference contained within the SignedInfo
     * @throws SignatureException thrown if the Signature does not contain exactly 1 Reference, or if there is an error
     *             obtaining the Reference instance
     */
    protected Reference validateReference(final XMLSignature apacheSig) throws SignatureException {
        final int numReferences = apacheSig.getSignedInfo().getLength();
        if (numReferences != 1) {
            log.error("Signature SignedInfo had invalid number of References: " + numReferences);
            throw new SignatureException("Signature SignedInfo must have exactly 1 Reference element");
        }

        Reference ref = null;
        try {
            ref = apacheSig.getSignedInfo().item(0);
        } catch (final XMLSecurityException e) {
            log.error("Apache XML Security exception obtaining Reference: {}", e.getMessage());
            throw new SignatureException("Could not obtain Reference from Signature/SignedInfo", e);
        }
        if (ref == null) {
            log.error("Signature Reference was null");
            throw new SignatureException("Signature Reference was null");
        }
        return ref;
    }

    /**
     * Validate the Signature's Reference URI.
     * 
     * First validate the Reference URI against the parent's ID itself.  Then validate that the 
     * URI (if non-empty) resolves to the same Element node as is cached by the SignableSAMLObject.
     * 
     * 
     * @param uri the Signature Reference URI attribute value
     * @param signableObject the SignableSAMLObject whose signature is being validated
     * @throws SignatureException  if the URI is invalid or doesn't resolve to the expected DOM node
     */
    protected void validateReferenceURI(final String uri, final SignableSAMLObject signableObject)
            throws SignatureException {
        final String id = signableObject.getSignatureReferenceID();
        validateReferenceURI(uri, id);
        
        if (Strings.isNullOrEmpty(uri)) {
            return;
        }
        
        final String uriID = uri.substring(1);
        
        final Element expected = signableObject.getDOM();
        if (expected == null) {
            log.error("SignableSAMLObject does not have a cached DOM Element.");
            throw new SignatureException("SignableSAMLObject does not have a cached DOM Element.");
        }
        final Document doc = expected.getOwnerDocument();
        
        final Element resolved = doc.getElementById(uriID);
        if (resolved == null) {
            log.error("DOM Document getElementById could not resolve the Element for id reference: {}", uriID);
            throw new SignatureException("DOM Document getElementById could not resolve the Element for id reference: "
                    + uriID);
        }
        
        if (!expected.isSameNode(resolved)) {
            log.error("Signature Reference URI '{}' did not resolve to the expected parent Element", uri);
            throw new SignatureException("Signature Reference URI did not resolve to the expected parent Element");
        }
    }
    
    /**
     * Validate the Reference URI and parent ID attribute values.
     * 
     * The URI must either be null or empty (indicating that the entire enclosing document was signed), or else it must
     * be a local document fragment reference and point to the SAMLObject parent via the latter's ID attribute value.
     * 
     * @param uri the Signature Reference URI attribute value
     * @param id the Signature parents ID attribute value
     * @throws SignatureException thrown if the URI or ID attribute values are invalid
     */
    protected void validateReferenceURI(final String uri, final String id) throws SignatureException {
        if (!Strings.isNullOrEmpty(uri)) {
            if (!uri.startsWith("#")) {
                log.error("Signature Reference URI was not a document fragment reference: " + uri);
                throw new SignatureException("Signature Reference URI was not a document fragment reference");
            } else if (Strings.isNullOrEmpty(id)) {
                log.error("SignableSAMLObject did not contain an ID attribute");
                throw new SignatureException("SignableSAMLObject did not contain an ID attribute");
            } else if (uri.length() < 2 || !id.equals(uri.substring(1))) {
                log.error("Reference URI '{}' did not point to SignableSAMLObject with ID '{}'", uri, id);
                throw new SignatureException("Reference URI did not point to parent ID");
            }
        }
    }

    /**
     * Validate the transforms included in the Signature Reference.
     * 
     * The Reference may contain at most 2 transforms. One of them must be the Enveloped signature transform. An
     * Exclusive Canonicalization transform (with or without comments) may also be present. No other transforms are
     * allowed.
     * 
     * @param reference the Signature reference containing the transforms to evaluate
     * @throws SignatureException thrown if the set of transforms is invalid
     */
    protected void validateTransforms(final Reference reference) throws SignatureException {
        Transforms transforms = null;
        try {
            transforms = reference.getTransforms();
        } catch (final XMLSecurityException e) {
            log.error("Apache XML Security error obtaining Transforms instance: {}", e.getMessage());
            throw new SignatureException("Apache XML Security error obtaining Transforms instance", e);
        }

        if (transforms == null) {
            log.error("Error obtaining Transforms instance, null was returned");
            throw new SignatureException("Transforms instance was null");
        }

        final int numTransforms = transforms.getLength();
        if (numTransforms > 2) {
            log.error("Invalid number of Transforms was present: " + numTransforms);
            throw new SignatureException("Invalid number of transforms");
        }

        boolean sawEnveloped = false;
        for (int i = 0; i < numTransforms; i++) {
            Transform transform = null;
            try {
                transform = transforms.item(i);
            } catch (final TransformationException e) {
                log.error("Error obtaining transform instance: {}", e.getMessage());
                throw new SignatureException("Error obtaining transform instance", e);
            }
            final String uri = transform.getURI();
            if (Transforms.TRANSFORM_ENVELOPED_SIGNATURE.equals(uri)) {
                log.debug("Saw Enveloped signature transform");
                sawEnveloped = true;
            } else if (Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS.equals(uri)
                    || Transforms.TRANSFORM_C14N_EXCL_WITH_COMMENTS.equals(uri)) {
                log.debug("Saw Exclusive C14N signature transform");
            } else {
                log.error("Saw invalid signature transform: " + uri);
                throw new SignatureException("Signature contained an invalid transform");
            }
        }

        if (!sawEnveloped) {
            log.error("Signature was missing the required Enveloped signature transform");
            throw new SignatureException("Transforms did not contain the required enveloped transform");
        }
    }

    /**
     * Validate that the Signature instance does not contain any ds:Object children.
     *
     * @param apacheSig the Apache XML Signature instance
     * @throws SignatureException if the signature contains ds:Object children
     */
    protected void validateObjectChildren(final XMLSignature apacheSig) throws SignatureException {
        if (apacheSig.getObjectLength() > 0) {
            log.error("Signature contained {} ds:Object child element(s)", apacheSig.getObjectLength());
            throw new SignatureException("Signature contained illegal ds:Object children");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy