def test_sign_case2(self): """Should sign a dynamicaly constructed template file using a key from a PEM file.""" root = parse_xml("data/sign2-in.xml") sign = xmlsig.template.create( c14n_method=xmlsig.constants.TransformExclC14N, sign_method=xmlsig.constants.TransformRsaSha1, ns=None) self.assertIsNotNone(sign) root.append(sign) ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1) xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) ki = xmlsig.template.ensure_key_info(sign) xmlsig.template.add_key_name(ki) xmlsig.template.add_key_value(ki) ctx = xmlsig.SignatureContext() with open(path.join(BASE_DIR, "data/rsakey.pem"), "rb") as key_file: ctx.private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend()) ctx.key_name = 'rsakey.pem' self.assertEqual("rsakey.pem", ctx.key_name) ctx.sign(sign) with open(path.join(BASE_DIR, "data/rsacert.pem"), "rb") as cert_file: x509 = load_pem_x509_certificate(cert_file.read(), default_backend()) ctx.verify(sign) ctx.public_key = x509.public_key() ctx.verify(sign) compare("data/sign2-out.xml", root)
def get_xml_soap_with_signature(xml_soap_without_signature, Id, certificate_file, certificate_password): wsse = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" wsu = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" X509v3 = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" parser = etree.XMLParser(remove_comments=True) root = etree.fromstring(xml_soap_without_signature, parser=parser) signature_id = "{}".format(Id) signature = xmlsig.template.create( xmlsig.constants.TransformExclC14N, xmlsig.constants.TransformRsaSha256, # solo me ha funcionado con esta "SIG-" + signature_id) ref = xmlsig.template.add_reference(signature, xmlsig.constants.TransformSha256, uri="#id-" + signature_id) xmlsig.template.add_transform(ref, xmlsig.constants.TransformExclC14N) ki = xmlsig.template.ensure_key_info(signature, name="KI-" + signature_id) ctx = xmlsig.SignatureContext() ctx.load_pkcs12(get_pkcs12(certificate_file, certificate_password)) for element in root.iter("{%s}Security" % wsse): element.append(signature) ki_str = etree.SubElement(ki, "{%s}SecurityTokenReference" % wsse) ki_str.attrib["{%s}Id" % wsu] = "STR-" + signature_id ki_str_reference = etree.SubElement(ki_str, "{%s}Reference" % wsse) ki_str_reference.attrib['URI'] = "#X509-" + signature_id ki_str_reference.attrib['ValueType'] = X509v3 ctx.sign(signature) ctx.verify(signature) return root
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 test_sign_case4(self): """Should sign a file using a dynamically created template, key from PEM and an X509 cert with custom ns.""" root = parse_xml("data/sign4-in.xml") elem_id = root.get('ID', None) if elem_id: elem_id = '#' + elem_id sign = xmlsig.template.create( c14n_method=xmlsig.constants.TransformExclC14N, sign_method=xmlsig.constants.TransformRsaSha1, ns="ds") self.assertIsNotNone(sign) root.append(sign) ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1, uri=elem_id) xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) xmlsig.template.add_transform(ref, xmlsig.constants.TransformExclC14N) ki = xmlsig.template.ensure_key_info(sign) xmlsig.template.x509_data_add_certificate( xmlsig.template.add_x509_data(ki)) ctx = xmlsig.SignatureContext() with open(path.join(BASE_DIR, "data/rsakey.pem"), "rb") as key_file: ctx.private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend()) ctx.key_name = 'rsakey.pem' self.assertEqual("rsakey.pem", ctx.key_name) with open(path.join(BASE_DIR, "data/rsacert.pem"), "rb") as cert_file: ctx.x509 = load_pem_x509_certificate(cert_file.read(), default_backend()) ctx.sign(sign) ctx.verify(sign) compare("data/sign4-out.xml", root)
def test_signature(self): self._activate_certificate(self.certificate_password) self.move.action_post() self.move.name = "2999/99999" self.main_company.partner_id.country_id = self.env.ref("base.es") self.wizard.with_context( active_ids=self.move.ids, active_model="account.move" ).create_facturae_file() generated_facturae = etree.fromstring(base64.b64decode(self.wizard.facturae)) ns = "http://www.w3.org/2000/09/xmldsig#" self.assertEqual( len(generated_facturae.xpath("//ds:Signature", namespaces={"ds": ns})), 1 ) node = generated_facturae.find(".//ds:Signature", {"ds": ns}) ctx = xmlsig.SignatureContext() verification_error = False error_message = "" try: ctx.verify(node) except Exception as e: verification_error = True error_message = str(e) self.assertEqual( verification_error, False, "Error found during verification of the signature of " + "the move: %s" % error_message, )
def test_hmac(self): template = parse_xml('data/sign-hmac-in.xml') # Create a signature template for RSA-SHA1 enveloped signature. sign = xmlsig.template.create( c14n_method=xmlsig.constants.TransformExclC14N, sign_method=xmlsig.constants.TransformHmacSha1, ns="ds") assert sign is not None # Add the <ds:Signature/> node to the document. template.append(sign) # Add the <ds:Reference/> node to the signature template. ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1) # Add the enveloped transform descriptor. xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) ref_obj = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1, uri="#R1") xmlsig.template.add_transform(ref_obj, xmlsig.constants.TransformBase64) obj = etree.SubElement(sign, etree.QName(xmlsig.constants.DSigNs, 'Object')) obj.set("Id", "R1") obj.text = base64.b64encode(b"Some Text") ctx = xmlsig.SignatureContext() ctx.private_key = b"secret" ctx.sign(sign) ctx.verify(sign) compare('data/sign-hmac-out.xml', template)
def test_fail_signature(self): """Should sign a dynamicaly constructed template file using a key from a PEM file.""" root = parse_xml("data/sign-fail_signature.xml") sign = root.xpath('//ds:Signature', namespaces={'ds': xmlsig.constants.DSigNs})[0] ctx = xmlsig.SignatureContext() with self.assertRaises(Exception): ctx.verify(sign)
def parse_facturae_invoice(self, xml_root, xsd_file): facturae_schema = etree.XMLSchema( etree.parse( tools.file_open( xsd_file, subdir="addons/account_invoice_import_facturae/data"))) facturae_schema.assertValid(xml_root) sign = xml_root.find('ds:Signature', namespaces={'ds': xmlsig.constants.DSigNs}) if sign is not None: xmlsig.SignatureContext().verify(sign) modality = xml_root.find('FileHeader/Modality').text if modality == 'L': raise ValidationError(_('System does not allow lots')) supplier_dict = self.facturae_parse_partner( xml_root, xml_root.find('Parties/SellerParty')) invoice = xml_root.find('Invoices/Invoice') inv_number_xpath = invoice.find('InvoiceHeader/InvoiceNumber') inv_type = 'in_invoice' inv_class = invoice.find('InvoiceHeader/InvoiceClass') if inv_class is not None and inv_class.text not in ['OO', 'OC']: inv_type = 'in_refund' date_dt = datetime.strptime( invoice.find('InvoiceIssueData/IssueDate').text, '%Y-%M-%d') date_start = False date_end = False amount_total = float(invoice.find('InvoiceTotals/InvoiceTotal').text) amount_untaxed = float( invoice.find('InvoiceTotals/TotalGrossAmountBeforeTaxes').text) res_lines = [ self.facturae_parse_line(xml_root, invoice, line) for line in invoice.find('Items').findall('InvoiceLine') ] attachments = {} res = { 'type': inv_type, 'partner': supplier_dict, 'invoice_number': inv_number_xpath.text, 'date': fields.Date.to_string(date_dt), 'date_due': False, 'date_start': date_start, 'date_end': date_end, 'currency': { 'iso': invoice.find('InvoiceIssueData/InvoiceCurrencyCode').text }, 'amount_total': amount_total, 'amount_untaxed': amount_untaxed, 'iban': False, 'bic': False, 'lines': res_lines, 'attachments': attachments, } logger.info('Result of Facturae XML parsing: %s', res) return res
def tes_sign_case1(self): """Should sign a pre-constructed template file using a key from a PEM file.""" root = parse_xml("data/sign1-in.xml") sign = root.xpath('//ds:Signature', namespaces={'ds': xmlsig.constants.DSigNs})[0] self.assertIsNotNone(sign) ctx = xmlsig.SignatureContext() with open(path.join(BASE_DIR, "data/rsakey.pem"), "rb") as key_file: ctx.private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend()) ctx.key_name = 'rsakey.pem' self.assertEqual("rsakey.pem", ctx.key_name) ctx.sign(sign) ctx.verify(sign) compare('sign1-out.xml', root)
def test_signature(self): pkcs12 = crypto.PKCS12() pkey = crypto.PKey() pkey.generate_key(crypto.TYPE_RSA, 512) x509 = crypto.X509() x509.set_pubkey(pkey) x509.set_serial_number(0) x509.get_subject().CN = "me" x509.set_issuer(x509.get_subject()) x509.gmtime_adj_notBefore(0) x509.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) x509.sign(pkey, 'md5') pkcs12.set_privatekey(pkey) pkcs12.set_certificate(x509) self.main_company.facturae_cert = base64.b64encode( pkcs12.export(passphrase='password')) self.main_company.facturae_cert_password = '******' self.main_company.partner_id.country_id = self.ref('base.es') self.wizard.with_context( active_ids=self.invoice.ids, active_model='account.invoice').create_facturae_file() generated_facturae = etree.fromstring( base64.b64decode(self.wizard.facturae)) ns = 'http://www.w3.org/2000/09/xmldsig#' self.assertEqual( len( generated_facturae.xpath('//ds:Signature', namespaces={'ds': ns})), 1) node = generated_facturae.find(".//ds:Signature", {'ds': ns}) ctx = xmlsig.SignatureContext() certificate = crypto.load_pkcs12( base64.b64decode(self.main_company.facturae_cert), 'password') certificate.set_ca_certificates(None) verification_error = False error_message = '' try: ctx.verify(node) except Exception as e: verification_error = True error_message = e.message pass self.assertEqual( verification_error, False, 'Error found during verification of the signature of ' + 'the invoice: %s' % error_message)
def test_sign_generated_template_pem_with_x509(self): """ Should sign a file using a dynamicaly created template, key from PEM file and an X509 certificate. """ # Load document file. template = parse_xml('data/sign-doc.xml') # Create a signature template for RSA-SHA1 enveloped signature. sign = xmlsig.template.create( c14n_method=xmlsig.constants.TransformExclC14N, sign_method=xmlsig.constants.TransformRsaSha1, ns=None) assert sign is not None # Add the <ds:Signature/> node to the document. template.append(sign) # Add the <ds:Reference/> node to the signature template. ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1) # Add the enveloped transform descriptor. xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) # Add the <ds:KeyInfo/> and <ds:KeyName/> nodes. key_info = xmlsig.template.ensure_key_info(sign) x509_data = xmlsig.template.add_x509_data(key_info) xmlsig.template.x509_data_add_certificate(x509_data) # Create a digital signature context (no key manager is needed). # Load private key (assuming that there is no password). # Set the key on the context. ctx = xmlsig.SignatureContext() with open(path.join(BASE_DIR, "data/keyStore.p12"), "rb") as key_file: ctx.load_pkcs12(crypto.load_pkcs12(key_file.read())) # Sign the template. ctx.sign(sign) ctx.verify(sign) # Assert the contents of the XML document against the expected result. compare('data/sign-res.xml', template)
def test_sign_case5(self): """Should sign a file using a dynamicaly created template, key from PEM file and an X509 certificate.""" root = parse_xml("data/sign5-in.xml") sign = xmlsig.template.create( c14n_method=xmlsig.constants.TransformExclC14N, sign_method=xmlsig.constants.TransformRsaSha256, ns=None, name="S1") self.assertIsNotNone(sign) root.append(sign) ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1, name="R1", uri_type="Type") xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) xmlsig.template.ensure_key_info(sign, name="KI1") ki = xmlsig.template.ensure_key_info(sign) x509 = xmlsig.template.add_x509_data(ki) xmlsig.template.x509_data_add_subject_name(x509) xmlsig.template.x509_data_add_certificate(x509) xmlsig.template.x509_data_add_ski(x509) x509_issuer_serial = xmlsig.template.x509_data_add_issuer_serial(x509) xmlsig.template.x509_issuer_serial_add_issuer_name(x509_issuer_serial) xmlsig.template.x509_issuer_serial_add_serial_number( x509_issuer_serial) xmlsig.template.add_key_value(ki) xmlsig.template.add_key_name(ki, 'rsakey.pem') ctx = xmlsig.SignatureContext() with open(path.join(BASE_DIR, "data/rsakey.pem"), "rb") as key_file: ctx.private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend()) with open(path.join(BASE_DIR, "data/rsacert.pem"), "rb") as cert_file: ctx.x509 = load_pem_x509_certificate(cert_file.read(), default_backend()) ctx.sign(sign) ctx.verify(sign) compare("data/sign5-out.xml", root)
def sign(root, certificate, tax_agency): """ Sign XML with PKCS #12 :author: Victor Laskurain <*****@*****.**> :return: SignatureValue """ def create_node_tree(root_node, elem_list): """Convierte una lista en XML. Cada elemento de la lista se interpretará de la siguiente manera: Si es un string se añadirá al texto suelto del nodo root. Si es una tupla/lista t se interpretará como sigue: t[0] es el nombre del elemento a crear. Puede tener prefijo de espacio de nombres. t[1] es la lista de atributos donde las posiciones pares son claves y los impares valores. t[2:] son los subelementos que se interpretan recursivamente """ for elem_def in elem_list: if isinstance(elem_def, str): root_node.text = (root_node.text or "") + elem_def else: ns = "" elemname = elem_def[0] attrs = elem_def[1] children = elem_def[2:] if ":" in elemname: ns, elemname = elemname.split(":") ns = root_node.nsmap[ns] node = xmlsig.utils.create_node(elemname, root_node, ns) for attr_name, attr_value in zip(attrs[::2], attrs[1::2]): node.set(attr_name, attr_value) create_node_tree(node, children) doc_id = "id-" + str(uuid4()) signature_id = "sig-" + doc_id kinfo_id = "ki-" + doc_id sp_id = "sp-" + doc_id signature = xmlsig.template.create( xmlsig.constants.TransformInclC14N, xmlsig.constants.TransformRsaSha256, signature_id, ) ref = xmlsig.template.add_reference(signature, xmlsig.constants.TransformSha256, uri="") xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) xmlsig.template.add_reference(signature, xmlsig.constants.TransformSha256, uri="#" + kinfo_id) xmlsig.template.add_reference( signature, xmlsig.constants.TransformSha256, uri="#" + sp_id, uri_type="http://uri.etsi.org/01903#SignedProperties", ) ki = xmlsig.template.ensure_key_info(signature, name=kinfo_id) data = xmlsig.template.add_x509_data(ki) xmlsig.template.x509_data_add_certificate(data) xmlsig.template.add_key_value(ki) ctx = xmlsig.SignatureContext() ctx.x509 = certificate[1] ctx.public_key = ctx.x509.public_key() ctx.private_key = certificate[0] dslist = ( "ds:Object", (), ( "etsi:QualifyingProperties", ("Target", "#" + signature_id), ( "etsi:SignedProperties", ("Id", sp_id), ( "etsi:SignedSignatureProperties", (), ("etsi:SigningTime", (), datetime.now().isoformat()), ( "etsi:SigningCertificateV2", (), ( "etsi:Cert", (), ( "etsi:CertDigest", (), ( "ds:DigestMethod", ( "Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256", ), ), ( "ds:DigestValue", (), b64encode( ctx.x509.fingerprint( hashes.SHA256())).decode(), ), ), ), ), ( "etsi:SignaturePolicyIdentifier", (), ( "etsi:SignaturePolicyId", (), ( "etsi:SigPolicyId", (), ("etsi:Identifier", (), tax_agency.sign_file_url), ( "etsi:Description", (), ), ), ( "etsi:SigPolicyHash", (), ( "ds:DigestMethod", ( "Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256", ), ), ("ds:DigestValue", (), tax_agency.sign_file_hash), ), ), ), ), ), ), ) root.append(signature) create_node_tree(signature, [dslist]) ctx.sign(signature) signature_value = signature.find( "ds:SignatureValue", namespaces=xmlsig.constants.NS_MAP).text # RFC2045 - Base64 Content-Transfer-Encoding (page 25) # Any characters outside of the base64 alphabet are to be ignored in # base64-encoded data. return signature_value.replace("\n", "")
def _sign_file(cert, password, request): min = 1 max = 99999 signature_id = 'Signature%05d' % random.randint(min, max) signed_properties_id = signature_id + '-SignedProperties%05d' \ % random.randint(min, max) key_info_id = 'KeyInfo%05d' % random.randint(min, max) reference_id = 'Reference%05d' % random.randint(min, max) object_id = 'Object%05d' % random.randint(min, max) etsi = 'http://uri.etsi.org/01903/v1.3.2#' sig_policy_identifier = 'http://www.facturae.es/' \ 'politica_de_firma_formato_facturae/' \ 'politica_de_firma_formato_facturae_v3_1' \ '.pdf' sig_policy_hash_value = 'Ohixl6upD6av8N7pEvDABhEL6hM=' root = etree.fromstring(request) sign = xmlsig.template.create( c14n_method=xmlsig.constants.TransformInclC14N, sign_method=xmlsig.constants.TransformRsaSha1, name=signature_id, ns="ds") key_info = xmlsig.template.ensure_key_info(sign, name=key_info_id) x509_data = xmlsig.template.add_x509_data(key_info) xmlsig.template.x509_data_add_certificate(x509_data) xmlsig.template.add_key_value(key_info) certificate = crypto.load_pkcs12(base64.b64decode(cert), password) xmlsig.template.add_reference( sign, xmlsig.constants.TransformSha1, uri='#' + signed_properties_id, uri_type='http://uri.etsi.org/01903#SignedProperties') xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1, uri='#' + key_info_id) ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1, name=reference_id) xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) object_node = etree.SubElement( sign, etree.QName(xmlsig.constants.DSigNs, 'Object'), nsmap={'etsi': etsi}, attrib={xmlsig.constants.ID_ATTR: object_id}) qualifying_properties = etree.SubElement( object_node, etree.QName(etsi, 'QualifyingProperties'), attrib={'Target': '#' + signature_id}) signed_properties = etree.SubElement( qualifying_properties, etree.QName(etsi, 'SignedProperties'), attrib={xmlsig.constants.ID_ATTR: signed_properties_id}) signed_signature_properties = etree.SubElement( signed_properties, etree.QName(etsi, 'SignedSignatureProperties')) now = datetime.now().replace(microsecond=0, tzinfo=pytz.utc) etree.SubElement(signed_signature_properties, etree.QName( etsi, 'SigningTime')).text = now.isoformat() signing_certificate = etree.SubElement( signed_signature_properties, etree.QName(etsi, 'SigningCertificate')) signing_certificate_cert = etree.SubElement( signing_certificate, etree.QName(etsi, 'Cert')) cert_digest = etree.SubElement(signing_certificate_cert, etree.QName(etsi, 'CertDigest')) etree.SubElement( cert_digest, etree.QName(xmlsig.constants.DSigNs, 'DigestMethod'), attrib={'Algorithm': 'http://www.w3.org/2000/09/xmldsig#sha1'}) hash_cert = hashlib.sha1( crypto.dump_certificate(crypto.FILETYPE_ASN1, certificate.get_certificate())) etree.SubElement( cert_digest, etree.QName( xmlsig.constants.DSigNs, 'DigestValue')).text = base64.b64encode(hash_cert.digest()) issuer_serial = etree.SubElement(signing_certificate_cert, etree.QName(etsi, 'IssuerSerial')) etree.SubElement( issuer_serial, etree.QName(xmlsig.constants.DSigNs, 'X509IssuerName') ).text = xmlsig.utils.get_rdns_name( certificate.get_certificate().to_cryptography().issuer.rdns) etree.SubElement( issuer_serial, etree.QName( xmlsig.constants.DSigNs, 'X509SerialNumber')).text = str( certificate.get_certificate().get_serial_number()) signature_policy_identifier = etree.SubElement( signed_signature_properties, etree.QName(etsi, 'SignaturePolicyIdentifier')) signature_policy_id = etree.SubElement( signature_policy_identifier, etree.QName(etsi, 'SignaturePolicyId')) sig_policy_id = etree.SubElement(signature_policy_id, etree.QName(etsi, 'SigPolicyId')) etree.SubElement(sig_policy_id, etree.QName( etsi, 'Identifier')).text = sig_policy_identifier etree.SubElement(sig_policy_id, etree.QName( etsi, 'Description')).text = "Política de Firma FacturaE v3.1" sig_policy_hash = etree.SubElement( signature_policy_id, etree.QName(etsi, 'SigPolicyHash')) etree.SubElement( sig_policy_hash, etree.QName(xmlsig.constants.DSigNs, 'DigestMethod'), attrib={'Algorithm': 'http://www.w3.org/2000/09/xmldsig#sha1'}) try: remote = urllib.request.urlopen(sig_policy_identifier) hash_value = base64.b64encode( hashlib.sha1(remote.read()).digest()) except urllib.request.HTTPError: hash_value = sig_policy_hash_value etree.SubElement( sig_policy_hash, etree.QName(xmlsig.constants.DSigNs, 'DigestValue')).text = hash_value signer_role = etree.SubElement(signed_signature_properties, etree.QName(etsi, 'SignerRole')) claimed_roles = etree.SubElement(signer_role, etree.QName(etsi, 'ClaimedRoles')) etree.SubElement(claimed_roles, etree.QName(etsi, 'ClaimedRole')).text = 'supplier' signed_data_object_properties = etree.SubElement( signed_properties, etree.QName(etsi, 'SignedDataObjectProperties')) data_object_format = etree.SubElement( signed_data_object_properties, etree.QName(etsi, 'DataObjectFormat'), attrib={'ObjectReference': '#' + reference_id}) etree.SubElement(data_object_format, etree.QName(etsi, 'Description')).text = 'Factura' etree.SubElement(data_object_format, etree.QName(etsi, 'MimeType')).text = 'text/xml' ctx = xmlsig.SignatureContext() key = crypto.load_pkcs12(base64.b64decode(cert), password) ctx.x509 = key.get_certificate().to_cryptography() ctx.public_key = ctx.x509.public_key() ctx.private_key = key.get_privatekey().to_cryptography_key() root.append(sign) ctx.sign(sign) return etree.tostring(root, xml_declaration=True, encoding='UTF-8')
def sign_file(cert, password, xml_firma): random_val = random.randint(1, 99999) signature_id = 'Signature-%s' % random_val signed_properties_id = 'SignedProperties-%s' % signature_id signature_value = 'SignatureValue-%s' % random_val qualifying_properties = 'QualifyingProperties-%05d' % random_val key_info_id = 'KeyInfoId-%s' % signature_id reference_id = 'Reference-%05d' % random_val object_id = 'XadesObjectId-%05d' % random_val xades = 'http://uri.etsi.org/01903/v1.3.2#' ds = 'http://www.w3.org/2000/09/xmldsig#' xades141 = 'http://uri.etsi.org/01903/v1.4.1#' sig_policy_identifier = 'http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf' sig_policy_hash_value = 'Ohixl6upD6av8N7pEvDABhEL6hM=' root = xml_firma certificate = crypto.load_pkcs12(cert, password) sign = etree.Element(etree.QName(ds, 'Signature'), nsmap={ 'ds': ds, 'xades': 'http://uri.etsi.org/01903/v1.3.2#' }, attrib={ xmlsig.constants.ID_ATTR: signature_id, }) signed_info = etree.SubElement(sign, etree.QName(ds, 'SignedInfo')) etree.SubElement(signed_info, etree.QName(ds, 'CanonicalizationMethod'), attrib={'Algorithm': xmlsig.constants.TransformInclC14N}) etree.SubElement(signed_info, etree.QName(ds, 'SignatureMethod'), attrib={'Algorithm': xmlsig.constants.TransformRsaSha256}) reference = etree.SubElement(signed_info, etree.QName(ds, 'Reference'), attrib={ xmlsig.constants.ID_ATTR: reference_id, 'URI': '' }) transforms = etree.SubElement( reference, etree.QName(ds, 'Transforms'), ) etree.SubElement( transforms, etree.QName(ds, 'Transform'), attrib={ 'Algorithm': 'http://www.w3.org/2000/09/xmldsig#enveloped-signature' }) etree.SubElement( reference, etree.QName(ds, 'DigestMethod'), attrib={'Algorithm': 'http://www.w3.org/2001/04/xmlenc#sha256'}) etree.SubElement(reference, etree.QName(ds, 'DigestValue')) sec_reference = etree.SubElement(signed_info, etree.QName(ds, 'Reference'), attrib={ xmlsig.constants.ID_ATTR: 'ReferenceKeyInfo', 'URI': '#' + key_info_id }) etree.SubElement( sec_reference, etree.QName(ds, 'DigestMethod'), attrib={'Algorithm': 'http://www.w3.org/2001/04/xmlenc#sha256'}) digest_value2 = hashlib.sha256( crypto.dump_certificate(crypto.FILETYPE_ASN1, certificate.get_certificate())) etree.SubElement(sec_reference, etree.QName( ds, 'DigestValue')).text = base64.b64encode(digest_value2.digest()) tr_reference = etree.SubElement( signed_info, etree.QName(ds, 'Reference'), attrib={ 'Type': 'http://uri.etsi.org/01903#SignedProperties', 'URI': '#' + signed_properties_id, }) etree.SubElement( tr_reference, etree.QName(ds, 'DigestMethod'), attrib={'Algorithm': 'http://www.w3.org/2001/04/xmlenc#sha256'}) digest_value3 = hashlib.sha256( crypto.dump_certificate(crypto.FILETYPE_ASN1, certificate.get_certificate())) etree.SubElement(tr_reference, etree.QName( ds, 'DigestValue')).text = base64.b64encode(digest_value3.digest()) etree.SubElement(sign, etree.QName(ds, 'SignatureValue'), attrib={xmlsig.constants.ID_ATTR: signature_value}) key_info = etree.SubElement(sign, etree.QName(ds, 'KeyInfo'), attrib={xmlsig.constants.ID_ATTR: key_info_id}) x509 = etree.SubElement( key_info, etree.QName(ds, 'X509Data'), ) etree.SubElement( x509, etree.QName(ds, 'X509Certificate'), ) etree.SubElement( key_info, etree.QName(ds, 'KeyValue'), ) object_node = etree.SubElement( sign, etree.QName(xmlsig.constants.DSigNs, 'Object'), attrib={xmlsig.constants.ID_ATTR: object_id}) qualifying_properties = etree.SubElement( object_node, etree.QName(xades, 'QualifyingProperties'), nsmap={ 'xades': xades, 'xades141': xades141 }, attrib={ xmlsig.constants.ID_ATTR: qualifying_properties, 'Target': '#' + signature_id }) signed_properties = etree.SubElement( qualifying_properties, etree.QName(xades, 'SignedProperties'), attrib={xmlsig.constants.ID_ATTR: signed_properties_id}) signed_signature_properties = etree.SubElement( signed_properties, etree.QName(xades, 'SignedSignatureProperties')) etree.SubElement( signed_signature_properties, etree.QName(xades, 'SigningTime')).text = datetime.datetime.now().strftime( '%Y-%m-%dT%H:%M:%S%z') signing_certificate = etree.SubElement( signed_signature_properties, etree.QName(xades, 'SigningCertificate')) signing_certificate_cert = etree.SubElement(signing_certificate, etree.QName(xades, 'Cert')) cert_digest = etree.SubElement(signing_certificate_cert, etree.QName(xades, 'CertDigest')) etree.SubElement( cert_digest, etree.QName(xmlsig.constants.DSigNs, 'DigestMethod'), attrib={'Algorithm': 'http://www.w3.org/2001/04/xmlenc#sha256'}) hash_cert = hashlib.sha256( crypto.dump_certificate(crypto.FILETYPE_ASN1, certificate.get_certificate())) etree.SubElement(cert_digest, etree.QName(xmlsig.constants.DSigNs, 'DigestValue')).text = base64.b64encode( hash_cert.digest()) issuer_serial = etree.SubElement(signing_certificate_cert, etree.QName(xades, 'IssuerSerial')) etree.SubElement( issuer_serial, etree.QName( xmlsig.constants.DSigNs, 'X509IssuerName')).text = xmlsig.utils.get_rdns_name( certificate.get_certificate().to_cryptography().issuer.rdns) etree.SubElement(issuer_serial, etree.QName( xmlsig.constants.DSigNs, 'X509SerialNumber')).text = str( certificate.get_certificate().get_serial_number()) signature_policy_identifier = etree.SubElement( signed_signature_properties, etree.QName(xades, 'SignaturePolicyIdentifier')) signature_policy_id = etree.SubElement( signature_policy_identifier, etree.QName(xades, 'SignaturePolicyId')) sig_policy_id = etree.SubElement(signature_policy_id, etree.QName(xades, 'SigPolicyId')) etree.SubElement(sig_policy_id, etree.QName(xades, 'Identifier')).text = sig_policy_identifier etree.SubElement(sig_policy_id, etree.QName(xades, 'Description')).text = 'facturae31' sig_policy_hash = etree.SubElement(signature_policy_id, etree.QName(xades, 'SigPolicyHash')) etree.SubElement( sig_policy_hash, etree.QName(xmlsig.constants.DSigNs, 'DigestMethod'), attrib={'Algorithm': 'http://www.w3.org/2000/09/xmldsig#sha1'}) try: remote = urllib.urlopen(sig_policy_identifier) hash_value = base64.b64encode(hashlib.sha1(remote.read()).digest()) except Exception as e: hash_value = sig_policy_hash_value etree.SubElement(sig_policy_hash, etree.QName(xmlsig.constants.DSigNs, 'DigestValue')).text = hash_value etsi = xades signer_role = etree.SubElement(signed_signature_properties, etree.QName(etsi, 'SignerRole')) claimed_roles = etree.SubElement(signer_role, etree.QName(etsi, 'ClaimedRoles')) etree.SubElement(claimed_roles, etree.QName(etsi, 'ClaimedRole')).text = 'emisor' ctx = xmlsig.SignatureContext() key = crypto.load_pkcs12(cert, password) ctx.x509 = key.get_certificate().to_cryptography() ctx.public_key = ctx.x509.public_key() ctx.private_key = key.get_privatekey().to_cryptography_key() # print (etree.tostring(sign)) root.append(sign) ctx.sign(sign) return etree.tostring(root, encoding='UTF-8', xml_declaration=True, standalone=False)
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 sign(root, certificate): """ Sign XML with PKCS #12 :author: Victor Laskurain <*****@*****.**> :return: SignatureValue """ def create_node_tree(root_node, elem_list): """Convierte una lista en XML. Cada elemento de la lista se interpretará de la siguiente manera: Si es un string se añadirá al texto suelto del nodo root. Si es una tupla/lista t se interpretará como sigue: t[0] es el nombre del elemento a crear. Puede tener prefijo de espacio de nombres. t[1] es la lista de atributos donde las posiciones pares son claves y los impares valores. t[2:] son los subelementos que se interpretan recursivamente """ for elem_def in elem_list: if isinstance(elem_def, str): root_node.text = (root_node.text or '') + elem_def else: ns = '' elemname = elem_def[0] attrs = elem_def[1] children = elem_def[2:] if ':' in elemname: ns, elemname = elemname.split(':') ns = root_node.nsmap[ns] node = xmlsig.utils.create_node(elemname, root_node, ns) for attr_name, attr_value in zip(attrs[::2], attrs[1::2]): node.set(attr_name, attr_value) create_node_tree(node, children) doc_id = 'id-' + str(uuid4()) signature_id = 'sig-' + doc_id kinfo_id = 'ki-' + doc_id sp_id = 'sp-' + doc_id signature = xmlsig.template.create( xmlsig.constants.TransformInclC14N, xmlsig.constants.TransformRsaSha256, signature_id, ) ref = xmlsig.template.add_reference(signature, xmlsig.constants.TransformSha256, uri='') xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) xmlsig.template.add_reference(signature, xmlsig.constants.TransformSha256, uri='#' + kinfo_id) xmlsig.template.add_reference(signature, xmlsig.constants.TransformSha256, uri="#" + sp_id) ki = xmlsig.template.ensure_key_info(signature, name=kinfo_id) data = xmlsig.template.add_x509_data(ki) xmlsig.template.x509_data_add_certificate(data) xmlsig.template.add_key_value(ki) ctx = xmlsig.SignatureContext() ctx.load_pkcs12(certificate) ctx.x509 = certificate.get_certificate().to_cryptography() ctx.public_key = ctx.x509.public_key() ctx.private_key = certificate.get_privatekey().to_cryptography_key() dslist = ('ds:Object', (), ('etsi:QualifyingProperties', ('Target', signature_id), ('etsi:SignedProperties', ('Id', sp_id), ('etsi:SignedSignatureProperties', (), ('etsi:SigningTime', (), datetime.now().isoformat()), ('etsi:SigningCertificateV2', (), ('etsi:Cert', (), ('etsi:CertDigest', (), ('ds:DigestMethod', ('Algorithm', 'http://www.w3.org/2000/09/xmldsig#sha256')), ('ds:DigestValue', (), b64encode(ctx.x509.fingerprint( hashes.SHA256())).decode())))), ('etsi:SignaturePolicyIdentifier', (), ('etsi:SignaturePolicyId', (), ('etsi:SigPolicyId', (), ('etsi:Identifier', (), 'http://ticketbai.eus/politicafirma'), ('etsi:Description', (), 'Política de Firma TicketBAI 1.0')), ('etsi:SigPolicyHash', (), ('ds:DigestMethod', ('Algorithm', 'http://www.w3.org/2000/09/xmldsig#sha256')), ('ds:DigestValue', (), 'lX1xDvBVAsPXkkJ7R07WCVbAm9e0H33I1sCpDtQNkbc=')))))))) root.append(signature) create_node_tree(signature, [dslist]) ctx.sign(signature) signature_value = signature.find( 'ds:SignatureValue', namespaces=xmlsig.constants.NS_MAP).text # RFC2045 - Base64 Content-Transfer-Encoding (page 25) # Any characters outside of the base64 alphabet are to be ignored in # base64-encoded data. return signature_value.replace('\n', '')
def _sign_file(self, move, request, public_cert, private_key): rand_min = 1 rand_max = 99999 signature_id = "Signature%05d" % random.randint(rand_min, rand_max) signed_properties_id = signature_id + "-SignedProperties%05d" % random.randint( rand_min, rand_max) key_info_id = "KeyInfo%05d" % random.randint(rand_min, rand_max) reference_id = "Reference%05d" % random.randint(rand_min, rand_max) object_id = "Object%05d" % random.randint(rand_min, rand_max) etsi = "http://uri.etsi.org/01903/v1.3.2#" sig_policy_identifier = ("http://www.facturae.es/" "politica_de_firma_formato_facturae/" "politica_de_firma_formato_facturae_v3_1" ".pdf") sig_policy_hash_value = "Ohixl6upD6av8N7pEvDABhEL6hM=" root = etree.fromstring(request) sign = xmlsig.template.create( c14n_method=xmlsig.constants.TransformInclC14N, sign_method=xmlsig.constants.TransformRsaSha1, name=signature_id, ns="ds", ) key_info = xmlsig.template.ensure_key_info(sign, name=key_info_id) x509_data = xmlsig.template.add_x509_data(key_info) xmlsig.template.x509_data_add_certificate(x509_data) xmlsig.template.add_key_value(key_info) with open(public_cert, "rb") as f: certificate = x509.load_pem_x509_certificate(f.read()) xmlsig.template.add_reference( sign, xmlsig.constants.TransformSha1, uri="#" + signed_properties_id, uri_type="http://uri.etsi.org/01903#SignedProperties", ) xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1, uri="#" + key_info_id) ref = xmlsig.template.add_reference(sign, xmlsig.constants.TransformSha1, name=reference_id, uri="") xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped) object_node = etree.SubElement( sign, etree.QName(xmlsig.constants.DSigNs, "Object"), nsmap={"etsi": etsi}, attrib={xmlsig.constants.ID_ATTR: object_id}, ) qualifying_properties = etree.SubElement( object_node, etree.QName(etsi, "QualifyingProperties"), attrib={"Target": "#" + signature_id}, ) signed_properties = etree.SubElement( qualifying_properties, etree.QName(etsi, "SignedProperties"), attrib={xmlsig.constants.ID_ATTR: signed_properties_id}, ) signed_signature_properties = etree.SubElement( signed_properties, etree.QName(etsi, "SignedSignatureProperties")) now = datetime.now().replace(microsecond=0, tzinfo=pytz.utc) etree.SubElement(signed_signature_properties, etree.QName(etsi, "SigningTime")).text = now.isoformat() signing_certificate = etree.SubElement( signed_signature_properties, etree.QName(etsi, "SigningCertificate")) signing_certificate_cert = etree.SubElement(signing_certificate, etree.QName(etsi, "Cert")) cert_digest = etree.SubElement(signing_certificate_cert, etree.QName(etsi, "CertDigest")) etree.SubElement( cert_digest, etree.QName(xmlsig.constants.DSigNs, "DigestMethod"), attrib={"Algorithm": "http://www.w3.org/2000/09/xmldsig#sha1"}, ) hash_cert = hashlib.sha1(certificate.public_bytes(Encoding.DER)) etree.SubElement(cert_digest, etree.QName(xmlsig.constants.DSigNs, "DigestValue")).text = base64.b64encode( hash_cert.digest()) issuer_serial = etree.SubElement(signing_certificate_cert, etree.QName(etsi, "IssuerSerial")) etree.SubElement( issuer_serial, etree.QName(xmlsig.constants.DSigNs, "X509IssuerName")).text = xmlsig.utils.get_rdns_name( certificate.issuer.rdns) etree.SubElement( issuer_serial, etree.QName(xmlsig.constants.DSigNs, "X509SerialNumber")).text = str( certificate.serial_number) signature_policy_identifier = etree.SubElement( signed_signature_properties, etree.QName(etsi, "SignaturePolicyIdentifier"), ) signature_policy_id = etree.SubElement( signature_policy_identifier, etree.QName(etsi, "SignaturePolicyId")) sig_policy_id = etree.SubElement(signature_policy_id, etree.QName(etsi, "SigPolicyId")) etree.SubElement(sig_policy_id, etree.QName( etsi, "Identifier")).text = sig_policy_identifier etree.SubElement(sig_policy_id, etree.QName( etsi, "Description")).text = "Política de Firma FacturaE v3.1" sig_policy_hash = etree.SubElement(signature_policy_id, etree.QName(etsi, "SigPolicyHash")) etree.SubElement( sig_policy_hash, etree.QName(xmlsig.constants.DSigNs, "DigestMethod"), attrib={"Algorithm": "http://www.w3.org/2000/09/xmldsig#sha1"}, ) hash_value = sig_policy_hash_value etree.SubElement(sig_policy_hash, etree.QName(xmlsig.constants.DSigNs, "DigestValue")).text = hash_value signer_role = etree.SubElement(signed_signature_properties, etree.QName(etsi, "SignerRole")) claimed_roles = etree.SubElement(signer_role, etree.QName(etsi, "ClaimedRoles")) etree.SubElement(claimed_roles, etree.QName(etsi, "ClaimedRole")).text = "supplier" signed_data_object_properties = etree.SubElement( signed_properties, etree.QName(etsi, "SignedDataObjectProperties")) data_object_format = etree.SubElement( signed_data_object_properties, etree.QName(etsi, "DataObjectFormat"), attrib={"ObjectReference": "#" + reference_id}, ) etree.SubElement(data_object_format, etree.QName(etsi, "Description")).text = "Factura" etree.SubElement(data_object_format, etree.QName(etsi, "MimeType")).text = "text/xml" ctx = xmlsig.SignatureContext() ctx.x509 = certificate ctx.public_key = certificate.public_key() with open(private_key, "rb") as f: ctx.private_key = serialization.load_pem_private_key(f.read(), password=None) root.append(sign) ctx.sign(sign) return etree.tostring(root, xml_declaration=True, encoding="UTF-8")