def sign_opaque_token_request(self, rendered_xml): root_element = etree.fromstring(rendered_xml) xmlsec.tree.add_ids(root_element, ["Id"]) # important! # refer to xml/opaque_token_request_unsigned.xml for example signature_element = root_element.xpath( '/soap:Envelope/soap:Header/wsse:Security/ds:Signature', namespaces=NAMESPACES)[0] key_path = self.file_path('saml_sp_key') assert key_path.isfile cer_path = self.file_path('saml_sp_cer') assert cer_path.isfile # Load private key (assuming that there is no password). key = xmlsec.Key.from_file(key_path, xmlsec.KeyFormat.PEM) assert key is not None # Load the certificate and add it to the key. key.load_cert_from_file(cer_path, xmlsec.KeyFormat.PEM) # Create a digital signature context (no key manager is needed). ctx = xmlsec.SignatureContext() ctx.key = key # Sign the template. ctx.sign(signature_element) # return a utf-8 encoded byte str # refer to xml/opaque_token_request_signed.xml for example return etree.tostring(root_element, pretty_print=True).decode('utf-8')
def assina_nfse(self, template): self._checar_certificado() key = xmlsec.Key.from_memory( self.chave_privada, format=xmlsec.constants.KeyDataFormatPem, password=self.senha, ) signature_node = xmlsec.template.create( template, c14n_method=consts.TransformInclC14N, sign_method=consts.TransformRsaSha1, ) template.append(signature_node) ref = xmlsec.template.add_reference(signature_node, consts.TransformSha1, uri="") xmlsec.template.add_transform(ref, consts.TransformEnveloped) xmlsec.template.add_transform(ref, consts.TransformInclC14N) ki = xmlsec.template.ensure_key_info(signature_node) xmlsec.template.add_x509_data(ki) ctx = xmlsec.SignatureContext() ctx.key = key ctx.key.load_cert_from_memory(self.cert, consts.KeyDataFormatPem) ctx.sign(signature_node) return etree.tostring(template, encoding=str)
def test_verify_with_pem_file(index): """Should verify a signed file using a key from a PEM file. """ # Load the XML document. template = parse_xml('sign%d-res.xml' % index) # Find the <Signature/> node. signature_node = xmlsec.tree.find_node(template, xmlsec.Node.SIGNATURE) assert signature_node is not None assert signature_node.tag.endswith(xmlsec.Node.SIGNATURE) # Create a digital signature context (no key manager is needed). ctx = xmlsec.SignatureContext() # Load the public key. filename = path.join(BASE_DIR, 'rsapub.pem') key = xmlsec.Key.from_file(filename, xmlsec.KeyFormat.PEM) assert key is not None # Set key name to the file name (note: this is just a test). key.name = path.basename(filename) # Set the key on the context. ctx.key = key assert ctx.key is not None assert ctx.key.name == path.basename(filename) # Verify the signature. assert ctx.verify(signature_node)
def sign_document_all(xml, key_path, cert_path): """ Signs all XML's <ds:Signature> nodes under the document root node. :param `etree.Element` xml: The XML to be signed. :param str key_path: Path to PEM key file. :param str cert_path: Path to PEM certificate file. :return: `etree.Element` to the signed document. Should be the same with the provided xml param. TODO: make sure we get all <ds:Signature> nodes in depth first order, otherwise we would break envolving signatures. Its not that it is not currently working, it is just without guaranteed order. """ # HACK inject a DTD preamble in to direct non-standard xml:id # resolution for <Reference URI = "#XXXX">. xml = prepend_dtd(xml) for signode in extract_signodes(xml): # Load Private Key and Public Certificate key = xmlsec.Key.from_file(key_path, xmlsec.KeyFormat.PEM) key.load_cert_from_file(cert_path, xmlsec.KeyFormat.PEM) # Create Crypto Context and load in Key/Cert ctx = xmlsec.SignatureContext() ctx.key = key ctx.sign(signode) return xml
def sign_document(xml, key_path, cert_path): """ Signs topmost XML <ds:Signature> node under the document root node. :param `etree.Element` xml: The XML to be signed. :param str key_path: Path to PEM key file. :param str cert_path: Path to PEM certificate file. :return: `etree.Element` to the signed document. Should be the same with the provided xml param. """ # HACK inject a DTD preamble in to direct non-standard xml:id # resolution for <Reference URI = "#XXXX">. xml = prepend_dtd(xml) signode = extract_signode(xml) # Load Private Key and Public Certificate key = xmlsec.Key.from_file(key_path, xmlsec.KeyFormat.PEM) key.load_cert_from_file(cert_path, xmlsec.KeyFormat.PEM) # Create Crypto Context and load in Key/Cert ctx = xmlsec.SignatureContext() ctx.key = key ctx.sign(signode) return xml
def test_sign_case4(): """Should sign a file using a dynamically created template, key from PEM and an X509 cert with custom ns.""" root = load_xml("sign4-in.xml") xmlsec.tree.add_ids(root, ["ID"]) elem_id = root.get('ID', None) if elem_id: elem_id = '#' + elem_id sign = xmlsec.template.create(root, consts.TransformExclC14N, consts.TransformRsaSha1, ns="ds") assert sign is not None root.append(sign) ref = xmlsec.template.add_reference(sign, consts.TransformSha1, uri=elem_id) xmlsec.template.add_transform(ref, consts.TransformEnveloped) xmlsec.template.add_transform(ref, consts.TransformExclC14N) ki = xmlsec.template.ensure_key_info(sign) xmlsec.template.add_x509_data(ki) ctx = xmlsec.SignatureContext() ctx.key = xmlsec.Key.from_file(rsakey, format=consts.KeyDataFormatPem) assert ctx.key is not None ctx.key.load_cert_from_file(rsacert, consts.KeyDataFormatPem) ctx.key.name = 'rsakey.pem' assert "rsakey.pem" == ctx.key.name ctx.sign(sign) assert load_xml("sign4-out.xml") == root
def _verify_envelope_with_key(envelope, key): soap_env = detect_soap_env(envelope) header = envelope.find(QName(soap_env, "Header")) if header is None: raise SignatureVerificationFailed() security = header.find(QName(ns.WSSE, "Security")) signature = security.find(QName(ns.DS, "Signature")) ctx = xmlsec.SignatureContext() # Find each signed element and register its ID with the signing context. refs = signature.xpath("ds:SignedInfo/ds:Reference", namespaces={"ds": ns.DS}) for ref in refs: # Get the reference URI and cut off the initial '#' referenced_id = ref.get("URI")[1:] referenced = envelope.xpath("//*[@wsu:Id='%s']" % referenced_id, namespaces={"wsu": ns.WSU})[0] ctx.register_id(referenced, "Id", ns.WSU) ctx.key = key try: ctx.verify(signature) except xmlsec.Error: # Sadly xmlsec gives us no details about the reason for the failure, so # we have nothing to pass on except that verification failed. raise SignatureVerificationFailed()
def sign_binary(msg, key, algorithm=xmlsec.Transform.RSA_SHA1, debug=False): """ Sign binary message :param msg: The element we should validate :type: bytes :param key: The private key :type: string :param debug: Activate the xmlsec debug :type: bool :return signed message :rtype str """ if isinstance(msg, str): msg = msg.encode('utf8') xmlsec.enable_debug_trace(debug) dsig_ctx = xmlsec.SignatureContext() dsig_ctx.key = xmlsec.Key.from_memory(key, xmlsec.KeyFormat.PEM, None) return dsig_ctx.sign_binary(compat.to_bytes(msg), algorithm)
def test_sign_binary_twice_not_possible(self): ctx = xmlsec.SignatureContext() ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem) data = self.load('sign6-in.bin') ctx.sign_binary(data, consts.TransformRsaSha1) with self.assertRaisesRegex(xmlsec.Error, 'Signature context already used; it is designed for one use only.'): ctx.sign_binary(data, consts.TransformRsaSha1)
def test_sign_case5(self): """Should sign a file using a dynamicaly created template, key from PEM file and an X509 certificate.""" root = self.load_xml("sign5-in.xml") sign = xmlsec.template.create(root, consts.TransformExclC14N, consts.TransformRsaSha1) self.assertIsNotNone(sign) root.append(sign) ref = xmlsec.template.add_reference(sign, consts.TransformSha1) xmlsec.template.add_transform(ref, consts.TransformEnveloped) ki = xmlsec.template.ensure_key_info(sign) x509 = xmlsec.template.add_x509_data(ki) xmlsec.template.x509_data_add_subject_name(x509) xmlsec.template.x509_data_add_certificate(x509) xmlsec.template.x509_data_add_ski(x509) x509_issuer_serial = xmlsec.template.x509_data_add_issuer_serial(x509) xmlsec.template.x509_issuer_serial_add_issuer_name(x509_issuer_serial, 'Test Issuer') xmlsec.template.x509_issuer_serial_add_serial_number(x509_issuer_serial, '1') ctx = xmlsec.SignatureContext() ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem) self.assertIsNotNone(ctx.key) ctx.key.load_cert_from_file(self.path('rsacert.pem'), consts.KeyDataFormatPem) ctx.key.name = 'rsakey.pem' self.assertEqual("rsakey.pem", ctx.key.name) ctx.sign(sign) self.assertEqual(self.load_xml("sign5-out.xml"), root)
def test_validate_binary_sign(self): ctx = xmlsec.SignatureContext() ctx.key = xmlsec.Key.from_file(self.path("rsakey.pem"), format=consts.KeyDataFormatPem) self.assertIsNotNone(ctx.key) ctx.key.name = 'rsakey.pem' self.assertEqual("rsakey.pem", ctx.key.name) ctx.verify_binary(self.load("sign6-in.bin"), consts.TransformRsaSha1, self.load("sign6-out.bin"))
def sign_envelope(envelope, keyfile, certfile, password=None): key = _make_sign_key(keyfile, certfile, password) reference_id = 'Reference' security = get_security_header(envelope) security.set(QName(ns.SOAP_ENV_11, 'mustUnderstand'), '1') x509type = 'http://docs.oasis-open.org/wss/2004/01/' \ 'oasis-200401-wss-x509-token-profile-1.0#X509v3' encoding_type = "http://docs.oasis-open.org/wss/2004/01/" \ "oasis-200401-wss-soap-message-security-1.0#Base64Binary" binary_token = etree.SubElement(security, QName(ns.WSSE, 'BinarySecurityToken'), attrib={ QName(ns.WSU, 'Id'): reference_id, 'ValueType': x509type, 'EncodingType': encoding_type }) binary_token.text = base64.b64encode( crypto.dump_certificate( crypto.FILETYPE_ASN1, crypto.load_pkcs12(keyfile, password).get_certificate())) signature = xmlsec.template.create(envelope, xmlsec.Transform.EXCL_C14N, xmlsec.Transform.RSA_SHA1, ns='ds') # Add a KeyInfo node with X509Data child to the Signature. XMLSec will fill # in this template with the actual certificate details when it signs. key_info = xmlsec.template.ensure_key_info(signature) sec_token_ref = etree.SubElement(key_info, QName(ns.WSSE, 'SecurityTokenReference')) etree.SubElement(sec_token_ref, QName(ns.WSSE, 'Reference'), attrib={ 'URI': '#' + reference_id, 'ValueType': x509type }) # Insert the Signature node in the wsse:Security header. security.append(signature) # Perform the actual signing. ctx = xmlsec.SignatureContext() ctx.key = key timestamp = etree.SubElement(security, QName(ns.WSU, 'Timestamp')) now = datetime.now() etree.SubElement(timestamp, QName( ns.WSU, 'Created')).text = now.strftime('%Y-%m-%dT%H:%M:%SZ') exp = now + timedelta(hours=1) etree.SubElement(timestamp, QName( ns.WSU, 'Expires')).text = exp.strftime('%Y-%m-%dT%H:%M:%SZ') soap_env = detect_soap_env(envelope) _sign_node(ctx, signature, envelope.find(QName(soap_env, 'Body'))) _sign_node(ctx, signature, security.find(QName(ns.WSU, 'Timestamp'))) ctx.sign(signature) return etree.fromstring(etree.tostring(envelope, method='c14n'))
def sign_xml_node(node: Element, key_file: str, cert_file: str, signature_method: str, digest_method: str, position: int = 0) -> None: """ Sign a XML element and insert the signature as a child element. :param node: The XML element to sign :param key_file: The path to a key file. :param cert_file: The path to a certificate file. :param signature_method: XMLSEC signature method, e.g., 'RSA_SHA1', 'RSA_SHA256', 'RSA_SHA512'. :param digest_method: XMLSEC digest method, e.g., 'SHA1', 'SHA256', 'SHA512'. :param position: The position of the signature. """ # Prepare signature template for xmlsec to fill it with the signature and additional data ctx = xmlsec.SignatureContext() signature = xmlsec.template.create( node, xmlsec.constants.TransformExclC14N, getattr(xmlsec.Transform, signature_method)) # type: ignore key_info = xmlsec.template.ensure_key_info(signature) x509_data = xmlsec.template.add_x509_data(key_info) xmlsec.template.x509_data_add_certificate(x509_data) xmlsec.template.x509_data_add_issuer_serial(x509_data) # Ensure the target node has an ID attribute and get its value. node_id = node.get(XML_ATTRIBUTE_ID) if not node_id: node_id = create_xml_uuid() node.set(XML_ATTRIBUTE_ID, node_id) # Unlike HTML, XML doesn't have a single standardized id so we need to tell xmlsec about our id. ctx.register_id(node, XML_ATTRIBUTE_ID, None) # Add reference to signature with URI attribute pointing to that ID. ref = xmlsec.template.add_reference(signature, getattr(xmlsec.Transform, digest_method), uri="#" + node_id) # type: ignore # XML normalization transform performed on the node contents before signing and verification. # 1. When enveloped signature method is used, the signature is included as a child of the signed element. # The signature is removed from the document before signing/verification. xmlsec.template.add_transform(ref, xmlsec.constants.TransformEnveloped) # 2. This ensures that changes to irrelevant whitespace, attribute ordering, etc. won't invalidate the signature. xmlsec.template.add_transform(ref, xmlsec.constants.TransformExclC14N) # xmlsec library adds unnecessary newlines to the signature template. They may cause troubles to other # XMLSEC implementations, so we remove any unnecessary whitespace to avoid compatibility issues. for elm in signature.iter(): if elm.text is not None and '\n' in elm.text: elm.text = elm.text.replace('\n', '') if elm.tail is not None and '\n' in elm.tail: elm.tail = elm.tail.replace('\n', '') remove_extra_xml_whitespace(signature) # Insert the signature as a child element. node.insert(position, signature) ctx.key = xmlsec.Key.from_file(key_file, xmlsec.constants.KeyDataFormatPem) ctx.key.load_cert_from_file(cert_file, xmlsec.constants.KeyDataFormatPem) ctx.sign(signature) # xmlsec library adds unnecessary tail newlines again, so we remove them. remove_extra_xml_whitespace(signature)
def add_sign(xml, key, cert, debug=False): """Add sign. Args: xml (str): SAML assertion key (Path): path enc key cert (Path): path to cert/pem debug (boolean): xmlsec enable debug trace Returns: str: signed SAML assertion Raises: Exception: if xml is empty """ if xml is None or xml == '': raise Exception('Empty string supplied as input') elem = fromstring(xml.encode('utf-8'), forbid_dtd=True) sign_algorithm_transform = xmlsec.Transform.ECDSA_SHA256 signature = xmlsec.template.create( elem, xmlsec.Transform.EXCL_C14N, sign_algorithm_transform, ns='ds', ) issuer = elem.findall('.//{urn:oasis:names:tc:SAML:2.0:assertion}Issuer') if issuer: issuer = issuer[0] issuer.addnext(signature) elem_to_sign = issuer.getparent() elem_id = elem_to_sign.get('ID', None) if elem_id is not None: if elem_id: elem_id = f'#{elem_id}' xmlsec.enable_debug_trace(debug) xmlsec.tree.add_ids(elem_to_sign, ['ID']) digest_algorithm_transform = xmlsec.Transform.SHA256 ref = xmlsec.template.add_reference( signature, digest_algorithm_transform, uri=elem_id, ) xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED) xmlsec.template.add_transform(ref, xmlsec.Transform.EXCL_C14N) key_info = xmlsec.template.ensure_key_info(signature) xmlsec.template.add_x509_data(key_info) dsig_ctx = xmlsec.SignatureContext() sign_key = xmlsec.Key.from_file(key, xmlsec.KeyFormat.PEM, None) sign_key.load_cert_from_file(cert, xmlsec.KeyFormat.PEM) dsig_ctx.key = sign_key dsig_ctx.sign(signature) return tostring(elem).decode()
def test_sign_generated_template_pem_with_x509(): """ Should sign a file using a dynamicaly created template, key from PEM file and an X509 certificate. """ # Load document file. template = parse_xml('sign3-doc.xml') # Create a signature template for RSA-SHA1 enveloped signature. signature_node = xmlsec.template.create( template, xmlsec.Transform.EXCL_C14N, xmlsec.Transform.RSA_SHA1) assert signature_node is not None # Add the <ds:Signature/> node to the document. template.append(signature_node) # Add the <ds:Reference/> node to the signature template. ref = xmlsec.template.add_reference(signature_node, xmlsec.Transform.SHA1) # Add the enveloped transform descriptor. xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED) # Add the <ds:KeyInfo/> and <ds:KeyName/> nodes. key_info = xmlsec.template.ensure_key_info(signature_node) xmlsec.template.add_x509_data(key_info) # Create a digital signature context (no key manager is needed). ctx = xmlsec.SignatureContext() # Load private key (assuming that there is no password). filename = path.join(BASE_DIR, 'rsakey.pem') key = xmlsec.Key.from_file(filename, xmlsec.KeyFormat.PEM) assert key is not None # Load the certificate and add it to the key. filename = path.join(BASE_DIR, 'rsacert.pem') key.load_cert_from_file(filename, xmlsec.KeyFormat.PEM) # Set key name to the file name (note: this is just a test). key.name = path.basename(filename) # Set the key on the context. ctx.key = key assert ctx.key is not None assert ctx.key.name == path.basename(filename) # Sign the template. ctx.sign(signature_node) # Assert the contents of the XML document against the expected result. compare('sign3-res.xml', template)
def test_validate_binary_sign(): ctx = xmlsec.SignatureContext() ctx.key = xmlsec.Key.from_file(rsakey, format=consts.KeyDataFormatPem) assert ctx.key is not None ctx.key.name = 'rsakey.pem' assert "rsakey.pem" == ctx.key.name ctx.verify_binary(load("sign6-in.bin"), consts.TransformRsaSha1, load("sign6-out.bin"))
def test_sign_binary(): ctx = xmlsec.SignatureContext() ctx.key = xmlsec.Key.from_file(rsakey, format=consts.KeyDataFormatPem) assert ctx.key is not None ctx.key.name = 'rsakey.pem' assert "rsakey.pem" == ctx.key.name sign = ctx.sign_binary(load("sign6-in.bin"), consts.TransformRsaSha1) assert load("sign6-out.bin") == sign
def test_enable_signature_transform_bad_args(self): ctx = xmlsec.SignatureContext() ctx.key = xmlsec.Key.from_file(self.path('rsakey.pem'), format=consts.KeyDataFormatPem) with self.assertRaises(TypeError): ctx.enable_signature_transform('') with self.assertRaises(TypeError): ctx.enable_signature_transform(0) with self.assertRaises(TypeError): ctx.enable_signature_transform(consts.KeyDataAes)
def verify_xml_signatures(node: Element, cert_file: str) -> List[SignatureInfo]: """ Verify all XML signatures from the provided node. :param node: A XML subtree. :param cert_file: A path to the certificate file. :return: A list of signature details if there are any signatures, an empty list otherwise. :raise SecurityError: If any of the element references or signatures are invalid. """ signature_info = [] signatures = node.findall(".//{}".format( QName(XML_SIG_NAMESPACE, 'Signature'))) if signatures: key = xmlsec.Key.from_file(cert_file, xmlsec.constants.KeyDataFormatCertPem) for sig_num, signature in enumerate(signatures, 1): # Find and register referenced elements referenced_elements = [] ctx = xmlsec.SignatureContext() refs = signature.findall('./{}/{}'.format( QName(XML_SIG_NAMESPACE, 'SignedInfo'), QName(XML_SIG_NAMESPACE, 'Reference'))) for ref_num, ref in enumerate(refs, 1): # ID is referenced in the URI attribute and prefixed with a hash. ref_id = ref.get(XML_ATTRIBUTE_URI) if ref_id is None or len(ref_id) < 2 or ref_id[0] != '#': raise SecurityError( 'Signature {}, reference {}: Invalid id {!r}.'.format( sig_num, ref_num, ref_id)) ref_id = ref_id[1:] ref_elms = node.xpath('//*[@{}=\'{}\']'.format( XML_ATTRIBUTE_ID, ref_id)) if not ref_elms: raise SecurityError( 'Signature {}, reference {}: Element with id {!r} not found.' .format(sig_num, ref_num, ref_id)) if len(ref_elms) > 1: raise SecurityError( 'Signature {}, reference {}: Element with id {!r} occurs more than once.' .format(sig_num, ref_num, ref_id)) referenced_elements.append(ref_elms[0]) # Unlike HTML, XML doesn't have a single standardized id so we need to tell xmlsec about our id. ctx.register_id(ref_elms[0], XML_ATTRIBUTE_ID, None) # Verify the signature try: ctx.key = key ctx.verify(signature) except xmlsec.Error: raise SecurityError('Signature {} is invalid.'.format(sig_num)) signature_info.append( SignatureInfo(signature, tuple(referenced_elements))) return signature_info
def handle(self, **opts): metadata = etree.parse(sys.stdin) NS = { 'namespaces': { 'saml': 'urn:oasis:names:tc:SAML:2.0:metadata', 'ds': 'http://www.w3.org/2000/09/xmldsig#', 'mdattr': 'urn:oasis:names:tc:SAML:metadata:attribute', 'assertion': 'urn:oasis:names:tc:SAML:2.0:assertion' } } # Verify the signature ctx = xmlsec.SignatureContext() ctx.key = xmlsec.Key.from_file(opts['certificate'], xmlsec.constants.KeyDataFormatCertPem) xmlsec.tree.add_ids(metadata.getroot(), ["ID"]) signature_node = xmlsec.tree.find_node(metadata.getroot(), xmlsec.constants.NodeSignature) ctx.verify(signature_node) idp_descriptors = metadata.xpath( ''' //saml:EntityDescriptor[ not( saml:Extensions/mdattr:EntityAttributes/assertion:Attribute[ @Name="http://macedir.org/entity-category" and assertion:AttributeValue/text()='http://refeds.org/category/hide-from-discovery' ] ) and @ID and saml:IDPSSODescriptor[ saml:SingleSignOnService[ @Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"]]]''', **NS) seen_idp_names = set() for idp_descriptor in idp_descriptors: try: idp = IDP.objects.get(name=idp_descriptor.attrib['ID']) except IDP.DoesNotExist: idp = IDP(name=idp_descriptor.attrib['ID']) seen_idp_names.add(idp.name) idp.entity_id = idp_descriptor.attrib['entityID'] idp.label = idp_descriptor.xpath( 'saml:Organization/saml:OrganizationDisplayName/text()', **NS)[0] idp.url = idp_descriptor.xpath( 'saml:IDPSSODescriptor/saml:SingleSignOnService[@Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"]/@Location', **NS)[0] idp.x509cert = idp_descriptor.xpath( 'saml:IDPSSODescriptor/saml:KeyDescriptor[1]//ds:X509Certificate/text()', **NS)[0] idp.save() IDP.objects.exclude(name__in=seen_idp_names).delete()
def sign_assertion(xml_string, private_key): key = xmlsec.Key.from_file(private_key, xmlsec.KeyFormat.PEM) root = etree.fromstring(xml_string) signature_node = xmlsec.tree.find_node(root, xmlsec.Node.SIGNATURE) sign_context = xmlsec.SignatureContext() sign_context.key = key sign_context.sign(signature_node) return etree.tostring(root)
def get_signature_context(signature, envelope): ctx = xmlsec.SignatureContext() # Find each signed element and register its ID with the signing context. refs = signature.xpath("ds:SignedInfo/ds:Reference", namespaces={"ds": DS_NS}) for ref in refs: # Get the reference URI and cut off the initial '#' referenced_id = ref.get("URI")[1:] referenced = envelope.xpath("//*[@wsu:Id='%s']" % referenced_id, namespaces={"wsu": WSU_NS})[0] ctx.register_id(referenced, "Id", WSU_NS) return ctx
def parse_detached( self, saml_request: str, relay_state: Optional[str], signature: Optional[str] = None, sig_alg: Optional[str] = None, ) -> AuthNRequest: """Validate and parse raw request with detached signature""" try: decoded_xml = decode_base64_and_inflate(saml_request) except UnicodeDecodeError: raise CannotHandleAssertion(ERROR_CANNOT_DECODE_REQUEST) verifier = self.provider.verification_kp if verifier and not (signature and sig_alg): raise CannotHandleAssertion(ERROR_SIGNATURE_REQUIRED_BUT_ABSENT) if signature and sig_alg: if not verifier: raise CannotHandleAssertion( ERROR_SIGNATURE_EXISTS_BUT_NO_VERIFIER) querystring = f"SAMLRequest={quote_plus(saml_request)}&" if relay_state is not None: querystring += f"RelayState={quote_plus(relay_state)}&" querystring += f"SigAlg={quote_plus(sig_alg)}" dsig_ctx = xmlsec.SignatureContext() key = xmlsec.Key.from_memory(verifier.certificate_data, xmlsec.constants.KeyDataFormatCertPem, None) dsig_ctx.key = key sign_algorithm_transform_map = { DSA_SHA1: xmlsec.constants.TransformDsaSha1, RSA_SHA1: xmlsec.constants.TransformRsaSha1, RSA_SHA256: xmlsec.constants.TransformRsaSha256, RSA_SHA384: xmlsec.constants.TransformRsaSha384, RSA_SHA512: xmlsec.constants.TransformRsaSha512, } sign_algorithm_transform = sign_algorithm_transform_map.get( sig_alg, xmlsec.constants.TransformRsaSha1) try: dsig_ctx.verify_binary( querystring.encode("utf-8"), sign_algorithm_transform, b64decode(signature), ) except xmlsec.Error as exc: raise CannotHandleAssertion(ERROR_FAILED_TO_VERIFY) from exc return self._parse_xml(decoded_xml, relay_state)
def validate_binary_sign(signed_query, signature, cert=None, algorithm=OneLogin_Saml2_Constants.RSA_SHA1, debug=False): """ Validates signed binary data (Used to validate GET Signature). :param signed_query: The element we should validate :type: string :param signature: The signature that will be validate :type: string :param cert: The public cert :type: string :param algorithm: Signature algorithm :type: string :param debug: Activate the xmlsec debug :type: bool """ try: xmlsec.enable_debug_trace(debug) dsig_ctx = xmlsec.SignatureContext() dsig_ctx.key = xmlsec.Key.from_memory(cert, xmlsec.KeyFormat.CERT_PEM, None) sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.Transform.DSA_SHA1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.Transform.RSA_SHA1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.Transform.RSA_SHA256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.Transform.RSA_SHA384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.Transform.RSA_SHA512 } sign_algorithm_transform = sign_algorithm_transform_map.get( algorithm, xmlsec.Transform.RSA_SHA1) dsig_ctx.verify_binary(compat.to_bytes(signed_query), sign_algorithm_transform, compat.to_bytes(signature)) return True except xmlsec.Error as e: if debug: print(e) return False
def sign_assertion(xml_string): xmlsec.enable_debug_trace() key = xmlsec.Key.from_file("./wso2carbon.pem", xmlsec.KeyFormat.PEM) root = etree.fromstring(text=xml_string) signature_node = xmlsec.tree.find_node(root, xmlsec.Node.SIGNATURE) #print(etree.tostring(signature_node)) sign_context = xmlsec.SignatureContext() sign_context.key = key sign_context.register_id(node=root) sign_context.sign(signature_node) return etree.tostring(root)
def _sign_saml_request(request, saml_auth, saml_security): sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.constants.TransformDsaSha1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.constants.TransformRsaSha1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.constants.TransformRsaSha256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.constants.TransformRsaSha384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.constants.TransformRsaSha512 } digest_algorithm_transform_map = { OneLogin_Saml2_Constants.SHA1: xmlsec.constants.TransformSha1, OneLogin_Saml2_Constants.SHA256: xmlsec.constants.TransformSha256, OneLogin_Saml2_Constants.SHA384: xmlsec.constants.TransformSha384, OneLogin_Saml2_Constants.SHA512: xmlsec.constants.TransformSha512 } # request_root = etree.fromstring(request) xmlsec.tree.add_ids(request_root, ["ID"]) signature_node = xmlsec.template.create( request_root, xmlsec.constants.TransformExclC14N, sign_algorithm_transform_map.get( saml_security.get("signatureAlgorithm", OneLogin_Saml2_Constants.RSA_SHA1), xmlsec.constants.TransformRsaSha1)) request_root.insert(1, signature_node) reference_node = xmlsec.template.add_reference( signature_node, digest_algorithm_transform_map.get( saml_security.get("digestAlgorithm", OneLogin_Saml2_Constants.SHA1), xmlsec.constants.TransformSha1), uri=f"#{request_root.get('ID')}") xmlsec.template.add_transform(reference_node, xmlsec.constants.TransformEnveloped) xmlsec.template.add_transform(reference_node, xmlsec.constants.TransformExclC14N) xmlsec.template.add_x509_data( xmlsec.template.ensure_key_info(signature_node)) signature_ctx = xmlsec.SignatureContext() signature_ctx.key = xmlsec.Key.from_memory( saml_auth.get_settings().get_sp_key(), xmlsec.constants.KeyDataFormatPem) signature_ctx.key.load_cert_from_memory( saml_auth.get_settings().get_sp_cert(), format=xmlsec.constants.KeyDataFormatPem) signature_ctx.sign(signature_node) request = OneLogin_Saml2_Utils.b64encode(etree.tostring(request_root)) return request
def verify(xml, stream): """ Verify the signaure of an XML document with the given certificate. Returns `True` if the document is signed with a valid signature. Returns `False` if the document is not signed or if the signature is invalid. :param lxml.etree._Element xml: The document to sign :param file stream: The private key to sign the document with :rtype: Boolean """ # Import xmlsec here to delay initializing the C library in # case we don't need it. import xmlsec # Find the <Signature/> node. signature_node = xmlsec.tree.find_node(xml, xmlsec.Node.SIGNATURE) if signature_node is None: # No `signature` node found; we cannot verify return False # Create a digital signature context (no key manager is needed). ctx = xmlsec.SignatureContext() # Register <Response/> and <Assertion/> ctx.register_id(xml) for assertion in xml.xpath("//*[local-name()='Assertion']"): ctx.register_id(assertion) # Load the public key. key = None for fmt in [xmlsec.KeyFormat.PEM, xmlsec.KeyFormat.CERT_PEM]: stream.seek(0) try: key = xmlsec.Key.from_memory(stream, fmt) break except ValueError: # xmlsec now throws when it can't load the key pass # Set the key on the context. ctx.key = key # Verify the signature. try: ctx.verify(signature_node) return True except Exception: return False
def check_verify(i): root = load_xml("sign%d-out.xml" % i) xmlsec.tree.add_ids(root, ["ID"]) sign = xmlsec.tree.find_node(root, consts.NodeSignature) assert sign is not None assert consts.NodeSignature == sign.tag.partition("}")[2] ctx = xmlsec.SignatureContext() ctx.key = xmlsec.Key.from_file(rsapub, format=consts.KeyDataFormatPem) assert ctx.key is not None ctx.key.name = 'rsapub.pem' assert "rsapub.pem" == ctx.key.name ctx.verify(sign)
def check_verify(self, i): root = self.load_xml("sign%d-out.xml" % i) xmlsec.tree.add_ids(root, ["ID"]) sign = xmlsec.tree.find_node(root, consts.NodeSignature) self.assertIsNotNone(sign) self.assertEqual(consts.NodeSignature, sign.tag.partition("}")[2]) ctx = xmlsec.SignatureContext() ctx.key = xmlsec.Key.from_file(self.path("rsapub.pem"), format=consts.KeyDataFormatPem) self.assertIsNotNone(ctx.key) ctx.key.name = 'rsapub.pem' self.assertEqual("rsapub.pem", ctx.key.name) ctx.verify(sign)
def sign_with_algorithm(self, message, Algorithm='RSAxSHA1'): """ SII specified RSA over SHA1 """ ctx = xmlsec.SignatureContext() ctx.key = xmlsec.Key.from_memory(key, format=xmlsec.constants.KeyDataFormatPem) """ Flatten data """ data = data.replace('\n', ' ').replace('\r', '').replace('\t', '').replace('> ', '>').replace(' <', '<') data = bytes(data, 'ISO-8859-1') sign = ctx.sign_binary(data, xmlsec.constants.TransformRsaSha1) """ To base 64 and back to ISO-8859-1""" base64_encoded_data = base64.b64encode(sign) return base64_encoded_data.decode('ISO-8859-1')