diff --git a/core/src/main/java/com/onelogin/saml2/exception/SettingsException.java b/core/src/main/java/com/onelogin/saml2/exception/SettingsException.java index 0fa11018..47a87952 100644 --- a/core/src/main/java/com/onelogin/saml2/exception/SettingsException.java +++ b/core/src/main/java/com/onelogin/saml2/exception/SettingsException.java @@ -10,7 +10,8 @@ public class SettingsException extends Exception { public static final int PRIVATE_KEY_NOT_FOUND = 4; public static final int PUBLIC_CERT_FILE_NOT_FOUND = 5; public static final int PRIVATE_KEY_FILE_NOT_FOUND = 6; - + public static final int UNSUPPORTED_BINDING = 7; + private int errorCode; public SettingsException(String message, int errorCode) { diff --git a/core/src/main/java/com/onelogin/saml2/util/Util.java b/core/src/main/java/com/onelogin/saml2/util/Util.java index 2467c953..060fe609 100644 --- a/core/src/main/java/com/onelogin/saml2/util/Util.java +++ b/core/src/main/java/com/onelogin/saml2/util/Util.java @@ -1081,6 +1081,29 @@ public static Document copyDocument(Document source) throws ParserConfigurationE * @throws XPathExpressionException */ public static String addSign(Document document, PrivateKey key, X509Certificate certificate, String signAlgorithm) throws XMLSecurityException, XPathExpressionException { + return addSign(document, key, certificate, signAlgorithm, Constants.C14N_WC); + } + + /** + * Signs the Document using the specified signature algorithm with the private key and the public certificate. + * + * @param document + * The document to be signed + * @param key + * The private key + * @param certificate + * The public certificate + * @param signAlgorithm + * Signature Algorithm + * @param c14nMethod + * Canonicalization method + * + * @return the signed document in string format + * + * @throws XMLSecurityException + * @throws XPathExpressionException + */ + public static String addSign(Document document, PrivateKey key, X509Certificate certificate, String signAlgorithm, String c14nMethod) throws XMLSecurityException, XPathExpressionException { org.apache.xml.security.Init.init(); // Check arguments. @@ -1095,7 +1118,7 @@ public static String addSign(Document document, PrivateKey key, X509Certificate if (key == null) { throw new IllegalArgumentException("Provided key was null"); } - + if (certificate == null) { throw new IllegalArgumentException("Provided certificate was null"); } @@ -1104,17 +1127,13 @@ public static String addSign(Document document, PrivateKey key, X509Certificate signAlgorithm = Constants.RSA_SHA1; } - // document.normalizeDocument(); - - String c14nMethod = Constants.C14N_WC; - // Signature object XMLSignature sig = new XMLSignature(document, null, signAlgorithm, c14nMethod); // Including the signature into the document before sign, because // this is an envelop signature Element root = document.getDocumentElement(); - document.setXmlStandalone(false); + document.setXmlStandalone(false); // If Issuer, locate Signature after Issuer, Otherwise as first child. NodeList issuerNodes = Util.query(document, "//saml:Issuer", null); @@ -1141,7 +1160,7 @@ public static String addSign(Document document, PrivateKey key, X509Certificate sig.addDocument(reference, transforms, Constants.SHA1); // Add the certification info - sig.addKeyInfo(certificate); + sig.addKeyInfo(certificate); // Sign the document sig.sign(key); @@ -1553,5 +1572,5 @@ private static byte[] toBytesUtf8(String str) { } } - + } diff --git a/toolkit/src/main/java/com/onelogin/saml2/Auth.java b/toolkit/src/main/java/com/onelogin/saml2/Auth.java index d89613d4..704e1351 100644 --- a/toolkit/src/main/java/com/onelogin/saml2/Auth.java +++ b/toolkit/src/main/java/com/onelogin/saml2/Auth.java @@ -11,6 +11,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Scanner; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -139,6 +140,8 @@ public class Auth { */ private String lastResponse; + private String requestPostBindingHTML; + /** * Initializes the SP SAML instance. * @@ -224,6 +227,13 @@ public Auth(Saml2Settings settings, HttpServletRequest request, HttpServletRespo throw new SettingsException(errorMsg, SettingsException.SETTINGS_INVALID); } LOGGER.debug("Settings validated"); + + + if (Constants.BINDING_HTTP_POST.equals(settings.getIdpSingleSignOnServiceBinding()) + || Constants.BINDING_HTTP_POST.equals(settings.getIdpSingleLogoutServiceBinding())) { + Scanner s = new Scanner(getClass().getClassLoader().getResourceAsStream("request-post-binding.html")).useDelimiter("\\A"); + this.requestPostBindingHTML = s.hasNext() ? s.next() : ""; + } } /** @@ -258,14 +268,8 @@ public void setStrict(Boolean value) * @throws SettingsException */ public String login(String returnTo, Boolean forceAuthn, Boolean isPassive, Boolean setNameIdPolicy, Boolean stay) throws IOException, SettingsException { - Map parameters = new HashMap(); - AuthnRequest authnRequest = new AuthnRequest(settings, forceAuthn, isPassive, setNameIdPolicy); - String samlRequest = authnRequest.getEncodedAuthnRequest(); - - parameters.put("SAMLRequest", samlRequest); - String relayState; if (returnTo == null) { relayState = ServletUtils.getSelfRoutedURLNoQuery(request); @@ -273,26 +277,72 @@ public String login(String returnTo, Boolean forceAuthn, Boolean isPassive, Bool relayState = returnTo; } - if (!relayState.isEmpty()) { - parameters.put("RelayState", relayState); - } + if (Constants.BINDING_HTTP_REDIRECT.equals(settings.getIdpSingleSignOnServiceBinding())) { + Map parameters = new HashMap<>(); + String samlRequest = authnRequest.getEncodedAuthnRequest(); - if (settings.getAuthnRequestsSigned()) { - String sigAlg = settings.getSignatureAlgorithm(); - String signature = this.buildRequestSignature(samlRequest, relayState, sigAlg); + parameters.put("SAMLRequest", samlRequest); - parameters.put("SigAlg", sigAlg); - parameters.put("Signature", signature); - } + if (!relayState.isEmpty()) { + parameters.put("RelayState", relayState); + } - String ssoUrl = getSSOurl(); - lastRequestId = authnRequest.getId(); - lastRequest = authnRequest.getAuthnRequestXml(); + if (settings.getAuthnRequestsSigned()) { + String sigAlg = settings.getSignatureAlgorithm(); + String signature = this.buildRequestSignature(samlRequest, relayState, sigAlg); + + parameters.put("SigAlg", sigAlg); + parameters.put("Signature", signature); + } - if (!stay) { - LOGGER.debug("AuthNRequest sent to " + ssoUrl + " --> " + samlRequest); + String ssoUrl = getSSOurl(); + lastRequestId = authnRequest.getId(); + lastRequest = authnRequest.getAuthnRequestXml(); + + if (!stay) { + LOGGER.debug("AuthNRequest sent to {} --> {}", ssoUrl, samlRequest); + } + return ServletUtils.sendRedirect(response, ssoUrl, parameters, stay); + } else if (Constants.BINDING_HTTP_POST.equals(settings.getIdpSingleSignOnServiceBinding())) { + String requestPostBinding = null; + + try { + String authnRequestXML; + + if (settings.getAuthnRequestsSigned()) { + authnRequestXML = Util.addSign( + Util.convertStringToDocument(authnRequest.getAuthnRequestXml()), + settings.getSPkey(), + settings.getSPcert(), + settings.getSignatureAlgorithm(), + Constants.C14NEXC); + + LOGGER.debug("Signed XML: {}", authnRequestXML); + } else { + authnRequestXML = authnRequest.getEncodedAuthnRequest(false); + } + + requestPostBinding = String.format( + this.requestPostBindingHTML, + settings.getOrganization().getOrgDisplayName(), + settings.getIdpSingleSignOnServiceUrl(), + "SAMLRequest", + Util.base64encoder(authnRequestXML), + relayState != null ? relayState : "" + ); + + if (!stay) { + ServletUtils.respondWithContentString(response, requestPostBinding, "text/html; charset=utf-8"); + } + + } catch (Exception e) { + LOGGER.error("Error {}", e.getMessage(), e); + } + + return requestPostBinding; + } else { + throw new SettingsException("Unsupported SSO binding: " + settings.getIdpSingleSignOnServiceBinding(), SettingsException.UNSUPPORTED_BINDING); } - return ServletUtils.sendRedirect(response, ssoUrl, parameters, stay); } /** @@ -361,11 +411,7 @@ public void login(String returnTo) throws IOException, SettingsException { * @throws SettingsException */ public String logout(String returnTo, String nameId, String sessionIndex, Boolean stay, String nameidFormat) throws IOException, XMLEntityException, SettingsException { - Map parameters = new HashMap(); - LogoutRequest logoutRequest = new LogoutRequest(settings, null, nameId, sessionIndex, nameidFormat); - String samlLogoutRequest = logoutRequest.getEncodedLogoutRequest(); - parameters.put("SAMLRequest", samlLogoutRequest); String relayState; if (returnTo == null) { @@ -374,26 +420,71 @@ public String logout(String returnTo, String nameId, String sessionIndex, Boolea relayState = returnTo; } - if (!relayState.isEmpty()) { - parameters.put("RelayState", relayState); - } + if (Constants.BINDING_HTTP_REDIRECT.equals(settings.getIdpSingleSignOnServiceBinding())) { + Map parameters = new HashMap<>(); - if (settings.getLogoutRequestSigned()) { - String sigAlg = settings.getSignatureAlgorithm(); - String signature = this.buildRequestSignature(samlLogoutRequest, relayState, sigAlg); + String samlLogoutRequest = logoutRequest.getEncodedLogoutRequest(); + parameters.put("SAMLRequest", samlLogoutRequest); - parameters.put("SigAlg", sigAlg); - parameters.put("Signature", signature); - } + if (!relayState.isEmpty()) { + parameters.put("RelayState", relayState); + } - String sloUrl = getSLOurl(); - lastRequestId = logoutRequest.getId(); - lastRequest = logoutRequest.getLogoutRequestXml(); + if (settings.getLogoutRequestSigned()) { + String sigAlg = settings.getSignatureAlgorithm(); + String signature = this.buildRequestSignature(samlLogoutRequest, relayState, sigAlg); - if (!stay) { - LOGGER.debug("Logout request sent to " + sloUrl + " --> " + samlLogoutRequest); + parameters.put("SigAlg", sigAlg); + parameters.put("Signature", signature); + } + + String sloUrl = getSLOurl(); + lastRequestId = logoutRequest.getId(); + lastRequest = logoutRequest.getLogoutRequestXml(); + + if (!stay) { + LOGGER.debug("Logout request sent to {} --> {}", sloUrl, samlLogoutRequest); + } + return ServletUtils.sendRedirect(response, sloUrl, parameters, stay); + } else if (Constants.BINDING_HTTP_POST.equals(settings.getIdpSingleSignOnServiceBinding())) { + String requestPostBinding = null; + + try { + String logoutRequestXML; + + if (settings.getLogoutRequestSigned()) { + logoutRequestXML = Util.addSign( + Util.convertStringToDocument(logoutRequest.getLogoutRequestXml()), + settings.getSPkey(), + settings.getSPcert(), + settings.getSignatureAlgorithm(), + Constants.C14NEXC); + + LOGGER.debug("Signed XML: {}", logoutRequestXML); + } else { + logoutRequestXML = logoutRequest.getEncodedLogoutRequest(false); + } + + requestPostBinding = String.format( + this.requestPostBindingHTML, + settings.getOrganization().getOrgDisplayName(), + settings.getIdpSingleSignOnServiceUrl(), + "SAMLRequest", + Util.base64encoder(logoutRequestXML), + relayState != null ? relayState : "" + ); + + if (!stay) { + ServletUtils.respondWithContentString(response, requestPostBinding, "text/html; charset=utf-8"); + } + } catch (Exception e) { + LOGGER.error("Error {}", e.getMessage(), e); + } + + return requestPostBinding; + } else { + throw new SettingsException("Unsupported SSO binding: " + settings.getIdpSingleSignOnServiceBinding(), SettingsException.UNSUPPORTED_BINDING); } - return ServletUtils.sendRedirect(response, sloUrl, parameters, stay); } /** @@ -435,8 +526,8 @@ public String logout(String returnTo, String nameId, String sessionIndex, Boolea * @throws XMLEntityException * @throws SettingsException */ - public void logout(String returnTo, String nameId, String sessionIndex, String nameidFormat) throws IOException, XMLEntityException, SettingsException { - logout(returnTo, nameId, sessionIndex, false, nameidFormat); + public String logout(String returnTo, String nameId, String sessionIndex, String nameidFormat) throws IOException, XMLEntityException, SettingsException { + return logout(returnTo, nameId, sessionIndex, false, nameidFormat); } /** @@ -454,8 +545,8 @@ public void logout(String returnTo, String nameId, String sessionIndex, String n * @throws XMLEntityException * @throws SettingsException */ - public void logout(String returnTo, String nameId, String sessionIndex) throws IOException, XMLEntityException, SettingsException { - logout(returnTo, nameId, sessionIndex, false, null); + public String logout(String returnTo, String nameId, String sessionIndex) throws IOException, XMLEntityException, SettingsException { + return logout(returnTo, nameId, sessionIndex, false, null); } /** @@ -465,8 +556,8 @@ public void logout(String returnTo, String nameId, String sessionIndex) throws I * @throws XMLEntityException * @throws SettingsException */ - public void logout() throws IOException, XMLEntityException, SettingsException { - logout(null, null, null, false); + public String logout() throws IOException, XMLEntityException, SettingsException { + return logout(null, null, null, false); } /** @@ -480,8 +571,8 @@ public void logout() throws IOException, XMLEntityException, SettingsException { * @throws XMLEntityException * @throws SettingsException */ - public void logout(String returnTo) throws IOException, XMLEntityException, SettingsException { - logout(returnTo, null, null); + public String logout(String returnTo) throws IOException, XMLEntityException, SettingsException { + return logout(returnTo, null, null); } @@ -533,17 +624,17 @@ public void processResponse(String requestId) throws Exception { lastMessageId = samlResponse.getId(); lastAssertionId = samlResponse.getAssertionId(); lastAssertionNotOnOrAfter = samlResponse.getAssertionNotOnOrAfter(); - LOGGER.debug("processResponse success --> " + samlResponseParameter); + LOGGER.debug("processResponse success --> {}", samlResponseParameter); } else { errors.add("invalid_response"); LOGGER.error("processResponse error. invalid_response"); - LOGGER.debug(" --> " + samlResponseParameter); + LOGGER.debug(" --> {}", samlResponseParameter); errorReason = samlResponse.getError(); } } else { errors.add("invalid_binding"); String errorMsg = "SAML Response not found, Only supported HTTP_POST Binding"; - LOGGER.error("processResponse error." + errorMsg); + LOGGER.error("processResponse error. {}", errorMsg); throw new Error(errorMsg, Error.SAML_RESPONSE_NOT_FOUND); } } @@ -567,7 +658,7 @@ public void processResponse() throws Exception { * * @throws Exception */ - public void processSLO(Boolean keepLocalSession, String requestId) throws Exception { + public String processSLO(Boolean keepLocalSession, String requestId, boolean stay) throws Exception { final HttpRequest httpRequest = ServletUtils.makeHttpRequest(this.request); final String samlRequestParameter = httpRequest.getParameter("SAMLRequest"); @@ -579,11 +670,11 @@ public void processSLO(Boolean keepLocalSession, String requestId) throws Except if (!logoutResponse.isValid(requestId)) { errors.add("invalid_logout_response"); LOGGER.error("processSLO error. invalid_logout_response"); - LOGGER.debug(" --> " + samlResponseParameter); - errorReason = logoutResponse.getError(); + LOGGER.debug(" --> {}", samlResponseParameter); + errorReason = logoutResponse.getError(); } else { String status = logoutResponse.getStatus(); - if (status == null || !status.equals(Constants.STATUS_SUCCESS)) { + if (!Constants.STATUS_SUCCESS.equals(status)) { errors.add("logout_not_success"); LOGGER.error("processSLO error. logout_not_success"); LOGGER.debug(" --> " + samlResponseParameter); @@ -601,11 +692,11 @@ public void processSLO(Boolean keepLocalSession, String requestId) throws Except if (!logoutRequest.isValid()) { errors.add("invalid_logout_request"); LOGGER.error("processSLO error. invalid_logout_request"); - LOGGER.debug(" --> " + samlRequestParameter); + LOGGER.debug(" --> {}", samlRequestParameter); errorReason = logoutRequest.getError(); } else { lastMessageId = logoutRequest.getId(); - LOGGER.debug("processSLO success --> " + samlRequestParameter); + LOGGER.debug("processSLO success --> {}", samlRequestParameter); if (!keepLocalSession) { request.getSession().invalidate(); } @@ -615,44 +706,94 @@ public void processSLO(Boolean keepLocalSession, String requestId) throws Except logoutResponseBuilder.build(inResponseTo); lastResponse = logoutResponseBuilder.getLogoutResponseXml(); - String samlLogoutResponse = logoutResponseBuilder.getEncodedLogoutResponse(); + String relayState = request.getParameter("RelayState"); - Map parameters = new LinkedHashMap(); + if (Constants.BINDING_HTTP_REDIRECT.equals(settings.getIdpSingleLogoutServiceBinding())) { + String samlLogoutResponse = logoutResponseBuilder.getEncodedLogoutResponse(); - parameters.put("SAMLResponse", samlLogoutResponse); + Map parameters = new LinkedHashMap<>(); - String relayState = request.getParameter("RelayState"); - if (relayState != null) { - parameters.put("RelayState", relayState); - } + parameters.put("SAMLResponse", samlLogoutResponse); - if (settings.getLogoutResponseSigned()) { - String sigAlg = settings.getSignatureAlgorithm(); - String signature = this.buildResponseSignature(samlLogoutResponse, relayState, sigAlg); + if (relayState != null) { + parameters.put("RelayState", relayState); + } - parameters.put("SigAlg", sigAlg); - parameters.put("Signature", signature); - } + if (settings.getLogoutResponseSigned()) { + String sigAlg = settings.getSignatureAlgorithm(); + String signature = this.buildResponseSignature(samlLogoutResponse, relayState, sigAlg); - String sloUrl = getSLOResponseUrl(); - LOGGER.debug("Logout response sent to " + sloUrl + " --> " + samlLogoutResponse); - ServletUtils.sendRedirect(response, sloUrl, parameters); + parameters.put("SigAlg", sigAlg); + parameters.put("Signature", signature); + } + + String sloUrl = getSLOResponseUrl(); + + if (!stay) { + LOGGER.debug("Logout response sent to {} --> {}", sloUrl, samlLogoutResponse); + } + return ServletUtils.sendRedirect(response, sloUrl, parameters, stay); + } else if (Constants.BINDING_HTTP_POST.equals(settings.getIdpSingleLogoutServiceBinding())) { + String requestPostBinding = null; + + try { + String logoutResponseXML; + + if (settings.getLogoutResponseSigned()) { + logoutResponseXML = Util.addSign( + Util.convertStringToDocument(logoutResponseBuilder.getLogoutResponseXml()), + settings.getSPkey(), + settings.getSPcert(), + settings.getSignatureAlgorithm(), + Constants.C14NEXC); + + LOGGER.debug("Signed XML: {}", logoutResponseXML); + } else { + logoutResponseXML = logoutResponseBuilder.getEncodedLogoutResponse(false); + } + + requestPostBinding = String.format( + this.requestPostBindingHTML, + settings.getOrganization().getOrgDisplayName(), + settings.getIdpSingleSignOnServiceUrl(), + "SAMLResponse", + Util.base64encoder(logoutResponseXML), + relayState != null ? relayState : "" + ); + + if (!stay) { + ServletUtils.respondWithContentString(response, requestPostBinding, "text/html; charset=utf-8"); + } + + return requestPostBinding; + } catch (Exception e) { + LOGGER.error("Error {}", e.getMessage(), e); + } + } else { + throw new SettingsException("Unsupported SSO binding: " + settings.getIdpSingleLogoutServiceBinding(), SettingsException.UNSUPPORTED_BINDING); + } } } else { errors.add("invalid_binding"); String errorMsg = "SAML LogoutRequest/LogoutResponse not found. Only supported HTTP_REDIRECT Binding"; - LOGGER.error("processSLO error." + errorMsg); + LOGGER.error("processSLO error. {}", errorMsg); throw new Error(errorMsg, Error.SAML_LOGOUTMESSAGE_NOT_FOUND); } - } + + return null; + } + + public String processSLO(Boolean keepLocalSession, String requestId) throws Exception { + return processSLO(keepLocalSession, requestId, false); + } /** * Process the SAML Logout Response / Logout Request sent by the IdP. * * @throws Exception */ - public void processSLO() throws Exception { - processSLO(false, null); + public String processSLO() throws Exception { + return processSLO(false, null, false); } /** @@ -819,8 +960,8 @@ public String buildResponseSignature(String samlResponse, String relayState, Str /** * Generates the Signature for a SAML Response * - * @param samlResponse - * The SAML Response + * @param samlMessage + * The SAML message * @param relayState * The RelayState * @param signAlgorithm @@ -839,7 +980,7 @@ private String buildSignature(String samlMessage, String relayState, String sign if (!settings.checkSPCerts()) { String errorMsg = "Trying to sign the " + type + " but can't load the SP private key"; - LOGGER.error("buildSignature error. " + errorMsg); + LOGGER.error("buildSignature error. {}", errorMsg); throw new SettingsException(errorMsg, SettingsException.PRIVATE_KEY_NOT_FOUND); } @@ -859,17 +1000,16 @@ private String buildSignature(String samlMessage, String relayState, String sign try { signature = Util.base64encoder(Util.sign(msg, key, signAlgorithm)); } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) { - String errorMsg = "buildSignature error." + e.getMessage(); - LOGGER.error(errorMsg); + LOGGER.error("buildSignature error. {}", e.getMessage()); } if (signature.isEmpty()) { String errorMsg = "There was a problem when calculating the Signature of the " + type; - LOGGER.error("buildSignature error. " + errorMsg); + LOGGER.error("buildSignature error. {}", errorMsg); throw new IllegalArgumentException(errorMsg); } - LOGGER.debug("buildResponseSignature success. --> " + signature); + LOGGER.debug("buildResponseSignature success. --> {}", signature); return signature; } diff --git a/toolkit/src/main/java/com/onelogin/saml2/servlet/ServletUtils.java b/toolkit/src/main/java/com/onelogin/saml2/servlet/ServletUtils.java index c62bdd71..6aec2efe 100644 --- a/toolkit/src/main/java/com/onelogin/saml2/servlet/ServletUtils.java +++ b/toolkit/src/main/java/com/onelogin/saml2/servlet/ServletUtils.java @@ -214,4 +214,21 @@ public static void sendRedirect(HttpServletResponse response, String location) t Map parameters =new HashMap(); sendRedirect(response, location, parameters); } + + /** + * Respond to a request with a given {@link String}. + * + * @param response The response. + * @param contentToWrite The content to write in the {@code response}. + * @param contentType The content type of the content to be written. + * @throws IOException If it fails to write in the {@code response}. + */ + public static void respondWithContentString(HttpServletResponse response, String contentToWrite, String contentType) throws IOException { + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType(contentType); + response.setContentLength(contentToWrite.getBytes("UTF-8").length); + response.getWriter().write(contentToWrite); + response.getWriter().flush(); + response.getWriter().close(); + } } diff --git a/toolkit/src/main/resources/request-post-binding.html b/toolkit/src/main/resources/request-post-binding.html new file mode 100644 index 00000000..28256e33 --- /dev/null +++ b/toolkit/src/main/resources/request-post-binding.html @@ -0,0 +1,17 @@ + + + + %s + + +
+ + + +
+ + + \ No newline at end of file