def sign_file(xml_file, root_id, assert_id, key_file, cert_file): """sign *xml_file* with *key_file* and include content of *cert_file*. *xml_file* can be a file, a filename string or an HTTP/FTP url. *key_file* contains the PEM encoded private key. It must be a filename string. *cert_file* contains a PEM encoded certificate (corresponding to *key_file*), included as `X509Data` in the dynamically created `Signature` template. """ # template aware infrastructure from dm.xmlsec.binding.tmpl import parse, Element, SubElement, \ fromstring, XML from dm.xmlsec.binding.tmpl import Signature doc = parse(xml_file) xmlsec.addIDs(doc.getroot(),['ID']) assertion = doc.findall('saml:Assertion', {"saml": "urn:oasis:names:tc:SAML:2.0:assertion"})[0] assertion_xml = sign_xml(tostring(assertion), assert_id, key_file, cert_file) assertion_doc = fromstring(assertion_xml) doc.getroot().remove(doc.findall("saml:Assertion", {"saml": "urn:oasis:names:tc:SAML:2.0:assertion"})[0]) doc.getroot().insert(0, assertion_doc) return sign_xml(tostring(doc), root_id, key_file, cert_file)
def sign_envelope(envelope, key_file, password=None): """Sign the given soap request with the given key""" doc = etree.fromstring(envelope) body = get_body(doc) queue = SignQueue() queue.push_and_mark(body) security_node = ensure_security_header(doc, queue) security_token_node = create_binary_security_token(key_file) signature_node = Signature( xmlsec.TransformExclC14N, xmlsec.TransformRsaSha1) security_node.append(security_token_node) security_node.append(signature_node) queue.insert_references(signature_node) key_info = create_key_info_node(security_token_node) signature_node.append(key_info) # Sign the generated xml xmlsec.addIDs(doc, ['Id']) dsigCtx = xmlsec.DSigCtx() dsigCtx.signKey = xmlsec.Key.load( key_file, xmlsec.KeyDataFormatPem, password, ) dsigCtx.sign(signature_node) return etree.tostring(doc)
def sign_envelope(envelope, key_file): """Sign the given soap request with the given key""" doc = etree.fromstring(envelope) body = get_body(doc) queue = SignQueue() queue.push_and_mark(body) security_node = ensure_security_header(doc, queue) security_token_node = create_binary_security_token(key_file) queue.push_and_mark(security_token_node) signature_node = Signature(xmlsec.TransformExclC14N, xmlsec.TransformRsaSha1) security_node.append(security_token_node) security_node.append(signature_node) queue.insert_references(signature_node) key_info = create_key_info_node(security_token_node) signature_node.append(key_info) # Sign the generated xml xmlsec.addIDs(doc, ['Id']) dsigCtx = xmlsec.DSigCtx() dsigCtx.signKey = xmlsec.Key.load(key_file, xmlsec.KeyDataFormatPem, None) dsigCtx.sign(signature_node) return etree.tostring(doc)
def testSignedHttpPostBinding(self): """ Test to use the binding: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST To sign a samlp:AuthnRequest you need to have a private key set for the service provider. To verify the assertion is signed correct you can also use the xmlsec1 command line tool: xmlsec1 --verify --id-attr:ID AuthnRequest --trusted-pem tests/certs/example.com/example.crt authn_signed_assertion.xml """ filename = join(dirname(__file__), '..', '..', '..', 'settings', 'example_settings_http_post_binding.json') stream = open(filename, 'r') settings = json.load(stream) stream.close() settings = OneLogin_Saml2_Settings(settings) authn_request = OneLogin_Saml2_Authn_Request(settings) authn_request_encoded = authn_request.get_request() decoded = b64decode(authn_request_encoded) inflated = decompress(decoded, -15) sample_output_directory = join(dirname(__file__), '..', '..', '..', 'sample_output') if not os.path.exists(sample_output_directory): os.makedirs(sample_output_directory) with open( join(dirname(__file__), '..', '..', '..', 'sample_output/authn_signed_assertion.xml'), 'wb') as f: f.write(inflated) # Turn the inflated xml (which is just a string) into a in memory XML document doc = fromstring(inflated) # Verification of enveloped signature node = doc.find(".//{%s}Signature" % xmlsec.DSigNs) key_file = join(dirname(__file__), '..', '..', '..', 'certs/example.com', 'example.pubkey') dsigCtx = xmlsec.DSigCtx() signKey = xmlsec.Key.load(key_file, xmlsec.KeyDataFormatPem, None) signKey.name = 'example.pubkey' # Note: the assignment below effectively copies the key dsigCtx.signKey = signKey # Add ID attributes different from xml:id # See the Notes on https://pypi.python.org/pypi/dm.xmlsec.binding/1.3.2 xmlsec.addIDs(doc, ["ID"]) # This raises an exception if the document does not verify dsigCtx.verify(node)
def testSignedHttpPostBinding(self): """ Test to use the binding: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST To sign a samlp:AuthnRequest you need to have a private key set for the service provider. To verify the assertion is signed correct you can also use the xmlsec1 command line tool: xmlsec1 --verify --id-attr:ID AuthnRequest --trusted-pem tests/certs/example.com/example.crt authn_signed_assertion.xml """ filename = join(dirname(__file__), '..', '..', '..', 'settings', 'example_settings_http_post_binding.json') stream = open(filename, 'r') settings = json.load(stream) stream.close() settings = OneLogin_Saml2_Settings(settings) authn_request = OneLogin_Saml2_Authn_Request(settings) authn_request_encoded = authn_request.get_request() decoded = b64decode(authn_request_encoded) inflated = decompress(decoded, -15) sample_output_directory = join(dirname(__file__), '..', '..', '..', 'sample_output') if not os.path.exists(sample_output_directory): os.makedirs(sample_output_directory) with open(join(dirname(__file__), '..', '..', '..', 'sample_output/authn_signed_assertion.xml'), 'wb') as f: f.write(inflated) # Turn the inflated xml (which is just a string) into a in memory XML document doc = fromstring(inflated) # Verification of enveloped signature node = doc.find(".//{%s}Signature" % xmlsec.DSigNs) key_file = join(dirname(__file__), '..', '..', '..', 'certs/example.com', 'example.pubkey') dsigCtx = xmlsec.DSigCtx() signKey = xmlsec.Key.load(key_file, xmlsec.KeyDataFormatPem, None) signKey.name = 'example.pubkey' # Note: the assignment below effectively copies the key dsigCtx.signKey = signKey # Add ID attributes different from xml:id # See the Notes on https://pypi.python.org/pypi/dm.xmlsec.binding/1.3.2 xmlsec.addIDs(doc, ["ID"]) # This raises an exception if the document does not verify dsigCtx.verify(node)
def verify_envelope(reply, key_file): """Verify that the given soap request is signed with the certificate""" doc = etree.fromstring(reply) node = doc.find(".//{%s}Signature" % xmlsec.DSigNs) if node is None: raise CertificationError("No signature node found") dsigCtx = xmlsec.DSigCtx() xmlsec.addIDs(doc, ['Id']) signKey = xmlsec.Key.load(key_file, xmlsec.KeyDataFormatPem) signKey.name = os.path.basename(key_file) dsigCtx.signKey = signKey try: dsigCtx.verify(node) except xmlsec.VerificationError: return False return True
def finalize(self): r = super(_BindingDOMSupportSignatureExtension, self).finalize() sr = self._signature_requests if sr is None: return r # perform all signature requests in reverse order from lxml.etree import parse, tostring from dm.xmlsec.binding import addIDs from StringIO import StringIO doc = self.document().toxml() doc_tree = parse(StringIO(doc)) addIDs(doc_tree.getroot(), ['ID']) while sr: r, i = sr.pop() r.sign(i, doc_tree) signed_doc = tostring(doc_tree) # interestingly: I must use "xml.dom.mindom" for parsing # ``pyxb.utils.domutils.StringToDOM`` does not work from xml.dom.minidom import parseString self.__document = parseString(signed_doc)
def verify_envelope(reply, key_file, password=None): """Verify that the given soap request is signed with the certificate""" doc = etree.fromstring(reply) node = doc.find(".//{%s}Signature" % xmlsec.DSigNs) if node is None: raise CertificationError("No signature node found") dsigCtx = xmlsec.DSigCtx() xmlsec.addIDs(doc, ['Id']) signKey = xmlsec.Key.load( key_file, xmlsec.KeyDataFormatPem, password, ) signKey.name = os.path.basename(key_file) dsigCtx.signKey = signKey try: dsigCtx.verify(node) except xmlsec.VerificationError, e: return e.args[1] == 2
def sign_file(xml_file, root_id, assertion_id,key_file, cert_file): #import pdb;pdb.set_trace() """sign *xml_file* with *key_file* and include content of *cert_file*. *xml_file* can be a file, a filename string or an HTTP/FTP url. *key_file* contains the PEM encoded private key. It must be a filename string. *cert_file* contains a PEM encoded certificate (corresponding to *key_file*), included as `X509Data` in the dynamically created `Signature` template. """ # template aware infrastructure from dm.xmlsec.binding.tmpl import parse, Element, SubElement, fromstring, XML from dm.xmlsec.binding.tmpl import Signature doc = parse(xml_file) xmlsec.addIDs(doc.getroot(),['ID']) signature = Signature(xmlsec.TransformExclC14N, xmlsec.TransformRsaSha1 ) doc.getroot().insert(0, signature) ref = signature.addReference(xmlsec.TransformSha1,None,'#'+root_id) #ref = signature.addReference(xmlsec.TransformSha1) ref.addTransform(xmlsec.TransformEnveloped) ref.addTransform(xmlsec.TransformExclC14N) key_info = signature.ensureKeyInfo() key_info.addX509Data() # now what we already know dsigCtx = xmlsec.DSigCtx() # Note: we do not provide read access to `dsigCtx.signKey`. # Therefore, unlike the `xmlsec` example, we must set the certificate signKey = xmlsec.Key.load(key_file, xmlsec.KeyDataFormatPem, None) signKey.loadCert(cert_file, xmlsec.KeyDataFormatPem) # Note: the assignment below effectively copies the key dsigCtx.signKey = signKey dsigCtx.sign(signature) return tostring(doc)
def verify_signatures(doc, val, keyname=None, context=None): """verify all signatures contained in *doc* and update *val*. *doc* is a string containing an XML document. *val* is its ``pyxb`` binding. *keyname*, if given, identifies the key that has verified the enclosing context, e.g. the transport. *context* is a signature context. Default: ``default_verify_context`` The function recursively descends *val* and tries to verify the signature for each encountered ``Signable`` instance. A ``VerifyError`` is raised when the signature verification fails. """ from lxml.etree import parse from StringIO import StringIO from dm.xmlsec.binding import addIDs def verify(node, keyname): """verify *node* and (recursively) its decendents.""" if isinstance(node, Signable): # this object is signable if getattr(node, node.S_SIGNATURE_ATTRIBUTE) is not None: # has its own signature -- verify it keyname = node.S_GET_KEYNAME() context.verify(dp, getattr(node, node.S_ID_ATTRIBUTE), keyname) node.set_signature_verification(keyname) # recurse ss = getattr(node, '_symbolSet', None) if ss is None: return # node._symbolSet returns a map eu --> list(child) for cl in ss().values(): for c in cl: verify(c, keyname) dp = parse(StringIO(doc)) if context is None: context = default_verify_context addIDs(dp.getroot(), ['ID']) verify(val, keyname)
def sign_envelope(envelope, key_file, add_to_queue=None): """Sign the given soap request body with the given key. An optional add_to_queue callable can be passed to add additional elements to the signing queue. This function gets passed the document tree and should return a collection of Elements.""" doc = etree.fromstring(envelope) body = get_body(doc) queue = SignQueue() queue.push_and_mark(body) if add_to_queue: if not hasattr(add_to_queue, '__call__'): raise ValueError('`zadd_to_queue` kwarg must be a callable') extra_sign_queue = add_to_queue(doc) if not hasattr(extra_sign_queue, '__iter__'): raise ValueError('`add_to_queue` must return an iterable value') for el in extra_sign_queue: queue.push_and_mark(el) security_node = ensure_security_header(doc, queue) security_token_node = create_binary_security_token(key_file) signature_node = Signature(xmlsec.TransformExclC14N, xmlsec.TransformRsaSha1) security_node.append(security_token_node) security_node.append(signature_node) queue.insert_references(signature_node) key_info = create_key_info_node(security_token_node) signature_node.append(key_info) # Sign the generated xml xmlsec.addIDs(doc, ['Id']) dsigCtx = xmlsec.DSigCtx() dsigCtx.signKey = xmlsec.Key.load(key_file, xmlsec.KeyDataFormatPem, None) dsigCtx.sign(signature_node) return etree.tostring(doc)
def validate_sign(xml, cert=None, fingerprint=None, validatecert=False, debug=False): """ Validates a signature (Message or Assertion). :param xml: The element we should validate :type: string | Document :param cert: The pubic cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool """ try: if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, Element): xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP) ) xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML) ) xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, basestring): elem = fromstring(str(xml)) else: raise Exception('Error parsing xml string') xmlsec.initialize() if debug: xmlsec.set_error_callback(print_xmlsec_errors) xmlsec.addIDs(elem, ["ID"]) signature_nodes = OneLogin_Saml2_Utils.query(elem, '//ds:Signature') if len(signature_nodes) > 0: signature_node = signature_nodes[0] if (cert is None or cert == '') and fingerprint: x509_certificate_nodes = OneLogin_Saml2_Utils.query(signature_node, '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate') if len(x509_certificate_nodes) > 0: x509_certificate_node = x509_certificate_nodes[0] x509_cert_value = x509_certificate_node.text x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint(x509_cert_value) if fingerprint == x509_fingerprint_value: cert = OneLogin_Saml2_Utils.format_cert(x509_cert_value) if cert is None or cert == '': return False dsig_ctx = xmlsec.DSigCtx() file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) if validatecert: mngr = xmlsec.KeysMngr() mngr.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem, xmlsec.KeyDataTypeTrusted) dsig_ctx = xmlsec.DSigCtx(mngr) else: dsig_ctx = xmlsec.DSigCtx() dsig_ctx.signKey = xmlsec.Key.load(file_cert.name, xmlsec.KeyDataFormatCertPem, None) file_cert.close() dsig_ctx.setEnabledKeyData([xmlsec.KeyDataX509]) dsig_ctx.verify(signature_node) return True else: return False except Exception: return False
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). :param xml: The element we should sign :type: string | Document :param key: The private key :type: string :param cert: The public :type: string :param debug: Activate the xmlsec debug :type: bool :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') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(xml.encode('utf-8')) elif isinstance(xml, Element): xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP)) xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML)) xml = xml.toxml() elem = fromstring(xml.encode('utf-8')) elif isinstance(xml, basestring): elem = fromstring(xml.encode('utf-8')) else: raise Exception('Error parsing xml string') error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.TransformDsaSha1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.TransformRsaSha1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.TransformRsaSha256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.TransformRsaSha384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.TransformRsaSha512 } sign_algorithm_transform = sign_algorithm_transform_map.get( sign_algorithm, xmlsec.TransformRsaSha1) signature = Signature(xmlsec.TransformExclC14N, sign_algorithm_transform, nsPrefix='ds') issuer = OneLogin_Saml2_Utils.query(elem, '//saml:Issuer') if len(issuer) > 0: issuer = issuer[0] issuer.addnext(signature) elem_to_sign = issuer.getparent() else: entity_descriptor = OneLogin_Saml2_Utils.query( elem, '//md:EntityDescriptor') if len(entity_descriptor) > 0: elem.insert(0, signature) else: elem[0].insert(0, signature) elem_to_sign = elem elem_id = elem_to_sign.get('ID', None) if elem_id is not None: if elem_id: elem_id = '#' + elem_id else: generated_id = generated_id = OneLogin_Saml2_Utils.generate_unique_id( ) elem_id = '#' + generated_id elem_to_sign.attrib['ID'] = generated_id xmlsec.addIDs(elem_to_sign, ["ID"]) 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) if elem_id: ref.attrib['URI'] = elem_id ref.addTransform(xmlsec.TransformEnveloped) ref.addTransform(xmlsec.TransformExclC14N) key_info = signature.ensureKeyInfo() key_info.addX509Data() dsig_ctx = xmlsec.DSigCtx() sign_key = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem, None) file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) sign_key.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem) file_cert.close() dsig_ctx.signKey = sign_key dsig_ctx.sign(signature) return tostring(elem, encoding='unicode').encode('utf-8') newdoc = parseString( tostring(elem, encoding='unicode').encode('utf-8')) return newdoc.saveXML(newdoc.firstChild)
def validate_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False, xpath=None): """ Validates a signature (Message or Assertion). :param xml: The element we should validate :type: string | Document :param cert: The pubic cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool :param xpath: The xpath of the signed element :type: string """ try: if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(xml.encode('utf-8')) elif isinstance(xml, Element): xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP)) xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML)) xml = xml.toxml() elem = fromstring(xml.encode('utf-8')) elif isinstance(xml, basestring): elem = fromstring(xml.encode('utf-8')) else: raise Exception('Error parsing xml string') if debug: xmlsec.set_error_callback(print_xmlsec_errors) xmlsec.addIDs(elem, ["ID"]) if xpath: signature_nodes = OneLogin_Saml2_Utils.query(elem, xpath) else: signature_nodes = OneLogin_Saml2_Utils.query( elem, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH) if len(signature_nodes) == 0: signature_nodes = OneLogin_Saml2_Utils.query( elem, OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH) if len(signature_nodes) == 1: signature_node = signature_nodes[0] return OneLogin_Saml2_Utils.validate_node_sign( signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug) else: return False except Exception: return False
def validate_node_sign(signature_node, elem, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False): """ Validates a signature node. :param signature_node: The signature node :type: Node :param xml: The element we should validate :type: Document :param cert: The public cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool """ try: if debug: xmlsec.set_error_callback(print_xmlsec_errors) xmlsec.addIDs(elem, ["ID"]) if (cert is None or cert == '') and fingerprint: x509_certificate_nodes = OneLogin_Saml2_Utils.query( signature_node, '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate') if len(x509_certificate_nodes) > 0: x509_certificate_node = x509_certificate_nodes[0] x509_cert_value = x509_certificate_node.text x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint( x509_cert_value, fingerprintalg) if fingerprint == x509_fingerprint_value: cert = OneLogin_Saml2_Utils.format_cert( x509_cert_value) # Check if Reference URI is empty # reference_elem = OneLogin_Saml2_Utils.query(signature_node, '//ds:Reference') # if len(reference_elem) > 0: # if reference_elem[0].get('URI') == '': # reference_elem[0].set('URI', '#%s' % signature_node.getparent().get('ID')) if cert is None or cert == '': return False file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) if validatecert: mngr = xmlsec.KeysMngr() mngr.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem, xmlsec.KeyDataTypeTrusted) dsig_ctx = xmlsec.DSigCtx(mngr) else: dsig_ctx = xmlsec.DSigCtx() dsig_ctx.signKey = xmlsec.Key.load(file_cert.name, xmlsec.KeyDataFormatCertPem, None) file_cert.close() dsig_ctx.setEnabledKeyData([xmlsec.KeyDataX509]) dsig_ctx.verify(signature_node) return True except Exception: return False
def validate_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False, xpath=None, multicerts=None): """ Validates a signature (Message or Assertion). :param xml: The element we should validate :type: string | Document :param cert: The pubic cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool :param xpath: The xpath of the signed element :type: string :param multicerts: Multiple public certs :type: list :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean """ if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(str(xml), forbid_dtd=True) elif isinstance(xml, Element): xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP) ) xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML) ) xml = xml.toxml() elem = fromstring(str(xml), forbid_dtd=True) elif isinstance(xml, basestring): elem = fromstring(str(xml), forbid_dtd=True) else: raise Exception('Error parsing xml string') error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) xmlsec.addIDs(elem, ["ID"]) if xpath: signature_nodes = OneLogin_Saml2_Utils.query(elem, xpath) else: signature_nodes = OneLogin_Saml2_Utils.query(elem, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH) if len(signature_nodes) == 0: signature_nodes = OneLogin_Saml2_Utils.query(elem, OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH) if len(signature_nodes) == 1: signature_node = signature_nodes[0] if not multicerts: return OneLogin_Saml2_Utils.validate_node_sign(signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug, raise_exceptions=True) else: # If multiple certs are provided, I may ignore cert and # fingerprint provided by the method and just check the # certs multicerts fingerprint = fingerprintalg = None for cert in multicerts: if OneLogin_Saml2_Utils.validate_node_sign(signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, False, raise_exceptions=False): return True raise OneLogin_Saml2_ValidationError('Signature validation failed. SAML Response rejected.') else: raise OneLogin_Saml2_ValidationError('Expected exactly one signature node; got {}.'.format(len(signature_nodes)), OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_SIGNATURES)
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). :param xml: The element we should sign :type: string | Document :param key: The private key :type: string :param cert: The public :type: string :param debug: Activate the xmlsec debug :type: bool :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') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(xml.encode('utf-8'), forbid_dtd=True) elif isinstance(xml, Element): xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP) ) xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML) ) xml = xml.toxml() elem = fromstring(xml.encode('utf-8'), forbid_dtd=True) elif isinstance(xml, basestring): elem = fromstring(xml.encode('utf-8'), forbid_dtd=True) else: raise Exception('Error parsing xml string') error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.TransformDsaSha1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.TransformRsaSha1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.TransformRsaSha256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.TransformRsaSha384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.TransformRsaSha512 } sign_algorithm_transform = sign_algorithm_transform_map.get(sign_algorithm, xmlsec.TransformRsaSha1) signature = Signature(xmlsec.TransformExclC14N, sign_algorithm_transform, nsPrefix='ds') issuer = OneLogin_Saml2_Utils.query(elem, '//saml:Issuer') if len(issuer) > 0: issuer = issuer[0] issuer.addnext(signature) elem_to_sign = issuer.getparent() else: entity_descriptor = OneLogin_Saml2_Utils.query(elem, '//md:EntityDescriptor') if len(entity_descriptor) > 0: elem.insert(0, signature) else: elem[0].insert(0, signature) elem_to_sign = elem elem_id = elem_to_sign.get('ID', None) if elem_id is not None: if elem_id: elem_id = '#' + elem_id else: generated_id = generated_id = OneLogin_Saml2_Utils.generate_unique_id() elem_id = '#' + generated_id elem_to_sign.attrib['ID'] = generated_id xmlsec.addIDs(elem_to_sign, ["ID"]) 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) if elem_id: ref.attrib['URI'] = elem_id ref.addTransform(xmlsec.TransformEnveloped) ref.addTransform(xmlsec.TransformExclC14N) key_info = signature.ensureKeyInfo() key_info.addX509Data() dsig_ctx = xmlsec.DSigCtx() sign_key = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem, None) file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) sign_key.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem) file_cert.close() dsig_ctx.signKey = sign_key dsig_ctx.sign(signature) return tostring(elem, encoding='unicode').encode('utf-8')
def validate_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False): """ Validates a signature (Message or Assertion). :param xml: The element we should validate :type: string | Document :param cert: The pubic cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool """ try: if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, Element): xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP)) xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML)) xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, basestring): elem = fromstring(str(xml)) else: raise Exception('Error parsing xml string') xmlsec.initialize() if debug: xmlsec.set_error_callback(print_xmlsec_errors) xmlsec.addIDs(elem, ["ID"]) signature_nodes = OneLogin_Saml2_Utils.query( elem, '/samlp:Response/ds:Signature') if not len(signature_nodes) > 0: signature_nodes += OneLogin_Saml2_Utils.query( elem, '/samlp:Response/saml:EncryptedAssertion/saml:Assertion/ds:Signature' ) signature_nodes += OneLogin_Saml2_Utils.query( elem, '/samlp:Response/saml:Assertion/ds:Signature') if len(signature_nodes) == 1: signature_node = signature_nodes[0] return OneLogin_Saml2_Utils.validate_node_sign( signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug) else: return False except Exception: return False
def validate_metadata_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False): """ Validates a signature of a EntityDescriptor. :param xml: The element we should validate :type: string | Document :param cert: The pubic cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean """ if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(str(xml), forbid_dtd=True) elif isinstance(xml, Element): xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_MD), 'xmlns:md', unicode(OneLogin_Saml2_Constants.NS_MD) ) xml = xml.toxml() elem = fromstring(str(xml), forbid_dtd=True) elif isinstance(xml, basestring): elem = fromstring(str(xml), forbid_dtd=True) else: raise Exception('Error parsing xml string') error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) xmlsec.addIDs(elem, ["ID"]) signature_nodes = OneLogin_Saml2_Utils.query(elem, '/md:EntitiesDescriptor/ds:Signature') if len(signature_nodes) == 0: signature_nodes += OneLogin_Saml2_Utils.query(elem, '/md:EntityDescriptor/ds:Signature') if len(signature_nodes) == 0: signature_nodes += OneLogin_Saml2_Utils.query(elem, '/md:EntityDescriptor/md:SPSSODescriptor/ds:Signature') signature_nodes += OneLogin_Saml2_Utils.query(elem, '/md:EntityDescriptor/md:IDPSSODescriptor/ds:Signature') if len(signature_nodes) > 0: for signature_node in signature_nodes: OneLogin_Saml2_Utils.validate_node_sign(signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug, raise_exceptions=True) return True else: raise Exception('Could not validate metadata signature: No signature nodes found.')
def add_sign_with_id(xml, uid, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1, digest_algorithm=OneLogin_Saml2_Constants.SHA1): # thanks to https://github.com/onelogin/python-saml/pull/78/files for the help. credit to @tachang xmlsec.initialize() xmlsec.set_error_callback(print_xmlsec_errors) # sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.TransformDsaSha1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.TransformRsaSha1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.TransformRsaSha256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.TransformRsaSha384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.TransformRsaSha512 } sign_algorithm_transform = sign_algorithm_transform_map.get(sign_algorithm, xmlsec.TransformRsaSha1) signature = Signature(xmlsec.TransformExclC14N, sign_algorithm_transform) if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): doc = xml elif isinstance(xml, Document): xml = xml.toxml() doc= fromstring(str(xml)) elif isinstance(xml, Element): xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP) ) xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML) ) xml = xml.toxml() doc = fromstring(str(xml)) elif isinstance(xml, basestring): doc = fromstring(str(xml)) else: raise Exception('Error parsing xml string') # # ID attributes different from xml:id must be made known by the application through a call # # to the addIds(node, ids) function defined by xmlsec. xmlsec.addIDs(doc, ['ID']) doc.insert(0, signature) digest_algorithm_transform_map = { OneLogin_Saml2_Constants.SHA1: xmlsec.TransformSha1, OneLogin_Saml2_Constants.SHA256: xmlsec.TransformSha256 } digest_algorithm_transform = digest_algorithm_transform_map.get(digest_algorithm, xmlsec.TransformRsaSha1) ref = signature.addReference(digest_algorithm_transform, uri="#%s" % uid) ref.addTransform(xmlsec.TransformEnveloped) ref.addTransform(xmlsec.TransformExclC14N) key_info = signature.ensureKeyInfo() key_info.addKeyName() key_info.addX509Data() dsig_ctx = xmlsec.DSigCtx() sign_key = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem, None) from tempfile import NamedTemporaryFile cert_file = NamedTemporaryFile(delete=True) cert_file.write(cert) cert_file.seek(0) sign_key.loadCert(cert_file.name, xmlsec.KeyDataFormatPem) dsig_ctx.signKey = sign_key # # Note: the assignment below effectively copies the key dsig_ctx.sign(signature) newdoc = parseString(etree.tostring(doc)) return newdoc.saveXML(newdoc.firstChild)
def validate_node_sign( signature_node, elem, cert=None, fingerprint=None, fingerprintalg="sha1", validatecert=False, debug=False ): """ Validates a signature node. :param signature_node: The signature node :type: Node :param xml: The element we should validate :type: Document :param cert: The pubic cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool """ try: xmlsec.initialize() if debug: xmlsec.set_error_callback(print_xmlsec_errors) xmlsec.addIDs(elem, ["ID"]) if (cert is None or cert == "") and fingerprint: x509_certificate_nodes = OneLogin_Saml2_Utils.query( signature_node, "//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate" ) if len(x509_certificate_nodes) > 0: x509_certificate_node = x509_certificate_nodes[0] x509_cert_value = x509_certificate_node.text x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint( x509_cert_value, fingerprintalg ) if fingerprint == x509_fingerprint_value: cert = OneLogin_Saml2_Utils.format_cert(x509_cert_value) if cert is None or cert == "": return False # Check if Reference URI is empty reference_elem = OneLogin_Saml2_Utils.query(signature_node, "//ds:Reference") if len(reference_elem) > 0: if reference_elem[0].get("URI") == "": reference_elem[0].set("URI", "#%s" % signature_node.getparent().get("ID")) dsig_ctx = xmlsec.DSigCtx() file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) if validatecert: mngr = xmlsec.KeysMngr() mngr.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem, xmlsec.KeyDataTypeTrusted) dsig_ctx = xmlsec.DSigCtx(mngr) else: dsig_ctx = xmlsec.DSigCtx() dsig_ctx.signKey = xmlsec.Key.load(file_cert.name, xmlsec.KeyDataFormatCertPem, None) file_cert.close() dsig_ctx.setEnabledKeyData([xmlsec.KeyDataX509]) dsig_ctx.verify(signature_node) return True except Exception: return False
def validate_sign(xml, cert=None, fingerprint=None, fingerprintalg="sha1", validatecert=False, debug=False): """ Validates a signature (Message or Assertion). :param xml: The element we should validate :type: string | Document :param cert: The pubic cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool """ try: if xml is None or xml == "": raise Exception("Empty string supplied as input") elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, Element): xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAMLP), "xmlns:samlp", unicode(OneLogin_Saml2_Constants.NS_SAMLP), ) xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAML), "xmlns:saml", unicode(OneLogin_Saml2_Constants.NS_SAML) ) xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, basestring): elem = fromstring(str(xml)) else: raise Exception("Error parsing xml string") xmlsec.initialize() if debug: xmlsec.set_error_callback(print_xmlsec_errors) xmlsec.addIDs(elem, ["ID"]) signature_nodes = OneLogin_Saml2_Utils.query(elem, "/samlp:Response/ds:Signature") if not len(signature_nodes) > 0: signature_nodes += OneLogin_Saml2_Utils.query( elem, "/samlp:Response/saml:EncryptedAssertion/saml:Assertion/ds:Signature" ) signature_nodes += OneLogin_Saml2_Utils.query(elem, "/samlp:Response/saml:Assertion/ds:Signature") if len(signature_nodes) == 1: signature_node = signature_nodes[0] return OneLogin_Saml2_Utils.validate_node_sign( signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug ) else: return False except Exception: return False
def validate_node_sign(signature_node, elem, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False): """ Validates a signature node. :param signature_node: The signature node :type: Node :param xml: The element we should validate :type: Document :param cert: The public cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean """ error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) xmlsec.addIDs(elem, ["ID"]) if (cert is None or cert == '') and fingerprint: x509_certificate_nodes = OneLogin_Saml2_Utils.query( signature_node, '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate') if len(x509_certificate_nodes) > 0: x509_certificate_node = x509_certificate_nodes[0] x509_cert_value = x509_certificate_node.text x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint( x509_cert_value, fingerprintalg) if fingerprint == x509_fingerprint_value: cert = OneLogin_Saml2_Utils.format_cert(x509_cert_value) # Check if Reference URI is empty # reference_elem = OneLogin_Saml2_Utils.query(signature_node, '//ds:Reference') # if len(reference_elem) > 0: # if reference_elem[0].get('URI') == '': # reference_elem[0].set('URI', '#%s' % signature_node.getparent().get('ID')) if cert is None or cert == '': raise OneLogin_Saml2_Error( 'Could not validate node signature: No certificate provided.', OneLogin_Saml2_Error.CERT_NOT_FOUND) file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) if validatecert: mngr = xmlsec.KeysMngr() mngr.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem, xmlsec.KeyDataTypeTrusted) dsig_ctx = xmlsec.DSigCtx(mngr) else: dsig_ctx = xmlsec.DSigCtx() dsig_ctx.signKey = xmlsec.Key.load(file_cert.name, xmlsec.KeyDataFormatCertPem, None) file_cert.close() dsig_ctx.setEnabledKeyData([xmlsec.KeyDataX509]) try: dsig_ctx.verify(signature_node) except Exception as err: raise OneLogin_Saml2_ValidationError( 'Signature validation failed. SAML Response rejected. %s', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE, err.__str__()) return True
def add_sign_with_id(xml, uid, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1, digest_algorithm=OneLogin_Saml2_Constants.SHA1): # thanks to https://github.com/onelogin/python-saml/pull/78/files for the help. credit to @tachang xmlsec.initialize() xmlsec.set_error_callback(print_xmlsec_errors) # sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.TransformDsaSha1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.TransformRsaSha1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.TransformRsaSha256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.TransformRsaSha384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.TransformRsaSha512 } sign_algorithm_transform = sign_algorithm_transform_map.get( sign_algorithm, xmlsec.TransformRsaSha1) signature = Signature(xmlsec.TransformExclC14N, sign_algorithm_transform) if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): doc = xml elif isinstance(xml, Document): xml = xml.toxml() doc = fromstring(str(xml)) elif isinstance(xml, Element): xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP)) xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML)) xml = xml.toxml() doc = fromstring(str(xml)) elif isinstance(xml, basestring): doc = fromstring(str(xml)) else: raise Exception('Error parsing xml string') # # ID attributes different from xml:id must be made known by the application through a call # # to the addIds(node, ids) function defined by xmlsec. xmlsec.addIDs(doc, ['ID']) doc.insert(0, signature) digest_algorithm_transform_map = { OneLogin_Saml2_Constants.SHA1: xmlsec.TransformSha1, OneLogin_Saml2_Constants.SHA256: xmlsec.TransformSha256 } digest_algorithm_transform = digest_algorithm_transform_map.get( digest_algorithm, xmlsec.TransformRsaSha1) ref = signature.addReference(digest_algorithm_transform, uri="#%s" % uid) ref.addTransform(xmlsec.TransformEnveloped) ref.addTransform(xmlsec.TransformExclC14N) key_info = signature.ensureKeyInfo() key_info.addKeyName() key_info.addX509Data() dsig_ctx = xmlsec.DSigCtx() sign_key = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem, None) from tempfile import NamedTemporaryFile cert_file = NamedTemporaryFile(delete=True) cert_file.write(cert) cert_file.seek(0) sign_key.loadCert(cert_file.name, xmlsec.KeyDataFormatPem) dsig_ctx.signKey = sign_key # # Note: the assignment below effectively copies the key dsig_ctx.sign(signature) newdoc = parseString(etree.tostring(doc)) return newdoc.saveXML(newdoc.firstChild)
def validate_node_sign(signature_node, elem, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False): """ Validates a signature node. :param signature_node: The signature node :type: Node :param xml: The element we should validate :type: Document :param cert: The public cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean """ error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) xmlsec.addIDs(elem, ["ID"]) if (cert is None or cert == '') and fingerprint: x509_certificate_nodes = OneLogin_Saml2_Utils.query(signature_node, '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate') if len(x509_certificate_nodes) > 0: x509_certificate_node = x509_certificate_nodes[0] x509_cert_value = OneLogin_Saml2_Utils.element_text(x509_certificate_node) x509_cert_value_formatted = OneLogin_Saml2_Utils.format_cert(x509_cert_value) x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint(x509_cert_value_formatted, fingerprintalg) if fingerprint == x509_fingerprint_value: cert = x509_cert_value_formatted # Check if Reference URI is empty # reference_elem = OneLogin_Saml2_Utils.query(signature_node, '//ds:Reference') # if len(reference_elem) > 0: # if reference_elem[0].get('URI') == '': # reference_elem[0].set('URI', '#%s' % signature_node.getparent().get('ID')) if cert is None or cert == '': raise OneLogin_Saml2_Error( 'Could not validate node signature: No certificate provided.', OneLogin_Saml2_Error.CERT_NOT_FOUND ) file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) if validatecert: mngr = xmlsec.KeysMngr() mngr.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem, xmlsec.KeyDataTypeTrusted) dsig_ctx = xmlsec.DSigCtx(mngr) else: dsig_ctx = xmlsec.DSigCtx() dsig_ctx.signKey = xmlsec.Key.load(file_cert.name, xmlsec.KeyDataFormatCertPem, None) file_cert.close() dsig_ctx.setEnabledKeyData([xmlsec.KeyDataX509]) try: dsig_ctx.verify(signature_node) except Exception as err: raise OneLogin_Saml2_ValidationError( 'Signature validation failed. SAML Response rejected. %s', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE, err.__str__() ) return True
def validate_metadata_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False): """ Validates a signature of a EntityDescriptor. :param xml: The element we should validate :type: string | Document :param cert: The pubic cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean """ if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, Element): xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_MD), 'xmlns:md', unicode(OneLogin_Saml2_Constants.NS_MD)) xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, basestring): elem = fromstring(str(xml)) else: raise Exception('Error parsing xml string') error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) xmlsec.addIDs(elem, ["ID"]) signature_nodes = OneLogin_Saml2_Utils.query( elem, '/md:EntitiesDescriptor/ds:Signature') if len(signature_nodes) == 0: signature_nodes += OneLogin_Saml2_Utils.query( elem, '/md:EntityDescriptor/ds:Signature') if len(signature_nodes) == 0: signature_nodes += OneLogin_Saml2_Utils.query( elem, '/md:EntityDescriptor/md:SPSSODescriptor/ds:Signature') signature_nodes += OneLogin_Saml2_Utils.query( elem, '/md:EntityDescriptor/md:IDPSSODescriptor/ds:Signature') if len(signature_nodes) > 0: for signature_node in signature_nodes: OneLogin_Saml2_Utils.validate_node_sign(signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug, raise_exceptions=True) return True else: raise Exception( 'Could not validate metadata signature: No signature nodes found.' )
def validate_metadata_sign( xml, cert=None, fingerprint=None, fingerprintalg="sha1", validatecert=False, debug=False ): """ Validates a signature of a EntityDescriptor. :param xml: The element we should validate :type: string | Document :param cert: The pubic cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool """ try: if xml is None or xml == "": raise Exception("Empty string supplied as input") elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, Element): xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_MD), "xmlns:md", unicode(OneLogin_Saml2_Constants.NS_MD) ) xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, basestring): elem = fromstring(str(xml)) else: raise Exception("Error parsing xml string") xmlsec.initialize() if debug: xmlsec.set_error_callback(print_xmlsec_errors) xmlsec.addIDs(elem, ["ID"]) signature_nodes = OneLogin_Saml2_Utils.query(elem, "/md:EntitiesDescriptor/ds:Signature") if len(signature_nodes) == 0: signature_nodes += OneLogin_Saml2_Utils.query(elem, "/md:EntityDescriptor/ds:Signature") if len(signature_nodes) == 0: signature_nodes += OneLogin_Saml2_Utils.query( elem, "/md:EntityDescriptor/md:SPSSODescriptor/ds:Signature" ) signature_nodes += OneLogin_Saml2_Utils.query( elem, "/md:EntityDescriptor/md:IDPSSODescriptor/ds:Signature" ) if len(signature_nodes) > 0: for signature_node in signature_nodes: if not OneLogin_Saml2_Utils.validate_node_sign( signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug ): return False return True else: return False except Exception: return False
def validate_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False, xpath=None, multicerts=None): """ Validates a signature (Message or Assertion). :param xml: The element we should validate :type: string | Document :param cert: The pubic cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool :param xpath: The xpath of the signed element :type: string :param multicerts: Multiple public certs :type: list :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean """ if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, Element): xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP)) xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML)) xml = xml.toxml() elem = fromstring(str(xml)) elif isinstance(xml, basestring): elem = fromstring(str(xml)) else: raise Exception('Error parsing xml string') error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) xmlsec.addIDs(elem, ["ID"]) if xpath: signature_nodes = OneLogin_Saml2_Utils.query(elem, xpath) else: signature_nodes = OneLogin_Saml2_Utils.query( elem, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH) if len(signature_nodes) == 0: signature_nodes = OneLogin_Saml2_Utils.query( elem, OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH) if len(signature_nodes) == 1: signature_node = signature_nodes[0] if not multicerts: return OneLogin_Saml2_Utils.validate_node_sign( signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug, raise_exceptions=True) else: # If multiple certs are provided, I may ignore cert and # fingerprint provided by the method and just check the # certs multicerts fingerprint = fingerprintalg = None for cert in multicerts: if OneLogin_Saml2_Utils.validate_node_sign( signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, False, raise_exceptions=False): return True raise OneLogin_Saml2_ValidationError( 'Signature validation failed. SAML Response rejected.') else: raise OneLogin_Saml2_ValidationError( 'Expected exactly one signature node; got {}.'.format( len(signature_nodes)), OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_SIGNATURES)
def validate_sign( xml, cert=None, fingerprint=None, fingerprintalg="sha1", validatecert=False, debug=False, xpath=None ): """ Validates a signature (Message or Assertion). :param xml: The element we should validate :type: string | Document :param cert: The pubic cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool :param xpath: The xpath of the signed element :type: string """ try: if xml is None or xml == "": raise Exception("Empty string supplied as input") elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(xml.encode("utf-8")) elif isinstance(xml, Element): xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAMLP), "xmlns:samlp", unicode(OneLogin_Saml2_Constants.NS_SAMLP), ) xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAML), "xmlns:saml", unicode(OneLogin_Saml2_Constants.NS_SAML) ) xml = xml.toxml() elem = fromstring(xml.encode("utf-8")) elif isinstance(xml, basestring): elem = fromstring(xml.encode("utf-8")) else: raise Exception("Error parsing xml string") if debug: xmlsec.set_error_callback(print_xmlsec_errors) xmlsec.addIDs(elem, ["ID"]) if xpath: signature_nodes = OneLogin_Saml2_Utils.query(elem, xpath) else: signature_nodes = OneLogin_Saml2_Utils.query(elem, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH) if len(signature_nodes) == 0: signature_nodes = OneLogin_Saml2_Utils.query(elem, OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH) if len(signature_nodes) == 1: signature_node = signature_nodes[0] return OneLogin_Saml2_Utils.validate_node_sign( signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug ) else: return False except Exception: return False