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());
}