Skip to content

Metadata without a signature is accepted when a signing certificate is configured #1037

Description

@christophdb

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

  1. 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
    
  2. Remove the ds:Signature element and serve the result over HTTPS.

  3. Configure a service provider against it:

    'metadata': {'remote': [{'url': 'https://example.org/entity.xml',
                             'cert': '/path/to/federation-signer.pem'}]},
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions