Summary
When a signing certificate is configured for a metadata source, parse_and_check_signature() still accepts a document whose ds:Signature element has simply been removed. An attacker who can serve or alter the metadata does not have to forge a signature — stripping it is enough, and the document is used without any error, warning or log entry.
Where
src/saml2/mdstore.py, current master:
def parse_and_check_signature(self, txt):
self.parse(txt)
if not self.cert:
return True
if not self.signed():
return True
...
The second early return is the problem. signed() only reports whether a Signature element is present in the parsed document, so removing it turns the configured certificate into a no-op. Both remote (MetaDataExtern) and mdq (MetaDataMDX) go through this method, so both sources are affected.
Why it matters
Configuring cert is the documented way to establish the authenticity of downloaded metadata:
To verify the authenticity of the metadata aggregate downloaded from the remote server and the MDQ server local copies of the metadata signing certificates should be used.
An operator who follows that advice reasonably expects that metadata which cannot be verified is not used. Today the guarantee holds only for metadata that happens to still carry a signature.
For comparison, OpenSAML's SignatureValidationFilter treats this as a distinct condition and rejects it by default:
getRequireSignedRoot() — Get whether incoming metadata's root element is required to be signed. Defaults to true.
Unsigned metadata is filtered out there with "Root metadata element was unsigned".
Reproduction
-
Take any signed metadata document, for example the response of a federation MDQ service, and confirm its signature verifies:
xmlsec1 --verify --pubkey-cert-pem federation-signer.pem \
--id-attr:ID urn:oasis:names:tc:SAML:2.0:metadata:EntityDescriptor entity.xml
# OK
-
Remove the ds:Signature element and serve the result over HTTPS.
-
Configure a service provider against it:
'metadata': {'remote': [{'url': 'https://example.org/entity.xml',
'cert': '/path/to/federation-signer.pem'}]},
-
Start an authentication. The endpoints from the unsigned document are used. Changing, say, the SingleSignOnService location before removing the signature sends the user to that location instead of the real IdP.
Observed with the same document in three variants against an otherwise unchanged deployment, all served over HTTPS with a valid certificate:
| document |
result |
| original, signed |
redirect to the real IdP |
| altered, signature invalid |
rejected, SignatureError |
| altered, signature removed |
redirect to the altered location |
Suggested fix
Treat a missing signature as a verification failure whenever cert is set, or make it configurable with a secure default, in the spirit of requireSignedRoot. Something along the lines of:
if not self.signed():
raise SignatureError("Metadata is not signed")
Happy to send a pull request if you agree with the direction, including whether it should be unconditional or opt-out.
Summary
When a signing certificate is configured for a metadata source,
parse_and_check_signature()still accepts a document whoseds:Signatureelement has simply been removed. An attacker who can serve or alter the metadata does not have to forge a signature — stripping it is enough, and the document is used without any error, warning or log entry.Where
src/saml2/mdstore.py, currentmaster:The second early return is the problem.
signed()only reports whether aSignatureelement is present in the parsed document, so removing it turns the configured certificate into a no-op. Bothremote(MetaDataExtern) andmdq(MetaDataMDX) go through this method, so both sources are affected.Why it matters
Configuring
certis the documented way to establish the authenticity of downloaded metadata:An operator who follows that advice reasonably expects that metadata which cannot be verified is not used. Today the guarantee holds only for metadata that happens to still carry a signature.
For comparison, OpenSAML's
SignatureValidationFiltertreats this as a distinct condition and rejects it by default:Unsigned metadata is filtered out there with "Root metadata element was unsigned".
Reproduction
Take any signed metadata document, for example the response of a federation MDQ service, and confirm its signature verifies:
Remove the
ds:Signatureelement and serve the result over HTTPS.Configure a service provider against it:
Start an authentication. The endpoints from the unsigned document are used. Changing, say, the
SingleSignOnServicelocation before removing the signature sends the user to that location instead of the real IdP.Observed with the same document in three variants against an otherwise unchanged deployment, all served over HTTPS with a valid certificate:
SignatureErrorSuggested fix
Treat a missing signature as a verification failure whenever
certis set, or make it configurable with a secure default, in the spirit ofrequireSignedRoot. Something along the lines of:Happy to send a pull request if you agree with the direction, including whether it should be unconditional or opt-out.