示例#1
0
    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')
示例#2
0
    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)
示例#3
0
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)
示例#4
0
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
示例#5
0
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
示例#6
0
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
示例#7
0
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()
示例#8
0
    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)
示例#9
0
 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)
示例#10
0
    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)
示例#11
0
    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"))
示例#12
0
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'))
示例#13
0
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)
示例#14
0
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()
示例#15
0
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)
示例#16
0
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"))
示例#17
0
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
示例#18
0
 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)
示例#19
0
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()
示例#21
0
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)
示例#22
0
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
示例#23
0
    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)
示例#24
0
    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)
示例#26
0
 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
示例#27
0
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
示例#28
0
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)
示例#29
0
    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)
示例#30
0
	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')