Skip to content

Commit e85e2cc

Browse files
committed
feat: configurable encryption algorithm types
1 parent 3fc608a commit e85e2cc

File tree

6 files changed

+186
-20
lines changed

6 files changed

+186
-20
lines changed

docs/howto/config.rst

+36
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,42 @@ Example::
656656

657657
"verify_encrypt_cert_assertion": verify_encrypt_cert
658658

659+
encrypt_assertion_session_key_algs
660+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
661+
List of block encryption algorithms which can be used to encrypt assertion.
662+
Values order is from highest to lowest priority. Default value is ["http://www.w3.org/2001/04/xmlenc#tripledes-cbc"]
663+
664+
Valid values are:
665+
- "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"
666+
- "http://www.w3.org/2001/04/xmlenc#aes128-cbc"
667+
- "http://www.w3.org/2001/04/xmlenc#aes192-cbc"
668+
- "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
669+
- "http://www.w3.org/2009/xmlenc11#aes128-gcm"
670+
- "http://www.w3.org/2009/xmlenc11#aes192-gcm"
671+
- "http://www.w3.org/2009/xmlenc11#aes256-gcm"
672+
673+
Example::
674+
675+
"encrypt_assertion_session_key_algs" : [
676+
"http://www.w3.org/2009/xmlenc11#aes256-gcm",
677+
"http://www.w3.org/2001/04/xmlenc#tripledes-cbc"
678+
]
679+
680+
encrypt_assertion_cert_key_algs
681+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
682+
List of key transport algorithms which can be used to encrypt session key used to encrypting assertion.
683+
Values order is from highest to lowest priority. Default value is ["http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"]
684+
685+
Valid values are:
686+
- "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
687+
- "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
688+
689+
Example::
690+
691+
"encrypt_assertion_cert_key_algs": [
692+
"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
693+
"http://www.w3.org/2001/04/xmlenc#rsa-1_5"
694+
]
659695

660696
Specific directives
661697
-------------------

src/saml2/config.py

+4
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
"signing_algorithm",
7777
"digest_algorithm",
7878
"http_client_timeout",
79+
"encrypt_assertion_session_key_algs",
80+
"encrypt_assertion_cert_key_algs",
7981
]
8082

8183
SP_ARGS = [
@@ -229,6 +231,8 @@ def __init__(self, homedir="."):
229231
self.signing_algorithm = None
230232
self.digest_algorithm = None
231233
self.http_client_timeout = None
234+
self.encrypt_assertion_session_key_algs = ["http://www.w3.org/2001/04/xmlenc#tripledes-cbc"]
235+
self.encrypt_assertion_cert_key_algs = ["http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"]
232236

233237
def setattr(self, context, attr, val):
234238
if context == "":

src/saml2/entity.py

+93-9
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
from saml2.samlp import SessionIndex
6161
from saml2.samlp import artifact_resolve_from_string
6262
from saml2.samlp import response_from_string
63-
from saml2.sigver import SignatureError
63+
from saml2.sigver import SignatureError, XMLSEC_SESSION_KEY_URI_TO_ALG
6464
from saml2.sigver import SigverError
6565
from saml2.sigver import get_pem_wrapped_unwrapped
6666
from saml2.sigver import make_temp
@@ -78,7 +78,6 @@
7878
from saml2.xmldsig import SIG_ALLOWED_ALG
7979
from saml2.xmldsig import DefaultSignature
8080

81-
8281
logger = logging.getLogger(__name__)
8382

8483
__author__ = "rolandh"
@@ -181,6 +180,9 @@ def __init__(self, entity_type, config=None, config_file="", virtual_organizatio
181180

182181
self.sec = security_context(self.config)
183182

183+
self.encrypt_assertion_session_key_algs = self.config.encrypt_assertion_session_key_algs
184+
self.encrypt_assertion_cert_key_algs = self.config.encrypt_assertion_cert_key_algs
185+
184186
if virtual_organization:
185187
if isinstance(virtual_organization, str):
186188
self.vorg = self.config.vorg[virtual_organization]
@@ -644,34 +646,97 @@ def has_encrypt_cert_in_metadata(self, sp_entity_id):
644646
return True
645647
return False
646648

647-
def _encrypt_assertion(self, encrypt_cert, sp_entity_id, response, node_xpath=None):
649+
def _encrypt_assertion(
650+
self,
651+
encrypt_cert,
652+
sp_entity_id,
653+
response,
654+
node_xpath=None,
655+
encrypt_cert_session_key_alg=None,
656+
encrypt_cert_cert_key_alg=None,
657+
):
648658
"""Encryption of assertions.
649659
650660
:param encrypt_cert: Certificate to be used for encryption.
651661
:param sp_entity_id: Entity ID for the calling service provider.
652662
:param response: A samlp.Response
663+
:param encrypt_cert_cert_key_alg: algorithm used for encrypting session key
664+
:param encrypt_cert_session_key_alg: algorithm used for encrypting assertion
665+
:param encrypt_cert_cert_key_alg:
653666
:param node_xpath: Unquie path to the element to be encrypted.
654667
:return: A new samlp.Resonse with the designated assertion encrypted.
655668
"""
656669
_certs = []
657670

658671
if encrypt_cert:
659-
_certs.append((None, encrypt_cert))
672+
_certs.append((None, encrypt_cert, None, None))
660673
elif sp_entity_id is not None:
661-
_certs = self.metadata.certs(sp_entity_id, "any", "encryption")
674+
_certs = self.metadata.certs(sp_entity_id, "any", "encryption", get_with_usage_and_encryption_methods=True)
662675
exception = None
663-
for _cert_name, _cert in _certs:
676+
677+
# take certs with encryption and encryption_methods first (priority 1)
678+
sorted_certs = []
679+
for _unpacked_cert in _certs:
680+
_cert_name, _cert, _cert_use, _cert_encryption_methods = _unpacked_cert
681+
if _cert_use == "encryption" and _cert_encryption_methods:
682+
sorted_certs.append(_unpacked_cert)
683+
684+
# take certs with encryption or encryption_methods (priority 2)
685+
for _unpacked_cert in _certs:
686+
_cert_name, _cert, _cert_use, _cert_encryption_methods = _unpacked_cert
687+
if _cert_use == "encryption" and _unpacked_cert not in sorted_certs:
688+
sorted_certs.append(_unpacked_cert)
689+
690+
for _unpacked_cert in _certs:
691+
if _unpacked_cert not in sorted_certs:
692+
sorted_certs.append(_unpacked_cert)
693+
694+
for _cert_name, _cert, _cert_use, _cert_encryption_methods in sorted_certs:
664695
wrapped_cert, unwrapped_cert = get_pem_wrapped_unwrapped(_cert)
665696
try:
666697
tmp = make_temp(
667698
wrapped_cert.encode("ascii"),
668699
decode=False,
669700
delete_tmpfiles=self.config.delete_tmpfiles,
670701
)
702+
703+
msg_enc = (
704+
encrypt_cert_session_key_alg
705+
if encrypt_cert_session_key_alg
706+
else self.encrypt_assertion_session_key_algs[0]
707+
)
708+
key_enc = (
709+
encrypt_cert_cert_key_alg if encrypt_cert_cert_key_alg else self.encrypt_assertion_cert_key_algs[0]
710+
)
711+
712+
if encrypt_cert != _cert and _cert_encryption_methods:
713+
viable_session_key_algs = []
714+
for alg in self.encrypt_assertion_session_key_algs:
715+
for cert_method in _cert_encryption_methods:
716+
if cert_method.get("algorithm") == alg:
717+
viable_session_key_algs.append(alg)
718+
719+
viable_cert_algs = []
720+
for alg in self.encrypt_assertion_cert_key_algs:
721+
for cert_method in _cert_encryption_methods:
722+
if cert_method.get("algorithm") == alg:
723+
viable_cert_algs.append(alg)
724+
725+
if viable_session_key_algs:
726+
msg_enc = viable_session_key_algs[0]
727+
728+
if viable_cert_algs:
729+
key_enc = viable_cert_algs[0]
730+
731+
key_type = XMLSEC_SESSION_KEY_URI_TO_ALG.get(msg_enc)
732+
671733
response = self.sec.encrypt_assertion(
672734
response,
673735
tmp.name,
674-
pre_encryption_part(key_name=_cert_name, encrypt_cert=unwrapped_cert),
736+
pre_encryption_part(
737+
key_name=_cert_name, encrypt_cert=unwrapped_cert, msg_enc=msg_enc, key_enc=key_enc
738+
),
739+
key_type=key_type,
675740
node_xpath=node_xpath,
676741
)
677742
return response
@@ -697,7 +762,11 @@ def _response(
697762
encrypt_assertion_self_contained=False,
698763
encrypted_advice_attributes=False,
699764
encrypt_cert_advice=None,
765+
encrypt_cert_advice_cert_key_alg=None,
766+
encrypt_cert_advice_session_key_alg=None,
700767
encrypt_cert_assertion=None,
768+
encrypt_cert_assertion_cert_key_alg=None,
769+
encrypt_cert_assertion_session_key_alg=None,
701770
sign_assertion=None,
702771
pefim=False,
703772
sign_alg=None,
@@ -731,8 +800,16 @@ def _response(
731800
element should be encrypted.
732801
:param encrypt_cert_advice: Certificate to be used for encryption of
733802
assertions in the advice element.
803+
:param encrypt_cert_advice_cert_key_alg: algorithm used for encrypting session key
804+
by encrypt_cert_advice
805+
:param encrypt_cert_advice_session_key_alg: algorithm used for encrypting assertion
806+
when using encrypt_cert_advice
734807
:param encrypt_cert_assertion: Certificate to be used for encryption
735808
of assertions.
809+
:param encrypt_cert_assertion_cert_key_alg: algorithm used for encrypting session key
810+
by encrypt_cert_assertion
811+
:param encrypt_cert_assertion_session_key_alg: algorithm used for encrypting assertion when
812+
using encrypt_cert_assertion
736813
:param sign_assertion: True if assertions should be signed.
737814
:param pefim: True if a response according to the PEFIM profile
738815
should be created.
@@ -856,6 +933,8 @@ def _response(
856933
sp_entity_id,
857934
response,
858935
node_xpath=node_xpath,
936+
encrypt_cert_session_key_alg=encrypt_cert_advice_session_key_alg,
937+
encrypt_cert_cert_key_alg=encrypt_cert_advice_cert_key_alg,
859938
)
860939
response = response_from_string(response)
861940

@@ -900,7 +979,13 @@ def _response(
900979
response = signed_instance_factory(response, self.sec, to_sign_assertion)
901980

902981
# XXX encrypt assertion
903-
response = self._encrypt_assertion(encrypt_cert_assertion, sp_entity_id, response)
982+
response = self._encrypt_assertion(
983+
encrypt_cert_assertion,
984+
sp_entity_id,
985+
response,
986+
encrypt_cert_session_key_alg=encrypt_cert_assertion_session_key_alg,
987+
encrypt_cert_cert_key_alg=encrypt_cert_assertion_cert_key_alg,
988+
)
904989
else:
905990
# XXX sign other parts! (defiend by to_sign)
906991
if to_sign:
@@ -1357,7 +1442,6 @@ def create_manage_name_id_response(
13571442
digest_alg=None,
13581443
**kwargs,
13591444
):
1360-
13611445
rinfo = self.response_args(request, bindings)
13621446

13631447
response = self._status_response(

src/saml2/mdstore.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ def __eq__(self, other):
476476

477477
return True
478478

479-
def certs(self, entity_id, descriptor, use="signing"):
479+
def certs(self, entity_id, descriptor, use="signing", get_with_usage_and_encryption_methods=False):
480480
"""
481481
Returns certificates for the given Entity
482482
"""
@@ -494,7 +494,10 @@ def extract_certs(srvs):
494494
for dat in key_info["x509_data"]:
495495
cert = repack_cert(dat["x509_certificate"]["text"])
496496
if cert not in res:
497-
res.append((key_name_txt, cert))
497+
if get_with_usage_and_encryption_methods:
498+
res.append((key_name_txt, cert, key_use, key.get("encryption_method")))
499+
else:
500+
res.append((key_name_txt, cert))
498501

499502
return res
500503

@@ -1327,7 +1330,7 @@ def subject_id_requirement(self, entity_id):
13271330
"name_format": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
13281331
"friendly_name": "subject-id",
13291332
"is_required": "true",
1330-
}
1333+
},
13311334
]
13321335
elif subject_id_req == "pairwise-id":
13331336
return [

src/saml2/server.py

+32-2
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,11 @@ def _authn_response(
457457
best_effort=False,
458458
encrypt_assertion=False,
459459
encrypt_cert_advice=None,
460+
encrypt_cert_advice_cert_key_alg=None,
461+
encrypt_cert_advice_session_key_alg=None,
460462
encrypt_cert_assertion=None,
463+
encrypt_cert_assertion_cert_key_alg=None,
464+
encrypt_cert_assertion_session_key_alg=None,
461465
authn_statement=None,
462466
encrypt_assertion_self_contained=False,
463467
encrypted_advice_attributes=False,
@@ -492,8 +496,16 @@ def _authn_response(
492496
element should be encrypted.
493497
:param encrypt_cert_advice: Certificate to be used for encryption of
494498
assertions in the advice element.
499+
:param encrypt_cert_advice_cert_key_alg: algorithm used for encrypting session key
500+
by encrypt_cert_advice
501+
:param encrypt_cert_advice_session_key_alg: algorithm used for encrypting assertion
502+
when using encrypt_cert_advice
495503
:param encrypt_cert_assertion: Certificate to be used for encryption
496504
of assertions.
505+
:param encrypt_cert_assertion_cert_key_alg: algorithm used for encrypting session key
506+
by encrypt_cert_assertion
507+
:param encrypt_cert_assertion_session_key_alg: algorithm used for encrypting assertion when
508+
using encrypt_cert_assertion
497509
:param authn_statement: Authentication statement.
498510
:param pefim: True if a response according to the PEFIM profile
499511
should be created.
@@ -598,7 +610,11 @@ def _authn_response(
598610
sp_entity_id=sp_entity_id,
599611
encrypt_assertion=encrypt_assertion,
600612
encrypt_cert_advice=encrypt_cert_advice,
613+
encrypt_cert_advice_cert_key_alg=encrypt_cert_advice_cert_key_alg,
614+
encrypt_cert_advice_session_key_alg=encrypt_cert_advice_session_key_alg,
601615
encrypt_cert_assertion=encrypt_cert_assertion,
616+
encrypt_cert_assertion_cert_key_alg=encrypt_cert_assertion_cert_key_alg,
617+
encrypt_cert_assertion_session_key_alg=encrypt_cert_assertion_session_key_alg,
602618
encrypt_assertion_self_contained=encrypt_assertion_self_contained,
603619
encrypted_advice_attributes=encrypted_advice_attributes,
604620
sign_assertion=sign_assertion,
@@ -724,7 +740,6 @@ def gather_authn_response_args(self, sp_entity_id, name_id_policy, userid, **kwa
724740
("encrypted_advice_attributes", "verify_encrypt_cert_advice", "encrypt_cert_advice", kwargs["pefim"]),
725741
("encrypt_assertion", "verify_encrypt_cert_assertion", "encrypt_cert_assertion", False),
726742
]:
727-
728743
if args[arg] or pefim:
729744
_enc_cert = self.config.getattr(attr, "idp")
730745

@@ -789,7 +804,11 @@ def create_authn_response(
789804
sign_response=None,
790805
sign_assertion=None,
791806
encrypt_cert_advice=None,
807+
encrypt_cert_advice_cert_key_alg=None,
808+
encrypt_cert_advice_session_key_alg=None,
792809
encrypt_cert_assertion=None,
810+
encrypt_cert_assertion_cert_key_alg=None,
811+
encrypt_cert_assertion_session_key_alg=None,
793812
encrypt_assertion=None,
794813
encrypt_assertion_self_contained=True,
795814
encrypted_advice_attributes=False,
@@ -822,8 +841,16 @@ def create_authn_response(
822841
element should be encrypted.
823842
:param encrypt_cert_advice: Certificate to be used for encryption of
824843
assertions in the advice element.
844+
:param encrypt_cert_advice_cert_key_alg: algorithm used for encrypting session key
845+
by encrypt_cert_advice
846+
:param encrypt_cert_advice_session_key_alg: algorithm used for encrypting assertion
847+
when using encrypt_cert_advice
825848
:param encrypt_cert_assertion: Certificate to be used for encryption
826849
of assertions.
850+
:param encrypt_cert_assertion_cert_key_alg: algorithm used for encrypting session key
851+
by encrypt_cert_assertion
852+
:param encrypt_cert_assertion_session_key_alg: algorithm used for encrypting assertion when
853+
using encrypt_cert_assertion
827854
:param pefim: True if a response according to the PEFIM profile
828855
should be created.
829856
:return: A response instance
@@ -869,6 +896,10 @@ def create_authn_response(
869896
sign_alg=sign_alg,
870897
digest_alg=digest_alg,
871898
session_not_on_or_after=session_not_on_or_after,
899+
encrypt_cert_advice_cert_key_alg=encrypt_cert_advice_cert_key_alg,
900+
encrypt_cert_advice_session_key_alg=encrypt_cert_advice_session_key_alg,
901+
encrypt_cert_assertion_cert_key_alg=encrypt_cert_assertion_cert_key_alg,
902+
encrypt_cert_assertion_session_key_alg=encrypt_cert_assertion_session_key_alg,
872903
**args,
873904
)
874905
except MissingValue as exc:
@@ -1054,7 +1085,6 @@ def create_ecp_authn_request_response(
10541085
digest_alg=None,
10551086
**kwargs,
10561087
):
1057-
10581088
# ----------------------------------------
10591089
# <ecp:Response
10601090
# ----------------------------------------

0 commit comments

Comments
 (0)