providers/saml: optionally verify SAML Signature

This commit is contained in:
Jens Langhammer 2020-05-06 18:03:12 +02:00
parent 75bb59a22a
commit fff05e35ac
5 changed files with 63 additions and 1 deletions

View File

@ -25,6 +25,7 @@ class SAMLProviderSerializer(ModelSerializer):
"digest_algorithm", "digest_algorithm",
"signature_algorithm", "signature_algorithm",
"signing_kp", "signing_kp",
"require_signing",
] ]

View File

@ -32,6 +32,7 @@ class SAMLProviderForm(forms.ModelForm):
"session_valid_not_on_or_after", "session_valid_not_on_or_after",
"property_mappings", "property_mappings",
"digest_algorithm", "digest_algorithm",
"require_signing",
"signature_algorithm", "signature_algorithm",
"signing_kp", "signing_kp",
] ]

View File

@ -0,0 +1,40 @@
# Generated by Django 3.0.3 on 2020-05-06 15:51
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_crypto", "0001_initial"),
("passbook_providers_saml", "0008_auto_20200305_1606"),
]
operations = [
migrations.AddField(
model_name="samlprovider",
name="require_signing",
field=models.BooleanField(
default=False,
help_text="Require Requests to be signed by an X509 Certificate. Must match the Certificate selected in `Singing Keypair`.",
),
),
migrations.AlterField(
model_name="samlprovider",
name="issuer",
field=models.TextField(help_text="Also known as EntityID"),
),
migrations.AlterField(
model_name="samlprovider",
name="signing_kp",
field=models.ForeignKey(
default=None,
help_text="Singing is enabled upon selection of a Key Pair.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="passbook_crypto.CertificateKeyPair",
verbose_name="Signing Keypair",
),
),
]

View File

@ -25,7 +25,7 @@ class SAMLProvider(Provider):
acs_url = models.URLField(verbose_name=_("ACS URL")) acs_url = models.URLField(verbose_name=_("ACS URL"))
audience = models.TextField(default="") audience = models.TextField(default="")
issuer = models.TextField() issuer = models.TextField(help_text=_("Also known as EntityID"))
assertion_valid_not_before = models.TextField( assertion_valid_not_before = models.TextField(
default="minutes=-5", default="minutes=-5",
@ -81,6 +81,15 @@ class SAMLProvider(Provider):
null=True, null=True,
help_text=_("Singing is enabled upon selection of a Key Pair."), help_text=_("Singing is enabled upon selection of a Key Pair."),
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
verbose_name=_("Signing Keypair"),
)
require_signing = models.BooleanField(
default=False,
help_text=_(
"Require Requests to be signed by an X509 Certificate. "
"Must match the Certificate selected in `Singing Keypair`."
),
) )
form = "passbook.providers.saml.forms.SAMLProviderForm" form = "passbook.providers.saml.forms.SAMLProviderForm"

View File

@ -1,8 +1,10 @@
"""Basic SAML Processor""" """Basic SAML Processor"""
from typing import TYPE_CHECKING, Dict, List, Union from typing import TYPE_CHECKING, Dict, List, Union
from cryptography.exceptions import InvalidSignature
from defusedxml import ElementTree from defusedxml import ElementTree
from django.http import HttpRequest from django.http import HttpRequest
from signxml import XMLVerifier
from structlog import get_logger from structlog import get_logger
from passbook.core.exceptions import PropertyMappingExpressionException from passbook.core.exceptions import PropertyMappingExpressionException
@ -146,6 +148,15 @@ class Processor:
"""Parses various parameters from _request_xml into _request_params.""" """Parses various parameters from _request_xml into _request_params."""
decoded_xml = decode_base64_and_inflate(self._saml_request) decoded_xml = decode_base64_and_inflate(self._saml_request)
if self._remote.require_signing and self._remote.signing_kp:
self._logger.debug("Verifying Request signature")
try:
XMLVerifier().verify(
decoded_xml, x509_cert=self._remote.signing_kp.certificate_data
)
except InvalidSignature as exc:
raise CannotHandleAssertion("Failed to verify signature") from exc
root = ElementTree.fromstring(decoded_xml) root = ElementTree.fromstring(decoded_xml)
params = {} params = {}