sources/saml: make signature and digest of SAML Source configurable
This commit is contained in:
@ -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",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -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(),
|
||||
|
||||
51
passbook/sources/saml/migrations/0007_auto_20201112_1055.py
Normal file
51
passbook/sources/saml/migrations/0007_auto_20201112_1055.py
Normal 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",
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user