Sinetex (sinetex v0.1.0)

Copy Markdown View Source

The security envelope of the RSFN, the network of the Brazilian Financial System, as defined in chapter 5 of the Manual de Segurança do SFN published by Banco Central do Brasil. Section numbers such as §5.5.3 refer to that manual (version 5.07; Volume I of version 6.00 keeps the same chapter 5).

An envelope is a 588-byte header (Sinetex.Header) followed by the body:

  • Security Protocol v3: AES-256-GCM, RSA-2048, SHA-256;
  • Security Protocol v2 (legacy): 3DES-EDE3-CBC, RSA-2048, SHA-256.

The library works on bytes. It does not parse the XML of the message, does not talk to the network and does not store keys.

Example

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

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

# Receiving
{:ok, %Sinetex.Opened{content: xml}} = Sinetex.open(incoming, as: me, from: peer)

Special treatment (C04)

C04MeaningCompressedEncrypted
0message or file, signed and encrypted (default)✔
1uses a certificate that is not activated yet (GEN0006)✔
2public broadcast, no recipient
3security message, e.g. GEN0004 (see error_envelope/2)
4file, not compressed✔
6file, not compressed, public
8file, compressed✔✔
10file, compressed, public✔

Every value is signed. That 1 and 4 are encrypted is market practice, not stated by the manual.

Errors

open/2 returns {:error, %Sinetex.Error{}} with the C03 code of the manual's error table. When there is more than one error in the header, the lowest code is reported (§5.5.3). Malformed input never raises.

Summary

Functions

Builds the envelope that reports a security error to a counterparty (§5.5.2, §5.5.4, §5.5.7): signed, not encrypted (C04 3), with C03 set.

Opens an envelope: checks the header, decrypts, verifies the signature and, for C04 8 and 10, decompresses.

Builds an envelope: compresses (C04 8 and 10), pads (v2), signs and encrypts content.

Functions

error_envelope(content, opts)

@spec error_envelope(binary(), keyword()) ::
  {:ok, binary()} | {:error, :des3_unavailable}

Builds the envelope that reports a security error to a counterparty (§5.5.2, §5.5.4, §5.5.7): signed, not encrypted (C04 3), with C03 set.

content is the GEN0004 message, which the caller builds.

Options

  • :c03 (required) - the error code, usually error.c03 from open/2.
  • :from, :to, :version - as in seal/2.

Example

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

open(envelope, opts)

@spec open(binary(), keyword()) ::
  {:ok, Sinetex.Opened.t()} | {:error, Sinetex.Error.t()}

Opens an envelope: checks the header, decrypts, verifies the signature and, for C04 8 and 10, decompresses.

Options

  • :as (required) - your Sinetex.Identity; decrypts C14.
  • :from (required) - the expected sender: a Sinetex.Certificate or a list of them. The certificate is picked by C13 and C12.
  • :known - other certificates you know, default []. A message signed with one of them is rejected with 0x0C ("certificate does not belong to the sender") instead of 0x0A.
  • :certificate_check - fn certificate, header -> :ok | {:error, c03} end, called with the sender certificate. Use it for what depends on the certificate registry: not activated (0x10), expired or revoked (0x11), invalid (0x14). C04 1 means the sender uses a certificate that is not activated yet; the header is passed so you can allow it.

  • :c14_errors - :manual (default) reports a failure to decrypt C14 as 0x0D, as the manual says. :uniform reports it as 0x0E, the same as a failure in the body, so the error code is not a padding oracle. See SECURITY.md.
  • :trim_padding - v2 only. The manual says the padding is not removed (§5.7.14), so by default the content comes back as signed, with its trailing 0x00. true removes up to 7 trailing 0x00 bytes, which also removes legitimate zeros at the end of the content.
  • :allow_clear - accept the "empty" header of §5.6.3 (GEN0001 echo), whose body is neither signed nor encrypted. Default false: such an envelope is rejected with 0x02.
  • :max_output_bytes - limit for decompressed content, default 512 MiB.
  • :max_input_bytes - limit for the envelope, default :infinity.

Return

{:ok, %Sinetex.Opened{}} or {:error, %Sinetex.Error{}}.

An envelope whose C03 is not 0x00 is a counterparty reporting an error; it is opened like any other and the code is in opened.header.c03. Do not answer it with another error (§5.5.5).

Invalid options raise ArgumentError.

seal(content, opts)

@spec seal(binary(), keyword()) ::
  {:ok, binary()}
  | {:error,
     :compression_required
     | :recipient_required
     | :des3_unavailable
     | :zip64_unsupported}

Builds an envelope: compresses (C04 8 and 10), pads (v2), signs and encrypts content.

Options

  • :from (required) - your Sinetex.Identity; signs the content.
  • :to - the recipient's Sinetex.Certificate. Required when C04 means encrypted; optional otherwise (when given, it fills C10/C11).
  • :version - 3 (default) or 2. Version 2 is legacy and needs 3DES in the host OpenSSL, which FIPS mode may disable.
  • :c04 - special treatment indicator, default 0. See the table in the module documentation.
  • :compression - :gzip or :zip. Required with C04 8 and 10 and not accepted otherwise. There is no default because the right format depends on the destination: clearing houses use GZIP, the manual describes ZIP (and the Bacen SFTP servers use ZIP with DEFLATE).
  • :zip_entry_name - name of the single ZIP entry, default "content".

Errors

  • {:error, :compression_required} - C04 8 or 10 without :compression;
  • {:error, :recipient_required} - encrypted C04 without :to;
  • {:error, :des3_unavailable} - version 2 and no 3DES in OpenSSL;
  • {:error, :zip64_unsupported} - ZIP content of 4 GiB or more.

Invalid options raise ArgumentError.