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")) # la DIAN no cumple a cabalidad token-profile 1.0 if signature is None: return SignatureVerificationFailed() 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 _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 verify_envelope(envelope, cert): """Verify WS-Security signature on given SOAP envelope with given cert. Expects a document like that found in the sample XML in the ``sign()`` docstring. Raise SignatureVerificationFailed on failure, silent on success. """ 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")) key_text = security.find(QName(ns.WSSE, "BinarySecurityToken")).text key = x509.load_der_x509_certificate(base64.b64decode(key_text)) ctx = xmlsig.SignatureContext() ctx.public_key = key.public_key() try: ctx.verify(signature) except Exception: # 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() from None
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_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 get_or_create_header(envelope): soap_env = detect_soap_env(envelope) # look for the Header element and create it if not found header_qname = "{%s}Header" % soap_env header = envelope.find(header_qname) if header is None: header = etree.Element(header_qname) envelope.insert(0, header) return header
def verify_envelope(envelope, certfile, cert_format=None): """Verify WS-Security signature on given SOAP envelope with given cert. Expects a document like that found in the sample XML in the ``sign()`` docstring. Raise SignatureVerificationFailed on failure, silent on success. """ soap_env = detect_soap_env(envelope) header = envelope.find(QName(soap_env, 'Header')) if not header: 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) if not cert_format: cert_format = xmlsec.KeyFormat.CERT_PEM key = xmlsec.Key.from_file(certfile, cert_format, None) 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 _verify_envelope_with_key(envelope, key): """Verify WS-Security signature on given SOAP envelope with given cert. Copy from zeep.wsse.signature except it does bail out if no signature is found. """ 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')) if security is None: raise SignatureVerificationFailed() signature = security.find(QName(ns.DS, 'Signature')) # Skip signature validation if not present, otherwise call the library function if signature is None: return else: zeep_verify_envelope(envelope, key)
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)