providers/saml: optionally verify SAML Signature
This commit is contained in:
parent
75bb59a22a
commit
fff05e35ac
|
@ -25,6 +25,7 @@ class SAMLProviderSerializer(ModelSerializer):
|
||||||
"digest_algorithm",
|
"digest_algorithm",
|
||||||
"signature_algorithm",
|
"signature_algorithm",
|
||||||
"signing_kp",
|
"signing_kp",
|
||||||
|
"require_signing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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"
|
||||||
|
|
|
@ -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 = {}
|
||||||
|
|
Reference in New Issue