Tspex.HTTP (tspex v0.1.0)

Copy Markdown View Source

The HTTP transport of Tspex.stamp/2: one POST of a time-stamp request to a TSA (RFC 3161, section 3.4), written on :gen_tcp and :ssl.

It is small on purpose, because it only has to do one thing and put limits on it that :httpc does not offer. For anything else (a proxy, a client certificate with an agent, HTTP/2, your own retries) pass your own :transport function to Tspex.stamp/2, a function of the URL, the request in DER and the options that returns {:ok, response_der} or {:error, reason}, and use Req, Finch or Mint inside it.

What it does:

  • POST with Content-Type: application/timestamp-query, Accept: application/timestamp-reply, User-Agent: tspex/<version> and Connection: close; one connection for each request.
  • https verifies the server: verify_peer, the system CA certificates (:public_key.cacerts_get/0) and the name of the URL against the certificate. http is accepted, because the token authenticates itself and many TSAs speak only http; the cost is that the hash of your data travels in the clear (a matter of privacy, not of integrity).
  • Only the status 200 is a success. Any other status ends the request at once, without reading its body, and is {:http_status, n}.
  • The body is limited by :max_response_bytes. A Content-Length above it is refused before a byte of the body is read, and a chunked or close-delimited body is cut at the limit, so a server cannot make you allocate what it wants.
  • No redirect unless you raise :max_redirects, and then only 307 and 308 (which keep the method and the body), never from https to http, and the :headers you gave are not sent to another host.
  • The type of the body is not required: some TSAs answer with text/plain. What decides is that the bytes are a valid DER response, which Tspex.verify/3 checks.
  • The time of :timeout is for the whole exchange.

Options:

  • :headers - extra headers as {name, value} strings, for authentication. They replace Content-Type, Accept and User-Agent of the same name; Host, Content-Length, Connection and Transfer-Encoding are refused. A name with anything that is not a token, or a value with a line break, raises ArgumentError.
  • :timeout - milliseconds for the whole exchange, 30 000.
  • :max_redirects - 0.
  • :max_response_bytes - 1 MiB.
  • :ssl - options for :ssl.connect/4 that replace the defaults: a cacerts: or cacertfile: for a private CA, or certfile: and keyfile: for a client certificate. verify: :verify_none is yours to choose, and the token is still verified, but then anyone on the path can read your request.

The reasons of {:error, reason}, which Tspex.stamp/2 turns into the detail of a :transport error: {:http_status, n}, :timeout, :too_large, {:connect, reason} (no address, connection refused), {:tls, reason} (the handshake, the certificate or the name) and {:other, reason} (a response that is not HTTP, a connection closed before the end of the body).

Needs the :ssl application only for https URLs.

Summary

Types

Why the request failed.

Functions

Sends body to url and returns the body of the answer.

Types

reason()

@type reason() ::
  {:http_status, pos_integer()}
  | :timeout
  | :too_large
  | {:connect, term()}
  | {:tls, term()}
  | {:other, term()}

Why the request failed.

Functions

post(url, body, opts \\ [])

@spec post(String.t(), binary(), keyword()) :: {:ok, binary()} | {:error, reason()}

Sends body to url and returns the body of the answer.

{:ok, response_der} = Tspex.HTTP.post("http://tsa.example.com/tsp", request.der)

See the module documentation for the options. A url that is not http or https with a host, or a bad option, raises ArgumentError.