Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 50 additions & 28 deletions src/onelogin/saml2/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,21 @@ def get_attributes(self):
if attr_text:
values.append(attr_text)

# Parse encrypted ids
for encrypted_id in attr.iterchildren('{%s}EncryptedID' % OneLogin_Saml2_Constants.NSMAP['saml']):
key = self.__settings.get_sp_key()
self.__prepare_keyinfo(encrypted_id)
encrypted_data = encrypted_id.getchildren()[0]

nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key)
values.append({
'NameID': {
'Format': nameid.get('Format'),
'NameQualifier': nameid.get('NameQualifier'),
'value': nameid.text
}
})

# Parse any nested NameID children
for nameid in attr.iterchildren('{%s}NameID' % OneLogin_Saml2_Constants.NSMAP['saml']):
values.append({
Expand Down Expand Up @@ -846,6 +861,39 @@ def __query(self, query, tagid=None):
document = self.document
return OneLogin_Saml2_XML.query(document, query, None, tagid)

def __prepare_keyinfo(self, node):
"""
Directly include the EncryptedKey in the KeyInfo of the EncryptedData. This
is needed for decrypting with OneLogin_Saml2_Utils.decrypt_element.
"""
encrypted_data_keyinfo = OneLogin_Saml2_XML.query(node, 'xenc:EncryptedData/ds:KeyInfo')
if not encrypted_data_keyinfo:
raise OneLogin_Saml2_ValidationError(
'No KeyInfo present, invalid Assertion',
OneLogin_Saml2_ValidationError.KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA
)
encrypted_data_keyinfo = encrypted_data_keyinfo[0]
children = encrypted_data_keyinfo.getchildren()
if not children:
raise OneLogin_Saml2_ValidationError(
'KeyInfo has no children nodes, invalid Assertion',
OneLogin_Saml2_ValidationError.CHILDREN_NODE_NOT_FOUND_IN_KEYINFO
)
for child in children:
if 'RetrievalMethod' in child.tag:
if child.attrib['Type'] != 'http://www.w3.org/2001/04/xmlenc#EncryptedKey':
raise OneLogin_Saml2_ValidationError(
'Unsupported Retrieval Method found',
OneLogin_Saml2_ValidationError.UNSUPPORTED_RETRIEVAL_METHOD
)
uri = child.attrib['URI']
if not uri.startswith('#'):
break
uri = uri.split('#')[1]
encrypted_key = OneLogin_Saml2_XML.query(node, './xenc:EncryptedKey[@Id=$tagid]', None, uri)
if encrypted_key:
encrypted_data_keyinfo.append(encrypted_key[0])

def __decrypt_assertion(self, xml):
"""
Decrypts the Assertion
Expand All @@ -869,35 +917,9 @@ def __decrypt_assertion(self, xml):
if encrypted_assertion_nodes:
encrypted_data_nodes = OneLogin_Saml2_XML.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData')
if encrypted_data_nodes:
keyinfo = OneLogin_Saml2_XML.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData/ds:KeyInfo')
if not keyinfo:
raise OneLogin_Saml2_ValidationError(
'No KeyInfo present, invalid Assertion',
OneLogin_Saml2_ValidationError.KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA
)
keyinfo = keyinfo[0]
children = keyinfo.getchildren()
if not children:
raise OneLogin_Saml2_ValidationError(
'KeyInfo has no children nodes, invalid Assertion',
OneLogin_Saml2_ValidationError.CHILDREN_NODE_NOT_FOUND_IN_KEYINFO
)
for child in children:
if 'RetrievalMethod' in child.tag:
if child.attrib['Type'] != 'http://www.w3.org/2001/04/xmlenc#EncryptedKey':
raise OneLogin_Saml2_ValidationError(
'Unsupported Retrieval Method found',
OneLogin_Saml2_ValidationError.UNSUPPORTED_RETRIEVAL_METHOD
)
uri = child.attrib['URI']
if not uri.startswith('#'):
break
uri = uri.split('#')[1]
encrypted_key = OneLogin_Saml2_XML.query(encrypted_assertion_nodes[0], './xenc:EncryptedKey[@Id=$tagid]', None, uri)
if encrypted_key:
keyinfo.append(encrypted_key[0])

encrypted_data = encrypted_data_nodes[0]
self.__prepare_keyinfo(encrypted_data.getparent())

decrypted = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key, debug=debug, inplace=True)
xml.replace(encrypted_assertion_nodes[0], decrypted)
return xml
Expand Down
50 changes: 49 additions & 1 deletion tests/src/OneLogin/saml2_tests/response_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License

from base64 import b64decode
from base64 import b64decode, b64encode
from lxml import etree
from datetime import datetime
from datetime import timedelta
Expand All @@ -14,9 +14,11 @@
from xml.dom.minidom import parseString

from onelogin.saml2 import compat
from onelogin.saml2.constants import OneLogin_Saml2_Constants
from onelogin.saml2.response import OneLogin_Saml2_Response
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from onelogin.saml2.utils import OneLogin_Saml2_Utils
from onelogin.saml2.xml_utils import OneLogin_Saml2_XML


class OneLogin_Saml2_Response_Test(unittest.TestCase):
Expand Down Expand Up @@ -1861,3 +1863,49 @@ def testGetAssertionNotOnOrAfter(self):
response.is_valid(request_data)
self.assertIsNone(response.get_error())
self.assertEqual(response.get_assertion_not_on_or_after(), 2671081021)

def testEncryptedId(self):
"""
Test that decrypting EncryptedID elements works as expected.
"""
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())

base64_content = self.file_contents(join(self.data_path, 'responses', 'valid_unsigned_response.xml.base64'))
xml = b64decode(base64_content)
response_element = OneLogin_Saml2_XML.to_etree(xml)

# Add an EncryptedID element to the existing response.
encrypted_id = OneLogin_Saml2_Utils.generate_name_id(
"123456782",
sp_nq=None,
nq="urn:etoegang:1.9:EntityConcernedID:RSIN",
sp_format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
cert=settings.get_sp_cert(),
)
attribute = (
'<saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" FriendlyName="ActingSubjectID" Name="urn:etoegang:core:LegalSubjectID">'
"<saml:AttributeValue>"
+ encrypted_id +
"</saml:AttributeValue></saml:Attribute>"
)
statement_element = OneLogin_Saml2_XML.query(response_element, '//saml:AttributeStatement')
encrypted_attribute_element = OneLogin_Saml2_XML.to_etree(attribute)
statement_element[0].append(encrypted_attribute_element)

# Try to parse the Response
response = OneLogin_Saml2_Response(
settings, b64encode(OneLogin_Saml2_XML.to_string(response_element))
)
response.is_valid(self.get_request_data())
attributes = response.get_attributes()

self.assertEqual(
attributes['urn:etoegang:core:LegalSubjectID'],
[
{'NameID': {
'Format': 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
'NameQualifier': 'urn:etoegang:1.9:EntityConcernedID:RSIN',
'value': '123456782'}
}
]
)