diff --git a/README.md b/README.md index b173844a..0cdd9de7 100644 --- a/README.md +++ b/README.md @@ -399,7 +399,14 @@ In addition to the required settings data (idp, sp), extra settings can be defin // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384' // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512' - "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + + // Algorithm that the toolkit will use on digest process. Options: + // 'http://www.w3.org/2000/09/xmldsig#sha1' + // 'http://www.w3.org/2001/04/xmlenc#sha256' + // 'http://www.w3.org/2001/04/xmldsig-more#sha384' + // 'http://www.w3.org/2001/04/xmlenc#sha512' + 'digestAlgorithm' => 'http://www.w3.org/2000/09/xmldsig#sha1 }, // Contact information template, it is recommended to supply diff --git a/demo-bottle/saml/advanced_settings.json b/demo-bottle/saml/advanced_settings.json index 5b9396ff..47ec8c28 100644 --- a/demo-bottle/saml/advanced_settings.json +++ b/demo-bottle/saml/advanced_settings.json @@ -10,7 +10,8 @@ "wantNameId" : true, "wantNameIdEncrypted": false, "wantAssertionsEncrypted": false, - "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + "digestAlgorithm": "http://www.w3.org/2000/09/xmldsig#sha1" }, "contactPerson": { "technical": { diff --git a/demo-django/saml/advanced_settings.json b/demo-django/saml/advanced_settings.json index ed0ba4ab..7efb5d1b 100644 --- a/demo-django/saml/advanced_settings.json +++ b/demo-django/saml/advanced_settings.json @@ -10,7 +10,8 @@ "wantNameId" : true, "wantNameIdEncrypted": false, "wantAssertionsEncrypted": false, - "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + "digestAlgorithm": "http://www.w3.org/2000/09/xmldsig#sha1" }, "contactPerson": { "technical": { diff --git a/demo-flask/saml/advanced_settings.json b/demo-flask/saml/advanced_settings.json index ed0ba4ab..7efb5d1b 100644 --- a/demo-flask/saml/advanced_settings.json +++ b/demo-flask/saml/advanced_settings.json @@ -10,7 +10,8 @@ "wantNameId" : true, "wantNameIdEncrypted": false, "wantAssertionsEncrypted": false, - "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + "digestAlgorithm": "http://www.w3.org/2000/09/xmldsig#sha1" }, "contactPerson": { "technical": { diff --git a/src/onelogin/saml2/metadata.py b/src/onelogin/saml2/metadata.py index fba83145..d8bc0c75 100644 --- a/src/onelogin/saml2/metadata.py +++ b/src/onelogin/saml2/metadata.py @@ -202,7 +202,7 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N return metadata @staticmethod - def sign_metadata(metadata, key, cert, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1): + def sign_metadata(metadata, key, cert, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1, digest_algorithm=OneLogin_Saml2_Constants.SHA1): """ Signs the metadata with the key/cert provided @@ -218,10 +218,13 @@ def sign_metadata(metadata, key, cert, sign_algorithm=OneLogin_Saml2_Constants.R :param sign_algorithm: Signature algorithm method :type sign_algorithm: string + :param digest_algorithm: Digest algorithm method + :type digest_algorithm: string + :returns: Signed Metadata :rtype: string """ - return OneLogin_Saml2_Utils.add_sign(metadata, key, cert, False, sign_algorithm) + return OneLogin_Saml2_Utils.add_sign(metadata, key, cert, False, sign_algorithm, digest_algorithm) @staticmethod def add_x509_key_descriptors(metadata, cert=None): diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py index b596e493..362819c5 100644 --- a/src/onelogin/saml2/settings.py +++ b/src/onelogin/saml2/settings.py @@ -283,6 +283,9 @@ def __add_default_values(self): # Signature Algorithm self.__security.setdefault('signatureAlgorithm', OneLogin_Saml2_Constants.RSA_SHA1) + # Digest Algorithm + self.__security.setdefault('digestAlgorithm', OneLogin_Saml2_Constants.SHA1) + # AttributeStatement required by default self.__security.setdefault('wantAttributeStatement', True) @@ -639,7 +642,10 @@ def get_sp_metadata(self): cert_metadata_file ) - metadata = OneLogin_Saml2_Metadata.sign_metadata(metadata, key_metadata, cert_metadata) + signature_algorithm = self.__security['signatureAlgorithm'] + digest_algorithm = self.__security['digestAlgorithm'] + + metadata = OneLogin_Saml2_Metadata.sign_metadata(metadata, key_metadata, cert_metadata, signature_algorithm, digest_algorithm) return metadata diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py index 6fa42e2d..00fefae3 100644 --- a/src/onelogin/saml2/utils.py +++ b/src/onelogin/saml2/utils.py @@ -797,7 +797,7 @@ def write_temp_file(content): return f_temp @staticmethod - def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1): + def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1, digest_algorithm=OneLogin_Saml2_Constants.SHA1): """ Adds signature key and senders certificate to an element (Message or Assertion). @@ -816,6 +816,12 @@ def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constant :param sign_algorithm: Signature algorithm method :type sign_algorithm: string + + :param digest_algorithm: Digest algorithm method + :type digest_algorithm: string + + :returns: Signed XML + :rtype: string """ if xml is None or xml == '': raise Exception('Empty string supplied as input') @@ -866,7 +872,15 @@ def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constant else: elem[0].insert(0, signature) - ref = signature.addReference(xmlsec.TransformSha1) + digest_algorithm_transform_map = { + OneLogin_Saml2_Constants.SHA1: xmlsec.TransformSha1, + OneLogin_Saml2_Constants.SHA256: xmlsec.TransformSha256, + OneLogin_Saml2_Constants.SHA384: xmlsec.TransformSha384, + OneLogin_Saml2_Constants.SHA512: xmlsec.TransformSha512 + } + digest_algorithm_transform = digest_algorithm_transform_map.get(digest_algorithm, xmlsec.TransformSha1) + + ref = signature.addReference(digest_algorithm_transform) ref.addTransform(xmlsec.TransformEnveloped) ref.addTransform(xmlsec.TransformExclC14N) diff --git a/tests/src/OneLogin/saml2_tests/metadata_test.py b/tests/src/OneLogin/saml2_tests/metadata_test.py index 85c951cd..02e551e7 100644 --- a/tests/src/OneLogin/saml2_tests/metadata_test.py +++ b/tests/src/OneLogin/saml2_tests/metadata_test.py @@ -14,6 +14,7 @@ from onelogin.saml2.metadata import OneLogin_Saml2_Metadata from onelogin.saml2.settings import OneLogin_Saml2_Settings +from onelogin.saml2.constants import OneLogin_Saml2_Constants class OneLogin_Saml2_Metadata_Test(unittest.TestCase): @@ -222,12 +223,32 @@ def testSignMetadata(self): self.assertIn('', signed_metadata) self.assertIn('', signed_metadata) + self.assertIn('', signed_metadata) self.assertIn('\n', signed_metadata) with self.assertRaisesRegexp(Exception, 'Empty string supplied as input'): OneLogin_Saml2_Metadata.sign_metadata('', key, cert) + signed_metadata_2 = OneLogin_Saml2_Metadata.sign_metadata(metadata, key, cert, OneLogin_Saml2_Constants.RSA_SHA256, OneLogin_Saml2_Constants.SHA384) + self.assertIn('', signed_metadata_2) + + self.assertIn('urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', signed_metadata_2) + + self.assertIn('', signed_metadata_2) + self.assertIn('', signed_metadata_2) + self.assertIn('', signed_metadata_2) + self.assertIn('\n', signed_metadata_2) + def testAddX509KeyDescriptors(self): """ Tests the addX509KeyDescriptors method of the OneLogin_Saml2_Metadata diff --git a/tests/src/OneLogin/saml2_tests/utils_test.py b/tests/src/OneLogin/saml2_tests/utils_test.py index 63439f68..459b0996 100644 --- a/tests/src/OneLogin/saml2_tests/utils_test.py +++ b/tests/src/OneLogin/saml2_tests/utils_test.py @@ -810,6 +810,34 @@ def testAddSign(self): with self.assertRaisesRegexp(Exception, 'Error parsing xml string'): OneLogin_Saml2_Utils.add_sign(1, key, cert) + def testAddSignCheckAlg(self): + """ + Tests the add_sign method of the OneLogin_Saml2_Utils + Case: Review signature & digest algorithm + """ + settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) + key = settings.get_sp_key() + cert = settings.get_sp_cert() + + xml_authn = b64decode(self.file_contents(join(self.data_path, 'requests', 'authn_request.xml.base64'))) + xml_authn_signed = OneLogin_Saml2_Utils.add_sign(xml_authn, key, cert) + self.assertIn('', xml_authn_signed) + self.assertIn('', xml_authn_signed) + self.assertIn('', xml_authn_signed) + self.assertIn('', xml_authn_signed) + + xml_authn_signed_2 = OneLogin_Saml2_Utils.add_sign(xml_authn, key, cert, False, OneLogin_Saml2_Constants.RSA_SHA256, OneLogin_Saml2_Constants.SHA384) + self.assertIn('', xml_authn_signed_2) + self.assertIn('', xml_authn_signed_2) + self.assertIn('', xml_authn_signed_2) + self.assertIn('', xml_authn_signed_2) + + xml_authn_signed_3 = OneLogin_Saml2_Utils.add_sign(xml_authn, key, cert, False, OneLogin_Saml2_Constants.RSA_SHA384, OneLogin_Saml2_Constants.SHA512) + self.assertIn('', xml_authn_signed_3) + self.assertIn('', xml_authn_signed_3) + self.assertIn('', xml_authn_signed_3) + self.assertIn('', xml_authn_signed_3) + def testValidateSign(self): """ Tests the validate_sign method of the OneLogin_Saml2_Utils