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

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

Go to download

The OpenSAML-J library provides tools to support developers working with the Security Assertion Markup Language (SAML).

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.security;

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.apache.xml.security.utils.IdResolver;
import org.opensaml.common.SignableSAMLObject;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.impl.SignatureImpl;
import org.opensaml.xml.util.DatatypeHelper;
import org.opensaml.xml.validation.ValidationException;
import org.opensaml.xml.validation.Validator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * 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 Validator {

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

    /** {@inheritDoc} */
    public void validate(Signature signature) throws ValidationException {

        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 ValidationException thrown if the signature is not valid with respect to the profile
     */
    protected void validateSignatureImpl(SignatureImpl sigImpl) throws ValidationException {

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

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

        Reference ref = validateReference(apacheSig);

        String uri = ref.getURI();
        
        validateReferenceURI(uri, 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 ValidationException thrown if the Signature does not contain exactly 1 Reference, or if there is an error
     *             obtaining the Reference instance
     */
    protected Reference validateReference(XMLSignature apacheSig) throws ValidationException {
        int numReferences = apacheSig.getSignedInfo().getLength();
        if (numReferences != 1) {
            log.error("Signature SignedInfo had invalid number of References: " + numReferences);
            throw new ValidationException("Signature SignedInfo must have exactly 1 Reference element");
        }

        Reference ref = null;
        try {
            ref = apacheSig.getSignedInfo().item(0);
        } catch (XMLSecurityException e) {
            log.error("Apache XML Security exception obtaining Reference", e);
            throw new ValidationException("Could not obtain Reference from Signature/SignedInfo", e);
        }
        if (ref == null) {
            log.error("Signature Reference was null");
            throw new ValidationException("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 ValidationException  if the URI is invalid or doesn't resolve to the expected DOM node
     */
    protected void validateReferenceURI(String uri, SignableSAMLObject signableObject) throws ValidationException {
        String id = signableObject.getSignatureReferenceID();
        validateReferenceURI(uri, id);
        
        if (DatatypeHelper.isEmpty(uri)) {
            return;
        }
        
        String uriID = uri.substring(1);
        
        Element expected = signableObject.getDOM();
        if (expected == null) {
            log.error("SignableSAMLObject does not have a cached DOM Element.");
            throw new ValidationException("SignableSAMLObject does not have a cached DOM Element.");
        }
        Document doc = expected.getOwnerDocument();
        
        Element resolved = IdResolver.getElementById(doc, uriID);
        if (resolved == null) {
            log.error("Apache xmlsec IdResolver could not resolve the Element for id reference: {}", uriID);
            throw new ValidationException("Apache xmlsec IdResolver 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 ValidationException("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 ValidationException thrown if the URI or ID attribute values are invalid
     */
    protected void validateReferenceURI(String uri, String id) throws ValidationException {
        if (!DatatypeHelper.isEmpty(uri)) {
            if (!uri.startsWith("#")) {
                log.error("Signature Reference URI was not a document fragment reference: " + uri);
                throw new ValidationException("Signature Reference URI was not a document fragment reference");
            } else if (DatatypeHelper.isEmpty(id)) {
                log.error("SignableSAMLObject did not contain an ID attribute");
                throw new ValidationException("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 ValidationException("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 ValidationException thrown if the set of transforms is invalid
     */
    protected void validateTransforms(Reference reference) throws ValidationException {
        Transforms transforms = null;
        try {
            transforms = reference.getTransforms();
        } catch (XMLSecurityException e) {
            log.error("Apache XML Security error obtaining Transforms instance", e);
            throw new ValidationException("Apache XML Security error obtaining Transforms instance", e);
        }

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

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

        boolean sawEnveloped = false;
        for (int i = 0; i < numTransforms; i++) {
            Transform transform = null;
            try {
                transform = transforms.item(i);
            } catch (TransformationException e) {
                log.error("Error obtaining transform instance", e);
                throw new ValidationException("Error obtaining transform instance", e);
            }
            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 ValidationException("Signature contained an invalid transform");
            }
        }

        if (!sawEnveloped) {
            log.error("Signature was missing the required Enveloped signature transform");
            throw new ValidationException("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 ValidationException if the signature contains ds:Object children
     */
    protected void validateObjectChildren(XMLSignature apacheSig) throws ValidationException {
        if (apacheSig.getObjectLength() > 0) {
            log.error("Signature contained {} ds:Object child element(s)", apacheSig.getObjectLength());
            throw new ValidationException("Signature contained illegal ds:Object children");
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy