def test_wrapping_attack(self): """ Test resistance to attempted wrapping attack """ case = self.cases['SAML_assertion1'] print("XML input :\n{}\n\n".format(case.as_buf('in.xml'))) tbs = case.as_etree('in.xml') signed = xmlsec.sign(tbs, key_spec=self.private_keyspec, cert_spec=self.public_keyspec) attack = case.as_etree('evil.xml') attack.append(signed) refs = xmlsec.verified(attack, self.public_keyspec) self.assertTrue(len(refs) == 1) print("verified XML: %s" % etree.tostring(refs[0])) seen_foo = False seen_bar = False for av in refs[0].findall(".//{%s}AttributeValue" % 'urn:oasis:names:tc:SAML:2.0:assertion'): print(etree.tostring(av)) print(av.text) if av.text == 'Foo': seen_foo = True elif av.text == 'Bar': seen_bar = True self.assertTrue(av.text != 'admin') self.assertTrue(seen_foo and seen_bar)
def test_sign_alert_signed(self): """Tests alert signature creation.""" plain_alert = etree.fromstring(self.valid_alert_content) signed_alert = utils.SignAlert(plain_alert, self.TEST_USER_NAME) cert_path = os.path.join(settings.CREDENTIALS_DIR, self.TEST_USER_NAME + ".cert") self.assertTrue(xmlsec.verified(signed_alert, cert_path))
def test_wrapping_attack(self): """ Test resistance to attempted wrapping attack """ case = self.cases['SAML_assertion1'] print("XML input :\n{}\n\n".format(case.as_buf('in.xml'))) tbs = case.as_etree('in.xml') signed = xmlsec.sign(tbs, key_spec=self.private_keyspec, cert_spec=self.public_keyspec) attack = case.as_etree('evil.xml') attack.append(signed) refs = xmlsec.verified(attack, self.public_keyspec) self.assertTrue(len(refs) == 1) print("verified XML: %s" % etree.tostring(refs[0])) seen_foo = False seen_bar = False for av in refs[0].findall(".//{%s}AttributeValue" % 'urn:oasis:names:tc:SAML:2.0:assertion'): print(etree.tostring(av)) print(av.text) if av.text == 'Foo': seen_foo = True elif av.text == 'Bar': seen_bar = True self.assertTrue(av.text != 'admin') self.assertTrue(seen_foo and seen_bar)
def test_sign_alert_signed(self): """Tests alert signature creation.""" plain_alert = etree.fromstring(self.valid_alert_content) signed_alert = utils.SignAlert(plain_alert, self.TEST_USER_NAME) cert_path = os.path.join(settings.CREDENTIALS_DIR, self.TEST_USER_NAME + ".cert") self.assertTrue(xmlsec.verified(signed_alert, cert_path))
def check_signature(t, key, only_one_signature=False): if key is not None: log.debug("verifying signature using %s" % key) refs = xmlsec.verified(t, key, drop_signature=True) if only_one_signature and len(refs) != 1: raise MetadataException("XML metadata contains %d signatures - exactly 1 is required" % len(refs)) t = refs[0] # prevent wrapping attacks return t
def check_signature(t, key, only_one_signature=False): if key is not None: log.debug("verifying signature using %s" % key) refs = xmlsec.verified(t, key, drop_signature=True) if only_one_signature and len(refs) != 1: raise MetadataException("XML metadata contains %d signatures - exactly 1 is required" % len(refs)) t = refs[0] # prevent wrapping attacks return t
def check_signature(self, t, key): if key is not None: if log.isDebugEnabled(): log.debug("verifying signature using %s" % key) refs = xmlsec.verified(t, key) if len(refs) != 1: raise MetadataException("XML metadata contains %d signatures - exactly 1 is required" % len(refs)) t = refs[0] # prevent wrapping attacks return t
def check_signature(self, t, key): if key is not None: if log.isDebugEnabled(): log.debug("verifying signature using %s" % key) refs = xmlsec.verified(t, key) if len(refs) != 1: raise MetadataException( "XML metadata contains %d signatures - exactly 1 is required" % len(refs)) t = refs[0] # prevent wrapping attacks return t
def parse_metadata(self, fn, key=None, base_url=None, fail_on_error=False, filter_invalid=True, validate=True, post=None): """Parse a piece of XML and split it up into EntityDescriptor elements. Each such element is stored in the MDRepository instance. :param fn: a file-like object containing SAML metadata :param key: a certificate (file) or a SHA1 fingerprint to use for signature verification :param base_url: use this base url to resolve relative URLs for XInclude processing :param fail_on_error: (default: False) :param filter_invalid: (default True) remove invalid EntityDescriptor elements rather than raise an errror :param validate: (default: True) set to False to turn off all XML schema validation :param post: A callable that will be called to modify the parse-tree before any validation (but after xinclude processing) """ try: t = etree.parse(fn, base_url=base_url, parser=etree.XMLParser(resolve_entities=False)) t.xinclude() if key is not None: try: log.debug("verifying signature using %s" % key) refs = xmlsec.verified(t, key) if len(refs) != 1: raise MetadataException("XML metadata contains %d signatures - exactly 1 is required" % len(refs)) t = refs[0] # prevent wrapping attacks except Exception, ex: tb = traceback.format_exc() print tb log.error(ex) return None if post is not None: t = post(t) if validate: if filter_invalid: for e in t.findall('{%s}EntityDescriptor' % NS['md']): if not schema().validate(e): error = _e(schema().error_log, m=base_url) log.debug("removing '%s': schema validation failed (%s)" % (e.get('entityID'), error)) e.getparent().remove(e) self.fire(type=EVENT_DROP_ENTITY, url=base_url, entityID=e.get('entityID'), error=error) else: # Having removed the invalid entities this should now never happen... schema().assertValid(t)
def test_duo_vuln_attack(self): """ Test https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations """ case = self.cases['SAML_assertion_sha256'] print("XML input :\n{}\n\n".format(case.as_buf('in.xml'))) signed = xmlsec.sign(case.as_etree('in.xml'), key_spec=self.private_keyspec, cert_spec=self.public_keyspec) refs = xmlsec.verified(signed, self.public_keyspec) self.assertTrue(len(refs) == 1) print("verified XML: %s" % etree.tostring(refs[0])) assert('evil' not in [x.text for x in refs[0].findall(".//{%s}AttributeValue" % 'urn:oasis:names:tc:SAML:2.0:assertion')])
def test_duo_vuln_attack(self): """ Test https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations """ case = self.cases['SAML_assertion_sha256'] print("XML input :\n{}\n\n".format(case.as_buf('in.xml'))) signed = xmlsec.sign(case.as_etree('in.xml'), key_spec=self.private_keyspec, cert_spec=self.public_keyspec) refs = xmlsec.verified(signed, self.public_keyspec) self.assertTrue(len(refs) == 1) print("verified XML: %s" % etree.tostring(refs[0])) assert('evil' not in [x.text for x in refs[0].findall(".//{%s}AttributeValue" % 'urn:oasis:names:tc:SAML:2.0:assertion')])
def test_sign_verify_SAML_assertion_unwrap2(self): """ Test signing a SAML assertion, and return verified data. """ case = self.cases['SAML_assertion1'] print("XML input :\n{}\n\n".format(case.as_buf('in.xml'))) tbs = case.as_etree('in.xml') signed = xmlsec.sign(tbs, key_spec=self.private_keyspec, cert_spec=self.public_keyspec) refs = xmlsec.verified(signed, self.public_keyspec) self.assertTrue(len(refs) == 1) print("verified XML: %s" % etree.tostring(refs[0])) self.assertTrue(tbs.tag == refs[0].tag) set1 = set(etree.tostring(i, method='c14n') for i in root(tbs)) set2 = set(etree.tostring(i, method='c14n') for i in root(refs[0])) self.assertTrue(set1 == set2)
def test_sign_verify_SAML_assertion_unwrap2(self): """ Test signing a SAML assertion, and return verified data. """ case = self.cases['SAML_assertion1'] print("XML input :\n{}\n\n".format(case.as_buf('in.xml'))) tbs = case.as_etree('in.xml') signed = xmlsec.sign(tbs, key_spec=self.private_keyspec, cert_spec=self.public_keyspec) refs = xmlsec.verified(signed, self.public_keyspec) self.assertTrue(len(refs) == 1) print("verified XML: %s" % etree.tostring(refs[0])) self.assertTrue(tbs.tag == refs[0].tag) set1 = set(etree.tostring(i, method='c14n') for i in root(tbs)) set2 = set(etree.tostring(i, method='c14n') for i in root(refs[0])) self.assertTrue(set1 == set2)
schema().assertValid(t) except DocumentInvalid, ex: traceback.print_exc() log.debug("schema validation failed on '%s': %s" % ( base_url, _e(ex.error_log, m=base_url))) raise MetadataException("schema validation failed") except Exception, ex: # log.debug(_e(schema().error_log)) log.error(ex) if fail_on_error: raise ex return None if key is not None: try: log.debug("verifying signature using %s" % key) refs = xmlsec.verified(t, key) if len(refs) != 1: raise MetadataException( "XML metadata contains %d signatures - exactly 1 is required" % len(refs)) t = refs[0] # prevent wrapping attacks except Exception, ex: tb = traceback.format_exc() print tb log.error(ex) return None return t def _index_entity(self, e): #log.debug("adding %s to index" % e.get('entityID')) if 'ID' in e.attrib:
# Having removed the invalid entities this should now never happen... schema().assertValid(t) except DocumentInvalid, ex: traceback.print_exc() log.debug("schema validation failed on '%s': %s" % (base_url, _e(ex.error_log, m=base_url))) raise MetadataException("schema validation failed") except Exception, ex: #log.debug(_e(schema().error_log)) log.error(ex) if fail_on_error: raise ex return None if key is not None: try: log.debug("verifying signature using %s" % key) refs = xmlsec.verified(t, key) if len(refs) != 1: raise MetadataException("XML metadata contains %d signatures - exactly 1 is required" % len(refs)) t = refs[0] # prevent wrapping attacks except Exception, ex: tb = traceback.format_exc() print tb log.error(ex) return None return t def _index_entity(self, e): #log.debug("adding %s to index" % e.get('entityID')) if 'ID' in e.attrib: del e.attrib['ID']