Skip to content

Commit 3e6b4c9

Browse files
author
Roland Hedberg
committed
Merge pull request #222 from HaToHo/master
XML Encryption
2 parents 52028d3 + e85fffa commit 3e6b4c9

20 files changed

+1782
-395
lines changed

example/idp2/idp.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,9 +335,7 @@ def do(self, query, binding_in, relay_state="", encrypt_cert=None,
335335

336336
_resp = IDP.create_authn_response(
337337
identity, userid=self.user,
338-
encrypt_cert=encrypt_cert,
339-
encrypt_assertion_self_contained=True,
340-
encrypted_advice_attributes=True,
338+
encrypt_cert_assertion=encrypt_cert,
341339
**resp_args)
342340
except Exception as excp:
343341
logging.error(exception_trace(excp))
@@ -518,7 +516,7 @@ def do_authentication(environ, start_response, authn_context, key,
518516

519517
PASSWD = {
520518
"daev0001": "qwerty",
521-
"haho0032": "qwerty",
519+
"testuser": "qwerty",
522520
"roland": "dianakra",
523521
"babs": "howes",
524522
"upper": "crust"}

example/idp2/idp_user.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,22 @@
3535
#USERS = LDAPDict(**ldap_settings)
3636

3737
USERS = {
38-
"haho0032": {
39-
"sn": "Hoerberg",
40-
"givenName": "Hasse",
38+
"testuser": {
39+
"sn": "Testsson",
40+
"givenName": "Test",
4141
"eduPersonAffiliation": "student",
4242
"eduPersonScopedAffiliation": "[email protected]",
43-
"eduPersonPrincipalName": "haho@example.com",
44-
"uid": "haho0032",
43+
"eduPersonPrincipalName": "test@example.com",
44+
"uid": "testuser",
4545
"eduPersonTargetedID": "one!for!all",
4646
"c": "SE",
4747
"o": "Example Co.",
4848
"ou": "IT",
4949
"initials": "P",
5050
"schacHomeOrganization": "example.com",
51-
"email": "hans@example.com",
52-
"displayName": "Hans Hoerberg",
53-
"labeledURL": "http://www.example.com/haho My homepage",
51+
"email": "test@example.com",
52+
"displayName": "Test Testsson",
53+
"labeledURL": "http://www.example.com/test My homepage",
5454
"norEduPersonNIN": "SE199012315555"
5555
},
5656
"roland": {

example/sp-wsgi/sp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from saml2.s_utils import sid
3636
from saml2.s_utils import rndstr
3737
#from srtest import exception_trace
38-
from saml2.md import Extensions
38+
from saml2.samlp import Extensions
3939
import xmldsig as ds
4040

4141
logger = logging.getLogger("")

src/saml2/__init__.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -591,13 +591,14 @@ def get_prefix_map(self, elements):
591591

592592
def get_xml_string_with_self_contained_assertion_within_advice_encrypted_assertion(self, assertion_tag, advice_tag):
593593
for tmp_encrypted_assertion in self.assertion.advice.encrypted_assertion:
594-
prefix_map = self.get_prefix_map([tmp_encrypted_assertion._to_element_tree().
595-
find(assertion_tag)])
596-
597-
tree = self._to_element_tree()
598-
599-
self.set_prefixes(tree.find(assertion_tag).find(advice_tag).find(tmp_encrypted_assertion._to_element_tree()
600-
.tag).find(assertion_tag), prefix_map)
594+
if tmp_encrypted_assertion.encrypted_data is None:
595+
prefix_map = self.get_prefix_map([tmp_encrypted_assertion._to_element_tree().find(assertion_tag)])
596+
tree = self._to_element_tree()
597+
encs = tree.find(assertion_tag).find(advice_tag).findall(tmp_encrypted_assertion._to_element_tree().tag)
598+
for enc in encs:
599+
assertion = enc.find(assertion_tag)
600+
if assertion is not None:
601+
self.set_prefixes(assertion, prefix_map)
601602

602603
return ElementTree.tostring(tree, encoding="UTF-8")
603604

src/saml2/client_base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ def create_name_id_mapping_request(self, name_id_policy,
542542
# ======== response handling ===========
543543

544544
def parse_authn_request_response(self, xmlstr, binding, outstanding=None,
545-
outstanding_certs=None, decrypt=True):
545+
outstanding_certs=None):
546546
""" Deal with an AuthnResponse
547547
548548
:param xmlstr: The reply as a xml string
@@ -573,7 +573,6 @@ def parse_authn_request_response(self, xmlstr, binding, outstanding=None,
573573
"attribute_converters": self.config.attribute_converters,
574574
"allow_unknown_attributes":
575575
self.config.allow_unknown_attributes,
576-
"decrypt": decrypt
577576
}
578577
try:
579578
resp = self._parse_response(xmlstr, AuthnResponse,

src/saml2/config.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
"cert_handler_extra_class",
6969
"generate_cert_func",
7070
"generate_cert_info",
71-
"verify_encrypt_cert",
71+
"verify_encrypt_cert_advice",
72+
"verify_encrypt_cert_assertion",
7273
"tmp_cert_file",
7374
"tmp_key_file",
7475
"validate_certificate",
@@ -98,6 +99,8 @@
9899
"sign_assertion",
99100
"sign_response",
100101
"encrypt_assertion",
102+
"encrypted_advice_attributes",
103+
"encrypt_assertion_self_contained",
101104
"want_authn_requests_signed",
102105
"want_authn_requests_only_with_valid_cert",
103106
"provided_attributes",
@@ -220,7 +223,8 @@ def __init__(self, homedir="."):
220223
self.allow_unsolicited = False
221224
self.extension_schema = {}
222225
self.cert_handler_extra_class = None
223-
self.verify_encrypt_cert = None
226+
self.verify_encrypt_cert_advice = None
227+
self.verify_encrypt_cert_assertion = None
224228
self.generate_cert_func = None
225229
self.generate_cert_info = None
226230
self.tmp_cert_file = None

src/saml2/entity.py

Lines changed: 114 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import base64
22
#from binascii import hexlify
3+
import copy
34
import logging
45
from hashlib import sha1
56
from Crypto.PublicKey import RSA
@@ -37,7 +38,7 @@
3738
from saml2.s_utils import success_status_factory
3839
from saml2.s_utils import decode_base64_and_inflate
3940
from saml2.s_utils import UnsupportedBinding
40-
from saml2.samlp import AuthnRequest, SessionIndex
41+
from saml2.samlp import AuthnRequest, SessionIndex, response_from_string
4142
from saml2.samlp import AuthzDecisionQuery
4243
from saml2.samlp import AuthnQuery
4344
from saml2.samlp import AssertionIDRequest
@@ -502,10 +503,46 @@ def _add_info(self, msg, **kwargs):
502503
else:
503504
msg.extension_elements = extensions
504505

506+
def has_encrypt_cert_in_metadata(self, sp_entity_id):
507+
if sp_entity_id is not None:
508+
_certs = self.metadata.certs(sp_entity_id, "any", "encryption")
509+
if len(_certs) > 0:
510+
return True
511+
return False
512+
513+
514+
def _encrypt_assertion(self, encrypt_cert, sp_entity_id, response, node_xpath=None):
515+
_certs = []
516+
cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary)
517+
if encrypt_cert:
518+
_certs = []
519+
_certs.append(encrypt_cert)
520+
elif sp_entity_id is not None:
521+
_certs = self.metadata.certs(sp_entity_id, "any", "encryption")
522+
exception = None
523+
for _cert in _certs:
524+
try:
525+
begin_cert = "-----BEGIN CERTIFICATE-----\n"
526+
end_cert = "\n-----END CERTIFICATE-----\n"
527+
if begin_cert not in _cert:
528+
_cert = "%s%s" % (begin_cert, _cert)
529+
if end_cert not in _cert:
530+
_cert = "%s%s" % (_cert, end_cert)
531+
_, cert_file = make_temp(_cert, decode=False)
532+
response = cbxs.encrypt_assertion(response, cert_file,
533+
pre_encryption_part(), node_xpath=node_xpath)
534+
return response
535+
except Exception as ex:
536+
exception = ex
537+
pass
538+
if exception:
539+
raise exception
540+
return response
541+
505542
def _response(self, in_response_to, consumer_url=None, status=None,
506-
issuer=None, sign=False, to_sign=None,
543+
issuer=None, sign=False, to_sign=None, sp_entity_id=None,
507544
encrypt_assertion=False, encrypt_assertion_self_contained=False, encrypted_advice_attributes=False,
508-
encrypt_cert=None, **kwargs):
545+
encrypt_cert_advice=None, encrypt_cert_assertion=None,sign_assertion=None, pefim=False, **kwargs):
509546
""" Create a Response.
510547
Encryption:
511548
encrypt_assertion must be true for encryption to be performed. If encrypted_advice_attributes also is
@@ -542,43 +579,79 @@ def _response(self, in_response_to, consumer_url=None, status=None,
542579
if not sign and to_sign and not encrypt_assertion:
543580
return signed_instance_factory(response, self.sec, to_sign)
544581

545-
if encrypt_assertion:
546-
node_xpath = None
582+
has_encrypt_cert = self.has_encrypt_cert_in_metadata(sp_entity_id)
583+
if not has_encrypt_cert and encrypt_cert_advice is None:
584+
encrypted_advice_attributes = False
585+
if not has_encrypt_cert and encrypt_cert_assertion is None:
586+
encrypt_assertion = False
587+
588+
if encrypt_assertion or (encrypted_advice_attributes and response.assertion.advice is not None and
589+
len(response.assertion.advice.assertion) == 1):
547590
if sign:
548591
response.signature = pre_signature_part(response.id,
549592
self.sec.my_cert, 1)
550593
sign_class = [(class_name(response), response.id)]
551594
cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary)
595+
encrypt_advice = False
552596
if encrypted_advice_attributes and response.assertion.advice is not None \
553-
and len(response.assertion.advice.assertion) == 1:
554-
tmp_assertion = response.assertion.advice.assertion[0]
555-
response.assertion.advice.encrypted_assertion = []
556-
response.assertion.advice.encrypted_assertion.append(EncryptedAssertion())
557-
if isinstance(tmp_assertion, list):
558-
response.assertion.advice.encrypted_assertion[0].add_extension_elements(tmp_assertion)
559-
else:
560-
response.assertion.advice.encrypted_assertion[0].add_extension_element(tmp_assertion)
561-
response.assertion.advice.assertion = []
597+
and len(response.assertion.advice.assertion) > 0:
598+
_assertions = response.assertion
599+
if not isinstance(_assertions, list):
600+
_assertions = [_assertions]
601+
for _assertion in _assertions:
602+
_assertion.advice.encrypted_assertion = []
603+
_assertion.advice.encrypted_assertion.append(EncryptedAssertion())
604+
_advice_assertions = copy.deepcopy(_assertion.advice.assertion)
605+
_assertion.advice.assertion = []
606+
if not isinstance(_advice_assertions, list):
607+
_advice_assertions = [_advice_assertions]
608+
for tmp_assertion in _advice_assertions:
609+
to_sign_advice = []
610+
if sign_assertion and not pefim:
611+
tmp_assertion.signature = pre_signature_part(tmp_assertion.id, self.sec.my_cert, 1)
612+
to_sign_advice.append((class_name(tmp_assertion), tmp_assertion.id))
613+
#tmp_assertion = response.assertion.advice.assertion[0]
614+
_assertion.advice.encrypted_assertion[0].add_extension_element(tmp_assertion)
615+
616+
if encrypt_assertion_self_contained:
617+
advice_tag = response.assertion.advice._to_element_tree().tag
618+
assertion_tag = tmp_assertion._to_element_tree().tag
619+
response = \
620+
response.get_xml_string_with_self_contained_assertion_within_advice_encrypted_assertion(
621+
assertion_tag, advice_tag)
622+
node_xpath = ''.join(["/*[local-name()=\"%s\"]" % v for v in
623+
["Response", "Assertion", "Advice", "EncryptedAssertion", "Assertion"]])
624+
625+
if to_sign_advice:
626+
response = signed_instance_factory(response, self.sec, to_sign_advice)
627+
response = self._encrypt_assertion(encrypt_cert_advice, sp_entity_id, response, node_xpath=node_xpath)
628+
response = response_from_string(response)
629+
630+
if encrypt_assertion:
631+
to_sign_assertion = []
632+
if sign_assertion is not None and sign_assertion:
633+
_assertions = response.assertion
634+
if not isinstance(_assertions, list):
635+
_assertions = [_assertions]
636+
for _assertion in _assertions:
637+
_assertion.signature = pre_signature_part(_assertion.id, self.sec.my_cert, 1)
638+
to_sign_assertion.append((class_name(_assertion), _assertion.id))
562639
if encrypt_assertion_self_contained:
563-
advice_tag = response.assertion.advice._to_element_tree().tag
564-
assertion_tag = tmp_assertion._to_element_tree().tag
565-
response = response.get_xml_string_with_self_contained_assertion_within_advice_encrypted_assertion(
566-
assertion_tag, advice_tag)
567-
node_xpath = ''.join(["/*[local-name()=\"%s\"]" % v for v in
568-
["Response", "Assertion", "Advice", "EncryptedAssertion", "Assertion"]])
569-
elif encrypt_assertion_self_contained:
570-
assertion_tag = response.assertion._to_element_tree().tag
571-
response = pre_encrypt_assertion(response)
572-
response = response.get_xml_string_with_self_contained_assertion_within_encrypted_assertion(
573-
assertion_tag)
640+
try:
641+
assertion_tag = response.assertion._to_element_tree().tag
642+
except:
643+
assertion_tag = response.assertion[0]._to_element_tree().tag
644+
response = pre_encrypt_assertion(response)
645+
response = response.get_xml_string_with_self_contained_assertion_within_encrypted_assertion(
646+
assertion_tag)
647+
else:
648+
response = pre_encrypt_assertion(response)
649+
if to_sign_assertion:
650+
response = signed_instance_factory(response, self.sec, to_sign_assertion)
651+
response = self._encrypt_assertion(encrypt_cert_assertion, sp_entity_id, response)
574652
else:
575-
response = pre_encrypt_assertion(response)
576-
if to_sign:
577-
response = signed_instance_factory(response, self.sec, to_sign)
578-
_, cert_file = make_temp("%s" % encrypt_cert, decode=False)
579-
response = cbxs.encrypt_assertion(response, cert_file,
580-
pre_encryption_part(), node_xpath=node_xpath)
581-
# template(response.assertion.id))
653+
if to_sign:
654+
response = signed_instance_factory(response, self.sec, to_sign)
582655
if sign:
583656
return signed_instance_factory(response, self.sec, sign_class)
584657
else:
@@ -968,23 +1041,23 @@ def _parse_response(self, xmlstr, response_cls, service, binding,
9681041
logger.debug("XMLSTR: %s" % xmlstr)
9691042

9701043
if response:
1044+
keys = None
9711045
if outstanding_certs:
9721046
try:
9731047
cert = outstanding_certs[response.in_response_to]
9741048
except KeyError:
975-
key_file = ""
1049+
keys = None
9761050
else:
977-
_, key_file = make_temp("%s" % cert["key"],
978-
decode=False)
979-
else:
980-
key_file = ""
1051+
if not isinstance(cert, list):
1052+
cert = [cert]
1053+
keys = []
1054+
for _cert in cert:
1055+
keys.append(_cert["key"])
9811056
only_identity_in_encrypted_assertion = False
9821057
if "only_identity_in_encrypted_assertion" in kwargs:
9831058
only_identity_in_encrypted_assertion = kwargs["only_identity_in_encrypted_assertion"]
984-
decrypt = True
985-
if "decrypt" in kwargs:
986-
decrypt = kwargs["decrypt"]
987-
response = response.verify(key_file, decrypt=decrypt)
1059+
1060+
response = response.verify(keys)
9881061

9891062
if not response:
9901063
return None

src/saml2/extension/pefim.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import saml2
44
from saml2 import SamlBase
5-
from saml2.xmldsig import X509Data
5+
from saml2.xmldsig import KeyInfo
66

77
NAMESPACE = 'urn:net:eustix:names:tc:PEFIM:0.0:assertion'
88

@@ -16,19 +16,31 @@ class SPCertEncType_(SamlBase):
1616
c_attributes = SamlBase.c_attributes.copy()
1717
c_child_order = SamlBase.c_child_order[:]
1818
c_cardinality = SamlBase.c_cardinality.copy()
19-
c_children['{http://www.w3.org/2000/09/xmldsig#}X509Data'] = ('x509_data',
20-
[X509Data])
19+
c_children['{http://www.w3.org/2000/09/xmldsig#}KeyInfo'] = ('key_info',
20+
[KeyInfo])
21+
c_cardinality['key_info'] = {"min": 1}
22+
c_attributes['VerifyDepth'] = ('verify_depth', 'unsignedByte', False)
23+
c_child_order.extend(['key_info'])
2124

2225
def __init__(self,
26+
key_info=None,
2327
x509_data=None,
28+
verify_depth='1',
2429
text=None,
2530
extension_elements=None,
2631
extension_attributes=None):
2732
SamlBase.__init__(self,
2833
text=text,
2934
extension_elements=extension_elements,
3035
extension_attributes=extension_attributes)
31-
self.x509_data = x509_data
36+
if key_info:
37+
self.key_info = key_info
38+
elif x509_data:
39+
self.key_info = KeyInfo(x509_data=x509_data)
40+
else:
41+
self.key_info = []
42+
self.verify_depth = verify_depth
43+
#self.x509_data = x509_data
3244

3345

3446
def spcertenc_type__from_string(xml_string):
@@ -62,5 +74,4 @@ def spcertenc_from_string(xml_string):
6274

6375

6476
def factory(tag, **kwargs):
65-
return ELEMENT_BY_TAG[tag](**kwargs)
66-
77+
return ELEMENT_BY_TAG[tag](**kwargs)

0 commit comments

Comments
 (0)