Skip to content

Commit f9d8ec3

Browse files
Merge pull request Samuel-Oliveira#320 from jardelnovaes/develop
Nota Técnica 2025.001 - Ajustar assinatura QRCode Contingência
2 parents 3bdcf2d + 14dc65d commit f9d8ec3

File tree

2 files changed

+85
-12
lines changed

2 files changed

+85
-12
lines changed

src/main/java/br/com/swconsultoria/nfe/util/NFCeUtil.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public static String getCodeQRCodeContingencia(String chave, String ambiente, St
9595
* @param valorNF : Campo de Valor da Nota (W16)
9696
* @param tpDestinatario : 1=CNPJ; 2=CPF; 3=idEstrangeiro; Caso Destinatário estrangeiro ou não identificado, informar apenas nulo ou vazio
9797
* @param identDest : Identificação do Destinatário CPF ou CNPJ na NFC-e.; Caso Destinatário estrangeiro ou não identificado, informar apenas nulo ou vazio
98-
* @param urlConsulta : Url De Consulta da Nfc-e do Estado
98+
* @param urlConsulta : Url De Consulta da Nfc-e do Estado
9999
*
100100
* Para NFC-e emitida em contingência “off-line”:
101101
* https://endereco-consultaQRCode?p=<chave_acesso>|<versao_qrcode>|<tpAmb>|<dia_data_emissao>|<vNF>|<tp_idDest>|<idDest>|<assinatura>
@@ -106,14 +106,14 @@ public static String getCodeQRCodeContingenciaV3(String chave, String ambiente,
106106
String tpDestinatario, String identDest, String urlConsulta,
107107
Certificado certificado) throws NfeException {
108108

109-
String valor = String.format("%s?p=%s|3|%s|%s|%s|%s|%s",
110-
urlConsulta, chave, ambiente, dhEmi.substring(8, 10), valorNF,
109+
String valor = String.format("%s|3|%s|%s|%s|%s|%s",
110+
chave, ambiente, dhEmi.substring(8, 10), valorNF,
111111
Optional.ofNullable(tpDestinatario).orElse(""),
112112
Optional.ofNullable(identDest).orElse(""));
113113

114-
String assinatura = "";
115-
return valor + "|" + assinarQrCodeV3(valor,certificado);
114+
String assinatura = assinarQrCodeV3(valor, certificado);
116115

116+
return urlConsulta + "?p=" + valor + "|" + assinatura;
117117
}
118118

119119
/**

src/test/java/br/com/swconsultoria/nfe/util/NFCeUtilTest.java

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package br.com.swconsultoria.nfe.util;
22

3+
import br.com.swconsultoria.certificado.Certificado;
4+
import br.com.swconsultoria.certificado.CertificadoService;
5+
import br.com.swconsultoria.certificado.exception.CertificadoException;
6+
import br.com.swconsultoria.nfe.exception.NfeException;
37
import org.junit.jupiter.api.Test;
48

9+
import java.io.FileNotFoundException;
10+
import java.net.URI;
11+
import java.net.URISyntaxException;
512
import java.nio.charset.StandardCharsets;
6-
import java.security.NoSuchAlgorithmException;
13+
import java.nio.file.Paths;
14+
import java.security.*;
715
import java.util.Base64;
16+
import java.util.Objects;
817

9-
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
10-
import static org.junit.jupiter.api.Assertions.assertEquals;
11-
import static org.junit.jupiter.api.Assertions.assertThrows;
18+
import static org.junit.jupiter.api.Assertions.*;
1219

1320
class NFCeUtilTest {
1421

@@ -29,24 +36,90 @@ void geraHashCSRTParametrosInvalidos() {
2936
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, ()
3037
-> geraHashCSRTBase64(null, csrt)
3138
);
32-
assertEquals(exception.getMessage(), "Chave não deve ser nula ou vazia");
39+
assertEquals("Chave não deve ser nula ou vazia", exception.getMessage());
3340

3441
//Chave Com menos Caracteres
3542
exception = assertThrows(IllegalArgumentException.class, ()
3643
-> geraHashCSRTBase64("123", csrt)
3744
);
38-
assertEquals(exception.getMessage(), "Chave deve conter 44 caracteres.");
45+
assertEquals("Chave deve conter 44 caracteres.", exception.getMessage());
3946

4047
//CSRC Vazio
4148
exception = assertThrows(IllegalArgumentException.class, ()
4249
-> geraHashCSRTBase64(chave, "")
4350
);
44-
assertEquals(exception.getMessage(), "CSRT não deve ser nulo ou vazio");
51+
assertEquals("CSRT não deve ser nulo ou vazio", exception.getMessage());
4552
}
4653

4754
private byte[] geraHashCSRTBase64(String chave, String csrt) throws NoSuchAlgorithmException {
4855
//O XSD é xs:base64Binary e já faz o base64, aqui é uma "simulação" para os testes
4956
return Base64.getEncoder().encode(NFCeUtil.geraHashCSRT(chave, csrt));
5057
}
5158

59+
@Test
60+
void getCodeQRCodeContingenciaV3Sucesso() throws Exception {
61+
String chave = "41180678393592000146558900000006041028190697";
62+
String tpAmp = "2";
63+
String dhEmi = "2025-07-01T12:37:06-03:00";
64+
String vNF = "12.34";
65+
String tpDestRegraQrCode = "2";
66+
String cpf = "11111111111";
67+
String dadosAssinar = obterDadosAssinar(chave, tpAmp, dhEmi, vNF, tpDestRegraQrCode, cpf);
68+
Certificado certificado = obterCertificado();
69+
70+
String qrcodeGerado = NFCeUtil.getCodeQRCodeContingenciaV3(chave, tpAmp, dhEmi, vNF, tpDestRegraQrCode, cpf,
71+
"https://fake.it", certificado);
72+
byte[] assinatura = Base64.getDecoder().decode(qrcodeGerado.split("\\|")[7]);
73+
74+
assertTrue(isAssinaturaValida(certificado, dadosAssinar, assinatura));
75+
}
76+
77+
/**
78+
* Neste caso da assinatura não bater a Sefaz retorna:
79+
* 583-Rejeicao: Valor da assinatura do qrCode difere do valor calculado
80+
*/
81+
@Test
82+
void getCodeQRCodeContingenciaV3FalhaDadosDivergentes()
83+
throws FileNotFoundException, URISyntaxException, CertificadoException, NfeException, GeneralSecurityException {
84+
String chave = "41180678393592000146558900000006041028190697";
85+
String tpAmp = "2";
86+
String dhEmi = "2025-07-01T12:37:06-03:00";
87+
String vNF = "12.34";
88+
String tpDestRegraQrCode = "2";
89+
String cpf = "11111111111";
90+
91+
String dadosAssinar = obterDadosAssinar(chave, tpAmp, dhEmi, vNF, tpDestRegraQrCode, "22222222222");
92+
Certificado certificado = obterCertificado();
93+
94+
String qrcodeGerado = NFCeUtil.getCodeQRCodeContingenciaV3(chave, tpAmp, dhEmi, vNF, tpDestRegraQrCode, cpf,
95+
"https://fake.it", certificado);
96+
byte[] assinatura = Base64.getDecoder().decode(qrcodeGerado.split("\\|")[7]);
97+
98+
assertFalse(isAssinaturaValida(certificado, dadosAssinar, assinatura));
99+
}
100+
101+
private String obterDadosAssinar(String chave, String tpAmp, String dhEmi, String vNF, String tpDestRegraQrCode,
102+
String cpf) {
103+
//Conforme manual v6.0 QRCode - Assinar apenas Campos 1 ao 7, incluindo os separadores |.
104+
return String.format("%s|3|%s|%s|%s|%s|%s", chave, tpAmp, dhEmi.substring(8, 10), vNF, tpDestRegraQrCode, cpf);
105+
}
106+
107+
private Certificado obterCertificado() throws URISyntaxException, CertificadoException, FileNotFoundException {
108+
URI uri = Objects.requireNonNull(NFCeUtilTest.class.getClassLoader()
109+
.getResource("NAO_UTILIZE.pfx"))
110+
.toURI();
111+
return CertificadoService.certificadoPfx(Paths.get(uri).toString(), "123456");
112+
}
113+
114+
private boolean isAssinaturaValida(Certificado certificado, String dadosAssinar, byte[] assinatura)
115+
throws GeneralSecurityException, CertificadoException {
116+
Signature verifier = Signature.getInstance("SHA1withRSA");
117+
KeyStore keyStore = CertificadoService.getKeyStore(certificado);
118+
PublicKey publicKey = keyStore.getCertificate(certificado.getNome()).getPublicKey();
119+
120+
verifier.initVerify(publicKey);
121+
verifier.update(dadosAssinar.getBytes(StandardCharsets.UTF_8));
122+
return verifier.verify(assinatura);
123+
}
124+
52125
}

0 commit comments

Comments
 (0)