Guia: o manual e a biblioteca

Copy Markdown View Source

Este guia percorre o capítulo 5 do Manual de Segurança do SFN e mostra onde cada regra está na biblioteca. A numeração das seções é a do Vol. I da versão 6.00, igual à da versão 5.07 no capítulo 5.

A especificação completa, com a origem de cada regra, está em docs/SPEC.md (em inglês). Lá, cada regra é marcada como normativa (está no manual), prática de mercado (o manual é omisso; precisa ser confirmada com tráfego real) ou decisão da biblioteca.

Mapa das seções

Seção do manualAssuntoNa biblioteca
§5.1, Tabela 1, Anexo ACampos C01–C15 do cabeçalhoSinetex.Header
§5.1.4, §5.2.6Número de série em C11/C13Sinetex.Serial; campo serial_field de Sinetex.Certificate
§4.2.6OU=CSPB-n e o código da PC (C10/C12)Sinetex.Certificate.load/2, opção :pc
§4.2.7, §4.2.8, §4.4.16RSA-2048, expoente diferente de 3Sinetex.Certificate.load/2, Sinetex.Identity.load/3
§5.1.2, §5.1.2.1, Anexo AVersão 2: 3DES, paridade ímpar, IVSinetex.seal/2 com version: 2
§5.1.3 a §5.1.3.4Versão 3: AES-256-GCM, chave e nonce no C14Sinetex.seal/2 (padrão)
§5.1.7SHA-256 com chaves de 2048 bitsC09 = 0x03
§5.2 (§5.2.3 a §5.2.9)Emissão: compactar, completar, assinar, cifrarSinetex.seal/2
§5.2.8Chave simétrica nova a cada mensagemSinetex.seal/2
§5.3 (§5.3.2 a §5.3.6)RecepçãoSinetex.open/2
§5.4Registro de auditoriaainda não implementado
§5.5.1Tabela de erros (C03, EGEN99xx)Sinetex.Error
§5.5.3Vários erros: vale o de menor códigoSinetex.open/2
§5.5.2, §5.5.4, §5.5.7GEN0004 só assinada, com o C03Sinetex.error_envelope/2
§5.5.5Não responder erro com erroheader.c03 em Sinetex.Opened
§5.5.8Erro fora do escopo de segurança (0xFF)Sinetex.Error
§5.6, §5.6.1, §5.6.2Tratamento especial (C04)opção :c04 de Sinetex.seal/2
§5.6.3Cabeçalho "vazio" (eco da GEN0001)opção allow_clear: true de Sinetex.open/2
§5.7.13C04 6 e 10: uso público, sem cifraopção :c04
§5.7.14O padding da v2 não é removidoopção :trim_padding de Sinetex.open/2
§5.8.27ZIP com DEFLATE nos servidores SFTPcompression: :zip
§4.4.10, §4.5.11, §4.5.12, §5.3.2Certificado ativo, revogado ou vencidoopção :certificate_check de Sinetex.open/2

Certificados e chaves (§4.2, §4.4.16, §5.1.4)

{:ok, me} = Sinetex.Identity.load(File.read!("me.crt"), File.read!("me.key"))
{:ok, peer} = Sinetex.Certificate.load(File.read!("peer.crt"))

Na carga, a biblioteca confere o que o protocolo exige: chave RSA de exatamente 2048 bits (o que garante o bit mais significativo do módulo ligado), expoente público diferente de 3 e número de série de até 16 bytes, que é o que cabe nos 32 caracteres de C11/C13.

O código da PC vem de OU=CSPB-n no emissor do certificado (§4.2.6). Um certificado fora do padrão SPB, como os que algumas câmaras usam, não tem esse campo: informe o código com pc:. Sem ele, a carga falha com {:error, :pc_code_required}. A biblioteca não tenta adivinhar a AC pelo nome.

A chave privada pode vir em PEM ou DER, PKCS#1 ou PKCS#8, inclusive PKCS#8 cifrado (password:).

Emissão (§5.2)

Sinetex.seal/2 segue a ordem do manual:

  1. compacta, se o C04 for 8 ou 10;
  2. completa com 0x00 até múltiplo de 8 (só na v2, §5.2.3);
  3. assina o resultado com RSASSA-PKCS1-v1_5 e SHA-256 (C15);
  4. cifra com uma chave nova (§5.2.8), se o C04 indicar cifra, e põe a chave cifrada com a chave pública do destinatário no C14;
  5. preenche o cabeçalho.
{:ok, envelope} = Sinetex.seal(xml, from: me, to: peer)
{:ok, envelope} = Sinetex.seal(arquivo, from: me, to: peer, c04: 8, compression: :gzip)

Sem cifra (C04 2, 3, 6 e 10), o destinatário é opcional; o C14 vai zerado.

Recepção (§5.3) e erros (§5.5)

Sinetex.open/2 trabalha em duas fases:

  • Fase A, cabeçalho. Todas as checagens rodam e, se houver mais de um erro, vale o de menor código (§5.5.3).
  • Fase B, criptografia. Decifra o C14, decifra o corpo, verifica a assinatura e descompacta, nessa ordem, parando no primeiro erro. Por isso um 0x0B (assinatura) só aparece quando a decifragem deu certo, embora seu código seja menor que 0x0D e 0x0E.
case Sinetex.open(envelope, as: me, from: peer) do
  {:ok, %Sinetex.Opened{content: content, header: header}} -> ...
  {:error, %Sinetex.Error{c03: c03, egen: egen, field: field}} -> ...
end

O erro traz o código do C03, o código da GEN0004 (EGEN9901 a EGEN9920), o campo do cabeçalho e uma mensagem. O motivo interno (campo reason) serve só para log: não o mande para a contraparte.

Os erros que dependem do seu cadastro de certificados (§4.4.10, §4.5.11, §5.3.2) ficam com você, pela opção :certificate_check:

C03EGENQuando
0x10EGEN9916certificado não ativado
0x11EGEN9917certificado vencido ou revogado
0x14EGEN9920certificado inválido
check = fn cert, header ->
  cond do
    revogado?(cert) -> {:error, 0x11}
    not ativo?(cert) and header.c04 != 1 -> {:error, 0x10}
    true -> :ok
  end
end

Sinetex.open(envelope, as: me, from: peer, certificate_check: check)

O C04 1 indica que o remetente usa um certificado ainda não ativado (o caso da GEN0006); o cabeçalho é passado para a função justamente para permitir isso.

Pela regra de §4.5.12, o certificado deixa de valer às 24h do dia anterior ao "válido até". A biblioteca ainda não faz essa conta; use cert.not_after.

Responder com GEN0004 (§5.5.2, §5.5.4, §5.5.5, §5.5.7)

A biblioteca nunca responde a ninguém. Para devolver um erro, monte o XML da GEN0004 e chame Sinetex.error_envelope/2, que gera um envelope só assinado (C04 3) com o C03 preenchido:

with {:error, %Sinetex.Error{} = error} <- Sinetex.open(envelope, as: me, from: peer) do
  Sinetex.error_envelope(gen0004_xml(error.egen), from: me, to: peer, c03: error.c03)
end

Uma mensagem recebida com C03 diferente de zero é a contraparte reportando um erro. Ela abre normalmente, o código fica em opened.header.c03, e não deve ser respondida com outro erro (§5.5.5).

Antes de responder automaticamente às falhas de C14 (0x0D), leia o SECURITY.md: esse código pode servir de oráculo para um ataque à cifra RSA. A opção c14_errors: :uniform troca o 0x0D por 0x0E.

Tratamento especial, C04 (§5.6)

C04UsoCompactadoCifrado
0mensagem ou arquivo, assinado e cifrado (padrão)✔
1certificado ainda não ativado (GEN0006)✔
2broadcast público, sem destinatário
3mensagem de segurança, como a GEN0004
4arquivo não compactado✔
6arquivo não compactado, uso público (§5.7.13)
8arquivo compactado✔✔
10arquivo compactado, uso público (§5.7.13)✔

Todos são assinados. O manual não diz com todas as letras que 1 e 4 são cifrados: é o que se observa na prática.

O cabeçalho "vazio" de §5.6.3, com tudo zerado menos o C01, traz o corpo em claro, sem assinatura. Por padrão ele é recusado com 0x02; com allow_clear: true, o conteúdo volta marcado protection: :clear.

Compactação (§5.6.2, §5.8.27)

O manual fala em ZIP; nos servidores SFTP do Bacen, ZIP com DEFLATE (§5.8.27). As câmaras usam GZIP. Como o formato depende do destino, a emissão com C04 8 ou 10 exige compression: :gzip ou compression: :zip.

Na recepção, o formato é reconhecido pelos magic bytes, e opened.compression diz qual foi encontrado. Conteúdo marcado como compactado que não é GZIP nem ZIP volta como chegou.

Versão 2 (§5.1.2, §5.7.14)

A versão 2 usa 3DES e completa o conteúdo com 0x00 até múltiplo de 8. O manual manda não remover esse padding (§5.7.14), e a biblioteca devolve o conteúdo exatamente como foi assinado. Se o seu conteúdo não termina em zeros legítimos, trim_padding: true remove até 7 zeros finais.

Prefira a versão 3 sempre que a contraparte aceitar. Num OpenSSL em modo FIPS, o 3DES pode não estar disponível.

Conferir com tráfego real

Alguns pontos seguem a prática de mercado porque o manual é omisso, como a ordem dos bytes do C01 e o preenchimento do C14 quando não há cifra. A lista está na seção 9 da SPEC. Para conferi-los num ambiente de homologação, tools/header_dump.exs imprime o cabeçalho de um envelope sem decifrar nada e sem precisar de chaves:

elixir tools/header_dump.exs envelope.bin
elixir tools/header_dump.exs --offset 64 registro.bin   # pula 64 bytes antes do cabeçalho