Я пытаюсь добавить цифровую подпись для электронного счета в моей стране.Подпись использовать 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
но я понятия не имею, чтопроблема может быть
Есть идеи?
ПРИМЕЧАНИЕ: СЕРТИФИКАТНАЯ ИНФОРМАЦИЯ ВЫРЕЗАНА