XML цифровая подпись в Python - PullRequest
0 голосов
/ 13 ноября 2018

Я пытаюсь добавить цифровую подпись для электронного счета в моей стране.Подпись использовать XADES-EPS в качестве стандарта.

Я использую xmlsig и etree для создания синтаксиса подписи в Python 3, например,

def sign_file(cert, password, xml_firma):
    min = 1
    max = 99999

    xmlns_uris = {'ds': 'http://myhost.com/p.xsd'}

    random_val = random.randint(min, max)

    signature_id = 'Signature-' + str(random_val)
    #signed_properties_id = signature_id + '-SignedProperties%05d' \
    #                       % random.randint(min, max)

    signed_properties_id = 'SignedProperties-' + signature_id
    signature_value = 'SignatureValue-' + str(random_val)

    qualifying_properties = 'QualifyingProperties-%05d' % random.randint(min, max)

    #key_info_id = 'KeyInfo%05d' % random.randint(min, max)
    key_info_id = 'KeyInfoId-' + signature_id

    reference_id = 'Reference-%05d' % random.randint(min, max)
    #object_id = 'Object%05d' % random.randint(min, max)
    object_id = 'XadesObjectId-%05d' % random.randint(min, max)

    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 = 'https://www.hacienda.go.cr/ATV/ComprobanteElectronico/docs/esquemas/2016/v4/Resolucion%20Comprobantes%20Electronicos%20%20DGT-R-48-2016.pdf'
    sig_policy_hash_value = 'V8lVVNGDCPen6VELRD1Ja8HARFk='

    xml_firmar = base64decode(xml_firma)

    root = etree.fromstring(xml_firmar)

    certificate = crypto.load_pkcs12(base64.b64decode(cert), password)

    # GENERAR NODO PERSONALIZADO PARA FE
    sign = etree.Element(
        etree.QName(ds, 'Signature'),
        nsmap={'ds': ds},
        attrib={
            xmlsig.constants.ID_ATTR: signature_id,
        }
    )

    #annotate_with_xmlns_prefixes(sign, 'ds')
    #add_xmnls_attributes(sign, xmlns_uris)

    # GENERO EL NODO ds:SignedInfo
    signed_info = etree.SubElement(
        sign,
        etree.QName(ds, 'SignedInfo')
    )

    # CREO EL NODO ds:CanonicalizationMethod DENTRO DE signed_info
    etree.SubElement(
        signed_info,
        etree.QName(ds, 'CanonicalizationMethod'),
        attrib={
            'Algorithm': xmlsig.constants.TransformInclC14N
        }
    )

    # CREO EL NODO ds:SignatureMethod DENTRO DE signed_info
    etree.SubElement(
        signed_info,
        etree.QName(ds, 'SignatureMethod'),
        attrib={
            'Algorithm': xmlsig.constants.TransformRsaSha256
        }
    )

    # CREO EL NODO ds:SignatureMethod DENTRO DE signed_info
    reference = etree.SubElement(
        signed_info,
        etree.QName(ds, 'Reference'),
        attrib={
            xmlsig.constants.ID_ATTR: reference_id,
            'URI': ''
        }
    )

    # CREO EL NODO ds:Transforms DENTRO DE reference
    transforms = etree.SubElement(
        reference,
        etree.QName(ds, 'Transforms'),
    )

    # CREO EL NODO ds:Transform DENTRO DE trasnforms
    etree.SubElement(
        transforms,
        etree.QName(ds, 'Transform'),
        attrib={
            'Algorithm': 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'
        }
    )

    # CREO EL NODO ds:DigestMethod DENTRO DE reference
    etree.SubElement(
        reference,
        etree.QName(ds, 'DigestMethod'),
        attrib={
            'Algorithm': 'http://www.w3.org/2001/04/xmlenc#sha256'
        }
    )

    # OBTENGO EL DIGEST VALUE DEL CERTIFICADO PARA EL NODO DIGESTVALUE
    #digest_value = hashlib.sha256(
    #    crypto.dump_certificate(
    #        crypto.FILETYPE_ASN1,
    #        certificate.get_certificate()
    #    )
    #)

    # GENERO EL NODO ds:DigestValue DENTRO DE reference
    etree.SubElement(
        reference,
        etree.QName(ds, 'DigestValue')
    )#.text = base64.b64encode(digest_value.digest())

    # CREO EL SEGUNDO NODO ds:SignatureMethod DENTRO DE signed_info
    sec_reference = etree.SubElement(
        signed_info,
        etree.QName(ds, 'Reference'),
        attrib={
            xmlsig.constants.ID_ATTR: 'ReferenceKeyInfo',
            'URI': '#' + key_info_id

        }
    )

    # CREO EL NODO ds:DigestMethod DENTRO DE reference
    etree.SubElement(
        sec_reference,
        etree.QName(ds, 'DigestMethod'),
        attrib={
            'Algorithm': 'http://www.w3.org/2001/04/xmlenc#sha256'
        }
    )

    # OBTENGO EL DIGEST VALUE DEL CERTIFICADO PARA EL NODO DIGESTVALUE
    #digest_value2 = hashlib.sha256(
    #    crypto.dump_certificate(
    #        crypto.FILETYPE_ASN1,
    #        certificate.get_certificate()
    #    )
    #)

    # GENERO EL NODO ds:DigestValue DENTRO DE reference
    etree.SubElement(
        sec_reference,
        etree.QName(ds, 'DigestValue')
    )#.text = base64.b64encode(digest_value2.digest())

    # CREO EL TERCER NODO ds:Reference DENTRO DE signed_info
    tr_reference = etree.SubElement(
        signed_info,
        etree.QName(ds, 'Reference'),
        attrib={
            'Type': 'http://uri.etsi.org/01903#SignedProperties',
            'URI': '#' + signed_properties_id,
        }
    )

    # CREO EL NODO ds:DigestMethod DENTRO DE reference
    etree.SubElement(
        tr_reference,
        etree.QName(ds, 'DigestMethod'),
        attrib={
            'Algorithm': 'http://www.w3.org/2001/04/xmlenc#sha256'
        }
    )

    # OBTENGO EL DIGEST VALUE DEL CERTIFICADO PARA EL NODO DIGESTVALUE
    #digest_value3 = hashlib.sha256(
    #    crypto.dump_certificate(
    #        crypto.FILETYPE_ASN1,
    #        certificate.get_certificate()
    #    )
    #)

    # GENERO EL NODO ds:DigestValue DENTRO DE reference
    etree.SubElement(
        tr_reference,
        etree.QName(ds, 'DigestValue')
    )#.text = base64.b64encode(digest_value3.digest())

    # GENERO EL NODO ds:SignatureValue
    etree.SubElement(
        sign,
        etree.QName(ds, 'SignatureValue'),
        attrib={
            xmlsig.constants.ID_ATTR: signature_value
        }
    )

    # GENERO EL NODO ds:KeyInfo
    key_info = etree.SubElement(
        sign,
        etree.QName(ds, 'KeyInfo'),
        attrib={
            xmlsig.constants.ID_ATTR: key_info_id
        }
    )


    # GENERO EL NODO ds:X509Data
    x509 = etree.SubElement(
        key_info,
        etree.QName(ds, 'X509Data'),
    )

    # GENERO EL NODO ds:X509Certificate
    etree.SubElement(
        x509,
        etree.QName(ds, 'X509Certificate'),
    )

    # GENERO EL NODO ds:KeyValue
    etree.SubElement(
        key_info,
        etree.QName(ds, 'KeyValue'),
    )



    #AQUI EMPIEZO A CREAR EL NODO DE QUALIFYNG PROPERTIES
    object_node = etree.SubElement(
        sign,
        etree.QName(xmlsig.constants.DSigNs, 'Object'),
        #nsmap={'etsi': etsi},
        attrib={xmlsig.constants.ID_ATTR: object_id}
        #nsmap={'xades': xades},
        #attrib={xmlsig.constants.ID_ATTR: object_id} NO SE NECESITA EN EL XML DE HACIENDA
    )

    #CREO EL SUBNODO QUALIFYING PROPERTIES
    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
        })

    #CREO EL NODO xades:SignedProperties DENTRO DE QUALIFYING PROPERTIES
    signed_properties = etree.SubElement(
        qualifying_properties,
        etree.QName(xades, 'SignedProperties'),
        attrib={
            xmlsig.constants.ID_ATTR: signed_properties_id #ESTO HAY QUE CAMBIARLO PARA QUE SEA COMO LO PIDE HACIENDA
        }
    )

    # CREO EL NODO xades:SignedSignatureProperties DENTRO DE SIGNED PROPERTIES
    signed_signature_properties = etree.SubElement(
        signed_properties,
        etree.QName(xades, 'SignedSignatureProperties')
    )

    #GENERO LA HORA PARA EL NODO xades:SigningTime
    #now = datetime.datetime.now().replace(
    #    microsecond=0, tzinfo=pytz.utc
    #)

    # GENERO EL NODO xades:SigningTime Y LE PONGO LA HORA
    etree.SubElement(
        signed_signature_properties,
        etree.QName(xades, 'SigningTime')
    ).text = get_time_hacienda()

    #GENERO EL NODO xades:SigningCertificate
    signing_certificate = etree.SubElement(
        signed_signature_properties,
        etree.QName(xades, 'SigningCertificate')
    )

    #GENERO EL NODO xades:Cert DENTRO DE xades:SigningCertificate
    signing_certificate_cert = etree.SubElement(
        signing_certificate,
        etree.QName(xades, 'Cert')
    )

    #GENERO EL NODO xades:CertDigest DENTRO DE xades:cert
    cert_digest = etree.SubElement(
        signing_certificate_cert,
        etree.QName(xades, 'CertDigest')
    )

    #GENERO EL NODO ds:DigestMethod DENTRO DE xades:CertDigest
    etree.SubElement(
        cert_digest,
        etree.QName(xmlsig.constants.DSigNs, 'DigestMethod'),
        attrib={
            'Algorithm': 'http://www.w3.org/2001/04/xmlenc#sha256'
        }
    )

    # OBTENGO EL DIGEST VALUE DEL CERTIFICADO PARA EL NODO DIGESTVALUE
    hash_cert = hashlib.sha256(
        crypto.dump_certificate(
            crypto.FILETYPE_ASN1,
            certificate.get_certificate()
        )
    )

    # GENERO EL NODO ds:DigestValue DENTRO DE xades:CertDigest Y LE PONGO EL VALOR DEL DIGESTVALUE ANTERIOR
    etree.SubElement(
        cert_digest,
        etree.QName(xmlsig.constants.DSigNs, 'DigestValue')
    ).text = base64.b64encode(hash_cert.digest())

    # GENERO EL NODO xades:IssuerSerial DENTRO DE xades:Cert
    issuer_serial = etree.SubElement(
        signing_certificate_cert,
        etree.QName(xades, 'IssuerSerial')
    )

    # GENERO EL NODO ds:X509IssuerName DENTRO DE 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)

    # GENERO EL NODO ds:X509SerialNumber DENTRO DE xades:IssuerSerial
    etree.SubElement(
        issuer_serial,
        etree.QName(xmlsig.constants.DSigNs, 'X509SerialNumber')
    ).text = str(certificate.get_certificate().get_serial_number())

    # GENERO EL NODO xades:SignaturePolicyIdentifier DENTRO DE sign
    signature_policy_identifier = etree.SubElement(
        signed_signature_properties,
        etree.QName(xades, 'SignaturePolicyIdentifier')
    )

    # GENERO EL NODO xades:SignaturePolicyId DENTRO DE xades:SignaturePolicyIdentifier
    signature_policy_id = etree.SubElement(
        signature_policy_identifier,
        etree.QName(xades, 'SignaturePolicyId')
    )

    # GENERO EL NODO xades:SigPolicyId DENTRO DE xades:SignaturePolicyId
    sig_policy_id = etree.SubElement(
        signature_policy_id,
        etree.QName(xades, 'SigPolicyId')
    )

    # GENERO EL NODO xades:Identifier DENTRO DE xades:SigPolicyId
    etree.SubElement(
        sig_policy_id,
        etree.QName(xades, 'Identifier')
    ).text = sig_policy_identifier

    #BORRO ESTE NODO PUES EN FE COSTA RICA NO SE NECESITA
    etree.SubElement(
        sig_policy_id,
        etree.QName(xades, 'Description')
    )#.text = "Política de Firma FacturaE v3.1"

    # GENERO EL NODO xades:Identifier DENTRO DE signature_policy_id
    sig_policy_hash = etree.SubElement(
        signature_policy_id,
        etree.QName(xades, 'SigPolicyHash')
    )

    # GENERO EL NODO ds:DigestMethod DENTRO DE xades:Identifier
    etree.SubElement(
        sig_policy_hash,
        etree.QName(xmlsig.constants.DSigNs, 'DigestMethod'),
        attrib={
            'Algorithm': 'http://www.w3.org/2000/09/xmldsig#sha1'
        })

    #GENERO EL DIGEST PARA EL CERTIFICADO LEYENDOLO DE LA URL DE HACIENDA
    try:
        remote = urllib.request.urlopen(sig_policy_identifier)
        hash_value = base64.b64encode(hashlib.sha256(remote.read()).digest())
    except urllib.request.HTTPError:
        hash_value = sig_policy_hash_value

    # GENERO EL NODO ds:DigestValue ds:DigestMethod DENTRO DE sig_policy_hash
    etree.SubElement(
        sig_policy_hash,
        etree.QName(xmlsig.constants.DSigNs, 'DigestValue')
    ).text = hash_value

    #NO REQUERIDO PARA FE COSTA RICA
    #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(xades, 'SignedDataObjectProperties')
    )

    data_object_format = etree.SubElement(
        signed_data_object_properties,
        etree.QName(xades, 'DataObjectFormat'),
        attrib={
            'ObjectReference': '#' + reference_id
        }
    )

    #etree.SubElement(
    #    data_object_format,
    #    etree.QName(etsi, 'Description')
    #).text = 'Factura'

    etree.SubElement(
        data_object_format,
        etree.QName(xades, '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)

    root.xpath(
        '//ds:Signature', namespaces={'ds': xmlsig.constants.DSigNs}
    )[0]

    ctx.sign(sign)

    #xml_bytes = etree.tostring(root, xml_declaration=True)

    #return stringToBase64(xml_bytes)


    return etree.tostring(
        root
    )

Этот код создает подпись xml следующим образом

<ds:Signature Id="Signature-17044">
    <ds:SignedInfo>
      <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
      <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
      <ds:Reference Id="Reference-74608" URI="">
        <ds:Transforms>
          <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
        </ds:Transforms>
        <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <ds:DigestValue>+fhrrmGMWpvy8IEowaa2fLh905rLv6xRmKB4zD6Omtg=</ds:DigestValue>
      </ds:Reference>
      <ds:Reference Id="ReferenceKeyInfo" URI="#KeyInfoId-Signature-17044">
        <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <ds:DigestValue>zXjLpycv3XvGI47RJR0qIoxtZoApAHwkdjDqRKiGpTU=</ds:DigestValue>
      </ds:Reference>
      <ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#SignedProperties-Signature-17044">
        <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <ds:DigestValue>sHX8p3YfyvxNIwL2NC54sZdYfvsfXvfYTCks/8BMQlQ=</ds:DigestValue>
      </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue Id="SignatureValue-17044">B9Mb3Ogm1QNkpu1N8o4rF0EP4a/vM9Yjgk7EiYZBQmU/r6+yZ/pVC27XYJubbcoxhlth1pk4y15d95I7RfqdqOwMt8dj6P9FKljPH2jCRU0IlHCul3eudjaBWfbu2+fjpyIFRO53kHhOfumkstMKtSCAPSgHRV0wLb06JcZhVCUXUNy+rJfElxzJjhNq+aHcIjayf2TVs1w/elFOz9Ax2v18xol6BRyHnlrQEUtD57AiVurkkcGs0oUqrDGV7IN3YebBJWge/ttXF+5Bz9NnBHQD4iU5ul3aGhcOwq7FY7qxIbmqBHhmcLi7ZGVCXfiEU58Sn6Sa8yBjOILh5Wgzng==</ds:SignatureValue>
    <ds:KeyInfo Id="KeyInfoId-Signature-17044">
      <ds:X509Data>
        <ds:X509Certificate>MIIFRjCCAy6gAwIBAgIGAWZEbfKCMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNVBAYTAkNSMSkwJwYDVQQKDCBNSU5JU1RFUklPIERFIEhBQ0lFTkRBIC0gU0FOREJPWDEMMAoGA1UECwwD</ds:X509Certificate>
      </ds:X509Data>
      <ds:KeyValue>
        <ds:RSAKeyValue>
          <ds:Modulus>pVc+nKRrS6q2O05DEO7r1smiY/0PDTZpYSirjEzPaxQ2hRvZDGaxX8wEzqiAE7i9icZbqtwaQPECBRhlzjEK4lxbDhESqYI34CKiMMeARquVGn9WJ/EQjQhGm1KgqROvizX3LRcxDgJzd6pbKPxqTUC/gDAy/PEJJuD9WOH0NVYCREZtD3ShkiIt3DphbDK84Whqhvt47ZyFRSIgjxrRkerhOZj3EzFjr4aIADXUXY2LJGTyKHEe8WcvA9k/aaZGZaTRINOwgk4KMSzNXBDweoxXzN0bJukbxXEicLqO7cJHqoaZX0gCDU8KfAN2UAtmhQ/IRgkSD6O2F72kC86ePQ==</ds:Modulus>
          <ds:Exponent>AQAB</ds:Exponent>
        </ds:RSAKeyValue>
      </ds:KeyValue>
    </ds:KeyInfo>
    <ds:Object Id="XadesObjectId-41822">
      <xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#" Id="QualifyingProperties-01611" Target="#Signature-17044">
        <xades:SignedProperties Id="SignedProperties-Signature-17044">
          <xades:SignedSignatureProperties>
            <xades:SigningTime>2018-11-12T15:24:21-06:00</xades:SigningTime>
            <xades:SigningCertificate>
              <xades:Cert>
                <xades:CertDigest>
                  <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                  <ds:DigestValue>gVmykfOS2A4p8ALJ6XVLcdTagMz+hMG4ikjzFipgymI=</ds:DigestValue>
                </xades:CertDigest>
                <xades:IssuerSerial>
                  <ds:X509IssuerName>CN=CA PERSONA JURIDICA - SANDBOX,OU=DGT,O=MINISTERIO DE HACIENDA - SANDBOX,C=CR</ds:X509IssuerName>
                  <ds:X509SerialNumber>1538746348162</ds:X509SerialNumber>
                </xades:IssuerSerial>
              </xades:Cert>
            </xades:SigningCertificate>
            <xades:SignaturePolicyIdentifier>
              <xades:SignaturePolicyId>
                <xades:SigPolicyId>
                  <xades:Identifier>https://www.hacienda.go.cr/ATV/ComprobanteElectronico/docs/esquemas/2016/v4/Resolucion%20Comprobantes%20Electronicos%20%20DGT-R-48-2016.pdf</xades:Identifier>
                  <xades:Description/>
                </xades:SigPolicyId>
                <xades:SigPolicyHash>
                  <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                  <ds:DigestValue>a5aV6NckKzC/0CR4tQeTg2ULnhUNK2uDxsO5VuRInTE=</ds:DigestValue>
                </xades:SigPolicyHash>
              </xades:SignaturePolicyId>
            </xades:SignaturePolicyIdentifier>
          </xades:SignedSignatureProperties>
          <xades:SignedDataObjectProperties>
            <xades:DataObjectFormat ObjectReference="#Reference-74608">
              <xades:MimeType>text/xml</xades:MimeType>
            </xades:DataObjectFormat>
          </xades:SignedDataObjectProperties>
        </xades:SignedProperties>
      </xades:QualifyingProperties>
    </ds:Object>
  </ds:Signature>

XML-результат имеет то, что мне нужно, но я всегда получаю ответ подписи инвалидов.При проверке подписи это ответ, который я получаю

Проверка подписи ... xmlsec:

func = xmlSecOpenSSLEvpDigestVerify: file = digests.c: line = 250: obj = sha256: subj = unknown: ошибка = 12: неверные данные: данные и дайджест не соответствуют FAIL SignedInfo Список литературы (нормально / все): 1/2 Ссылки манифестов (хорошо / все): 0/0

но я понятия не имею, чтопроблема может быть

Есть идеи?

ПРИМЕЧАНИЕ: СЕРТИФИКАТНАЯ ИНФОРМАЦИЯ ВЫРЕЗАНА

...