Skip to content

Commit d4c74d5

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

File tree

8 files changed

+252
-40
lines changed

8 files changed

+252
-40
lines changed

docs/howto/config.rst

+54
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,60 @@ 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+
- "http://www.w3.org/2009/xmlenc11#rsa-oaep" (xmlsec1 version>=1.3.0 is required)
689+
690+
Example::
691+
692+
"encrypt_assertion_cert_key_algs": [
693+
"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
694+
"http://www.w3.org/2001/04/xmlenc#rsa-1_5"
695+
]
696+
697+
default_rsa_oaep_mgf_alg
698+
^^^^^^^^^^^^^^^^^^^^^^^^
699+
If encryption key from metadata has no encryption method specified or does not have one matching configuration and
700+
"http://www.w3.org/2009/xmlenc11#rsa-oaep" is selected, it will be used with mask generation function specified by
701+
this configuration option. Default value is None
702+
703+
Valid values are:
704+
- "http://www.w3.org/2009/xmlenc11#mgf1sha1"
705+
- "http://www.w3.org/2009/xmlenc11#mgf1sha224"
706+
- "http://www.w3.org/2009/xmlenc11#mgf1sha256"
707+
- "http://www.w3.org/2009/xmlenc11#mgf1sha384"
708+
- "http://www.w3.org/2009/xmlenc11#mgf1sha512"
709+
710+
Example::
711+
712+
"default_rsa_oaep_mgf_alg": "http://www.w3.org/2009/xmlenc11#mgf1sha1"
659713

660714
Specific directives
661715
-------------------

src/saml2/config.py

+6
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@
7676
"signing_algorithm",
7777
"digest_algorithm",
7878
"http_client_timeout",
79+
"encrypt_assertion_session_key_algs",
80+
"encrypt_assertion_cert_key_algs",
81+
"default_rsa_oaep_mgf_alg",
7982
]
8083

8184
SP_ARGS = [
@@ -229,6 +232,9 @@ def __init__(self, homedir="."):
229232
self.signing_algorithm = None
230233
self.digest_algorithm = None
231234
self.http_client_timeout = None
235+
self.encrypt_assertion_session_key_algs = ["http://www.w3.org/2001/04/xmlenc#tripledes-cbc"]
236+
self.encrypt_assertion_cert_key_algs = ["http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"]
237+
self.default_rsa_oaep_mgf_alg = None
232238

233239
def setattr(self, context, attr, val):
234240
if context == "":

src/saml2/entity.py

+102-10
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, RSA_OAEP
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,10 @@ 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+
self.default_rsa_oaep_mgf_alg = self.config.default_rsa_oaep_mgf_alg
186+
184187
if virtual_organization:
185188
if isinstance(virtual_organization, str):
186189
self.vorg = self.config.vorg[virtual_organization]
@@ -194,7 +197,6 @@ def __init__(self, entity_type, config=None, config_file="", virtual_organizatio
194197
self.sourceid = self.metadata.construct_source_id()
195198
else:
196199
self.sourceid = {}
197-
198200
self.msg_cb = msg_cb
199201

200202
def reload_metadata(self, metadata_conf):
@@ -644,34 +646,105 @@ 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 _get_first_matching_alg(self, priority_list, metadata_list):
650+
for alg in priority_list:
651+
for cert_method in metadata_list:
652+
if cert_method.get("algorithm") == alg:
653+
return cert_method
654+
return None
655+
656+
def _encrypt_assertion(
657+
self,
658+
encrypt_cert,
659+
sp_entity_id,
660+
response,
661+
node_xpath=None,
662+
encrypt_cert_session_key_alg=None,
663+
encrypt_cert_cert_key_alg=None,
664+
):
648665
"""Encryption of assertions.
649666
650667
:param encrypt_cert: Certificate to be used for encryption.
651668
:param sp_entity_id: Entity ID for the calling service provider.
652669
:param response: A samlp.Response
670+
:param encrypt_cert_cert_key_alg: algorithm used for encrypting session key
671+
:param encrypt_cert_session_key_alg: algorithm used for encrypting assertion
672+
:param encrypt_cert_cert_key_alg:
653673
:param node_xpath: Unquie path to the element to be encrypted.
654674
:return: A new samlp.Resonse with the designated assertion encrypted.
655675
"""
656676
_certs = []
657677

658678
if encrypt_cert:
659-
_certs.append((None, encrypt_cert))
679+
_certs.append((None, encrypt_cert, None, None))
660680
elif sp_entity_id is not None:
661-
_certs = self.metadata.certs(sp_entity_id, "any", "encryption")
681+
_certs = self.metadata.certs(sp_entity_id, "any", "encryption", get_with_usage_and_encryption_methods=True)
662682
exception = None
663-
for _cert_name, _cert in _certs:
683+
684+
# take certs with encryption and encryption_methods first (priority 1)
685+
sorted_certs = []
686+
for _unpacked_cert in _certs:
687+
_cert_name, _cert, _cert_use, _cert_encryption_methods = _unpacked_cert
688+
if _cert_use == "encryption" and _cert_encryption_methods:
689+
sorted_certs.append(_unpacked_cert)
690+
691+
# take certs with encryption or encryption_methods (priority 2)
692+
for _unpacked_cert in _certs:
693+
_cert_name, _cert, _cert_use, _cert_encryption_methods = _unpacked_cert
694+
if _cert_use == "encryption" and _unpacked_cert not in sorted_certs:
695+
sorted_certs.append(_unpacked_cert)
696+
697+
for _unpacked_cert in _certs:
698+
if _unpacked_cert not in sorted_certs:
699+
sorted_certs.append(_unpacked_cert)
700+
701+
for _cert_name, _cert, _cert_use, _cert_encryption_methods in sorted_certs:
664702
wrapped_cert, unwrapped_cert = get_pem_wrapped_unwrapped(_cert)
665703
try:
666704
tmp = make_temp(
667705
wrapped_cert.encode("ascii"),
668706
decode=False,
669707
delete_tmpfiles=self.config.delete_tmpfiles,
670708
)
709+
710+
msg_enc = (
711+
encrypt_cert_session_key_alg
712+
if encrypt_cert_session_key_alg
713+
else self.encrypt_assertion_session_key_algs[0]
714+
)
715+
key_enc = (
716+
encrypt_cert_cert_key_alg if encrypt_cert_cert_key_alg else self.encrypt_assertion_cert_key_algs[0]
717+
)
718+
719+
rsa_oaep_mgf_alg = self.default_rsa_oaep_mgf_alg if key_enc == RSA_OAEP else None
720+
if encrypt_cert != _cert and _cert_encryption_methods:
721+
viable_session_key_alg = self._get_first_matching_alg(
722+
self.encrypt_assertion_session_key_algs, _cert_encryption_methods
723+
)
724+
if viable_session_key_alg:
725+
msg_enc = viable_session_key_alg.get("algorithm")
726+
727+
viable_cert_alg = self._get_first_matching_alg(
728+
self.encrypt_assertion_cert_key_algs, _cert_encryption_methods
729+
)
730+
if viable_cert_alg:
731+
key_enc = viable_cert_alg.get("algorithm")
732+
mgf = viable_cert_alg.get("mgf")
733+
rsa_oaep_mgf_alg = mgf.get("algorithm") if mgf else None
734+
735+
key_type = XMLSEC_SESSION_KEY_URI_TO_ALG.get(msg_enc)
736+
671737
response = self.sec.encrypt_assertion(
672738
response,
673739
tmp.name,
674-
pre_encryption_part(key_name=_cert_name, encrypt_cert=unwrapped_cert),
740+
pre_encryption_part(
741+
key_name=_cert_name,
742+
encrypt_cert=unwrapped_cert,
743+
msg_enc=msg_enc,
744+
key_enc=key_enc,
745+
rsa_oaep_mgf_alg=rsa_oaep_mgf_alg,
746+
),
747+
key_type=key_type,
675748
node_xpath=node_xpath,
676749
)
677750
return response
@@ -697,7 +770,11 @@ def _response(
697770
encrypt_assertion_self_contained=False,
698771
encrypted_advice_attributes=False,
699772
encrypt_cert_advice=None,
773+
encrypt_cert_advice_cert_key_alg=None,
774+
encrypt_cert_advice_session_key_alg=None,
700775
encrypt_cert_assertion=None,
776+
encrypt_cert_assertion_cert_key_alg=None,
777+
encrypt_cert_assertion_session_key_alg=None,
701778
sign_assertion=None,
702779
pefim=False,
703780
sign_alg=None,
@@ -731,8 +808,16 @@ def _response(
731808
element should be encrypted.
732809
:param encrypt_cert_advice: Certificate to be used for encryption of
733810
assertions in the advice element.
811+
:param encrypt_cert_advice_cert_key_alg: algorithm used for encrypting session key
812+
by encrypt_cert_advice
813+
:param encrypt_cert_advice_session_key_alg: algorithm used for encrypting assertion
814+
when using encrypt_cert_advice
734815
:param encrypt_cert_assertion: Certificate to be used for encryption
735816
of assertions.
817+
:param encrypt_cert_assertion_cert_key_alg: algorithm used for encrypting session key
818+
by encrypt_cert_assertion
819+
:param encrypt_cert_assertion_session_key_alg: algorithm used for encrypting assertion when
820+
using encrypt_cert_assertion
736821
:param sign_assertion: True if assertions should be signed.
737822
:param pefim: True if a response according to the PEFIM profile
738823
should be created.
@@ -856,6 +941,8 @@ def _response(
856941
sp_entity_id,
857942
response,
858943
node_xpath=node_xpath,
944+
encrypt_cert_session_key_alg=encrypt_cert_advice_session_key_alg,
945+
encrypt_cert_cert_key_alg=encrypt_cert_advice_cert_key_alg,
859946
)
860947
response = response_from_string(response)
861948

@@ -900,7 +987,13 @@ def _response(
900987
response = signed_instance_factory(response, self.sec, to_sign_assertion)
901988

902989
# XXX encrypt assertion
903-
response = self._encrypt_assertion(encrypt_cert_assertion, sp_entity_id, response)
990+
response = self._encrypt_assertion(
991+
encrypt_cert_assertion,
992+
sp_entity_id,
993+
response,
994+
encrypt_cert_session_key_alg=encrypt_cert_assertion_session_key_alg,
995+
encrypt_cert_cert_key_alg=encrypt_cert_assertion_cert_key_alg,
996+
)
904997
else:
905998
# XXX sign other parts! (defiend by to_sign)
906999
if to_sign:
@@ -1357,7 +1450,6 @@ def create_manage_name_id_response(
13571450
digest_alg=None,
13581451
**kwargs,
13591452
):
1360-
13611453
rinfo = self.response_args(request, bindings)
13621454

13631455
response = self._status_response(

src/saml2/md.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from saml2 import xmldsig as ds
1111
from saml2 import xmlenc as xenc
1212

13-
1413
NAMESPACE = "urn:oasis:names:tc:SAML:2.0:metadata"
1514

1615

@@ -803,13 +802,15 @@ def __init__(
803802
text=None,
804803
extension_elements=None,
805804
extension_attributes=None,
805+
mgf=None,
806806
):
807807
SamlBase.__init__(
808808
self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes
809809
)
810810
self.key_info = key_info
811811
self.encryption_method = encryption_method or []
812812
self.use = use
813+
self.mgf = mgf
813814

814815

815816
def key_descriptor_type__from_string(xml_string):

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 [

0 commit comments

Comments
 (0)