sources/saml: make signature and digest of SAML Source configurable

This commit is contained in:
Jens Langhammer
2020-11-12 11:56:15 +01:00
parent 9877ef99c4
commit 9deb3ad80f
6 changed files with 218 additions and 25 deletions

View File

@ -19,8 +19,10 @@ class SAMLSourceSerializer(ModelSerializer):
"allow_idp_initiated",
"name_id_policy",
"binding_type",
"temporary_user_delete_after",
"signing_kp",
"digest_algorithm",
"signature_algorithm",
"temporary_user_delete_after",
]

View File

@ -35,8 +35,10 @@ class SAMLSourceForm(forms.ModelForm):
"binding_type",
"name_id_policy",
"allow_idp_initiated",
"temporary_user_delete_after",
"signing_kp",
"digest_algorithm",
"signature_algorithm",
"temporary_user_delete_after",
]
widgets = {
"name": forms.TextInput(),

View File

@ -0,0 +1,51 @@
# Generated by Django 3.1.3 on 2020-11-12 10:55
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_crypto", "0002_create_self_signed_kp"),
("passbook_sources_saml", "0006_samlsource_allow_idp_initiated"),
]
operations = [
migrations.AddField(
model_name="samlsource",
name="digest_algorithm",
field=models.CharField(
choices=[("sha1", "SHA1"), ("sha256", "SHA256")],
default="sha256",
max_length=50,
),
),
migrations.AddField(
model_name="samlsource",
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="samlsource",
name="signing_kp",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Keypair which is used to sign outgoing requests. Leave empty to disable signing.",
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="passbook_crypto.certificatekeypair",
verbose_name="Singing Keypair",
),
),
]

View File

@ -96,11 +96,33 @@ class SAMLSource(Source):
signing_kp = models.ForeignKey(
CertificateKeyPair,
default=None,
blank=True,
null=True,
verbose_name=_("Singing Keypair"),
help_text=_(
"Certificate Key Pair of the IdP which Assertion's Signature is validated against."
"Keypair which is used to sign outgoing requests. Leave empty to disable signing."
),
on_delete=models.PROTECT,
on_delete=models.SET_DEFAULT,
)
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",
)
@property

View File

@ -14,10 +14,14 @@ from passbook.providers.saml.utils.encoding import deflate_and_base64_encode
from passbook.providers.saml.utils.time import get_time_string
from passbook.sources.saml.models import SAMLSource
from passbook.sources.saml.processors.constants import (
DSA_SHA1,
NS_MAP,
NS_SAML_ASSERTION,
NS_SAML_PROTOCOL,
RSA_SHA1,
RSA_SHA256,
RSA_SHA384,
RSA_SHA512,
)
SESSION_REQUEST_ID = "passbook_source_saml_request_id"
@ -77,7 +81,11 @@ class RequestProcessor:
auth_n_request = self.get_auth_n()
if self.source.signing_kp:
signed_request = XMLSigner().sign(
signed_request = XMLSigner(
c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#",
signature_algorithm=self.source.signature_algorithm,
digest_algorithm=self.source.digest_algorithm,
).sign(
auth_n_request,
cert=self.source.signing_kp.certificate_data,
key=self.source.signing_kp.key_data,
@ -103,12 +111,22 @@ class RequestProcessor:
response_dict["RelayState"] = self.relay_state
if self.source.signing_kp:
sig_alg = RSA_SHA256
sign_algorithm_transform_map = {
DSA_SHA1: xmlsec.constants.TransformDsaSha1,
RSA_SHA1: xmlsec.constants.TransformRsaSha1,
RSA_SHA256: xmlsec.constants.TransformRsaSha256,
RSA_SHA384: xmlsec.constants.TransformRsaSha384,
RSA_SHA512: xmlsec.constants.TransformRsaSha512,
}
sign_algorithm_transform = sign_algorithm_transform_map.get(
self.source.signature_algorithm, xmlsec.constants.TransformRsaSha1
)
# Create the full querystring in the correct order to be signed
querystring = f"SAMLRequest={quote_plus(saml_request)}&"
if "RelayState" in response_dict:
querystring += f"RelayState={quote_plus(response_dict['RelayState'])}&"
querystring += f"SigAlg={quote_plus(sig_alg)}"
querystring += f"SigAlg={quote_plus(self.source.signature_algorithm)}"
ctx = xmlsec.SignatureContext()
@ -122,9 +140,9 @@ class RequestProcessor:
ctx.key = key
signature = ctx.sign_binary(
querystring.encode("utf-8"), xmlsec.constants.TransformRsaSha256
querystring.encode("utf-8"), sign_algorithm_transform
)
response_dict["Signature"] = b64encode(signature).decode()
response_dict["SigAlg"] = sig_alg
response_dict["SigAlg"] = self.source.signature_algorithm
return response_dict