Skip to content

Commit 6561b3d

Browse files
authored
Merge pull request #69 from miszobi/issue/62-stricter-inresponseto-checking
#62: check InResponseTo in Response more strictly
2 parents 70aa5c7 + 6df96c0 commit 6561b3d

8 files changed

Lines changed: 147 additions & 8 deletions

File tree

core/src/main/java/com/onelogin/saml2/authn/SamlResponse.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import javax.xml.xpath.XPathExpressionException;
1212

1313
import com.onelogin.saml2.model.SubjectConfirmationIssue;
14+
import org.apache.commons.lang3.ObjectUtils;
1415
import org.joda.time.DateTime;
1516
import org.slf4j.Logger;
1617
import org.slf4j.LoggerFactory;
@@ -172,13 +173,16 @@ public boolean isValid(String requestId) {
172173
}
173174
}
174175

175-
String responseInResponseTo = rootElement.getAttribute("InResponseTo");
176+
String responseInResponseTo = rootElement.hasAttribute("InResponseTo") ? rootElement.getAttribute("InResponseTo") : null;
177+
if (requestId == null && responseInResponseTo != null && settings.isRejectUnsolicitedResponsesWithInResponseTo()) {
178+
throw new Exception("The Response has an InResponseTo attribute: " + responseInResponseTo +
179+
" while no InResponseTo was expected");
180+
}
181+
176182
// Check if the InResponseTo of the Response matches the ID of the AuthNRequest (requestId) if provided
177-
if (requestId != null && !requestId.isEmpty() && rootElement.hasAttribute("InResponseTo")) {
178-
if (!responseInResponseTo.equals(requestId)) {
183+
if (requestId != null && !ObjectUtils.equals(responseInResponseTo, requestId)) {
179184
throw new Exception("The InResponseTo of the Response: " + responseInResponseTo
180185
+ ", does not match the ID of the AuthNRequest sent by the SP: " + requestId);
181-
}
182186
}
183187

184188
if (!this.encrypted && settings.getWantAssertionsEncrypted()) {
@@ -310,8 +314,9 @@ private void validateSubjectConfirmation(String responseInResponseTo) throws Exc
310314
}
311315

312316
Node inResponseTo = subjectConfirmationDataNodes.item(c).getAttributes().getNamedItem("InResponseTo");
313-
if (inResponseTo != null && !inResponseTo.getNodeValue().equals(responseInResponseTo)) {
314-
validationIssues.add(new SubjectConfirmationIssue(i, "SubjectConfirmationData has an invalid InResponseTo value"));
317+
if (inResponseTo == null && responseInResponseTo != null ||
318+
inResponseTo != null && !inResponseTo.getNodeValue().equals(responseInResponseTo)) {
319+
validationIssues.add(new SubjectConfirmationIssue(i, "SubjectConfirmationData has an invalid InResponseTo value"));;
315320
continue;
316321
}
317322

core/src/main/java/com/onelogin/saml2/settings/Saml2Settings.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import java.util.LinkedList;
99
import java.util.List;
1010

11+
import com.onelogin.saml2.authn.SamlResponse;
12+
import com.onelogin.saml2.logout.LogoutResponse;
1113
import org.slf4j.Logger;
1214
import org.slf4j.LoggerFactory;
1315
import org.w3c.dom.Document;
@@ -70,6 +72,7 @@ public class Saml2Settings {
7072
private String requestedAuthnContextComparison = "exact";
7173
private Boolean wantXMLValidation = true;
7274
private String signatureAlgorithm = Constants.RSA_SHA1;
75+
private boolean rejectUnsolicitedResponsesWithInResponseTo = false;
7376

7477
// Misc
7578
private List<Contact> contacts = new LinkedList<Contact>();
@@ -634,6 +637,28 @@ public void setSignatureAlgorithm(String signatureAlgorithm) {
634637
this.signatureAlgorithm = signatureAlgorithm;
635638
}
636639

640+
/**
641+
* Controls if unsolicited Responses are rejected if they contain an InResponseTo value.
642+
*
643+
* If false using a validate method {@link SamlResponse#isValid(String)} with a null argument will
644+
* accept messages with any (or none) InResponseTo value.
645+
*
646+
* If true using these methods with a null argument will only accept messages with no InRespoonseTo value,
647+
* and reject messages where the value is set.
648+
*
649+
* In all cases using validate with a specified request ID will only accept responses that have the same
650+
* InResponseTo id set.
651+
*
652+
* @param rejectUnsolicitedResponsesWithInResponseTo whether to strictly check the InResponseTo attribute
653+
*/
654+
public void setRejectUnsolicitedResponsesWithInResponseTo(boolean rejectUnsolicitedResponsesWithInResponseTo) {
655+
this.rejectUnsolicitedResponsesWithInResponseTo = rejectUnsolicitedResponsesWithInResponseTo;
656+
}
657+
658+
public boolean isRejectUnsolicitedResponsesWithInResponseTo() {
659+
return rejectUnsolicitedResponsesWithInResponseTo;
660+
}
661+
637662
/**
638663
* Set contacts info that will be listed on the Service Provider metadata
639664
*

core/src/main/java/com/onelogin/saml2/settings/SettingsBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public class SettingsBuilder {
8282
public final static String SECURITY_REQUESTED_AUTHNCONTEXTCOMPARISON = "onelogin.saml2.security.requested_authncontextcomparison";
8383
public final static String SECURITY_WANT_XML_VALIDATION = "onelogin.saml2.security.want_xml_validation";
8484
public final static String SECURITY_SIGNATURE_ALGORITHM = "onelogin.saml2.security.signature_algorithm";
85+
public final static String SECURITY_REJECT_UNSOLICITED_RESPONSES_WITH_INRESPONSETO = "onelogin.saml2.security.reject_unsolicited_responses_with_inresponseto";
8586

8687
// Misc
8788
public final static String CONTACT_TECHNICAL_GIVEN_NAME = "onelogin.saml2.contacts.technical.given_name";
@@ -268,6 +269,11 @@ private void loadSecuritySetting() {
268269
String signatureAlgorithm = loadStringProperty(SECURITY_SIGNATURE_ALGORITHM);
269270
if (signatureAlgorithm != null && !signatureAlgorithm.isEmpty())
270271
saml2Setting.setSignatureAlgorithm(signatureAlgorithm);
272+
273+
Boolean rejectUnsolicitedResponsesWithInResponseTo = loadBooleanProperty(SECURITY_REJECT_UNSOLICITED_RESPONSES_WITH_INRESPONSETO);
274+
if (rejectUnsolicitedResponsesWithInResponseTo != null) {
275+
saml2Setting.setRejectUnsolicitedResponsesWithInResponseTo(rejectUnsolicitedResponsesWithInResponseTo);
276+
}
271277
}
272278

273279
/**

core/src/test/java/com/onelogin/saml2/test/authn/AuthnResponseTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,19 @@ public void testIsValidSubjectConfirmation_invalidInResponseTo() throws Exceptio
10571057
assertResponseValid(settings, samlResponseEncoded, true, false, "A valid SubjectConfirmation was not found on this Response: SubjectConfirmationData has an invalid InResponseTo value");
10581058
}
10591059

1060+
@Test
1061+
public void testIsValidSubjectConfirmation_unmatchedInResponseTo() throws Exception {
1062+
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.my.properties").build();
1063+
settings.setWantAssertionsSigned(false);
1064+
settings.setWantMessagesSigned(true);
1065+
1066+
final String samlResponseEncoded = loadSignMessageAndEncode("data/responses/invalids/invalid_unpaired_inresponsesto.xml");
1067+
1068+
assertResponseValid(settings, samlResponseEncoded, true, false,
1069+
"A valid SubjectConfirmation was not found on this Response: SubjectConfirmationData has an invalid InResponseTo value");
1070+
assertResponseValid(settings, samlResponseEncoded, false, true, null);
1071+
}
1072+
10601073
@Test
10611074
public void testIsValidSubjectConfirmation_invalidRecipient() throws Exception {
10621075
final Saml2Settings settings = new SettingsBuilder().fromFile("config/config.min.properties").build();
@@ -1177,6 +1190,34 @@ public void testIsInValidRequestId() throws Exception {
11771190
assertThat(samlResponse.getError(), containsString("The InResponseTo of the Response"));
11781191
}
11791192

1193+
@Test
1194+
public void testUnexpectedRequestId() throws Exception {
1195+
Saml2Settings acceptingUnexpectedInResponseTo = new SettingsBuilder().fromFile("config/config.my.properties").build();
1196+
Saml2Settings rejectingUnexpectedInResponseTo = new SettingsBuilder().fromFile("config/config.my.properties").build();
1197+
rejectingUnexpectedInResponseTo.setRejectUnsolicitedResponsesWithInResponseTo(true);
1198+
1199+
final String samlResponseEncoded = Util.getFileAsString("data/responses/valid_response.xml.base64");
1200+
1201+
assertResponseValid(acceptingUnexpectedInResponseTo, samlResponseEncoded, true, true, null);
1202+
assertResponseValid(rejectingUnexpectedInResponseTo, samlResponseEncoded, true, false,
1203+
"The Response has an InResponseTo attribute: ONELOGIN_5fe9d6e499b2f0913206aab3f7191729049bb807 while no InResponseTo was expected");
1204+
}
1205+
1206+
@Test
1207+
public void testMissingExpectedRequestId() throws Exception {
1208+
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.my.properties").build();
1209+
settings.setWantMessagesSigned(true);
1210+
settings.setWantAssertionsSigned(false);
1211+
settings.setStrict(true);
1212+
1213+
// message with no InResponseTo
1214+
final String samlResponseEncoded = loadSignMessageAndEncode("data/responses/valid_idp_initiated_response.xml");
1215+
1216+
final SamlResponse samlResponse = new SamlResponse(settings, mockRequestWithSamlResponse(samlResponseEncoded));
1217+
assertFalse(samlResponse.isValid("expected-id"));
1218+
assertEquals(samlResponse.getError(), "The InResponseTo of the Response: null, does not match the ID of the AuthNRequest sent by the SP: expected-id");
1219+
}
1220+
11801221
/**
11811222
* Tests the isValid method of SamlResponse
11821223
* Case: invalid signing issues

core/src/test/resources/data/responses/invalids/invalid_subjectconfirmation_multiple_issues.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0"?>
2-
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfxc32aed67-820f-4296-0c20-205a10dd5787" Version="2.0" IssueInstant="2011-06-17T14:54:14Z" Destination="http://localhost:8080/java-saml-jspsample/acs.jsp" InResponseTo="_57bcbf70-7b1f-012e-c821-782bcb13bb38">
2+
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfxc32aed67-820f-4296-0c20-205a10dd5787" Version="2.0" IssueInstant="2011-06-17T14:54:14Z" Destination="http://localhost:8080/java-saml-jspsample/acs.jsp">
33
<saml:Issuer>https://pitbulk.no-ip.org/simplesaml/saml2/idp/metadata.php</saml:Issuer>
44
<samlp:Status>
55
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>

core/src/test/resources/data/responses/invalids/invalid_subjectconfirmation_no_notonorafter.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0"?>
2-
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfxc32aed67-820f-4296-0c20-205a10dd5787" Version="2.0" IssueInstant="2011-06-17T14:54:14Z" Destination="http://localhost:8080/java-saml-jspsample/acs.jsp" InResponseTo="_57bcbf70-7b1f-012e-c821-782bcb13bb38">
2+
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfxc32aed67-820f-4296-0c20-205a10dd5787" Version="2.0" IssueInstant="2011-06-17T14:54:14Z" Destination="http://localhost:8080/java-saml-jspsample/acs.jsp">
33
<saml:Issuer>https://pitbulk.no-ip.org/simplesaml/saml2/idp/metadata.php</saml:Issuer>
44
<samlp:Status>
55
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0"?>
2+
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfxc32aed67-820f-4296-0c20-205a10dd5787" Version="2.0" IssueInstant="2011-06-17T14:54:14Z" InResponseTo="unpaired" Destination="http://localhost:8080/java-saml-jspsample/acs.jsp">
3+
<saml:Issuer>https://pitbulk.no-ip.org/simplesaml/saml2/idp/metadata.php</saml:Issuer>
4+
<samlp:Status>
5+
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
6+
</samlp:Status>
7+
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="pfx7841991c-c73f-4035-e2ee-c170c0e1d3e4" Version="2.0" IssueInstant="2011-06-17T14:54:14Z">
8+
<saml:Issuer>https://pitbulk.no-ip.org/simplesaml/saml2/idp/metadata.php</saml:Issuer>
9+
<saml:Subject>
10+
<saml:NameID SPNameQualifier="hello.com" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">someone@example.com</saml:NameID>
11+
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
12+
<saml:SubjectConfirmationData NotOnOrAfter="2020-06-17T14:59:14Z" Recipient="http://localhost:8080/java-saml-jspsample/acs.jsp"/>
13+
</saml:SubjectConfirmation>
14+
</saml:Subject>
15+
<saml:Conditions NotBefore="2010-06-17T14:53:44Z" NotOnOrAfter="2021-06-17T14:59:14Z">
16+
<saml:AudienceRestriction>
17+
<saml:Audience>http://localhost:8080/java-saml-jspsample/metadata.jsp</saml:Audience>
18+
</saml:AudienceRestriction>
19+
</saml:Conditions>
20+
<saml:AuthnStatement AuthnInstant="2011-06-17T14:54:07Z" SessionNotOnOrAfter="2021-06-17T22:54:14Z" SessionIndex="_51be37965feb5579d803141076936dc2e9d1d98ebf">
21+
<saml:AuthnContext>
22+
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
23+
</saml:AuthnContext>
24+
</saml:AuthnStatement>
25+
<saml:AttributeStatement>
26+
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
27+
<saml:AttributeValue xsi:type="xs:string">someone@example.com</saml:AttributeValue>
28+
</saml:Attribute>
29+
</saml:AttributeStatement>
30+
</saml:Assertion>
31+
</samlp:Response>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0"?>
2+
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfxc32aed67-820f-4296-0c20-205a10dd5787" Version="2.0" IssueInstant="2011-06-17T14:54:14Z" Destination="http://localhost:8080/java-saml-jspsample/acs.jsp">
3+
<saml:Issuer>https://pitbulk.no-ip.org/simplesaml/saml2/idp/metadata.php</saml:Issuer>
4+
<samlp:Status>
5+
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
6+
</samlp:Status>
7+
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="pfx7841991c-c73f-4035-e2ee-c170c0e1d3e4" Version="2.0" IssueInstant="2011-06-17T14:54:14Z">
8+
<saml:Issuer>https://pitbulk.no-ip.org/simplesaml/saml2/idp/metadata.php</saml:Issuer>
9+
<saml:Subject>
10+
<saml:NameID SPNameQualifier="hello.com" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">someone@example.com</saml:NameID>
11+
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
12+
<saml:SubjectConfirmationData NotOnOrAfter="2020-06-17T14:59:14Z" Recipient="http://localhost:8080/java-saml-jspsample/acs.jsp"/>
13+
</saml:SubjectConfirmation>
14+
</saml:Subject>
15+
<saml:Conditions NotBefore="2010-06-17T14:53:44Z" NotOnOrAfter="2021-06-17T14:59:14Z">
16+
<saml:AudienceRestriction>
17+
<saml:Audience>http://localhost:8080/java-saml-jspsample/metadata.jsp</saml:Audience>
18+
</saml:AudienceRestriction>
19+
</saml:Conditions>
20+
<saml:AuthnStatement AuthnInstant="2011-06-17T14:54:07Z" SessionNotOnOrAfter="2021-06-17T22:54:14Z" SessionIndex="_51be37965feb5579d803141076936dc2e9d1d98ebf">
21+
<saml:AuthnContext>
22+
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
23+
</saml:AuthnContext>
24+
</saml:AuthnStatement>
25+
<saml:AttributeStatement>
26+
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
27+
<saml:AttributeValue xsi:type="xs:string">someone@example.com</saml:AttributeValue>
28+
</saml:Attribute>
29+
</saml:AttributeStatement>
30+
</saml:Assertion>
31+
</samlp:Response>

0 commit comments

Comments
 (0)