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

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