Example #1
0
def _signature_prepare(envelope, key):
    """Prepare envelope and sign."""
    soap_env = detect_soap_env(envelope)

    # Create the Signature node.
    signature = xmlsec.template.create(envelope, xmlsec.Transform.EXCL_C14N,
                                       xmlsec.Transform.RSA_SHA1)

    # 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)
    x509_data = xmlsec.template.add_x509_data(key_info)
    xmlsec.template.x509_data_add_issuer_serial(x509_data)
    xmlsec.template.x509_data_add_certificate(x509_data)

    # Insert the Signature node in the wsse:Security header.
    security = get_security_header(envelope)
    security.insert(0, signature)
    security.append(etree.Element(QName(ns.WSU, "Timestamp")))

    # Perform the actual signing.
    ctx = xmlsec.SignatureContext()
    ctx.key = key
    _sign_node(ctx, signature, envelope.find(QName(soap_env, "Body")))
    _sign_node(ctx, signature, security.find(QName(ns.WSU, "Timestamp")))
    ctx.sign(signature)

    # Place the X509 data inside a WSSE SecurityTokenReference within
    # KeyInfo. The recipient expects this structure, but we can't rearrange
    # like this until after signing, because otherwise xmlsec won't populate
    # the X509 data (because it doesn't understand WSSE).
    sec_token_ref = etree.SubElement(key_info,
                                     QName(ns.WSSE, "SecurityTokenReference"))
    return security, sec_token_ref, x509_data
Example #2
0
    def sign(self, envelope, headers):
        print(etree.tostring(envelope, pretty_print=True))

        signature = xmlsec.template.create(
            envelope,
            xmlsec.Transform.EXCL_C14N,
            xmlsec.Transform.RSA_SHA1,
        )

        key_info = xmlsec.template.ensure_key_info(signature)
        x509_data = xmlsec.template.add_x509_data(key_info)
        x509_issuer_serial = etree.Element('{ns}X509IssuerSerial')
        x509_data.append(x509_issuer_serial)
        x509_certificate = etree.Element('{ns}X509Certificate')
        x509_data.append(x509_certificate)

        key = xmlsec.Key.from_file(self.keyfile, xmlsec.KeyFormat.PEM)
        key.load_cert_from_file(self.cert_file, xmlsec.KeyFormat.PEM)

        security = utils.get_security_header(envelope)
        security.insert(0, signature)

        ctx = xmlsec.SignatureContext()
        ctx.key = key
        self._sign_node(
            ctx, signature,
            envelope.find('{http://schemas.xmlsoap.org/soap/envelope/}Body'))
        ctx.sign(signature)

        sec_token_ref = etree.SubElement(
            key_info, '{}SecurityTokenReference'.format(self.wssens))
        sec_token_ref.append(x509_data)

        return envelope, headers
Example #3
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'))
Example #4
0
 def apply(self, envelope: str, headers: Any) -> Any:
     security = utils.get_security_header(envelope)
     created = datetime.now()
     expired = created + timedelta(days=5)
     timestamp = utils.WSU("Timestamp")
     timestamp.append(
         utils.WSU("Created", created.strftime("%Y-%m-%dT%H:%M:%S.000Z")))
     timestamp.append(
         utils.WSU("Expires", expired.strftime("%Y-%m-%dT%H:%M:%S.000Z")))
     security.append(timestamp)
     return super().apply(envelope, headers)
Example #5
0
    def apply(self, envelope, headers):
        security = utils.get_security_header(envelope)

        created = datetime.utcnow()
        expired = created + timedelta(minutes=5)

        timestamp = utils.WSU('Timestamp')
        timestamp.append(utils.WSU('Created', created.replace(microsecond=0).isoformat()+'Z'))
        timestamp.append(utils.WSU('Expires', expired.replace(microsecond=0).isoformat()+'Z'))

        security.append(timestamp)

        super().apply(envelope, headers)
        return envelope, headers
    def _add_timestamp(self, envelope):
        """Adds WSU Timestamp element to the Security header. WSU Timestamp element is needed for WS-Security

        :param envelope:
        :return:
        """
        security = utils.get_security_header(envelope)

        created = datetime.utcnow()
        expired = created + timedelta(seconds=30000)

        timestamp = utils.WSU('Timestamp')
        timestamp.append(utils.WSU('Created', created.replace(microsecond=0).isoformat()+'Z'))
        timestamp.append(utils.WSU('Expires', expired.replace(microsecond=0).isoformat()+'Z'))

        security.append(timestamp)
Example #7
0
    def sign(self, envelope, headers):
        security = utils.get_security_header(envelope)

        # The token placeholder might already exists since it is specified in
        # the WSDL.
        token = security.find('{%s}UsernameToken' % NSMAP['wsse'])
        if token is None:
            token = WSSE.UsernameToken()
            security.append(token)

        # Create the sub elements of the UsernameToken element
        elements = [WSSE.Username(self.username)]
        if self.password is not None or self.password_digest is not None:
            if self.use_digest:
                elements.extend(self._create_password_digest())
            else:
                elements.extend(self._create_password_text())

        token.extend(elements)
        return envelope, headers
Example #8
0
def test_get_security_header():
    doc = etree.fromstring("""
        <soap-env:Envelope
            xmlns:ns0="http://example.com/stockquote.xsd"
            xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
            xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        >
          <soap-env:Body>
            <ns0:TradePriceRequest>
              <tickerSymbol>foobar</tickerSymbol>
              <ns0:country/>
            </ns0:TradePriceRequest>
          </soap-env:Body>
        </soap-env:Envelope>
    """.strip())

    element = utils.get_security_header(doc)
    assert element.tag == '{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security'
Example #9
0
def test_get_security_header():
    doc = etree.fromstring("""
        <soap-env:Envelope
            xmlns:ns0="http://example.com/stockquote.xsd"
            xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
            xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        >
          <soap-env:Body>
            <ns0:TradePriceRequest>
              <tickerSymbol>foobar</tickerSymbol>
              <ns0:country/>
            </ns0:TradePriceRequest>
          </soap-env:Body>
        </soap-env:Envelope>
    """.strip())

    element = utils.get_security_header(doc)
    assert element.tag == '{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security'  # noqa
Example #10
0
    def apply(self, envelope, headers):
        security = utils.get_security_header(envelope)

        token = utils.WSSE.UsernameToken()
        security.append(token)

        # Extra values
        nonce = os.urandom(16)
        timestamp = datetime.datetime.utcnow().isoformat()

        # Create the sub elements of the UsernameToken element
        elements = [
            utils.WSSE.Username(self.username),
            utils.WSSE.Password(
                hashlib.sha256(self.password.encode('utf-8')).hexdigest()),
            utils.WSSE.Nonce(base64.b64encode(nonce).decode('utf-8')),
            utils.WSU.Created(timestamp)
        ]

        token.extend(elements)
        return envelope, headers
Example #11
0
    def apply(self, envelope, headers):
        security = utils.get_security_header(envelope)

        # The token placeholder might already exists since it is specified in
        # the WSDL.
        token = security.find('{%s}UsernameToken' % ns.WSSE)
        if token is None:
            token = utils.WSSE.UsernameToken()
            security.append(token)

        # Create the sub elements of the UsernameToken element
        elements = [
            utils.WSSE.Username(self.username)
        ]
        if self.password is not None or self.password_digest is not None:
            if self.use_digest:
                elements.extend(self._create_password_digest())
            else:
                elements.extend(self._create_password_text())

        token.extend(elements)
        return envelope, headers
Example #12
0
def sign_envelope(envelope, public_cert, private_key, 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(
        public_cert.public_bytes(encoding=serialization.Encoding.DER)
    )
    signature = xmlsig.template.create(
        c14n_method=xmlsig.constants.TransformExclC14N,
        sign_method=xmlsig.constants.TransformRsaSha1,
        ns="ds",
    )
    envelope.append(signature)

    # 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 = xmlsig.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 = xmlsig.SignatureContext()
    ctx.x509 = public_cert
    ctx.public_key = public_cert.public_key()
    ctx.private_key = private_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"))
Example #13
0
def _signature_prepare(envelope,
                       key,
                       signature_method,
                       digest_method,
                       signatures=None):
    """Prepare all the data for signature.

    Mostly copied from zeep.wsse.signature.
    """
    soap_env = detect_soap_env(envelope)
    # Create the Signature node.
    signature = xmlsec.template.create(
        envelope,
        xmlsec.Transform.EXCL_C14N,  # type: ignore
        signature_method or xmlsec.Transform.RSA_SHA1)  # type: ignore

    # 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)
    x509_data = xmlsec.template.add_x509_data(key_info)
    xmlsec.template.x509_data_add_issuer_serial(x509_data)
    xmlsec.template.x509_data_add_certificate(x509_data)

    # Insert the Signature node in the wsse:Security header.
    security = get_security_header(envelope)
    security.insert(0, signature)

    # Prepare Timestamp
    timestamp = Element(QName(ns.WSU, 'Timestamp'))
    created = Element(QName(ns.WSU, 'Created'))
    created.text = get_timestamp()
    expires = Element(QName(ns.WSU, 'Expires'))
    expires.text = get_timestamp(datetime.datetime.utcnow() +
                                 datetime.timedelta(minutes=5))
    timestamp.append(created)
    timestamp.append(expires)
    security.insert(0, timestamp)

    # Perform the actual signing.
    ctx = xmlsec.SignatureContext()
    ctx.key = key
    # Sign default elements
    _sign_node(ctx, signature, security.find(QName(ns.WSU, 'Timestamp')),
               digest_method)

    # Sign elements defined in WSDL
    if signatures is not None:
        if signatures['body'] or signatures['everything']:
            _sign_node(ctx, signature, envelope.find(QName(soap_env, 'Body')),
                       digest_method)
        header = get_or_create_header(envelope)
        if signatures['everything']:
            for node in header.iterchildren():
                # Everything doesn't mean everything ...
                if node.nsmap.get(node.prefix) not in OMITTED_HEADERS:
                    _sign_node(ctx, signature, node, digest_method)
        else:
            for node in signatures['header']:
                _sign_node(ctx, signature,
                           header.find(QName(node['Namespace'], node['Name'])),
                           digest_method)

    # Remove newlines from signature...
    for element in signature.iter():
        if element.text is not None and '\n' in element.text:
            element.text = element.text.replace('\n', '')
        if element.tail is not None and '\n' in element.tail:
            element.tail = element.tail.replace('\n', '')

    ctx.sign(signature)

    # Place the X509 data inside a WSSE SecurityTokenReference within
    # KeyInfo. The recipient expects this structure, but we can't rearrange
    # like this until after signing, because otherwise xmlsec won't populate
    # the X509 data (because it doesn't understand WSSE).
    sec_token_ref = SubElement(key_info,
                               QName(ns.WSSE, 'SecurityTokenReference'))
    return security, sec_token_ref, x509_data
Example #14
0
def sign_envelope(envelope, keyfile, certfile, password=None):
    """Sign given SOAP envelope with WSSE sig using given key and cert.

    Sign the wsu:Timestamp node in the wsse:Security header and the soap:Body;
    both must be present.

    Add a ds:Signature node in the wsse:Security header containing the
    signature.

    Use EXCL-C14N transforms to normalize the signed XML (so that irrelevant
    whitespace or attribute ordering changes don't invalidate the
    signature). Use SHA1 signatures.

    Expects to sign an incoming document something like this (xmlns attributes
    omitted for readability):

    <soap:Envelope>
      <soap:Header>
        <wsse:Security mustUnderstand="true">
          <wsu:Timestamp>
            <wsu:Created>2015-06-25T21:53:25.246276+00:00</wsu:Created>
            <wsu:Expires>2015-06-25T21:58:25.246276+00:00</wsu:Expires>
          </wsu:Timestamp>
        </wsse:Security>
      </soap:Header>
      <soap:Body>
        ...
      </soap:Body>
    </soap:Envelope>

    After signing, the sample document would look something like this (note the
    added wsu:Id attr on the soap:Body and wsu:Timestamp nodes, and the added
    ds:Signature node in the header, with ds:Reference nodes with URI attribute
    referencing the wsu:Id of the signed nodes):

    <soap:Envelope>
      <soap:Header>
        <wsse:Security mustUnderstand="true">
          <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
            <SignedInfo>
              <CanonicalizationMethod
                  Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
              <SignatureMethod
                  Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
              <Reference URI="#id-d0f9fd77-f193-471f-8bab-ba9c5afa3e76">
                <Transforms>
                  <Transform
                      Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </Transforms>
                <DigestMethod
                    Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <DigestValue>nnjjqTKxwl1hT/2RUsBuszgjTbI=</DigestValue>
              </Reference>
              <Reference URI="#id-7c425ac1-534a-4478-b5fe-6cae0690f08d">
                <Transforms>
                  <Transform
                      Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </Transforms>
                <DigestMethod
                    Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <DigestValue>qAATZaSqAr9fta9ApbGrFWDuCCQ=</DigestValue>
              </Reference>
            </SignedInfo>
            <SignatureValue>Hz8jtQb...bOdT6ZdTQ==</SignatureValue>
            <KeyInfo>
              <wsse:SecurityTokenReference>
                <X509Data>
                  <X509Certificate>MIIDnzC...Ia2qKQ==</X509Certificate>
                  <X509IssuerSerial>
                    <X509IssuerName>...</X509IssuerName>
                    <X509SerialNumber>...</X509SerialNumber>
                  </X509IssuerSerial>
                </X509Data>
              </wsse:SecurityTokenReference>
            </KeyInfo>
          </Signature>
          <wsu:Timestamp wsu:Id="id-7c425ac1-534a-4478-b5fe-6cae0690f08d">
            <wsu:Created>2015-06-25T22:00:29.821700+00:00</wsu:Created>
            <wsu:Expires>2015-06-25T22:05:29.821700+00:00</wsu:Expires>
          </wsu:Timestamp>
        </wsse:Security>
      </soap:Header>
      <soap:Body wsu:Id="id-d0f9fd77-f193-471f-8bab-ba9c5afa3e76">
        ...
      </soap:Body>
    </soap:Envelope>

    """
    # Create the Signature node.
    signature = xmlsec.template.create(
        envelope,
        xmlsec.Transform.EXCL_C14N,
        xmlsec.Transform.RSA_SHA1,
    )

    # 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)
    x509_data = xmlsec.template.add_x509_data(key_info)
    xmlsec.template.x509_data_add_issuer_serial(x509_data)
    xmlsec.template.x509_data_add_certificate(x509_data)

    # Load the signing key and certificate.
    key = xmlsec.Key.from_file(keyfile,
                               xmlsec.KeyFormat.PEM,
                               password=password)
    key.load_cert_from_file(certfile, xmlsec.KeyFormat.PEM)

    # Insert the Signature node in the wsse:Security header.
    security = get_security_header(envelope)
    security.insert(0, signature)

    # Perform the actual signing.
    ctx = xmlsec.SignatureContext()
    ctx.key = key

    security.append(etree.Element(QName(ns.WSU, 'Timestamp')))

    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)

    # Place the X509 data inside a WSSE SecurityTokenReference within
    # KeyInfo. The recipient expects this structure, but we can't rearrange
    # like this until after signing, because otherwise xmlsec won't populate
    # the X509 data (because it doesn't understand WSSE).
    sec_token_ref = etree.SubElement(key_info,
                                     QName(ns.WSSE, 'SecurityTokenReference'))
    sec_token_ref.append(x509_data)