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

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