view gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/TicketValidator.java @ 5948:d7b9b3e3c61a

Make instantiation of saml.User easier. Most of the parameters of the constructor can be taken from the Assertion object, so there's no reason to pass them separately. Also, trying to check the validity dates isn't useful for the single sign on case. See comments in the hasExpired method.
author Bernhard Herzog <bh@intevation.de>
date Wed, 08 May 2013 17:56:14 +0200
parents c1806821860b
children 0a0b4bfdf372
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt f�r Gew�sserkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

package org.dive4elements.river.client.server.auth.saml;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.Key;
import java.util.Iterator;
import java.util.Date;
import javax.security.cert.X509Certificate;
import javax.security.cert.CertificateException;
import javax.xml.crypto.Data;
import javax.xml.crypto.NodeSetData;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;

import org.apache.log4j.Logger;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Validator for SAML tickets.
 */
public class TicketValidator {

    /**
     * The logger used by the TicketValidator instances.
     */
    private static Logger logger = Logger.getLogger(TicketValidator.class);

    /**
     * The trusted Key for signature checks.
     */
    private Key trustedKey;

    /**
     * Creates a new TicketValidator from a trusted key.
     * @param trustedKey  The trusted key for the signature checks.
     */
    public TicketValidator(Key trustedKey) {
        this.trustedKey = trustedKey;
    }

    /**
     * Creates a new TicketValidator, loading the trusted key from a
     * file.
     * @param filename The filename of the X509 certificate containing
     * the trusted public key.
     */
    public TicketValidator(String filename) throws IOException,
                                                   CertificateException {
        this.trustedKey = loadKey(filename);
    }

    /**
     * Loads the public key from a file containing an X509 certificate.
     */
    private Key loadKey(String filename) throws IOException,
                                                CertificateException {
        X509Certificate cert = X509Certificate.getInstance(
                                               new FileInputStream(filename));
        cert.checkValidity(new Date());
        return cert.getPublicKey();
    }


    /**
     * Check the ticket represented by the given DOM element.
     * @param root the DOM element under which the signature can be
     * found.
     * @return The assertion element from the signed data.
     */
    public Assertion checkTicket(Element root) throws Exception {
        markAssertionIdAttributes(root);

        Node signode = XPathUtils.xpathNode(root, ".//ds:Signature");

        DOMValidateContext context = new DOMValidateContext(this.trustedKey,
                                                            signode);
        context.setProperty("javax.xml.crypto.dsig.cacheReference", true);

        XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
        XMLSignature signature = factory.unmarshalXMLSignature(context);
        if (!signature.validate(context)) {
            logger.error("Signature of SAML ticket could not be validated.");
            return null;
        }

        Element assertionElement = extractAssertion(signature, context);
        if (assertionElement == null) {
            logger.error("Could not extract assertion from signed content.");
            return null;
        }

        Assertion assertion = new Assertion(assertionElement);
        if (!assertion.isValidNow()) {
            logger.error("Ticket is not valid now"
                         + " (NotBefore: " + assertion.getFrom()
                         + ", NotOnOrAfter: " + assertion.getUntil());
            return null;
        }

        return assertion;
    }

    /**
     * Mark the AssertionID attribute of SAML Assertion elements as ID
     * attribute, so that the signature checker can resolve the
     * references properly and find the signed data.
     */
    private void markAssertionIdAttributes(Element root) {
        NodeList nodes = XPathUtils.xpathNodeList(root, "saml:Assertion");
        for (int i = 0; i < nodes.getLength(); i++) {
            Element el = (Element)nodes.item(i);
            el.setIdAttribute("AssertionID", true);
        }
    }

    private Element extractAssertion(XMLSignature sig,
                                     DOMValidateContext context) {
        for (Object obj: sig.getSignedInfo().getReferences()) {
            Data data = ((Reference)obj).getDereferencedData();
            if (data instanceof NodeSetData) {
                Iterator i = ((NodeSetData)data).iterator();
                for (int k = 0; i.hasNext(); k++) {
                    Object node = i.next();
                    if (node instanceof Element) {
                        Element el = (Element)node;
                        if (el.getTagName().equals("Assertion"))
                            return el;
                    }
                }
            }
        }

        return null;
    }
}

http://dive4elements.wald.intevation.org