HTTPDigest (HTTPDigest v0.1.0)

Copy Markdown View Source

RFC 9530 Digest Fields: build, parse, and verify Content-Digest, Repr-Digest, Want-Content-Digest, and Want-Repr-Digest.

Content vs Representation

Content-Digest covers the actual message content bytes, what is on the wire after any content coding. Repr-Digest covers the selected representation, which under Content-Encoding: gzip or a Range request is a different byte string. RFC 3230's single Digest field was obsoleted precisely because it never said which of the two it meant.

This library never guesses: you pass the bytes, it hashes them. For Repr-Digest you must pass the representation bytes as you define them.

See HTTPDigest.Stream for incremental hashing, HTTPDigest.Plug for incoming Plug request verification, and HTTPDigest.Req for outgoing Req requests.

Summary

Functions

Builds a Want-Content-Digest header value from {algorithm, preference} pairs.

Builds a Want-Repr-Digest header value. See build_want_content_digest/1.

Builds a Content-Digest header value over body (iodata).

Parses a Content-Digest header value into a map of algorithm to raw digest bytes.

Parses a Repr-Digest header value. See parse_content_digest/2.

Parses a Want-Content-Digest header into a map of algorithm to preference.

Parses a Want-Repr-Digest header. See parse_want_content_digest/1.

Builds a Repr-Digest header value over the representation bytes.

Selects the response digest algorithm requested by a Want-* header.

Verifies a Content-Digest header against body.

Verifies a digest header against precomputed digests, such as %{sha256: <<...>>} from a streaming body reader.

Verifies a Repr-Digest header against representation bytes. See verify_content/3.

Functions

build_want_content_digest(prefs)

@spec build_want_content_digest(keyword(integer())) ::
  {:ok, String.t()} | {:error, HTTPDigest.Error.t()}

Builds a Want-Content-Digest header value from {algorithm, preference} pairs.

Preferences are integers from 0 to 10. A preference of 0 means not acceptable.

iex> HTTPDigest.build_want_content_digest(sha512: 10, sha256: 5)
{:ok, "sha-512=10, sha-256=5"}

build_want_repr_digest(prefs)

@spec build_want_repr_digest(keyword(integer())) ::
  {:ok, String.t()} | {:error, HTTPDigest.Error.t()}

Builds a Want-Repr-Digest header value. See build_want_content_digest/1.

content_digest(body, opts \\ [])

@spec content_digest(
  iodata(),
  keyword()
) :: {:ok, String.t()} | {:error, HTTPDigest.Error.t()}

Builds a Content-Digest header value over body (iodata).

iex> HTTPDigest.content_digest(~s({"hello": "world"}))
{:ok, "sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:"}

Options:

  • :algorithms - list of :sha256 / :sha512 (default [:sha256])
  • :allow_insecure - permit :md5 / :sha (default false)

parse_content_digest(header, opts \\ [])

@spec parse_content_digest(
  binary(),
  keyword()
) :: {:ok, map()} | {:error, HTTPDigest.Error.t()}

Parses a Content-Digest header value into a map of algorithm to raw digest bytes.

Known algorithms get atom keys such as :sha256. Unknown dictionary keys are preserved under string keys because RFC 9530 receivers must ignore unknown algorithms unless the caller opts into on_unknown: :error.

parse_repr_digest(header, opts \\ [])

@spec parse_repr_digest(
  binary(),
  keyword()
) :: {:ok, map()} | {:error, HTTPDigest.Error.t()}

Parses a Repr-Digest header value. See parse_content_digest/2.

parse_want_content_digest(header)

@spec parse_want_content_digest(binary()) ::
  {:ok, map()} | {:error, HTTPDigest.Error.t()}

Parses a Want-Content-Digest header into a map of algorithm to preference.

Known algorithms get atom keys. Unknown algorithms are preserved under string keys.

parse_want_repr_digest(header)

@spec parse_want_repr_digest(binary()) ::
  {:ok, map()} | {:error, HTTPDigest.Error.t()}

Parses a Want-Repr-Digest header. See parse_want_content_digest/1.

repr_digest(representation, opts \\ [])

@spec repr_digest(
  iodata(),
  keyword()
) :: {:ok, String.t()} | {:error, HTTPDigest.Error.t()}

Builds a Repr-Digest header value over the representation bytes.

Mechanically identical to content_digest/2; the caller is responsible for supplying the representation bytes, such as the uncompressed document when the message content is gzip-coded, or the full document on a range response.

select_from_want(header, opts)

@spec select_from_want(
  binary(),
  keyword()
) :: {:ok, atom()} | {:error, HTTPDigest.Error.t()}

Selects the response digest algorithm requested by a Want-* header.

Among supported algorithms with preference greater than 0, the highest preference wins. Ties go to the stronger algorithm.

verify_content(body, header, opts \\ [])

@spec verify_content(iodata(), binary(), keyword()) ::
  {:ok, atom()} | {:error, HTTPDigest.Error.t()}

Verifies a Content-Digest header against body.

The default :strongest policy checks only the strongest digest present whose algorithm is supported, so a tampered strong digest fails even when a weaker digest still matches. policy: :any accepts the strongest matching digest and should only be used when compatibility requires it.

verify_digests(computed, header, opts \\ [])

@spec verify_digests(%{optional(atom()) => binary()}, binary(), keyword()) ::
  {:ok, atom()} | {:error, HTTPDigest.Error.t()}

Verifies a digest header against precomputed digests, such as %{sha256: <<...>>} from a streaming body reader.

verify_repr(representation, header, opts \\ [])

@spec verify_repr(iodata(), binary(), keyword()) ::
  {:ok, atom()} | {:error, HTTPDigest.Error.t()}

Verifies a Repr-Digest header against representation bytes. See verify_content/3.