providers/saml: add changeable signature and digest algorithm

This commit is contained in:
Jens Langhammer 2020-02-17 16:28:18 +01:00
parent 41689fe3ce
commit a5629c5155
5 changed files with 77 additions and 12 deletions

View File

@ -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",

View File

@ -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),
),
]

View File

@ -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()

View File

@ -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

View File

@ -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", {})