# HG changeset patch # User Bernhard Herzog # Date 1368028573 -7200 # Node ID c1806821860b4bd5e9ebb8b84ad8dfbbb2b88509 # Parent 05da3cfa4054439c68e46af45611c86f8fa0f8e0 Add SAML ticket validator classes. diff -r 05da3cfa4054 -r c1806821860b gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/TicketValidator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/TicketValidator.java Wed May 08 17:56:13 2013 +0200 @@ -0,0 +1,148 @@ +/* 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; + } +}