Skip to content

Instantly share code, notes, and snippets.

@JuniorPolegato
Created June 9, 2016 21:14
Show Gist options
  • Save JuniorPolegato/afe62b33db5255c2865cab92b854c5ce to your computer and use it in GitHub Desktop.
Save JuniorPolegato/afe62b33db5255c2865cab92b854c5ce to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import lxml.etree
import xmlsec
import urllib2
###############################################################################
# Ajustes para NFe
###############################################################################
# Devido ao nome da chave no Brasil ter "/" e ":",
# no arquivo pk11.py, colocar no início "import urllib2" e abaixo da linha 47:
# (module_path, sep, keyqs) = o.path.rpartition('/')
# colocar a linha:
# keyqs = urllib2.unquote(keyqs)
# Alguns cartões não tem especificação de nome da chave, assim para funcionar
# deve-se retirar (CKA_LABEL, keyname) na linha 135:
# key = _find_object(session, [(CKA_LABEL, keyname), (CKA_CLASS, ...
###############################################################################
# O ID slot deve vir da lista de slots, assim, no arquivo pk11.py, trocar:
# session = lib.openSession(slot) # linha 164
# por:
# session = lib.openSession(lib.getSlotList()[slot])
###############################################################################
# O atributo reference_uri no NFe é dado por "Id", mas o xmlsec procura por
# "id" e "ID", não encontrando o da NFe, assim deve-se adicionar este em
# __init__.py, na linha 48:
# id_attributes = pyconfig.setting("xmlsec.id_attributes", ['ID', 'id'])
# ficando:
# id_attributes = pyconfig.setting("xmlsec.id_attributes", ['ID', 'id', 'Id'])
###############################################################################
# Para sair o certificado na assinatura, é preciso, no arquivo __init__.py,
# na função _load_keyspec, abaixo da linha 165:
# 'source': 'pkcs11',
# colocar a linha:
# 'data': data,
# e depois, na função sign, trocar a linha 551:
# public = None
# por:
# public = private if private['source'] == 'pkcs11' else None
###############################################################################
# Leitora Gemalto de R$ 25,00 compatívle com Linux
# Usando lib da A.E.T. Europe B.V., do pacote safesignidentityclient 3.0.88-12
# para Debian. Essa versão é importante para suporte ao cartão Certisign 2015
# O keyname, na minha leitora de cartão, é o label do Private Key obtido com:
# pkcs11-tool -O -l --module /usr/lib/libaetpkss.so
module = '/usr/lib/libaetpkss.so'
keyname = 'Nome da empresa:12345678901 (01/01/2016 ~ 01/01/2018)'
pin = '1234'
# Dados da NFe para assinar
chave = '12345678901234567890123456789012345678901234'
nfe = '/tmp/%s-nfe.xml' % chave # caminho para a NFe da chave acima
reference_uri = '#NFe' + chave
xml = lxml.etree.parse(nfe)
# Tags XML da assinatura conforme padrão da NFe
transforms = (xmlsec.constants.TRANSFORM_ENVELOPED_SIGNATURE,
xmlsec.constants.TRANSFORM_C14N_INCLUSIVE)
xmlsec.add_enveloped_signature(xml,
transforms=transforms,
reference_uri=reference_uri,
pos=-1)
# Especificação para usar o A3
keyname = urllib2.quote(keyname, '')
pk11_uri = 'pkcs11://%s/%s?pin=%s' % (module, keyname, pin)
# Assinando a NFe com A3
xmlsec.sign(xml, pk11_uri)
# Salvando a NFe assinada
xml.write(nfe[:-3] + '-assinada.xml',
encoding=xml.docinfo.encoding,
xml_declaration=True)
@MarcoDev1987
Copy link

MarcoDev1987 commented Jul 22, 2022

Bom dia amigo, parabéns pela façanha, esse código está sendo procurado por muitos programadores.
Eu sou novo na área então tenho várias dúvidas, mas acho que é a mais importante para implementação está na linha 58 do seu código, onde você seleciona em qual tag vai entrar a assinatura, certo? No meu caso, especificamente, tenho que assinar uma NFS-e que é diferente, obviamente, da NFe, ou seja a tag de assinatura vai estar em local diferente, como posso adaptar ao meu código, em que esta assinatura vai entrar depois da tag " </LoteRPs" ?
A segunda dúvida, aproveitando o embalo é: a variável 'module' e 'kayname', eu consigo encontrar nas especificações do fabricante do meu certificado do tipo Token/USB?
A terceira duvida é: a variável chave, onde que encontro essa numeração?

@JuniorPolegato
Copy link
Author

JuniorPolegato commented Jul 23, 2022

Olá!

No meu caso a assinatura é por NFe, e não por lote, sendo que esta tem uma chave única de 44 dígitos que pode ser consultada na Sefaz do estado emissor ou no ambiente nacional https://www.nfe.fazenda.gov.br , creio que você já deva ter acessado este para consultar alguma NFe.

Assim cada NFe (ou XML), a ser assinada tem que ter uma TAG com atributo "Id", no caso da NFe é a TAG "infNFe" que tem um atributo "Id" com conteúdo "NFe<chave_nfe_44_dígitos>".

Dessa forma, passando a "reference_uri" como sendo o conteúdo do "Id" precedido por "#" para o xmlsec, este irá procurar no XML qual TAG tem um atributo "Id" com o referido conteúdo e adicionar a TAG "Signature" após essa TAG encontrada, no caso da NFe é a TAG "infNFe".

No seu caso, se for esse padrão, sua TAG "LoteRPs" deverá ter um atributo "Id", cujo conteúdo será a "reference_uri" precedido por "#". Seria importante ver um XML desse seu assinado para se situar melhor.

No xmlsec, não me recordo bem, mas lembro que é possível especificar TAG e/ou atributo que terá o conteúdo a ser canonizado e assinado, teria que ver na documentação, se for esse seu caso de precisar assinar fora desse padrão universal que segue a NFe.

A variável "module" é a biblioteca compilada (.so no Linux ou .dll no Windows) fornecida pelo fabricante para acessar o dispositivo, onde no meu caso no Linux, leitora Gemalto e .so da A.E.T. Europe B.V (chip da leitora/cartão), no seu caso será para seu token USB, que é o mesmo que você coloca no Firefox Linux. No Windows não sei dizer.

A variável "keyname" é o nome/label do certificado exibido por esse "module", tal como eu documentei no código, obtido executando "pkcs11-tool -O -l --module /usr/lib/libaetpkss.so", no meu caso.

Fechando, fiz isso há muito tempo mais como prova de conceito de que é possível utilizar A3 no Linux, funcionou, mas nunca coloquei em produção devido a facilidade do A1 NFe e independência desse fabricantes e revendedores que te dão zero de atenção ou suporte, e também no A1 NFe posso ter vários e especificar um CPF responsável cada, podendo revogar a qualquer momento sem perdas. A3 hoje em dia é de obrigatoriedade muito específica ( https://www.certisign.com.br/certificado-digital/indicacao-uso ) e quase não usado, venho utilizando somente com e-CPF para sócios/diretores/contadores em nuvem, muito mais seguro e cada acesso fica registrado.

Abraços.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment