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", |             "assertion_valid_not_on_or_after", | ||||||
|             "session_valid_not_on_or_after", |             "session_valid_not_on_or_after", | ||||||
|             "property_mappings", |             "property_mappings", | ||||||
|  |             "digest_algorithm", | ||||||
|  |             "signature_algorithm", | ||||||
|             "signing", |             "signing", | ||||||
|             "signing_cert", |             "signing_cert", | ||||||
|             "signing_key", |             "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 = models.BooleanField(default=True) | ||||||
|     signing_cert = models.TextField(verbose_name=_("Singing Certificate")) |     signing_cert = models.TextField(verbose_name=_("Singing Certificate")) | ||||||
|     signing_key = models.TextField() |     signing_key = models.TextField() | ||||||
|  | |||||||
| @ -88,10 +88,5 @@ def get_response_xml(parameters, saml_provider: SAMLProvider, assertion_id=""): | |||||||
|     signature_xml = get_signature_xml() |     signature_xml = get_signature_xml() | ||||||
|     params["RESPONSE_SIGNATURE"] = signature_xml |     params["RESPONSE_SIGNATURE"] = signature_xml | ||||||
|  |  | ||||||
|     signed = sign_with_signxml( |     signed = sign_with_signxml(raw_response, saml_provider, reference_uri=assertion_id,) | ||||||
|         saml_provider.signing_key, |  | ||||||
|         raw_response, |  | ||||||
|         saml_provider.signing_cert, |  | ||||||
|         reference_uri=assertion_id, |  | ||||||
|     ) |  | ||||||
|     return signed |     return signed | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| """Signing code goes here.""" | """Signing code goes here.""" | ||||||
|  | from typing import TYPE_CHECKING | ||||||
|  |  | ||||||
| from cryptography.hazmat.backends import default_backend | from cryptography.hazmat.backends import default_backend | ||||||
| from cryptography.hazmat.primitives import serialization | from cryptography.hazmat.primitives import serialization | ||||||
| from lxml import etree  # nosec | from lxml import etree  # nosec | ||||||
| @ -7,25 +9,34 @@ from structlog import get_logger | |||||||
|  |  | ||||||
| from passbook.lib.utils.template import render_to_string | from passbook.lib.utils.template import render_to_string | ||||||
|  |  | ||||||
|  | if TYPE_CHECKING: | ||||||
|  |     from passbook.providers.saml.models import SAMLProvider | ||||||
|  |  | ||||||
| LOGGER = get_logger() | 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""" |     """Sign Data with signxml""" | ||||||
|     key = serialization.load_pem_private_key( |     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, |         password=None, | ||||||
|         backend=default_backend(), |         backend=default_backend(), | ||||||
|     ) |     ) | ||||||
|     # defused XML is not used here because it messes up XML namespaces |     # defused XML is not used here because it messes up XML namespaces | ||||||
|     # Data is trusted, so lxml is ok |     # Data is trusted, so lxml is ok | ||||||
|     root = etree.fromstring(data)  # nosec |     root = etree.fromstring(data)  # nosec | ||||||
|     signer = XMLSigner(c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#") |     signer = XMLSigner( | ||||||
|     signed = signer.sign(root, key=key, cert=[cert], reference_uri=reference_uri) |         c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#", | ||||||
|     XMLVerifier().verify(signed, x509_cert=cert) |         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 |     return etree.tostring(signed).decode("utf-8")  # nosec | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_signature_xml(): | def get_signature_xml() -> str: | ||||||
|     """Returns XML Signature for subject.""" |     """Returns XML Signature for subject.""" | ||||||
|     return render_to_string("saml/xml/signature.xml", {}) |     return render_to_string("saml/xml/signature.xml", {}) | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer