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