Initial alpha2 code
authorcantor <cantor@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Tue, 11 Jun 2002 03:58:13 +0000 (03:58 +0000)
committercantor <cantor@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Tue, 11 Jun 2002 03:58:13 +0000 (03:58 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@57 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/edu/internet2/middleware/shibboleth/shire/ShireServlet.java [new file with mode: 0755]
src/edu/internet2/middleware/shibboleth/shire/XMLOriginSiteMapper.java [new file with mode: 0755]

diff --git a/src/edu/internet2/middleware/shibboleth/shire/ShireServlet.java b/src/edu/internet2/middleware/shibboleth/shire/ShireServlet.java
new file mode 100755 (executable)
index 0000000..d36d9f4
--- /dev/null
@@ -0,0 +1,292 @@
+package edu.internet2.middleware.shibboleth.shire;
+
+import edu.internet2.middleware.shibboleth.common.*;
+
+import java.io.*;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.text.*;
+import javax.servlet.*;
+import javax.servlet.http.*;
+import org.doomdark.uuid.*;
+import org.opensaml.*;
+
+/**
+ *  Implements a SAML POST profile consumer
+ *
+ * @author     Scott Cantor, Sridhar Muppidi
+ * @created    June 10, 2002
+ */
+public class ShireServlet extends HttpServlet
+{
+    private String cookieName = null;
+    private String sessionDir = null;
+    private boolean sslOnly = true;
+    private boolean checkAddress = true;
+    private boolean verbose = false;
+    private XMLOriginSiteMapper mapper = null;
+
+    private static void HTMLFormat(PrintWriter pw, String buf)
+    {
+        for (int i = 0; i < buf.length(); i++)
+        {
+            if (buf.charAt(i) == '<')
+                pw.write("&lt;");
+            else if (buf.charAt(i) == '>')
+                pw.write("&gt;");
+            else if (buf.charAt(i) == '&')
+                pw.write("&amp;");
+            else
+                pw.write(buf.charAt(i));
+        }
+    }
+
+    /**
+     *  Use the following servlet init parameters:<P>
+     *
+     *
+     *  <DL>
+     *    <DT> keystore-path <I>(required)</I> </DT>
+     *    <DD> A pathname to the trusted CA roots to accept</DD>
+     *    <DT> registry-alias <I>(optional)</I> </DT>
+     *    <DD> An alias in the provided keystore for the cert that can verify
+     *    the origin site registry signature</DD>
+     *    <DT> registry-uri <I>(required)</I> </DT>
+     *    <DD> The origin site registry URI to install</DD>
+     *    <DT> cookie-name <I>(required)</I> </DT>
+     *    <DD> Name of session cookie to set in browser</DD>
+     *    <DT> ssl-only <I>(defaults to true)</I> </DT>
+     *    <DD> If true, allow only SSL-protected POSTs and issue a secure cookie
+     *    </DD>
+     *    <DT> check-address <I>(defaults to true)</I> </DT>
+     *    <DD> If true, check client's IP address against assertion</DD>
+     *    <DT> session-dir <I>(defaults to /tmp)</I> </DT>
+     *    <DD> Directory in which to place session files</DD>
+     *    <DT> verbose <I>(defaults to false)</I> </DT>
+     *    <DD> Verbosity of redirection response</DD>
+     *  </DL>
+     *
+     *
+     * @exception  ServletException  Raised if the servlet cannot be initialized
+     */
+    public void init()
+        throws ServletException
+    {
+        ServletConfig conf = getServletConfig();
+
+        cookieName = conf.getInitParameter("cookie-name");
+        if (cookieName == null)
+            throw new ServletException("ShireServlet.init() missing init parameter: cookie-name");
+
+        sessionDir = conf.getInitParameter("session-dir");
+        if (sessionDir == null)
+            sessionDir = "/tmp";
+
+        String temp = conf.getInitParameter("ssl-only");
+        if (temp != null && (temp.equalsIgnoreCase("false") || temp.equals("0")))
+            sslOnly = false;
+
+        temp = conf.getInitParameter("check-address");
+        if (temp != null && (temp.equalsIgnoreCase("false") || temp.equals("0")))
+            checkAddress = false;
+
+        temp = conf.getInitParameter("verbose");
+        if (temp != null && (temp.equalsIgnoreCase("true") || temp.equals("1")))
+            verbose = true;
+
+        try
+        {
+            Key k = null;
+            KeyStore ks = KeyStore.getInstance("JKS");
+            ks.load(new FileInputStream(conf.getInitParameter("keystore-path")), null);
+            if (conf.getInitParameter("keystore-alias") != null)
+            {
+                Certificate cert = ks.getCertificate(conf.getInitParameter("keystore-alias"));
+                if (cert == null || (k = cert.getPublicKey()) == null)
+                    throw new ServletException("ShireServlet.init() unable to find registry verification certificate/key");
+            }
+            mapper = new XMLOriginSiteMapper(conf.getInitParameter("registry-uri"), k, ks);
+        }
+        catch (org.apache.xml.security.exceptions.XMLSecurityException e)
+        {
+            throw new ServletException(e.getMessage());
+        }
+        catch (java.security.KeyStoreException e)
+        {
+            throw new ServletException("ShireServlet.init() unable to load Java keystore");
+        }
+        catch (java.security.NoSuchAlgorithmException e)
+        {
+            throw new ServletException("ShireServlet.init() unable to load Java keystore");
+        }
+        catch (java.security.cert.CertificateException e)
+        {
+            throw new ServletException("ShireServlet.init() unable to load Java keystore");
+        }
+        catch (FileNotFoundException e)
+        {
+            throw new ServletException("ShireServlet.init() unable to locate Java keystore");
+        }
+        catch (IOException e)
+        {
+            throw new ServletException("ShireServlet.init() unable to load Java keystore");
+        }
+        catch (org.xml.sax.SAXException e)
+        {
+            throw new ServletException("ShireServlet.init() unable to load origin site registry");
+        }
+    }
+
+    /**
+     *  Processes a sign-on submission<P>
+     *
+     *
+     *
+     * @param  request               HTTP request context
+     * @param  response              HTTP response context
+     * @exception  IOException       Thrown if an I/O error occurs
+     * @exception  ServletException  Thrown if a servlet engine error occurs
+     */
+    public void doPost(HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException
+    {
+        // Output page opener.
+        response.setContentType("text/html");
+        PrintWriter out = response.getWriter();
+        out.println("<HTML>");
+        out.println("<HEAD>");
+        out.println("<TITLE>Shibboleth Session Establisher</TITLE>");
+        out.println("</HEAD>");
+        out.println("<BODY>");
+        out.println("<H3>Shibboleth Session Establisher</H3>");
+
+        if (sslOnly && !request.isSecure())
+        {
+            out.println("<H4>There was a problem with this submission.</H4>");
+            out.println("<P>Access to this site requires the use of SSL. To correct the problem, please re-enter the desired target URL into your browser and make sure it begins with 'https'.</P>");
+            out.println("</BODY></HTML>");
+            return;
+        }
+
+        String target = request.getParameter("TARGET");
+        if (target == null || target.length() == 0)
+        {
+            out.println("<H4>There was a problem with this submission.</H4>");
+            out.println("<P>The target location was unspecified. To correct the problem, please re-enter the desired target URL into your browser.</P>");
+            out.println("</BODY></HTML>");
+            return;
+        }
+        else if (verbose)
+            out.println("<P><B>Target URL:</B>" + target + "</P>");
+
+        String responseData = request.getParameter("SAMLResponse");
+        if (responseData == null || responseData.length() == 0)
+        {
+            out.println("<H4>There was a problem with this submission.</H4>");
+            out.println("<P>The assertion of your Shibboleth identity was missing. To correct the problem, please re-enter the desired target URL into your browser.</P>");
+            out.println("</BODY></HTML>");
+            return;
+        }
+
+        // Process the SAML response inside an exception handler.
+        try
+        {
+            // Get a profile object using our specifics.
+            String[] policies = {Constants.POLICY_CLUBSHIB};
+            ShibPOSTProfile profile =
+                ShibPOSTProfileFactory.getInstance(policies, mapper, HttpUtils.getRequestURL(request).toString(), 300);
+
+            // Try and accept the response...
+            SAMLResponse r = profile.accept(responseData.getBytes());
+
+            // We've got a valid signed response we can trust (or the whole response was empty...)
+            if (verbose)
+            {
+                ByteArrayOutputStream bytestr = new ByteArrayOutputStream();
+                r.toStream(bytestr);
+                out.println("<P><B>Parsed SAML Response:</B></P>");
+                out.println("<P>");
+                HTMLFormat(out, bytestr.toString(response.getCharacterEncoding()));
+                out.println("</P>");
+            }
+
+            // Get the statement we need.
+            SAMLAuthenticationStatement s = profile.getSSOStatement(r);
+            if (s == null)
+            {
+                out.println("<H4>There was a problem with this submission.</H4>");
+                out.println("<P>The assertion of your Shibboleth identity was missing or incompatible with the policies of this site. To correct the problem, please re-enter the desired target URL into your browser. If the problem persists, please contact the technical staff at your site.</P>");
+                out.println("</BODY></HTML>");
+                return;
+            }
+
+            if (checkAddress)
+            {
+                if (verbose)
+                    out.println("<P><B>Client Address:</B>" + request.getRemoteAddr() + "</P>");
+                if (s.getSubjectIP() == null || !s.getSubjectIP().equals(request.getRemoteAddr()))
+                {
+                    if (verbose && s.getSubjectIP() != null)
+                        out.println("<P><B>Supplied Client Address:</B>" + s.getSubjectIP() + "</P>");
+                    out.println("<H4>There was a problem with this submission.</H4>");
+                    out.println("<P>The IP address provided by your origin site was either missing or did not match your current address. To correct this problem, you may need to bypass a local proxy server and/or contact your origin site technical staff.</P>");
+                    out.println("</BODY></HTML>");
+                }
+            }
+
+            // All we really need is here...
+            String handle = s.getSubject().getName();
+            String domain = s.getSubject().getNameQualifier();
+            SAMLAuthorityBinding[] bindings = s.getBindings();
+
+            if (verbose)
+            {
+                out.println("<P><B>Shibboleth Origin Site:</B>" + domain + "</P>");
+                out.println("<P><B>Shibboleth Handle:</B>" + handle + "</P>");
+                if (bindings != null)
+                    out.println("<P><B>Shibboleth AA URL:</B>" + bindings[0].getLocation() + "</P>");
+            }
+
+            // Generate a random session file.
+            String filename = UUIDGenerator.getInstance().generateRandomBasedUUID().toString().replace('-', '_');
+            String pathname = null;
+            if (sessionDir.endsWith(File.separator))
+                pathname = sessionDir + filename;
+            else
+                pathname = sessionDir + File.separatorChar + filename;
+            PrintWriter fout = new PrintWriter(new FileWriter(pathname));
+
+            if (verbose)
+                out.println("<P><B>Session Pathname:</B>" + pathname + "</P>");
+
+            fout.println("Handle=" + handle);
+            fout.println("Domain=" + domain);
+            fout.println("PBinding0=" + bindings[0].getBinding());
+            fout.println("LBinding0=" + bindings[0].getLocation());
+            fout.println("EOF");
+            fout.close();
+
+            out.println("<P>Redirecting you to your target...</P>");
+            out.println("<P>Allow 10-15 seconds, then click <A HREF='" + target + "'>here</A> if you do not get redirected.</P>");
+            out.println("</BODY></HTML>");
+
+            // Set the session cookie.
+            Cookie cookie = new Cookie(cookieName, filename);
+            cookie.setPath("/");
+            response.addCookie(cookie);
+
+            // Redirect back to the requested resource.
+            response.sendRedirect(target);
+        }
+        catch (SAMLException e)
+        {
+            out.println("<H4>There was a problem with this submission.</H4>");
+            out.println("<P>The system detected the following error while processing your submission:</P>");
+            out.println("<BLOCKQUOTE>" + e.toString() + "</BLOCKQUOTE>");
+            out.println("<P>Please contact this site's administrator to resolve the problem.</P>");
+            out.println("</BODY></HTML>");
+        }
+    }
+}
+
diff --git a/src/edu/internet2/middleware/shibboleth/shire/XMLOriginSiteMapper.java b/src/edu/internet2/middleware/shibboleth/shire/XMLOriginSiteMapper.java
new file mode 100755 (executable)
index 0000000..9adbffa
--- /dev/null
@@ -0,0 +1,250 @@
+package edu.internet2.middleware.shibboleth.shire;
+
+import edu.internet2.middleware.shibboleth.common.*;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+import javax.xml.parsers.*;
+import org.apache.xml.security.c14n.Canonicalizer;
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.keys.KeyInfo;
+import org.apache.xml.security.signature.*;
+import org.apache.xml.security.transforms.Transform;
+import org.apache.xml.security.transforms.Transforms;
+import org.w3c.dom.*;
+import org.xml.sax.SAXException;
+
+/**
+ *  OriginSiteMapper implementation using an XML file to populate an in-memory
+ *  database from an optionally-signed XML file
+ *
+ * @author     Scott Cantor
+ * @created    June 8, 2002
+ */
+public class XMLOriginSiteMapper implements OriginSiteMapper
+{
+
+    private HashMap originSites = null;
+    private HashMap hsKeys = null;
+    private KeyStore ks = null;
+
+    /**
+     *  Constructor for the XMLOriginSiteMapper object
+     *
+     * @param  registryURI               Tells where to find/download origin
+     *      site registry file
+     * @param  verifyKey                 Optional key to verify signature with
+     * @param  ks                        Key store containing the trusted roots
+     *      to be used by SHIRE
+     * @exception  SAXException          Raised if the registry file cannot be
+     *      parsed and loaded
+     * @exception  java.io.IOException   Description of Exception
+     * @exception  XMLSecurityException  Description of Exception
+     */
+    public XMLOriginSiteMapper(String registryURI, Key verifyKey, KeyStore ks)
+        throws SAXException, java.io.IOException, XMLSecurityException
+    {
+        this.ks = ks;
+        boolean verified = false;
+
+        DocumentBuilder builder = null;
+        try
+        {
+            builder = org.opensaml.XML.parserPool.get();
+            Document doc = builder.parse(registryURI);
+            Element e = doc.getDocumentElement();
+            if (!XML.SHIB_NS.equals(e.getNamespaceURI()) || !"Sites".equals(e.getLocalName()))
+                throw new SAXException("XMLOriginSiteMapper() requires shib:Sites as root element");
+
+            // Loop over the OriginSite elements.
+            Node os = e.getFirstChild();
+            while (os != null)
+            {
+                if (os.getNodeType() != Node.ELEMENT_NODE)
+                {
+                    os = os.getNextSibling();
+                    continue;
+                }
+
+                if (org.opensaml.XML.XMLSIG_NS.equals(os.getNamespaceURI()) && "Signature".equals(os.getLocalName()))
+                {
+                    XMLSignature sig = new XMLSignature((Element)os, null);
+
+                    // First, we verify that what is signed is what we expect.
+                    SignedInfo sinfo = sig.getSignedInfo();
+                    if (sinfo.getCanonicalizationMethodURI().equals(Canonicalizer.ALGO_ID_C14N_WITH_COMMENTS) ||
+                        sinfo.getCanonicalizationMethodURI().equals(Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS))
+//                        sinfo.getCanonicalizationMethodURI().equals(Canonicalizer.ALGO_ID_C14N_EXCL_WITH_COMMENTS) ||
+//                        sinfo.getCanonicalizationMethodURI().equals(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS))
+                    {
+                        Reference ref = sinfo.item(0);
+                        if (ref.getURI() == null || ref.getURI().equals(""))
+                        {
+                            Transforms trans = ref.getTransforms();
+                            if (trans.getLength() == 1 && trans.item(0).getURI().equals(Transforms.TRANSFORM_ENVELOPED_SIGNATURE))
+                            {
+                                // Lastly, we check the signature value.
+                                if (sig.checkSignatureValue(verifyKey))
+                                    verified = true;
+                            }
+                        }
+                    }
+                    break;
+                }
+
+                if (!XML.SHIB_NS.equals(os.getNamespaceURI()) || !"OriginSite".equals(os.getLocalName()))
+                {
+                    os = os.getNextSibling();
+                    continue;
+                }
+
+                // os is an OriginSite.
+                String os_name = ((Element)os).getAttributeNS(null, "Name").trim();
+                if (os_name.length() == 0)
+                {
+                    os = os.getNextSibling();
+                    continue;
+                }
+
+                OriginSite os_obj = new OriginSite(os_name);
+                originSites.put(os_name, os_obj);
+
+                Node os_child = os.getFirstChild();
+                while (os_child != null)
+                {
+                    if (os_child.getNodeType() != Node.ELEMENT_NODE)
+                    {
+                        os_child = os_child.getNextSibling();
+                        continue;
+                    }
+
+                    // Process the various kinds of OriginSite children that we care about...
+                    if (XML.SHIB_NS.equals(os_child.getNamespaceURI()) && "HandleService".equals(os_child.getLocalName()))
+                    {
+                        String hs_name = ((Element)os_child).getAttributeNS(null, "Name").trim();
+                        if (hs_name.length() > 0)
+                        {
+                            os_obj.handleServices.add(hs_name);
+
+                            // Check for KeyInfo.
+                            Node ki = os_child.getFirstChild();
+                            while (ki != null && ki.getNodeType() != Node.ELEMENT_NODE)
+                                ki = ki.getNextSibling();
+                            if (ki != null && org.opensaml.XML.XMLSIG_NS.equals(ki.getNamespaceURI()) &&
+                                "KeyInfo".equals(ki.getLocalName()))
+                            {
+                                try
+                                {
+                                    KeyInfo kinfo = new KeyInfo((Element)ki, null);
+                                    PublicKey pubkey = kinfo.getPublicKey();
+                                    if (pubkey != null)
+                                        hsKeys.put(hs_name, pubkey);
+                                }
+                                catch (XMLSecurityException exc)
+                                {
+                                }
+                            }
+                        }
+                    }
+                    else if (XML.SHIB_NS.equals(os_child.getNamespaceURI()) && "Domain".equals(os_child.getLocalName()))
+                    {
+                        String dom = os_child.getFirstChild().getNodeValue().trim();
+                        if (dom.length() > 0)
+                            os_obj.domains.add(dom);
+                    }
+                    os_child = os_child.getNextSibling();
+                }
+                os = os.getNextSibling();
+            }
+
+            if (verifyKey != null && !verified)
+                throw new XMLSecurityException("XMLOriginSiteMapper() unable to verify signature on registry file");
+        }
+        catch (ParserConfigurationException pce)
+        {
+            throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "XMLOriginSiteMapper() parser configuration error");
+        }
+        finally
+        {
+            if (builder != null)
+                org.opensaml.XML.parserPool.put(builder);
+        }
+    }
+
+    /**
+     *  Provides an iterator over the trusted Handle Services for the specified
+     *  origin site
+     *
+     * @param  originSite  The DNS name of the origin site to query
+     * @return             An iterator over the Handle Service DNS names
+     */
+    public Iterator getHandleServiceNames(String originSite)
+    {
+        OriginSite o = (OriginSite)originSites.get(originSite);
+        if (o != null)
+            return o.handleServices.iterator();
+        return null;
+    }
+
+    /**
+     *  Returns a preconfigured key to use in verifying a signature created by
+     *  the specified HS<P>
+     *
+     *  Any key returned is implicitly trusted and a certificate signed by
+     *  another trusted entity is not sought or required
+     *
+     * @param  handleService  Description of Parameter
+     * @return                A trusted key (probably public but could be
+     *      secret) or null
+     */
+    public Key getHandleServiceKey(String handleService)
+    {
+        return (Key)hsKeys.get(handleService);
+    }
+
+    /**
+     *  Provides an iterator over the security domain expressions for which the
+     *  specified origin site is considered to be authoritative
+     *
+     * @param  originSite  The DNS name of the origin site to query
+     * @return             An iterator over a set of regular expression strings
+     */
+    public Iterator getSecurityDomains(String originSite)
+    {
+        OriginSite o = (OriginSite)originSites.get(originSite);
+        if (o != null)
+            return o.domains.iterator();
+        return null;
+    }
+
+    /**
+     *  Gets a key store containing certificate entries that are trusted to sign
+     *  Handle Service certificates that are encountered during processing<P>
+     *
+     *
+     *
+     * @return    A key store containing trusted certificate issuers
+     */
+    public KeyStore getTrustedRoots()
+    {
+        return ks;
+    }
+
+    private class OriginSite
+    {
+
+        private Vector domains = null;
+        private Vector handleServices = null;
+
+        private OriginSite(String name)
+        {
+            domains = new Vector();
+            domains.add(name);
+            handleServices = new Vector();
+        }
+    }
+}
+