SefazNfe.SOAP.HTTPC (sefaz_nfe v0.1.0)

Copy Markdown View Source

Default SefazNfe.SOAP client: an mTLS POST over :httpc.

:httpc ships with OTP, so a host embedding this library gains no transitive dependency for the one HTTP call the library makes. The Erlang API stays inside this module.

Isolation

Requests run on a named :httpc profile rather than the default one, so the library never inherits or mutates the host's global HTTP settings — proxies, cookies, connection limits. The profile is started by SefazNfe.Application.

TLS

SEFAZ requires the A1 as a client certificate and the peer is verified against the system CA bundle: verify_none against a fiscal authority would defeat the point of mTLS. :customize_hostname_check is set because several SEFAZ hosts serve wildcard certificates.

TLS 1.2 only, measured rather than assumed: these endpoints refuse a 1.3 handshake and OTP negotiates the highest offered version first. A host whose endpoints have moved on can add 1.3 back through :tls_options.

A decode_error surfaces as {:tls, :decode_error, :otp_cert_auths_bug} rather than a bare alert, because it names a runtime limitation rather than anything the caller configured.

A known incompatibility

:ssl cannot complete a handshake with the SVRS and PR endpoints, which authorize for 17 of the 27 states. They answer {:tls, :decode_error} — raised by the client while decoding the server hello — where openssl s_client -tls1_2 connects to the same host and negotiates ECDHE-RSA-AES256-GCM-SHA384 without complaint. The server hello is well-formed when captured that way, and restricting versions, ciphers, curves, signature algorithms or SNI changes nothing.

The ten UFs served by SP, RS, MG, BA, GO, PE, MT, MS, AM and SVAN work. For the rest, a host can supply its own transport through the SefazNfe.SOAP behaviour — which is why the behaviour exists.

Summary

Functions

Names why a connection failed.

Client options for an mTLS connection, with :tls_options from opts merged over the defaults.

Functions

connect_error(details)

@spec connect_error(term()) :: :unreachable | {:tls, atom()} | {:dns, atom()}

Names why a connection failed.

A TLS alert is reported as {:tls, alert} rather than a generic :unreachable, because the two need opposite responses: :unknown_ca means the trust store is missing a root and no amount of retrying fixes it, while an unreachable host is worth backing off on. Several SEFAZ UFs — SP and MT among them — serve certificates chained to an ICP-Brasil root that is not in any OS bundle; see SefazNfe.Certificate for supplying it.

ssl_options(cert, opts \\ [])

@spec ssl_options(SefazNfe.Certificate.t(), keyword()) :: keyword()

Client options for an mTLS connection, with :tls_options from opts merged over the defaults.

The escape hatch exists because SEFAZ TLS stacks vary by UF, and a host should not have to fork the library to add a cipher or a CA.