Blame SOURCES/0008-Fix-signature-checking-on-unsigned-response-with-mul.patch

a4238c
From ea7e5efe9741e1b1787a58af16cb15b40c23be5a Mon Sep 17 00:00:00 2001
a4238c
From: Benjamin Dauvergne <bdauvergne@entrouvert.com>
a4238c
Date: Mon, 8 Mar 2021 11:33:26 +0100
a4238c
Subject: [PATCH] Fix signature checking on unsigned response with multiple
a4238c
 assertions
a4238c
a4238c
CVE-2021-28091 : when AuthnResponse messages are not signed (which is
a4238c
permitted by the specifiation), all assertion's signatures should be
a4238c
checked, but currently after the first signed assertion is checked all
a4238c
following assertions are accepted without checking their signature, and
a4238c
the last one is considered the main assertion.
a4238c
a4238c
This patch :
a4238c
* check signatures from all assertions if the message is not signed,
a4238c
* refuse messages with assertion from different issuers than the one on
a4238c
  the message, to prevent assertion bundling event if they are signed.
a4238c
---
a4238c
 lasso/saml-2.0/login.c | 102 +++++++++++++++++++++++++++++------------
a4238c
 1 file changed, 73 insertions(+), 29 deletions(-)
a4238c
a4238c
diff --git a/lasso/saml-2.0/login.c b/lasso/saml-2.0/login.c
a4238c
index 0d4bb1da1..cf62c1cc9 100644
a4238c
--- a/lasso/saml-2.0/login.c
a4238c
+++ b/lasso/saml-2.0/login.c
a4238c
@@ -1257,7 +1257,11 @@ lasso_saml20_login_check_assertion_signature(LassoLogin *login,
a4238c
 	original_node = lasso_node_get_original_xmlnode(LASSO_NODE(assertion));
a4238c
 	goto_cleanup_if_fail_with_rc(original_node, LASSO_PROFILE_ERROR_CANNOT_VERIFY_SIGNATURE);
a4238c
 
a4238c
-	rc = profile->signature_status = lasso_provider_verify_saml_signature(remote_provider, original_node, NULL);
a4238c
+	/* Shouldn't set the profile->signature_status here as we're only
a4238c
+	 * checking the assertion signature.
a4238c
+	 * Instead, we'll set the status after all the assertions are iterated.
a4238c
+	 */
a4238c
+	rc = lasso_provider_verify_saml_signature(remote_provider, original_node, NULL);
a4238c
 
a4238c
 #define log_verify_assertion_signature_error(msg) \
a4238c
 			message(G_LOG_LEVEL_WARNING, "Could not verify signature of assertion" \
a4238c
@@ -1282,18 +1286,6 @@ cleanup:
a4238c
 	return rc;
a4238c
 }
a4238c
 
a4238c
-static gboolean
a4238c
-_lasso_check_assertion_issuer(LassoSaml2Assertion *assertion, const gchar *provider_id)
a4238c
-{
a4238c
-	if (! LASSO_SAML2_ASSERTION(assertion) || ! provider_id)
a4238c
-		return FALSE;
a4238c
-
a4238c
-	if (! assertion->Issuer || ! assertion->Issuer->content)
a4238c
-		return FALSE;
a4238c
-
a4238c
-	return lasso_strisequal(assertion->Issuer->content,provider_id);
a4238c
-}
a4238c
-
a4238c
 static gint
a4238c
 _lasso_saml20_login_decrypt_assertion(LassoLogin *login, LassoSamlp2Response *samlp2_response)
a4238c
 {
a4238c
@@ -1358,11 +1350,23 @@ _lasso_saml20_login_decrypt_assertion(LassoLogin *login, LassoSamlp2Response *sa
a4238c
 	return 0;
a4238c
 }
a4238c
 
a4238c
+/* Verify that an assertion comes from a designated Issuer */
a4238c
+static gboolean
a4238c
+_lasso_check_assertion_issuer(LassoSaml2Assertion *assertion, const gchar *provider_id)
a4238c
+{
a4238c
+	if (! LASSO_SAML2_ASSERTION(assertion) || ! provider_id)
a4238c
+		return FALSE;
a4238c
+	if (! assertion->Issuer || ! assertion->Issuer->content)
a4238c
+		return FALSE;
a4238c
+	return lasso_strisequal(assertion->Issuer->content,provider_id);
a4238c
+}
a4238c
+
a4238c
 static gint
a4238c
 lasso_saml20_login_process_response_status_and_assertion(LassoLogin *login)
a4238c
 {
a4238c
 	LassoSamlp2StatusResponse *response;
a4238c
 	LassoSamlp2Response *samlp2_response = NULL;
a4238c
+	LassoSaml2Assertion *last_assertion = NULL;
a4238c
 	LassoProfile *profile;
a4238c
 	char *status_value;
a4238c
 	lasso_error_t rc = 0;
a4238c
@@ -1404,34 +1408,62 @@ lasso_saml20_login_process_response_status_and_assertion(LassoLogin *login)
a4238c
 
a4238c
 	/* Decrypt all EncryptedAssertions */
a4238c
 	_lasso_saml20_login_decrypt_assertion(login, samlp2_response);
a4238c
-	/* traverse all assertions */
a4238c
-	goto_cleanup_if_fail_with_rc (samlp2_response->Assertion != NULL,
a4238c
-			LASSO_PROFILE_ERROR_MISSING_ASSERTION);
a4238c
 
a4238c
+	/* Check there is at least one assertion */
a4238c
+	goto_cleanup_if_fail_with_rc (samlp2_response->Assertion != NULL, LASSO_PROFILE_ERROR_MISSING_ASSERTION);
a4238c
+
a4238c
+	/* In case of verify_hint as 'FORCE', if there's no response signature,
a4238c
+	 * we reject.
a4238c
+	 * In case of 'MAYBE', if response signature is present and valid, or
a4238c
+	 * not present, then we proceed with checking assertion signature(s).
a4238c
+	 * In any case, if there's a response signature and it's not valid,
a4238c
+	 * we reject.
a4238c
+	*/
a4238c
 	verify_hint = lasso_profile_get_signature_verify_hint(profile);
a4238c
+	if (profile->signature_status == LASSO_DS_ERROR_SIGNATURE_NOT_FOUND) {
a4238c
+		if (verify_hint == LASSO_PROFILE_SIGNATURE_VERIFY_HINT_FORCE) {
a4238c
+			goto_cleanup_with_rc(profile->signature_status);
a4238c
+		}
a4238c
+	} else if (profile->signature_status != 0) {
a4238c
+		goto_cleanup_with_rc(profile->signature_status);
a4238c
+	}
a4238c
 
a4238c
 	lasso_foreach_full_begin(LassoSaml2Assertion*, assertion, it, samlp2_response->Assertion);
a4238c
 		LassoSaml2Subject *subject = NULL;
a4238c
 
a4238c
-		lasso_assign_gobject (login->private_data->saml2_assertion, assertion);
a4238c
+		/* All Assertions MUST come from the same issuer as the Response. */
a4238c
+		if (! _lasso_check_assertion_issuer(assertion, profile->remote_providerID)) {
a4238c
+			goto_cleanup_with_rc(LASSO_PROFILE_ERROR_INVALID_ISSUER);
a4238c
+		}
a4238c
 
a4238c
-		/* If signature has already been verified on the message, and assertion has the same
a4238c
-		 * issuer as the message, the assertion is covered. So no need to verify a second
a4238c
-		 * time */
a4238c
-		if (profile->signature_status != 0 
a4238c
-			|| ! _lasso_check_assertion_issuer(assertion,
a4238c
-				profile->remote_providerID)
a4238c
-			|| verify_hint == LASSO_PROFILE_SIGNATURE_VERIFY_HINT_FORCE) {
a4238c
+		if (profile->signature_status != 0) {
a4238c
+			/* When response signature is not present */
a4238c
+			if (verify_hint == LASSO_PROFILE_SIGNATURE_VERIFY_HINT_MAYBE) {
a4238c
+				assertion_signature_status =
a4238c
+					lasso_saml20_login_check_assertion_signature(login, assertion);
a4238c
+				if (assertion_signature_status) {
a4238c
+					goto_cleanup_with_rc(assertion_signature_status);
a4238c
+				}
a4238c
+			}
a4238c
+		} else {
a4238c
+			/* response signature is present and valid */
a4238c
 			assertion_signature_status = lasso_saml20_login_check_assertion_signature(login,
a4238c
-					assertion);
a4238c
-			/* If signature validation fails, it is the return code for this function */
a4238c
+							assertion);
a4238c
 			if (assertion_signature_status) {
a4238c
-				rc = LASSO_PROFILE_ERROR_CANNOT_VERIFY_SIGNATURE;
a4238c
+				/* assertion signature is not valid or not present */
a4238c
+				if (verify_hint == LASSO_PROFILE_SIGNATURE_VERIFY_HINT_FORCE) {
a4238c
+					/* In case of FORCE, we reject right away */
a4238c
+					goto_cleanup_with_rc(assertion_signature_status);
a4238c
+				} else if (verify_hint == LASSO_PROFILE_SIGNATURE_VERIFY_HINT_MAYBE) {
a4238c
+					/* In case of MAYBE, if assertion signature is present and invalid, then we reject */
a4238c
+					if (assertion_signature_status != LASSO_DS_ERROR_SIGNATURE_NOT_FOUND) {
a4238c
+						goto_cleanup_with_rc(assertion_signature_status);
a4238c
+					}
a4238c
+				}
a4238c
 			}
a4238c
 		}
a4238c
-
a4238c
 		lasso_extract_node_or_fail(subject, assertion->Subject, SAML2_SUBJECT,
a4238c
-				LASSO_PROFILE_ERROR_MISSING_SUBJECT);
a4238c
+			LASSO_PROFILE_ERROR_MISSING_SUBJECT);
a4238c
 
a4238c
 		/* Verify Subject->SubjectConfirmationData->InResponseTo */
a4238c
 		if (login->private_data->request_id) {
a4238c
@@ -1446,8 +1478,20 @@ lasso_saml20_login_process_response_status_and_assertion(LassoLogin *login)
a4238c
 		/** Handle nameid */
a4238c
 		lasso_check_good_rc(lasso_saml20_profile_process_name_identifier_decryption(profile,
a4238c
 					&subject->NameID, &subject->EncryptedID));
a4238c
+
a4238c
+		last_assertion = assertion;
a4238c
 	lasso_foreach_full_end();
a4238c
 
a4238c
+	/* set the profile signature status only after all the signatures are
a4238c
+	 * verified.
a4238c
+	 */
a4238c
+	profile->signature_status = rc;
a4238c
+
a4238c
+	/* set the default assertion to the last one */
a4238c
+	if (last_assertion) {
a4238c
+		lasso_assign_gobject (login->private_data->saml2_assertion, last_assertion);
a4238c
+	}
a4238c
+
a4238c
 	switch (verify_hint) {
a4238c
 		case LASSO_PROFILE_SIGNATURE_VERIFY_HINT_FORCE:
a4238c
 		case LASSO_PROFILE_SIGNATURE_VERIFY_HINT_MAYBE:
a4238c
-- 
a4238c
2.26.3
a4238c