Skip to content

Commit c193ee3

Browse files
committed
Improve decrypt method, Add an option to decrypt an element in place or copy it before decryption.
1 parent e446db5 commit c193ee3

File tree

4 files changed

+105
-16
lines changed

4 files changed

+105
-16
lines changed

src/onelogin/saml2/response.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,7 @@ def __decrypt_assertion(self, xml):
778778
keyinfo.append(encrypted_key[0])
779779

780780
encrypted_data = encrypted_data_nodes[0]
781-
decrypted = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key, debug)
781+
decrypted = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key, debug=debug, inplace=True)
782782
xml.replace(encrypted_assertion_nodes[0], decrypted)
783783
return xml
784784

src/onelogin/saml2/utils.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@
1010
"""
1111

1212
import base64
13-
from datetime import datetime
13+
from copy import deepcopy
1414
import calendar
15+
from datetime import datetime
16+
from defusedxml.lxml import fromstring
1517
from hashlib import sha1, sha256, sha384, sha512
1618
from isodate import parse_duration as duration_parser
1719
import re
1820
from textwrap import wrap
1921
from functools import wraps
2022
from uuid import uuid4
23+
from xml.dom.minidom import Element
2124

2225
import zlib
2326
import xmlsec
@@ -655,7 +658,7 @@ def get_status(dom):
655658
return status
656659

657660
@staticmethod
658-
def decrypt_element(encrypted_data, key, debug=False):
661+
def decrypt_element(encrypted_data, key, debug=False, inplace=False):
659662
"""
660663
Decrypts an encrypted element.
661664
@@ -668,10 +671,20 @@ def decrypt_element(encrypted_data, key, debug=False):
668671
:param debug: Activate the xmlsec debug
669672
:type: bool
670673
674+
:param inplace: update passed data with decrypted result
675+
:type: bool
676+
671677
:returns: The decrypted element.
672678
:rtype: lxml.etree.Element
673679
"""
674-
encrypted_data = OneLogin_Saml2_XML.to_etree(encrypted_data)
680+
681+
if isinstance(encrypted_data, Element):
682+
encrypted_data = fromstring(str(encrypted_data.toxml()))
683+
if not inplace and isinstance(encrypted_data, OneLogin_Saml2_XML._element_class):
684+
encrypted_data = deepcopy(encrypted_data)
685+
elif isinstance(encrypted_data, OneLogin_Saml2_XML._text_class):
686+
encrypted_data = OneLogin_Saml2_XML._parse_etree(encrypted_data)
687+
675688
xmlsec.enable_debug_trace(debug)
676689
manager = xmlsec.KeysManager()
677690

tests/data/misc/sp4.key

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD4ZrcXcjCBOQS7
3+
stUabuXPYnXKvcoJUrMVPRX1zfrXvpfghCrykbL1TKoqGfmEA9oNRoMBOmZCgLlK
4+
eb0TfuEO/u1jf4rRFcK7U/dYEiX74bQgUnJUWTfFlhwPjxGhn9zDrc2tSpworJBV
5+
amyBZIo5Beap5OJLote/Wqp1DZjNyEZ2m8m+lv8udmejmlo5RMoIzuG3VdH6ADC9
6+
LKF+QsXC/HRZBhLE/y+75/XrNODvX8eM8+9Xp21QlVF1EIZDfNQ2iHsA8GEpJDC5
7+
aomTW/xExBysejnwP2ROrfm3PIfP64EbB4G01f8eErlXeUD0oQ0gECgIXsJpfBkD
8+
IWMHwx3/AgMBAAECggEAdbLNvFlJ7GDlAj75RJ4ZXAuOPrNw4LwDyON53U9tNP7F
9+
HgfiBa/NuPdLhclq9geRMUsg1dsjCw3NPiGy2mL7JszaFJQhZXLHI1Xk1CE9SD0o
10+
yUvniln/2CqJP0IOG6QQydM3qo24snkZpq9XnHPUHrLSGdwu8aHGUpAWRoJbzdzR
11+
tBWBn6SlkuaE52vcGh7eMdKSICRCg2/gg6LIi89pkiI9tfozAL2LPcDTRGp3DA3w
12+
U6OO8k+d1La4s9G0i22OGSwPxGerTHnBIzpeM/ivRwBypFy3EV9bbjQlheI53xAo
13+
ZMmGeSnQ89MWgY64pnWrX862Mf1EZYTjumDe2dl1kQKBgQD9pBG2BbcQ8qieTf84
14+
92LeOYTPRdd0N+gdyDKKorRO772zgxBwpSwO285nzy/FKSnpJIDtuee6OFClnDor
15+
Ui6lG1WPQeoSEdH1V10XkfSaoFOz7Hyv9H2dCLvW/VO9KYq07VAmQcvNZnqIW+tI
16+
edSHcQ3I8tnw4CiFa0BPvdhk9wKBgQD6tiuN2NvuNFFLvwpBGp3hjGyn6siyXDyP
17+
8IXQmP66NxKqcX/NafVO3bVh6VrPGd7PL1PloQZ5EBG2PPtRdf/g4aeZKZleCUXm
18+
9OgMEOUqdbTP9TGrmgNPtNBx3jnhnX/GTy/7GK77YlXEVplezWaerwRM7NCFCtp2
19+
W6K1M961OQKBgQDDSznr2hirrvuP8GRMW4a/rrAI3DDZplZN4CCySDbm9IcvGgJl
20+
iXgT9MDHg2q3t0sy3U18PYEkDEpkSZcsVfneXN6TEGCHCzuLWXovNM2O5VWtmrAi
21+
1vCFIf1nuuRoKP1I89SbsFuYyogcSBIwWsX+h1ji2cJfSmlI2VzKSVW93wKBgQDA
22+
sqwfRoMkP0oM8jUrfQ3Egm4xUiAYFxTlfXUcs7t13UaXgs08USifCYGUVAvcCoJa
23+
tIHDiVS0UEmMzKpOHmghrM9oxbR/tpjnv21reMDrNbVX8ZnPz3ykEtHz816BrtC6
24+
17qFQJ+d0CMj2XvghfdOGC8yAQL0fzcSqbQRmmCe4QKBgFWY9fqHEKdG/UlxZfBB
25+
C/QRNTJsrbZf9Ok/o1h6BHnK64xUc4elShEwV9IdC4QNW0UCr7WXoGLUkhfUphId
26+
q//KUDNc7VrWj5URsZcGi7WMkqNm9kPkpeuh3iSvh3+q7tK0/yfuj9ZQOjKzQnit
27+
VZBooJAJGdSqYgitpyxB71/n
28+
-----END PRIVATE KEY-----

tests/src/OneLogin/saml2_tests/utils_test.py

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from lxml import etree
99
from os.path import dirname, join, exists
1010
import unittest
11+
from defusedxml.lxml import fromstring
1112
from xml.dom.minidom import parseString
1213

1314
from onelogin.saml2 import compat
@@ -613,7 +614,7 @@ def testDecryptElement(self):
613614
encrypted_nameid_nodes = dom_nameid_enc.find('.//saml:EncryptedID', namespaces=OneLogin_Saml2_Constants.NSMAP)
614615
encrypted_data = encrypted_nameid_nodes[0]
615616
decrypted_nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key)
616-
self.assertEqual('{%s}NameID' % OneLogin_Saml2_Constants.NS_SAML, decrypted_nameid.tag)
617+
self.assertEqual('saml:NameID', decrypted_nameid.tag)
617618
self.assertEqual('2de11defd199f8d5bb63f9b7deb265ba5c675c10', decrypted_nameid.text)
618619

619620
xml_assertion_enc = b64decode(self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion_encrypted_nameid.xml.base64')))
@@ -636,24 +637,71 @@ def testDecryptElement(self):
636637
key2 = f.read()
637638
f.close()
638639

639-
self.assertRaises(Exception, OneLogin_Saml2_Utils.decrypt_element, encrypted_data, key2)
640+
# sp.key and sp2.key are equivalent we should be able to decrypt the nameID again
641+
decrypted_nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key2)
642+
self.assertIn('{%s}NameID' % (OneLogin_Saml2_Constants.NS_SAML), decrypted_nameid.tag)
643+
self.assertEqual('457bdb600de717891c77647b0806ce59c089d5b8', decrypted_nameid.text)
640644

641-
key_3_file_name = join(self.data_path, 'misc', 'sp2.key')
645+
key_3_file_name = join(self.data_path, 'misc', 'sp3.key')
642646
f = open(key_3_file_name, 'r')
643647
key3 = f.read()
644648
f.close()
645-
self.assertRaises(Exception, OneLogin_Saml2_Utils.decrypt_element, encrypted_data, key3)
649+
650+
# sp.key and sp3.key are equivalent we should be able to decrypt the nameID again
651+
decrypted_nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key3)
652+
self.assertIn('{%s}NameID' % (OneLogin_Saml2_Constants.NS_SAML), decrypted_nameid.tag)
653+
self.assertEqual('457bdb600de717891c77647b0806ce59c089d5b8', decrypted_nameid.text)
654+
655+
key_4_file_name = join(self.data_path, 'misc', 'sp4.key')
656+
f = open(key_4_file_name, 'r')
657+
key4 = f.read()
658+
f.close()
659+
660+
with self.assertRaisesRegex(Exception, "(1, 'failed to decrypt')"):
661+
OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key4)
662+
646663
xml_nameid_enc_2 = b64decode(self.file_contents(join(self.data_path, 'responses', 'invalids', 'encrypted_nameID_without_EncMethod.xml.base64')))
647-
dom_nameid_enc_2 = etree.fromstring(xml_nameid_enc_2)
648-
encrypted_nameid_nodes_2 = dom_nameid_enc_2.find('.//saml:EncryptedID', namespaces=OneLogin_Saml2_Constants.NSMAP)
649-
encrypted_data_2 = encrypted_nameid_nodes_2[0]
650-
self.assertRaises(Exception, OneLogin_Saml2_Utils.decrypt_element, encrypted_data_2, key)
664+
dom_nameid_enc_2 = parseString(xml_nameid_enc_2)
665+
encrypted_nameid_nodes_2 = dom_nameid_enc_2.getElementsByTagName('saml:EncryptedID')
666+
encrypted_data_2 = encrypted_nameid_nodes_2[0].firstChild
667+
668+
with self.assertRaisesRegex(Exception, "(1, 'failed to decrypt')"):
669+
OneLogin_Saml2_Utils.decrypt_element(encrypted_data_2, key)
651670

652671
xml_nameid_enc_3 = b64decode(self.file_contents(join(self.data_path, 'responses', 'invalids', 'encrypted_nameID_without_keyinfo.xml.base64')))
653-
dom_nameid_enc_3 = etree.fromstring(xml_nameid_enc_3)
654-
encrypted_nameid_nodes_3 = dom_nameid_enc_3.find('.//saml:EncryptedID', namespaces=OneLogin_Saml2_Constants.NSMAP)
655-
encrypted_data_3 = encrypted_nameid_nodes_3[0]
656-
self.assertRaises(Exception, OneLogin_Saml2_Utils.decrypt_element, encrypted_data_3, key)
672+
dom_nameid_enc_3 = parseString(xml_nameid_enc_3)
673+
encrypted_nameid_nodes_3 = dom_nameid_enc_3.getElementsByTagName('saml:EncryptedID')
674+
encrypted_data_3 = encrypted_nameid_nodes_3[0].firstChild
675+
676+
with self.assertRaisesRegex(Exception, "(1, 'failed to decrypt')"):
677+
OneLogin_Saml2_Utils.decrypt_element(encrypted_data_3, key)
678+
679+
def testDecryptElementInplace(self):
680+
"""
681+
Tests the decrypt_element method of the OneLogin_Saml2_Utils with inplace=True
682+
"""
683+
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
684+
685+
key = settings.get_sp_key()
686+
687+
xml_nameid_enc = b64decode(self.file_contents(join(self.data_path, 'responses', 'response_encrypted_nameid.xml.base64')))
688+
dom = fromstring(xml_nameid_enc)
689+
encrypted_node = dom.xpath('//saml:EncryptedID/xenc:EncryptedData', namespaces=OneLogin_Saml2_Constants.NSMAP)[0]
690+
691+
# can be decrypted twice when copy the node first
692+
for _ in range(2):
693+
decrypted_nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_node, key, inplace=False)
694+
self.assertIn('NameID', decrypted_nameid.tag)
695+
self.assertEqual('2de11defd199f8d5bb63f9b7deb265ba5c675c10', decrypted_nameid.text)
696+
697+
# can only be decrypted once in place
698+
decrypted_nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_node, key, inplace=True)
699+
self.assertIn('NameID', decrypted_nameid.tag)
700+
self.assertEqual('2de11defd199f8d5bb63f9b7deb265ba5c675c10', decrypted_nameid.text)
701+
702+
# can't be decrypted twice since it has been decrypted inplace
703+
with self.assertRaisesRegex(Exception, "(1, 'failed to decrypt')"):
704+
OneLogin_Saml2_Utils.decrypt_element(encrypted_node, key, inplace=True)
657705

658706
def testAddSign(self):
659707
"""

0 commit comments

Comments
 (0)