goauthentik / authentik

The authentication glue you need.
https://goauthentik.io
Other
7.8k stars 598 forks source link

Support for EncryptedAssertion #9172

Open nicolas-semaphor opened 2 months ago

nicolas-semaphor commented 2 months ago

Is your feature request related to a problem? Please describe. Some SAML IdP's will deny your SAMLRequest if you do not require the Assertion to be encrypted in the SAMLResponse. I would like to add support for requiring and supporting the EncryptedAssertion element when setting up SAML Federation sources.

Describe the solution you'd like A "simple" solution would be adding the option to use the signing key-pair for encryption and decryption when creating a SAML source. When enabled the metadata should be updated with an encryption Key Descriptor:

    def get_encryption_key_descriptor(self) -> Optional[Element]:
        """Get Encryption KeyDescriptor, if enabled for the source"""
        if self.want_assertions_encrypted:
            key_descriptor = Element(f"{{{NS_SAML_METADATA}}}KeyDescriptor")
            key_descriptor.attrib["use"] = "encryption"
            key_info = SubElement(key_descriptor, f"{{{NS_SIGNATURE}}}KeyInfo")
            x509_data = SubElement(key_info, f"{{{NS_SIGNATURE}}}X509Data")
            x509_certificate = SubElement(x509_data, f"{{{NS_SIGNATURE}}}X509Certificate")
            x509_certificate.text = strip_pem_header(
                self.source.signing_kp.certificate_data.replace("\r", "")
            ).replace("\n", "")
            return key_descriptor
        return None

When the SAMLResponse is received it should then contain an "EncryptedAssertion" element in the EncryptedData section. This needs to be decrypted, one could go about it this way:

    def _decrypt_response(self):
        """Decrypt SAMLResponse EncryptedAssertion Element"""

        manager = xmlsec.KeysManager()
        key = xmlsec.Key.from_memory(
            self._source.signing_kp.key_data,
            xmlsec.constants.KeyDataFormatPem,
        )

        manager.add_key(key)
        encryption_context = xmlsec.EncryptionContext(manager)

        encrypted_assertion = self._root.find(".//{urn:oasis:names:tc:SAML:2.0:assertion}EncryptedAssertion")
        encrypted_data = xmlsec.tree.find_child(encrypted_assertion, "EncryptedData", xmlsec.constants.EncNs) 
        decrypted_assertion = encryption_context.decrypt(encrypted_data)

        index_of = self._root.index(encrypted_assertion)
        self._root.remove(encrypted_assertion)
        self._root.insert(
                index_of,
                decrypted_assertion,
        )

After the EncryptedAssertion element has been decrypted and replaced by the decrypted Assertion element, the flow should be able to continue as normal. I've tested the above to work but it is a "raw" implementation.

Describe alternatives you've considered An alternate solution could be to make use of Authentiks API and maybe use something like OneLogins Ruby-SAML library (which has support for Encrypted Assertions) to federate and create users, but off course I would like to stick with the Python implementation. Maybe what I describe above is related to Outposts, but I'm not entirely certain.

janhalen commented 2 months ago

This is a requirement for our Open Source foundation to adopt Authentik as our IDP middleware for our modern application portfolio.

What can i do to help move this along?

janhalen commented 2 months ago

@BeryJu: To move things along, I will fork the code into a repo under our foundations sandbox org. then @nicolas-semaphor can add the changes we have made and maybe make it easier to understand and test the proposed changes, eventually leading up to a PR.

Heres the fork: https://github.com/OS2sandbox/sandbox-myndighedsidentitet-authentik

Next time @nicolas-semaphor has a timeslot for the project, the code can be merged into the fork..

janhalen commented 2 months ago

First bit of code is comitted to this branch: https://github.com/OS2sandbox/sandbox-myndighedsidentitet-authentik/tree/9172-backend-support-for-encrypted-assertions.

Comments and suggestions are welcome. Its a bit hard to get answers from the maintainers at the moment, but in this was we can openly collaborate on a PR in a seperate fork, before submitting it..

janhalen commented 3 weeks ago

This will contain the backend code, for it to be configurable in the UI, more work should be done outside of this issue.

nicolas-semaphor commented 3 weeks ago

I have submitted a PR: https://github.com/goauthentik/authentik/pull/10099