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
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.
@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.