You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by sh...@apache.org on 2026/08/18 12:49:17 UTC

(cloudstack) 18/20: saml: reject signed SAMLResponse when IdP has no signing certificate registered

This is an automated email from the ASF dual-hosted git repository.

shwstppr pushed a commit to branch 4.20
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 04598336311f821ea6bcf2e21599b59f9f08c1f3
Author: dahn <da...@gmail.com>
AuthorDate: Fri May 22 10:52:40 2026 +0200

    saml: reject signed SAMLResponse when IdP has no signing certificate registered
    
    When a SAML Response or EncryptedAssertion carries a ds:Signature element
    but the IdP metadata contains no signing certificate, the previous code
    silently skipped signature validation (the guard was
    `idpMetadata.getSigningCertificate() != null && sig != null`).
    An attacker could forge a SAMLResponse with an arbitrary bogus signature
    against a cert-less IdP and obtain a valid session — a full
    unauthenticated authentication bypass.
    
    Fix: when a signature IS present we now require a signing certificate to
    be registered.  If none is found we immediately reject the request with
    an ACCOUNT_ERROR.  The same guard is applied to the EncryptedAssertion
    signature block.
---
 .../api/command/SAML2LoginAPIAuthenticatorCmd.java | 78 ++++++++++++++--------
 1 file changed, 52 insertions(+), 26 deletions(-)

diff --git a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
index 584f2463754..cbc2207ce12 100644
--- a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
+++ b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
@@ -142,6 +142,10 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
         return responseObject;
     }
 
+    /**
+     * handles the case where signature is absent but required.
+     * @param signature should contain a value or be configured to be optional/not be checked
+     */
     protected void checkAndFailOnMissingSAMLSignature(Signature signature) {
         if (signature == null && SAML2AuthManager.SAMLCheckSignature.value()) {
             logger.error("Failing SAML login due to missing signature in the SAML response and signature check is enforced. " +
@@ -150,6 +154,51 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
         }
     }
 
+    /**
+     * Validates the given SAML {@link Signature} against the signing certificate registered for the IdP.
+     *
+     * note: when a signature element IS present in a SAMLResponse or Assertion we must
+     * always verify it.  Silently skipping verification when the IdP has no signing certificate
+     * registered would allow an attacker to forge an arbitrary response and bypass authentication.
+     * Therefore, if a signature is present but the IdP has no signing certificate we
+     * explicitly reject the request rather than trusting an unverifiable signature.
+     *
+     * @param sig         the {@link Signature} element extracted from the SAML message; may be {@code null}
+     *                    (no signature present — handled by {@link #checkAndFailOnMissingSAMLSignature})
+     * @param idpMetadata metadata for the IdP that issued the response
+     * @param params      original request parameters (used to build the serialised error response)
+     * @param responseType response serialisation type
+     * @throws ServerApiException if a signature is present but no signing certificate is registered,
+     *                            or if signature validation fails
+     */
+    protected void validateSAMLSignature(Signature sig, SAMLProviderMetadata idpMetadata,
+            Map<String, Object[]> params, String responseType) {
+        if (sig == null) {
+            return;
+        }
+        if (idpMetadata.getSigningCertificate() == null) {
+            logger.error("SAML Response contains a signature but the IdP has no signing certificate " +
+                    "registered; refusing to trust the signed SAMLResponse to prevent authentication bypass.");
+            throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, apiServer.getSerializedApiError(
+                    ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                    "IdP has no signing certificate registered; refusing to trust signed SAMLResponse. " +
+                    "Please register the IdP signing certificate or contact your administrator.",
+                    params, responseType));
+        }
+        BasicX509Credential credential = new BasicX509Credential();
+        credential.setEntityCertificate(idpMetadata.getSigningCertificate());
+        SignatureValidator validator = new SignatureValidator(credential);
+        try {
+            validator.validate(sig);
+        } catch (ValidationException e) {
+            logger.error("SAML Response's signature failed to be validated by IDP signing key: " + e.getMessage());
+            throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, apiServer.getSerializedApiError(
+                    ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+                    "SAML Response's signature failed to be validated by IDP signing key",
+                    params, responseType));
+        }
+    }
+
     @Override
     public String authenticate(final String command, final Map<String, Object[]> params, final HttpSession session, final InetAddress remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException {
         try {
@@ -238,20 +287,9 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
                 session.setAttribute(SAMLPluginConstants.SAML_IDPID, issuer.getValue());
 
                 Signature sig = processedSAMLResponse.getSignature();
+                // Validate the top-level response signature.
                 checkAndFailOnMissingSAMLSignature(sig);
-                if (idpMetadata.getSigningCertificate() != null && sig != null) {
-                    BasicX509Credential credential = new BasicX509Credential();
-                    credential.setEntityCertificate(idpMetadata.getSigningCertificate());
-                    SignatureValidator validator = new SignatureValidator(credential);
-                    try {
-                        validator.validate(sig);
-                    } catch (ValidationException e) {
-                        logger.error("SAML Response's signature failed to be validated by IDP signing key:" + e.getMessage());
-                        throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
-                                "SAML Response's signature failed to be validated by IDP signing key",
-                                params, responseType));
-                    }
-                }
+                validateSAMLSignature(sig, idpMetadata, params, responseType);
 
                 username = SAMLUtils.getValueFromAssertions(processedSAMLResponse.getAssertions(), SAML2AuthManager.SAMLUserAttributeName.value());
 
@@ -286,19 +324,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
                             }
                             Signature encSig = assertion.getSignature();
                             checkAndFailOnMissingSAMLSignature(encSig);
-                            if (idpMetadata.getSigningCertificate() != null && encSig != null) {
-                                BasicX509Credential sigCredential = new BasicX509Credential();
-                                sigCredential.setEntityCertificate(idpMetadata.getSigningCertificate());
-                                SignatureValidator validator = new SignatureValidator(sigCredential);
-                                try {
-                                    validator.validate(encSig);
-                                } catch (ValidationException e) {
-                                    logger.error("SAML Response's signature failed to be validated by IDP signing key:" + e.getMessage());
-                                    throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
-                                            "SAML Response's signature failed to be validated by IDP signing key",
-                                            params, responseType));
-                                }
-                            }
+                            validateSAMLSignature(encSig, idpMetadata, params, responseType);
                             if (assertion.getSubject() != null && assertion.getSubject().getNameID() != null) {
                                 session.setAttribute(SAMLPluginConstants.SAML_NAMEID, assertion.getSubject().getNameID().getValue());
                             }