providers/saml: add changeable signature and digest algorithm
This commit is contained in:
		| @ -40,6 +40,8 @@ class SAMLProviderForm(forms.ModelForm): | ||||
|             "assertion_valid_not_on_or_after", | ||||
|             "session_valid_not_on_or_after", | ||||
|             "property_mappings", | ||||
|             "digest_algorithm", | ||||
|             "signature_algorithm", | ||||
|             "signing", | ||||
|             "signing_cert", | ||||
|             "signing_key", | ||||
|  | ||||
| @ -0,0 +1,41 @@ | ||||
| # Generated by Django 3.0.3 on 2020-02-17 15:26 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("passbook_providers_saml", "0003_auto_20200216_1109"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="samlprovider", | ||||
|             name="digest_algorithm", | ||||
|             field=models.CharField( | ||||
|                 choices=[("sha1", "SHA1"), ("sha256", "SHA256")], | ||||
|                 default="sha256", | ||||
|                 max_length=50, | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="samlprovider", | ||||
|             name="signature_algorithm", | ||||
|             field=models.CharField( | ||||
|                 choices=[ | ||||
|                     ("rsa-sha1", "RSA-SHA1"), | ||||
|                     ("rsa-sha256", "RSA-SHA256"), | ||||
|                     ("ecdsa-sha256", "ECDSA-SHA256"), | ||||
|                     ("dsa-sha1", "DSA-SHA1"), | ||||
|                 ], | ||||
|                 default="rsa-sha256", | ||||
|                 max_length=50, | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name="samlprovider", | ||||
|             name="processor_path", | ||||
|             field=models.CharField(choices=[], max_length=255), | ||||
|         ), | ||||
|     ] | ||||
| @ -55,6 +55,22 @@ class SAMLProvider(Provider): | ||||
|         ), | ||||
|     ) | ||||
|  | ||||
|     digest_algorithm = models.CharField( | ||||
|         max_length=50, | ||||
|         choices=(("sha1", _("SHA1")), ("sha256", _("SHA256")),), | ||||
|         default="sha256", | ||||
|     ) | ||||
|     signature_algorithm = models.CharField( | ||||
|         max_length=50, | ||||
|         choices=( | ||||
|             ("rsa-sha1", _("RSA-SHA1")), | ||||
|             ("rsa-sha256", _("RSA-SHA256")), | ||||
|             ("ecdsa-sha256", _("ECDSA-SHA256")), | ||||
|             ("dsa-sha1", _("DSA-SHA1")), | ||||
|         ), | ||||
|         default="rsa-sha256", | ||||
|     ) | ||||
|  | ||||
|     signing = models.BooleanField(default=True) | ||||
|     signing_cert = models.TextField(verbose_name=_("Singing Certificate")) | ||||
|     signing_key = models.TextField() | ||||
|  | ||||
| @ -88,10 +88,5 @@ def get_response_xml(parameters, saml_provider: SAMLProvider, assertion_id=""): | ||||
|     signature_xml = get_signature_xml() | ||||
|     params["RESPONSE_SIGNATURE"] = signature_xml | ||||
|  | ||||
|     signed = sign_with_signxml( | ||||
|         saml_provider.signing_key, | ||||
|         raw_response, | ||||
|         saml_provider.signing_cert, | ||||
|         reference_uri=assertion_id, | ||||
|     ) | ||||
|     signed = sign_with_signxml(raw_response, saml_provider, reference_uri=assertion_id,) | ||||
|     return signed | ||||
|  | ||||
| @ -1,4 +1,6 @@ | ||||
| """Signing code goes here.""" | ||||
| from typing import TYPE_CHECKING | ||||
|  | ||||
| from cryptography.hazmat.backends import default_backend | ||||
| from cryptography.hazmat.primitives import serialization | ||||
| from lxml import etree  # nosec | ||||
| @ -7,25 +9,34 @@ from structlog import get_logger | ||||
|  | ||||
| from passbook.lib.utils.template import render_to_string | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from passbook.providers.saml.models import SAMLProvider | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| def sign_with_signxml(private_key, data, cert, reference_uri=None): | ||||
| def sign_with_signxml(data: str, provider: "SAMLProvider", reference_uri=None) -> str: | ||||
|     """Sign Data with signxml""" | ||||
|     key = serialization.load_pem_private_key( | ||||
|         str.encode("\n".join([x.strip() for x in private_key.split("\n")])), | ||||
|         str.encode("\n".join([x.strip() for x in provider.signing_key.split("\n")])), | ||||
|         password=None, | ||||
|         backend=default_backend(), | ||||
|     ) | ||||
|     # defused XML is not used here because it messes up XML namespaces | ||||
|     # Data is trusted, so lxml is ok | ||||
|     root = etree.fromstring(data)  # nosec | ||||
|     signer = XMLSigner(c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#") | ||||
|     signed = signer.sign(root, key=key, cert=[cert], reference_uri=reference_uri) | ||||
|     XMLVerifier().verify(signed, x509_cert=cert) | ||||
|     signer = XMLSigner( | ||||
|         c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#", | ||||
|         signature_algorithm=provider.signature_algorithm, | ||||
|         digest_algorithm=provider.digest_algorithm, | ||||
|     ) | ||||
|     signed = signer.sign( | ||||
|         root, key=key, cert=[provider.signing_cert], reference_uri=reference_uri | ||||
|     ) | ||||
|     XMLVerifier().verify(signed, x509_cert=provider.signing_cert) | ||||
|     return etree.tostring(signed).decode("utf-8")  # nosec | ||||
|  | ||||
|  | ||||
| def get_signature_xml(): | ||||
| def get_signature_xml() -> str: | ||||
|     """Returns XML Signature for subject.""" | ||||
|     return render_to_string("saml/xml/signature.xml", {}) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer