bh@5941: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde bh@5941: * Software engineering by Intevation GmbH bh@5941: * bh@5941: * This file is Free Software under the GNU AGPL (>=v3) bh@5941: * and comes with ABSOLUTELY NO WARRANTY! Check out the bh@5941: * documentation coming with Dive4Elements River for details. bh@5941: */ bh@5941: bh@5941: package org.dive4elements.river.client.server.auth.saml; bh@5941: bh@5941: import java.io.FileInputStream; bh@5941: import java.io.IOException; bh@5949: import java.io.InputStream; bh@5941: import java.security.Key; bh@5941: import java.util.Iterator; bh@5941: import java.util.Date; bh@5941: import javax.security.cert.X509Certificate; bh@5941: import javax.security.cert.CertificateException; bh@5941: import javax.xml.crypto.Data; bh@5941: import javax.xml.crypto.NodeSetData; bh@5941: import javax.xml.crypto.dsig.Reference; bh@5941: import javax.xml.crypto.dsig.XMLSignature; bh@5941: import javax.xml.crypto.dsig.XMLSignatureFactory; bh@5941: import javax.xml.crypto.dsig.dom.DOMValidateContext; bh@5941: bh@5941: import org.apache.log4j.Logger; bh@5941: bh@5941: import org.w3c.dom.Element; bh@5941: import org.w3c.dom.Node; bh@5941: import org.w3c.dom.NodeList; bh@5941: bh@5949: import org.dive4elements.artifacts.httpclient.utils.XMLUtils; bh@5949: bh@5949: bh@5941: /** bh@5941: * Validator for SAML tickets. bh@5941: */ bh@5941: public class TicketValidator { bh@5941: bh@5941: /** bh@5941: * The logger used by the TicketValidator instances. bh@5941: */ bh@5941: private static Logger logger = Logger.getLogger(TicketValidator.class); bh@5941: bh@5941: /** bh@5941: * The trusted Key for signature checks. bh@5941: */ bh@5941: private Key trustedKey; bh@5941: bh@5941: /** bh@5941: * Creates a new TicketValidator from a trusted key. bh@5941: * @param trustedKey The trusted key for the signature checks. bh@5941: */ bh@5941: public TicketValidator(Key trustedKey) { bh@5941: this.trustedKey = trustedKey; bh@5941: } bh@5941: bh@5941: /** bh@5941: * Creates a new TicketValidator, loading the trusted key from a bh@5941: * file. bh@5941: * @param filename The filename of the X509 certificate containing bh@5941: * the trusted public key. bh@5941: */ bh@5941: public TicketValidator(String filename) throws IOException, bh@5941: CertificateException { bh@5941: this.trustedKey = loadKey(filename); bh@5941: } bh@5941: bh@5941: /** bh@5941: * Loads the public key from a file containing an X509 certificate. bh@5941: */ bh@5941: private Key loadKey(String filename) throws IOException, bh@5941: CertificateException { bh@5941: X509Certificate cert = X509Certificate.getInstance( bh@5941: new FileInputStream(filename)); bh@5941: cert.checkValidity(new Date()); bh@5941: return cert.getPublicKey(); bh@5941: } bh@5941: bh@5941: bh@5941: /** bh@5941: * Check the ticket represented by the given DOM element. bh@5941: * @param root the DOM element under which the signature can be bh@5941: * found. bh@5941: * @return The assertion element from the signed data. bh@5941: */ bh@5941: public Assertion checkTicket(Element root) throws Exception { bh@5941: markAssertionIdAttributes(root); bh@5941: bh@5941: Node signode = XPathUtils.xpathNode(root, ".//ds:Signature"); bh@5941: bh@5941: DOMValidateContext context = new DOMValidateContext(this.trustedKey, bh@5941: signode); bh@5941: context.setProperty("javax.xml.crypto.dsig.cacheReference", true); bh@5941: bh@5941: XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM"); bh@5941: XMLSignature signature = factory.unmarshalXMLSignature(context); bh@5941: if (!signature.validate(context)) { bh@5941: logger.error("Signature of SAML ticket could not be validated."); bh@5941: return null; bh@5941: } bh@5941: bh@5941: Element assertionElement = extractAssertion(signature, context); bh@5941: if (assertionElement == null) { bh@5941: logger.error("Could not extract assertion from signed content."); bh@5941: return null; bh@5941: } bh@5941: bh@5941: Assertion assertion = new Assertion(assertionElement); bh@5941: if (!assertion.isValidNow()) { bh@5941: logger.error("Ticket is not valid now" bh@5941: + " (NotBefore: " + assertion.getFrom() bh@5941: + ", NotOnOrAfter: " + assertion.getUntil()); bh@5941: return null; bh@5941: } bh@5941: bh@5941: return assertion; bh@5941: } bh@5941: bh@5941: /** bh@5949: * Check the ticket read from an InputStream containing a SAML bh@5949: * document. bh@5949: * @param xml InputStream with the SAML ticket as XML bh@5949: * @return The assertion element from the signed data. bh@5949: */ bh@5949: public Assertion checkTicket(InputStream in) throws Exception { bh@5949: return checkTicket(XMLUtils.readDocument(in).getDocumentElement()); bh@5949: } bh@5949: bh@5949: /** bh@5941: * Mark the AssertionID attribute of SAML Assertion elements as ID bh@5941: * attribute, so that the signature checker can resolve the bh@5941: * references properly and find the signed data. bh@5941: */ bh@5941: private void markAssertionIdAttributes(Element root) { bh@5941: NodeList nodes = XPathUtils.xpathNodeList(root, "saml:Assertion"); bh@5941: for (int i = 0; i < nodes.getLength(); i++) { bh@5941: Element el = (Element)nodes.item(i); bh@5941: el.setIdAttribute("AssertionID", true); bh@5941: } bh@5941: } bh@5941: bh@5941: private Element extractAssertion(XMLSignature sig, bh@5941: DOMValidateContext context) { bh@5941: for (Object obj: sig.getSignedInfo().getReferences()) { bh@5941: Data data = ((Reference)obj).getDereferencedData(); bh@5941: if (data instanceof NodeSetData) { bh@5941: Iterator i = ((NodeSetData)data).iterator(); bh@5941: for (int k = 0; i.hasNext(); k++) { bh@5941: Object node = i.next(); bh@5941: if (node instanceof Element) { bh@5941: Element el = (Element)node; bh@5941: if (el.getTagName().equals("Assertion")) bh@5941: return el; bh@5941: } bh@5941: } bh@5941: } bh@5941: } bh@5941: bh@5941: return null; bh@5941: } bh@5941: }