diff --git a/README.md b/README.md index e6cf476c..f2087dd0 100644 --- a/README.md +++ b/README.md @@ -49,4 +49,4 @@ The following information needs to be available on the account: `accountSettings.setCertificate`: The x509 certificate fingerprint. This is provided from the identity provider when setting up the relationship, for this version the certificate must be 1024-bit. -In OneLogin, for this sample project, you'll want to set the SAML Consumer URL to "http://localhost:8080" and the SAML Audience and SAML Recipient to "http://localhost:8080/consume.jsp" +The DEV instance of Sungard SSO server is preconfigured for this sample project. The SAML Consumer URL is set to `http://localhost:8080` whereas the Audience and Recipient have been preconfigured to `http://localhost:8080/consume.jsp`. All the SP metadata used to configure the system are available under the `sample/metadata/` folder in the source tree. diff --git a/sample/metadata/sp_mywebapp_extdata.xml b/sample/metadata/sp_mywebapp_extdata.xml new file mode 100755 index 00000000..bb0a15a2 --- /dev/null +++ b/sample/metadata/sp_mywebapp_extdata.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + false + + + SAML2_mywebapp_SungardAS + + + + + + false + + + + + + + false + + + + + + + false + + + false + + + false + + + + + + + + false + + + + + + + + + false + + + false + + + + + + + + false + + + + + false + + + + diff --git a/sample/metadata/sp_mywebapp_metadata.xml b/sample/metadata/sp_mywebapp_metadata.xml new file mode 100755 index 00000000..3315b3fb --- /dev/null +++ b/sample/metadata/sp_mywebapp_metadata.xml @@ -0,0 +1,16 @@ + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + diff --git a/sample/src/main/webapp/consume.jsp b/sample/src/main/webapp/consume.jsp index aa60ac68..6e126163 100644 --- a/sample/src/main/webapp/consume.jsp +++ b/sample/src/main/webapp/consume.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> -<%@ page import="com.onelogin.*,com.onelogin.saml.*" %> +<%@ page import="java.util.*,com.onelogin.*,com.onelogin.saml.*" %> @@ -8,43 +8,48 @@ <% - String certificateS ="MIIDGTCCAoKgAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzET"+ -"MBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYD"+ -"VQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA2"+ -"MDUxNzE2MjBaFw0xODA2MDUxNzE2MjBaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI"+ -"DApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9u"+ -"ZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIGfMA0GCSqGSIb3DQEB"+ -"AQUAA4GNADCBiQKBgQCsalDL15zSKeEGy9c0Hao7+G02x6k/MlZuCwEvkPKUcl9Q"+ -"dGEgTW9uaWNhMREwDwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxv"+ -"Z2luLmNvbYIBATAOBgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADgYEANZvz"+ -"lB1Aq84AdOvsn2XKxBB/PmNZLqnM1VWRPaNcvjafx7eHd5qayXFNQz+bOLujENmg"+ -"Am5padbydG89SeefpOGcY2TMsVt0RUzxTnN3Zq5G6Ja2fAKOEX01ejdoPPMmStqq"+ -"Sw8k1wPUU8uLYJG5wmjf0rCb8RVaeAwMc+wcEIA="; + String cert = + "MIICQDCCAakCBEeNB0swDQYJKoZIhvcNAQEEBQAwZzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNh"+ + "bGlmb3JuaWExFDASBgNVBAcTC1NhbnRhIENsYXJhMQwwCgYDVQQKEwNTdW4xEDAOBgNVBAsTB09w"+ + "ZW5TU08xDTALBgNVBAMTBHRlc3QwHhcNMDgwMTE1MTkxOTM5WhcNMTgwMTEyMTkxOTM5WjBnMQsw"+ + "CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEUMBIGA1UEBxMLU2FudGEgQ2xhcmExDDAK"+ + "BgNVBAoTA1N1bjEQMA4GA1UECxMHT3BlblNTTzENMAsGA1UEAxMEdGVzdDCBnzANBgkqhkiG9w0B"+ + "AQEFAAOBjQAwgYkCgYEArSQc/U75GB2AtKhbGS5piiLkmJzqEsp64rDxbMJ+xDrye0EN/q1U5Of+"+ + "RkDsaN/igkAvV1cuXEgTL6RlafFPcUX7QxDhZBhsYF9pbwtMzi4A4su9hnxIhURebGEmxKW9qJNY"+ + "Js0Vo5+IgjxuEWnjnnVgHTs1+mq5QYTA7E6ZyL8CAwEAATANBgkqhkiG9w0BAQQFAAOBgQB3Pw/U"+ + "QzPKTPTYi9upbFXlrAKMwtFf2OW4yvGWWvlcwcNSZJmTJ8ARvVYOMEVNbsT4OFcfu2/PeYoAdiDA"+ + "cGy/F2Zuj8XJJpuQRSE6PtQqBuDEHjjmOQJ0rV/r8mO1ZCtHRhpZ5zYRjhRC9eCbjx9VrFax0JDC"+ + "/FfwWigmrW0Y0Q=="; // user account specific settings. Import the certificate here AccountSettings accountSettings = new AccountSettings(); - accountSettings.setCertificate(certificateS); + accountSettings.setCertificate(cert); Response samlResponse = new Response(accountSettings); samlResponse.loadXmlFromBase64(request.getParameter("SAMLResponse")); samlResponse.setDestinationUrl(request.getRequestURL().toString()); - if (samlResponse.isValid()) { - + if (samlResponse.isValid()) + { // the signature of the SAML Response is valid. The source is trusted - java.io.PrintWriter writer = response.getWriter(); - writer.write("OK!"); - String nameId = samlResponse.getNameId(); - writer.write(nameId); - writer.flush(); - - } else { - + java.io.PrintWriter writer = response.getWriter(); + writer.write("OK!"); + String nameId = samlResponse.getNameId(); + writer.write("
Hello " + nameId); + writer.write("! You have been authenticated. The SSO server returned the following attributes:
"); + Map attrs = samlResponse.getAttributes(); + for(String attr : attrs.keySet()) + { + writer.write("
*** " + attr + ": " + attrs.get(attr)); + } + writer.flush(); + } + else + { // the signature of the SAML Response is not valid - java.io.PrintWriter writer = response.getWriter(); - writer.write("Failed"); - writer.flush(); - + java.io.PrintWriter writer = response.getWriter(); + writer.write("FAILED: The SAML Response has expired or invalid."); + writer.flush(); } %> diff --git a/sample/src/main/webapp/index.jsp b/sample/src/main/webapp/index.jsp index cc4a8801..4e2f7dff 100644 --- a/sample/src/main/webapp/index.jsp +++ b/sample/src/main/webapp/index.jsp @@ -22,11 +22,28 @@ AccountSettings accSettings = new AccountSettings(); // The URL at the Identity Provider where to the authentication request should be sent - accSettings.setIdpSsoTargetUrl("https://app.onelogin.com/saml/signon/20956"); + accSettings.setIdpSsoTargetUrl("https://qa-sungard.sso.sungardas.io/service/saml2/idp"); + + String cert = + "MIICQDCCAakCBEeNB0swDQYJKoZIhvcNAQEEBQAwZzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNh"+ + "bGlmb3JuaWExFDASBgNVBAcTC1NhbnRhIENsYXJhMQwwCgYDVQQKEwNTdW4xEDAOBgNVBAsTB09w"+ + "ZW5TU08xDTALBgNVBAMTBHRlc3QwHhcNMDgwMTE1MTkxOTM5WhcNMTgwMTEyMTkxOTM5WjBnMQsw"+ + "CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEUMBIGA1UEBxMLU2FudGEgQ2xhcmExDDAK"+ + "BgNVBAoTA1N1bjEQMA4GA1UECxMHT3BlblNTTzENMAsGA1UEAxMEdGVzdDCBnzANBgkqhkiG9w0B"+ + "AQEFAAOBjQAwgYkCgYEArSQc/U75GB2AtKhbGS5piiLkmJzqEsp64rDxbMJ+xDrye0EN/q1U5Of+"+ + "RkDsaN/igkAvV1cuXEgTL6RlafFPcUX7QxDhZBhsYF9pbwtMzi4A4su9hnxIhURebGEmxKW9qJNY"+ + "Js0Vo5+IgjxuEWnjnnVgHTs1+mq5QYTA7E6ZyL8CAwEAATANBgkqhkiG9w0BAQQFAAOBgQB3Pw/U"+ + "QzPKTPTYi9upbFXlrAKMwtFf2OW4yvGWWvlcwcNSZJmTJ8ARvVYOMEVNbsT4OFcfu2/PeYoAdiDA"+ + "cGy/F2Zuj8XJJpuQRSE6PtQqBuDEHjjmOQJ0rV/r8mO1ZCtHRhpZ5zYRjhRC9eCbjx9VrFax0JDC"+ + "/FfwWigmrW0Y0Q=="; + accSettings.setCertificate(cert); // Generate an AuthRequest and send it to the identity provider AuthRequest authReq = new AuthRequest(appSettings, accSettings); - String reqString = accSettings.getIdp_sso_target_url()+"?SAMLRequest=" + URLEncoder.encode(authReq.getRequest(AuthRequest.base64),"UTF-8"); + String reqString + = accSettings.getIdp_sso_target_url() + +"?SAMLRequest=" + + URLEncoder.encode(authReq.getRequest(AuthRequest.base64),"UTF-8"); response.sendRedirect(reqString); %> diff --git a/src/main/java/com/onelogin/saml/AuthRequest.java b/src/main/java/com/onelogin/saml/AuthRequest.java index 14d900d3..382f4108 100644 --- a/src/main/java/com/onelogin/saml/AuthRequest.java +++ b/src/main/java/com/onelogin/saml/AuthRequest.java @@ -16,82 +16,85 @@ public class AuthRequest { - private final String id; - private final String issueInstant; - private final AppSettings appSettings; - public static final int base64 = 1; - private Deflater deflater; - - public AuthRequest(AppSettings appSettings, AccountSettings accountSettings){ - this.appSettings = appSettings; - id="_"+UUID.randomUUID().toString(); - SimpleDateFormat simpleDf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - issueInstant = simpleDf.format(new Date()); - } - - public String getRequest(int format) throws XMLStreamException, IOException { - String result = ""; - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - XMLStreamWriter writer = factory.createXMLStreamWriter(baos); - - writer.writeStartElement("samlp", "AuthnRequest", "urn:oasis:names:tc:SAML:2.0:protocol"); - writer.writeNamespace("samlp","urn:oasis:names:tc:SAML:2.0:protocol"); - - writer.writeAttribute("ID", id); - writer.writeAttribute("Version", "2.0"); - writer.writeAttribute("IssueInstant", this.issueInstant); - writer.writeAttribute("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); - writer.writeAttribute("AssertionConsumerServiceURL", this.appSettings.getAssertionConsumerServiceUrl()); - - writer.writeStartElement("saml","Issuer","urn:oasis:names:tc:SAML:2.0:assertion"); - writer.writeNamespace("saml","urn:oasis:names:tc:SAML:2.0:assertion"); - writer.writeCharacters(this.appSettings.getIssuer()); - writer.writeEndElement(); - - writer.writeStartElement("samlp", "NameIDPolicy", "urn:oasis:names:tc:SAML:2.0:protocol"); - - writer.writeAttribute("Format", "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified"); - writer.writeAttribute("AllowCreate", "true"); - writer.writeEndElement(); - - writer.writeStartElement("samlp","RequestedAuthnContext","urn:oasis:names:tc:SAML:2.0:protocol"); - - writer.writeAttribute("Comparison", "exact"); - - writer.writeStartElement("saml","AuthnContextClassRef","urn:oasis:names:tc:SAML:2.0:assertion"); - writer.writeNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); - writer.writeCharacters("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"); - writer.writeEndElement(); - - writer.writeEndElement(); - writer.writeEndElement(); - writer.flush(); + private final String id; + private final String issueInstant; + private final AppSettings appSettings; + public static final int base64 = 1; + private Deflater deflater; + + public AuthRequest(AppSettings appSettings, AccountSettings accountSettings){ + this.appSettings = appSettings; + id="_"+UUID.randomUUID().toString(); + SimpleDateFormat simpleDf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + issueInstant = simpleDf.format(new Date()); + } + + public String getRequest(int format) throws XMLStreamException, IOException { + String result = ""; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + XMLStreamWriter writer = factory.createXMLStreamWriter(baos); + + writer.writeStartElement("samlp", "AuthnRequest", "urn:oasis:names:tc:SAML:2.0:protocol"); + writer.writeNamespace("samlp","urn:oasis:names:tc:SAML:2.0:protocol"); + + writer.writeAttribute("ID", id); + writer.writeAttribute("Version", "2.0"); + writer.writeAttribute("IssueInstant", this.issueInstant); + writer.writeAttribute("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); + writer.writeAttribute("ProviderName", "https://www.mywebapp.com"); + writer.writeAttribute("Destination", "https://qa-sungard.sso.sungardas.io/service/saml2/idp"); + writer.writeAttribute("AssertionConsumerServiceURL", this.appSettings.getAssertionConsumerServiceUrl()); + + writer.writeStartElement("saml","Issuer","urn:oasis:names:tc:SAML:2.0:assertion"); + writer.writeNamespace("saml","urn:oasis:names:tc:SAML:2.0:assertion"); + writer.writeCharacters(this.appSettings.getIssuer()); + writer.writeEndElement(); + + writer.writeStartElement("samlp", "NameIDPolicy", "urn:oasis:names:tc:SAML:2.0:protocol"); + + //writer.writeAttribute("Format", "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified"); + writer.writeAttribute("Format", "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"); + writer.writeAttribute("AllowCreate", "true"); + writer.writeEndElement(); + + writer.writeStartElement("samlp","RequestedAuthnContext","urn:oasis:names:tc:SAML:2.0:protocol"); + + writer.writeAttribute("Comparison", "exact"); + + writer.writeStartElement("saml","AuthnContextClassRef","urn:oasis:names:tc:SAML:2.0:assertion"); + writer.writeNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); + writer.writeCharacters("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"); + writer.writeEndElement(); + + writer.writeEndElement(); + writer.writeEndElement(); + writer.flush(); - result = encodeSAMLRequest(baos.toByteArray()); - return result; - } + result = encodeSAMLRequest(baos.toByteArray()); + return result; + } - private String encodeSAMLRequest(byte[] pSAMLRequest) throws RuntimeException { + private String encodeSAMLRequest(byte[] pSAMLRequest) throws RuntimeException { - Base64 base64Encoder = new Base64(); + Base64 base64Encoder = new Base64(); - try { - ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); - Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, true); + try { + ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); + Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, true); - DeflaterOutputStream def = new DeflaterOutputStream(byteArray, deflater); - def.write(pSAMLRequest); - def.close(); - byteArray.close(); + DeflaterOutputStream def = new DeflaterOutputStream(byteArray, deflater); + def.write(pSAMLRequest); + def.close(); + byteArray.close(); - String stream = new String(base64Encoder.encode(byteArray.toByteArray())); + String stream = new String(base64Encoder.encode(byteArray.toByteArray())); - return stream.trim(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } + return stream.trim(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/com/onelogin/saml/Response.java b/src/main/java/com/onelogin/saml/Response.java index bde1b600..40ee6b71 100644 --- a/src/main/java/com/onelogin/saml/Response.java +++ b/src/main/java/com/onelogin/saml/Response.java @@ -37,197 +37,203 @@ public class Response { - private Document xmlDoc; - private NodeList assertions; - private Element rootElement; - private final AccountSettings accountSettings; - private final Certificate certificate; - private String currentUrl; - - public Response(AccountSettings accountSettings) throws CertificateException { - this.accountSettings = accountSettings; - certificate = new Certificate(); - certificate.loadCertificate(this.accountSettings.getCertificate()); - } - - public void loadXml(String xml) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException { - DocumentBuilderFactory fty = DocumentBuilderFactory.newInstance(); - fty.setNamespaceAware(true); - // XMLConstants with FEATURE_SECURE_PROCESSING prevents external document access. (XXE/XEE Possible Attacks). - fty.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - DocumentBuilder builder = fty.newDocumentBuilder(); - ByteArrayInputStream bais = new ByteArrayInputStream(xml.getBytes()); - xmlDoc = builder.parse(bais); - // Loop through the doc and tag every element with an ID attribute as an XML ID node. - XPath xpath = XPathFactory.newInstance().newXPath(); - XPathExpression expr = xpath.compile("//*[@ID]"); - NodeList nodeList = (NodeList) expr.evaluate(xmlDoc, XPathConstants.NODESET); - for (int i=0; i attributes = new HashMap(); - NodeList nodes = xmlDoc.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "Attribute"); - - if (nodes.getLength() != 0) { - for (int i = 0; i < nodes.getLength(); i++) { - NamedNodeMap attrName = nodes.item(i).getAttributes(); - String attName = attrName.getNamedItem("Name").getNodeValue(); - NodeList children = nodes.item(i).getChildNodes(); - - ArrayList attrValues = new ArrayList(); - for (int j = 0; j < children.getLength(); j++) { - attrValues.add(children.item(j).getTextContent()); - } - attributes.put(attName, attrValues); - } - } else { - return null; - } - return attributes; - } - - private boolean setIdAttributeExists() { - for (Method method : Element.class.getDeclaredMethods()) { - if (method.getName().equals("setIdAttribute")) { - return true; - } - } - return false; - } - - private void tagIdAttributes(Document xmlDoc) { - throw new UnsupportedOperationException("Not supported yet."); - } - - public void setDestinationUrl(String urld){ - currentUrl = urld; - } + + +// if (setIdAttributeExists()) { +// tagIdAttributes(xmlDoc); +// } + + X509Certificate cert = certificate.getX509Cert(); + DOMValidateContext ctx = new DOMValidateContext(cert.getPublicKey(), nodes.item(0)); + XMLSignatureFactory sigF = XMLSignatureFactory.getInstance("DOM"); + XMLSignature xmlSignature = sigF.unmarshalXMLSignature(ctx); + + return xmlSignature.validate(ctx); + } + + public String getNameId() throws Exception { + NodeList nodes = xmlDoc.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "NameID"); + if (nodes.getLength() == 0) { + throw new Exception("No name id found in Document."); + } + return nodes.item(0).getTextContent(); + } + + public String getAttribute(String name) { + HashMap attributes = getAttributes(); + if (!attributes.isEmpty()) { + return attributes.get(name).toString(); + } + return null; + } + + public HashMap getAttributes() { + HashMap attributes = new HashMap(); + NodeList nodes = xmlDoc.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "Attribute"); + + if (nodes.getLength() != 0) { + for (int i = 0; i < nodes.getLength(); i++) { + NamedNodeMap attrName = nodes.item(i).getAttributes(); + String attName = attrName.getNamedItem("Name").getNodeValue(); + NodeList children = nodes.item(i).getChildNodes(); + + ArrayList attrValues = new ArrayList(); + for (int j = 0; j < children.getLength(); j++) { + attrValues.add(children.item(j).getTextContent()); + } + attributes.put(attName, attrValues); + } + } else { + return null; + } + return attributes; + } + + private boolean setIdAttributeExists() { + for (Method method : Element.class.getDeclaredMethods()) { + if (method.getName().equals("setIdAttribute")) { + return true; + } + } + return false; + } + + private void tagIdAttributes(Document xmlDoc) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public void setDestinationUrl(String urld){ + currentUrl = urld; + } }