O envelope de segurança da RSFN (Rede do Sistema Financeiro Nacional), a rede que liga instituições financeiras, câmaras e o Banco Central do Brasil, para Elixir.

A biblioteca monta e abre o cabeçalho de segurança de 588 bytes definido no capítulo 5 do Manual de Segurança do SFN, que é público (Vol. I, versão 6.00; o capítulo 5 é igual ao da versão 5.07). Cobre o Protocolo de Segurança v3 (AES-256-GCM) e o v2 (3DES, legado): assinatura, cifra, compactação e a tabela de erros do C03. Versões novas do manual saem na página Comunicação Eletrônica de Dados do Banco Central.

Read in English.

Status: v0.1, pré-lançamento. A biblioteca segue o manual e é testada contra uma implementação independente. Alguns pontos em que o manual é omisso seguem a prática de mercado observada e ainda não foram confirmados com tráfego real. A lista está em docs/SPEC.md. A versão 1.0 depende dessa confirmação.

O Sinetex é um projeto independente, de código aberto. Não tem vínculo com o Banco Central do Brasil nem é endossado por ele.

O guia em português põe cada função ao lado da seção do manual que ela implementa.

Escopo

Dentro: o cabeçalho (C01–C15), emissão e recepção em v2 e v3, todos os valores de C04, a tabela de erros com a regra do menor código, GZIP e ZIP.

Fora: transporte (MQ, Connect:Direct, SFTP), o XML das mensagens, habilitação e ativação de certificados, LCR, armazenamento de chaves. A biblioteca trabalha com bytes: você entrega o conteúdo e as chaves e recebe o envelope, e vice-versa. Não tem dependências em tempo de execução além de :crypto e :public_key.

Instalação

def deps do
  [{:sinetex, "~> 0.1"}]
end

Elixir 1.16 ou mais novo, OTP 26 ou mais novo. A versão 2 do protocolo precisa de 3DES no OpenSSL ao qual o OTP está ligado.

Uso

Chaves e certificados

# Seu certificado e sua chave privada (PEM ou DER; PKCS#1, PKCS#8 ou PKCS#8 cifrado)
{:ok, me} = Sinetex.Identity.load(File.read!("me.crt"), File.read!("me.key"))

# O certificado da contraparte
{:ok, peer} = Sinetex.Certificate.load(File.read!("peer.crt"))

C10 e C12 levam o código da AC (PC). Ele é lido de OU=CSPB-n no emissor do certificado. Para certificados sem esse campo, informe o código: Sinetex.Certificate.load(pem, pc: 4).

Os certificados precisam ser RSA-2048, com número de série de até 16 bytes, como o cabeçalho exige.

Emissão

{:ok, envelope} = Sinetex.seal(xml, from: me, to: peer)

# Arquivo compactado (C04 8). O formato é obrigatório: veja abaixo.
{:ok, envelope} = Sinetex.seal(arquivo, from: me, to: peer, c04: 8, compression: :gzip)

# Versão 2
{:ok, envelope} = Sinetex.seal(xml, from: me, to: peer, version: 2)

Recepção

case Sinetex.open(envelope, as: me, from: peer) do
  {:ok, %Sinetex.Opened{content: content, header: header}} ->
    # header.c03 != 0 quer dizer que a contraparte está reportando um erro
    ...

  {:error, %Sinetex.Error{c03: c03, egen: egen} = error} ->
    # c03 é o código da tabela do manual; egen, o código da GEN0004 (EGEN99xx)
    Logger.warning(Exception.message(error))
end

:from aceita uma lista de certificados, para um remetente que está trocando de certificado. Outras opções:

OpçãoPadrão
:known[]Outros certificados. Mensagem assinada com um deles falha com 0x0C (certificado não é do emissor) em vez de 0x0A
:certificate_checknenhumfn cert, header -> :ok | {:error, c03} end, para o que depende do seu cadastro de certificados: não ativado (0x10), vencido ou revogado (0x11), inválido (0x14)
:c14_errors:manual:uniform reporta a falha ao decifrar o C14 como 0x0E em vez de 0x0D. Veja o SECURITY.md
:trim_paddingfalseA v2 mantém o padding 0x00, como manda o manual. true remove até 7 zeros finais
:allow_clearfalseAceita o cabeçalho "vazio" (todos os campos zerados, como no eco da GEN0001), cujo corpo não é assinado nem cifrado
:max_output_bytes512 MiBTeto do conteúdo descompactado
:max_input_bytes:infinityTeto do envelope

Reportar um erro

A biblioteca nunca responde a ninguém. Quando open/2 falha, você decide se devolve uma GEN0004. error_envelope/2 monta o envelope dela: assinado, sem cifra (C04 3), com o C03 preenchido. O XML da GEN0004 fica com você.

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

Compactação

Com C04 8 e 10, :compression é obrigatório na emissão. Não há padrão porque o formato certo depende do destino: as câmaras usam GZIP; o manual e os servidores SFTP do Bacen usam ZIP com DEFLATE.

Na recepção, o formato é detectado pelos magic bytes, não pelo C04, e %Sinetex.Opened{compression: :gzip | :zip | :none} diz qual foi encontrado. Conteúdo marcado como compactado que não é GZIP nem ZIP é devolvido como chegou, o que já foi observado na prática.

Inspecionar tráfego real

tools/header_dump.exs imprime o cabeçalho de um envelope sem decifrar nada e sem chaves. Não depende da biblioteca:

elixir tools/header_dump.exs envelope.bin

Documentação

  • Documentação da API: HexDocs.
  • docs/SPEC.md (inglês): o que a biblioteca faz com cada byte, com a origem de cada regra (manual, prática de mercado ou decisão da biblioteca).
  • guides/manual.md: cada função ao lado da seção do manual.
  • SECURITY.md (inglês): notas de segurança e como reportar uma vulnerabilidade.

Desenvolvimento

mix test

Os certificados e chaves de teste são gerados por test/support/gen_fixtures.sh (precisa do openssl) na primeira execução. Nenhuma chave ou amostra de terceiros está no repositório.

Os testes de interoperabilidade rodam contra uma implementação independente em Python, escrita só a partir da especificação. Eles precisam do Python 3 com o pacote cryptography e são pulados sem ele:

python3 -m venv .venv && .venv/bin/pip install cryptography
SINETEX_PYTHON=.venv/bin/python mix test

Licença

Apache-2.0: veja os arquivos LICENSE e NOTICE.