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
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
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 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)
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)
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
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'
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
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
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
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"))
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
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)