Skip to content

Commit

Permalink
Enhance parsing of StatusCode in SAML Responses (elastic#38628)
Browse files Browse the repository at this point in the history
* Enhance parsing of StatusCode in SAML Responses

<Status> elements in a failed response might contain two nested
<StatusCode> elements. We currently only parse the first one in
order to create a message that we attach to the Exception we return
and log. However this is generic and only gives out informarion
about whether the SAML IDP believes it's an error with the
request or if it couldn't handle the request for other reasons. The
encapsulated StatusCode has a more interesting error message that
potentially gives out the actual error as in Invalid nameid policy,
authentication failure etc.

This change ensures that we print that information also, and removes
Message and Details fields from the message when these are not
part of the Status element (which quite often is the case)
  • Loading branch information
jkakavas committed Feb 12, 2019
1 parent 946767a commit 7cd6d26
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ private SamlAttributes authenticateResponse(Element element, Collection<String>
throw samlException("SAML Response has no status code");
}
if (isSuccess(status) == false) {
throw samlException("SAML Response is not a 'success' response: Code={} Message={} Detail={}",
status.getStatusCode().getValue(), getMessage(status), getDetail(status));
throw samlException("SAML Response is not a 'success' response: {}", getStatusCodeMessage(status));
}
checkIssuer(response.getIssuer(), response);
checkResponseDestination(response);
Expand Down Expand Up @@ -137,6 +136,32 @@ private SamlAttributes authenticateResponse(Element element, Collection<String>
return new SamlAttributes(nameId, session, attributes);
}

private String getStatusCodeMessage(Status status) {
StatusCode firstLevel = status.getStatusCode();
StatusCode subLevel = firstLevel.getStatusCode();
StringBuilder sb = new StringBuilder();
if (StatusCode.REQUESTER.equals(firstLevel.getValue())) {
sb.append("The SAML IdP did not grant the request. It indicated that the Elastic Stack side sent something invalid (");
} else if (StatusCode.RESPONDER.equals(firstLevel.getValue())) {
sb.append("The request could not be granted due to an error in the SAML IDP side (");
} else if (StatusCode.VERSION_MISMATCH.equals(firstLevel.getValue())) {
sb.append("The request could not be granted because the SAML IDP doesn't support SAML 2.0 (");
} else {
sb.append("The request could not be granted, the SAML IDP responded with a non-standard Status code (");
}
sb.append(firstLevel.getValue()).append(").");
if (getMessage(status) != null) {
sb.append(" Message: [").append(getMessage(status)).append("]");
}
if (getDetail(status) != null) {
sb.append(" Detail: [").append(getDetail(status)).append("]");
}
if (null != subLevel) {
sb.append(" Specific status code which might indicate what the issue is: [").append(subLevel.getValue()).append("]");
}
return sb.toString();
}

private String getMessage(Status status) {
final StatusMessage sm = status.getStatusMessage();
return sm == null ? null : sm.getMessage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1374,7 +1374,7 @@ public void testContentIsAcceptedIfRestrictedToOurAudience() throws Exception {
}

public void testContentIsRejectedIfNotMarkedAsSuccess() throws Exception {
final String xml = getSimpleResponse(clock.instant()).replace(StatusCode.SUCCESS, StatusCode.REQUESTER);
final String xml = getStatusFailedResponse();
final SamlToken token = token(signDoc(xml));
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
assertThat(exception.getMessage(), containsString("not a 'success' response"));
Expand Down Expand Up @@ -1408,8 +1408,7 @@ public void testSignatureWrappingAttackOne() throws Exception {
<ForgedAssertion></ForgedAssertion>
</ForgedResponse>
*/
final Element response = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element response = (Element) legitimateDocument.getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element clonedResponse = (Element) response.cloneNode(true);
final Element clonedSignature = (Element) clonedResponse.
getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature").item(0);
Expand Down Expand Up @@ -1443,8 +1442,7 @@ public void testSignatureWrappingAttackTwo() throws Exception {
<ForgedAssertion></ForgedAssertion>
</ForgedResponse>
*/
final Element response = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element response = (Element) legitimateDocument.getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element clonedResponse = (Element) response.cloneNode(true);
final Element clonedSignature = (Element) clonedResponse.
getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature").item(0);
Expand Down Expand Up @@ -1482,8 +1480,7 @@ public void testSignatureWrappingAttackThree() throws Exception {
</LegitimateAssertion>
</Response>
*/
final Element response = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element response = (Element) legitimateDocument.getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element assertion = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20_NS, "Assertion").item(0);
final Element forgedAssertion = (Element) assertion.cloneNode(true);
Expand Down Expand Up @@ -1522,10 +1519,8 @@ public void testSignatureWrappingAttackFour() throws Exception {
</ForgedAssertion>
</Response>
*/
final Element response = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element assertion = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20_NS, "Assertion").item(0);
final Element response = (Element) legitimateDocument.getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element assertion = (Element) legitimateDocument.getElementsByTagNameNS(SAML20_NS, "Assertion").item(0);
final Element forgedAssertion = (Element) assertion.cloneNode(true);
forgedAssertion.setAttribute("ID", "_forged_assertion_id");
final Element clonedSignature = (Element) forgedAssertion.
Expand Down Expand Up @@ -1559,17 +1554,14 @@ public void testSignatureWrappingAttackFive() throws Exception {
<LegitimateAssertion></LegitimateAssertion>
</Response>
*/
final Element response = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element assertion = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20_NS, "Assertion").item(0);
final Element response = (Element) legitimateDocument.getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element assertion = (Element) legitimateDocument.getElementsByTagNameNS(SAML20_NS, "Assertion").item(0);
final Element signature = (Element) assertion.
getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature").item(0);
getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature").item(0);
assertion.removeChild(signature);
final Element forgedAssertion = (Element) assertion.cloneNode(true);
forgedAssertion.setAttribute("ID", "_forged_assertion_id");
final Element issuer = (Element) forgedAssertion.
getElementsByTagNameNS(SAML20_NS, "Issuer").item(0);
final Element issuer = (Element) forgedAssertion.getElementsByTagNameNS(SAML20_NS, "Issuer").item(0);
forgedAssertion.insertBefore(signature, issuer.getNextSibling());
response.insertBefore(forgedAssertion, assertion);
final SamlToken forgedToken = token(SamlUtils.toString((legitimateDocument.getDocumentElement())));
Expand Down Expand Up @@ -1598,10 +1590,8 @@ public void testSignatureWrappingAttackSix() throws Exception {
</ForgedAssertion>
</Response>
*/
final Element response = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element assertion = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20_NS, "Assertion").item(0);
final Element response = (Element) legitimateDocument.getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element assertion = (Element) legitimateDocument.getElementsByTagNameNS(SAML20_NS, "Assertion").item(0);
final Element forgedAssertion = (Element) assertion.cloneNode(true);
forgedAssertion.setAttribute("ID", "_forged_assertion_id");
final Element signature = (Element) assertion.
Expand All @@ -1610,8 +1600,7 @@ public void testSignatureWrappingAttackSix() throws Exception {
getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature").item(0);
forgedAssertion.removeChild(forgedSignature);
assertion.removeChild(signature);
final Element issuer = (Element) forgedAssertion.
getElementsByTagNameNS(SAML20_NS, "Issuer").item(0);
final Element issuer = (Element) forgedAssertion.getElementsByTagNameNS(SAML20_NS, "Issuer").item(0);
forgedAssertion.insertBefore(signature, issuer.getNextSibling());
signature.appendChild(assertion);
response.appendChild(forgedAssertion);
Expand Down Expand Up @@ -1642,11 +1631,9 @@ public void testSignatureWrappingAttackSeven() throws Exception {
</LegitimateAssertion>
</Response>
*/
final Element response = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element response = (Element) legitimateDocument.getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element extensions = legitimateDocument.createElement("Extensions");
final Element assertion = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20_NS, "Assertion").item(0);
final Element assertion = (Element) legitimateDocument.getElementsByTagNameNS(SAML20_NS, "Assertion").item(0);
response.insertBefore(extensions, assertion);
final Element forgedAssertion = (Element) assertion.cloneNode(true);
forgedAssertion.setAttribute("ID", "_forged_assertion_id");
Expand Down Expand Up @@ -1683,10 +1670,8 @@ public void testSignatureWrappingAttackEight() throws Exception {
</ForgedAssertion>
</Response>
*/
final Element response = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element assertion = (Element) legitimateDocument.
getElementsByTagNameNS(SAML20_NS, "Assertion").item(0);
final Element response = (Element) legitimateDocument.getElementsByTagNameNS(SAML20P_NS, "Response").item(0);
final Element assertion = (Element) legitimateDocument.getElementsByTagNameNS(SAML20_NS, "Assertion").item(0);
final Element forgedAssertion = (Element) assertion.cloneNode(true);
forgedAssertion.setAttribute("ID", "_forged_assertion_id");
final Element signature = (Element) assertion.
Expand All @@ -1695,8 +1680,7 @@ public void testSignatureWrappingAttackEight() throws Exception {
getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature").item(0);
forgedAssertion.removeChild(forgedSignature);
assertion.removeChild(signature);
final Element issuer = (Element) forgedAssertion.
getElementsByTagNameNS(SAML20_NS, "Issuer").item(0);
final Element issuer = (Element) forgedAssertion.getElementsByTagNameNS(SAML20_NS, "Issuer").item(0);
forgedAssertion.insertBefore(signature, issuer.getNextSibling());
Element object = legitimateDocument.createElement("Object");
object.appendChild(assertion);
Expand Down Expand Up @@ -2034,7 +2018,7 @@ private void encryptElement(Element element, X509Certificate certificate, boolea
}

private Element buildEncryptedKeyElement(Document document, EncryptedKey encryptedKey, X509Certificate certificate)
throws XMLSecurityException {
throws XMLSecurityException {
final XMLCipher cipher = XMLCipher.getInstance();
final org.apache.xml.security.keys.KeyInfo keyInfo = new org.apache.xml.security.keys.KeyInfo(document);
final X509Data x509Data = new X509Data(document);
Expand All @@ -2054,6 +2038,23 @@ private Response toResponse(String xml) throws SAXException, IOException, Parser
return authenticator.buildXmlObject(doc.getDocumentElement(), Response.class);
}

private String getStatusFailedResponse() {
final Instant now = clock.instant();
return "<?xml version='1.0' encoding='UTF-8'?>\n" +
"<proto:Response Destination='" + SP_ACS_URL + "' ID='" + randomId() + "' InResponseTo='" + requestId +
"' IssueInstant='" + now + "' Version='2.0'" +
" xmlns:proto='urn:oasis:names:tc:SAML:2.0:protocol'" +
" xmlns:assert='urn:oasis:names:tc:SAML:2.0:assertion'" +
" xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" +
" xmlns:xs='http://www.w3.org/2001/XMLSchema'" +
" xmlns:ds='http://www.w3.org/2000/09/xmldsig#' >" +
"<assert:Issuer>" + IDP_ENTITY_ID + "</assert:Issuer>" +
"<proto:Status><proto:StatusCode Value='urn:oasis:names:tc:SAML:2.0:status:Requester'>" +
"<proto:StatusCode Value='urn:oasis:names:tc:SAML:2.0:status:InvalidNameIDPolicy'/></proto:StatusCode>" +
"</proto:Status>" +
"</proto:Response>";
}

private String getSimpleResponse(Instant now) {
return getSimpleResponse(now, randomAlphaOfLengthBetween(12, 18), randomId());
}
Expand Down

0 comments on commit 7cd6d26

Please sign in to comment.