X402.HTTPSignature.StructuredField (X402 v0.9.0)

Copy Markdown View Source

The RFC 8941 Structured Field subset used by HTTP Message Signatures.

Signature-Input and Signature (RFC 9421 §4) are Dictionary fields whose members are a parameterized Inner List of Strings and a Byte Sequence respectively. This module serializes and parses exactly the grammar those fields need, using the strict rules of RFC 8941 §4 so that a value parsed and re-serialized reproduces the bytes a signer hashed.

Representation

Wire typeElixir value
Stringbinary
Integerinteger
Decimalfloat
Booleanboolean
Token{:token, binary}
Byte Sequence{:bytes, binary}
Item{bare_item, params}
Inner List{:inner_list, [item], params}
Parameters[{key, bare_item}] (ordered)

| Dictionary | [{key, item | inner_list}] (ordered) |

Strings and Tokens are distinct wire types, so a bare binary always serializes as a quoted String.

Summary

Types

A bare item value.

An ordered dictionary.

An inner list with its parameters.

An item with its parameters.

A dictionary member value.

Ordered parameters.

Functions

Parses a dictionary (RFC 8941 §4.2.2).

Parses an item with parameters (RFC 8941 §4.2.3), returning the rest of the input.

Serializes a bare item (RFC 8941 §4.1.3.1).

Serializes a dictionary (RFC 8941 §4.1.2).

Serializes an inner list with parameters (RFC 8941 §4.1.1.1).

Serializes an item with parameters (RFC 8941 §4.1.3).

Serializes a dictionary or parameter key (RFC 8941 §4.1.1.3).

Types

bare()

@type bare() ::
  binary()
  | integer()
  | float()
  | boolean()
  | {:token, binary()}
  | {:bytes, binary()}

A bare item value.

dictionary()

@type dictionary() :: [{binary(), member()}]

An ordered dictionary.

error()

@type error() :: :invalid_structured_field | :duplicate_key

inner_list()

@type inner_list() :: {:inner_list, [item()], params()}

An inner list with its parameters.

item()

@type item() :: {bare(), params()}

An item with its parameters.

member()

@type member() :: item() | inner_list()

A dictionary member value.

params()

@type params() :: [{binary(), bare()}]

Ordered parameters.

Functions

parse_dictionary(input, opts \\ [])

(since 0.9.0)
@spec parse_dictionary(binary(), [{:duplicate_keys, :overwrite | :error}]) ::
  {:ok, dictionary()} | {:error, error()}

Parses a dictionary (RFC 8941 §4.2.2).

Examples

iex> X402.HTTPSignature.StructuredField.parse_dictionary(
...>   ~S|sig1=("@method" "@path";req);created=1618884473;keyid="k", sig2=:AQID:|
...> )
{:ok, [
  {"sig1", {:inner_list, [{"@method", []}, {"@path", [{"req", true}]}], [{"created", 1618884473}, {"keyid", "k"}]}},
  {"sig2", {{:bytes, <<1, 2, 3>>}, []}}
]}

iex> X402.HTTPSignature.StructuredField.parse_dictionary("a, b;x=?0")
{:ok, [{"a", {true, []}}, {"b", {true, [{"x", false}]}}]}

iex> X402.HTTPSignature.StructuredField.parse_dictionary("sig1=(")
{:error, :invalid_structured_field}

iex> X402.HTTPSignature.StructuredField.parse_dictionary("a=1,")
{:error, :invalid_structured_field}

A repeated key (in the dictionary or in any parameter list) overwrites the earlier value as RFC 8941 §4.2.2 prescribes; pass duplicate_keys: :error to reject the field instead, which is what a signature verifier wants:

iex> X402.HTTPSignature.StructuredField.parse_dictionary("a=1, a=2")
{:ok, [{"a", {2, []}}]}

iex> X402.HTTPSignature.StructuredField.parse_dictionary("a=1, a=2", duplicate_keys: :error)
{:error, :duplicate_key}

iex> X402.HTTPSignature.StructuredField.parse_dictionary("a=1;x=1;x=2", duplicate_keys: :error)
{:error, :duplicate_key}

parse_item(input)

(since 0.9.0)
@spec parse_item(binary()) :: {:ok, item(), binary()} | :error

Parses an item with parameters (RFC 8941 §4.2.3), returning the rest of the input.

Examples

iex> X402.HTTPSignature.StructuredField.parse_item(~S|"@authority";req rest|)
{:ok, {"@authority", [{"req", true}]}, " rest"}

iex> X402.HTTPSignature.StructuredField.parse_item("token;n=1.25;b=?1")
{:ok, {{:token, "token"}, [{"n", 1.25}, {"b", true}]}, ""}

iex> X402.HTTPSignature.StructuredField.parse_item(~S|"unterminated|)
:error

serialize_bare(value)

(since 0.9.0)
@spec serialize_bare(bare()) :: {:ok, binary()} | {:error, error()}

Serializes a bare item (RFC 8941 §4.1.3.1).

Examples

iex> X402.HTTPSignature.StructuredField.serialize_bare("say \"hi\" \\")
{:ok, "\"say \\\"hi\\\" \\\\\""}

iex> X402.HTTPSignature.StructuredField.serialize_bare(-42)
{:ok, "-42"}

iex> X402.HTTPSignature.StructuredField.serialize_bare(1.5)
{:ok, "1.5"}

iex> X402.HTTPSignature.StructuredField.serialize_bare(false)
{:ok, "?0"}

iex> X402.HTTPSignature.StructuredField.serialize_bare({:bytes, <<1, 2, 3>>})
{:ok, ":AQID:"}

iex> X402.HTTPSignature.StructuredField.serialize_bare(1_000_000_000_000_000)
{:error, :invalid_structured_field}

serialize_dictionary(members)

(since 0.9.0)
@spec serialize_dictionary(dictionary()) :: {:ok, binary()} | {:error, error()}

Serializes a dictionary (RFC 8941 §4.1.2).

Examples

iex> X402.HTTPSignature.StructuredField.serialize_dictionary([
...>   {"sig1", {:inner_list, [{"@method", []}], [{"created", 1_618_884_473}]}}
...> ])
{:ok, ~S|sig1=("@method");created=1618884473|}

iex> X402.HTTPSignature.StructuredField.serialize_dictionary([{"sig1", {{:bytes, "abc"}, []}}])
{:ok, "sig1=:YWJj:"}

iex> X402.HTTPSignature.StructuredField.serialize_dictionary([{"Sig", {1, []}}])
{:error, :invalid_structured_field}

serialize_inner_list(arg)

(since 0.9.0)
@spec serialize_inner_list(inner_list()) :: {:ok, binary()} | {:error, error()}

Serializes an inner list with parameters (RFC 8941 §4.1.1.1).

Examples

iex> X402.HTTPSignature.StructuredField.serialize_inner_list(
...>   {:inner_list, [{"@query-param", [{"name", "Pet"}]}, {"@path", [{"req", true}]}], [{"tag", "x"}]}
...> )
{:ok, ~S|("@query-param";name="Pet" "@path";req);tag="x"|}

iex> X402.HTTPSignature.StructuredField.serialize_inner_list({:inner_list, [], []})
{:ok, "()"}

serialize_item(arg)

(since 0.9.0)
@spec serialize_item(item()) :: {:ok, binary()} | {:error, error()}

Serializes an item with parameters (RFC 8941 §4.1.3).

Examples

iex> X402.HTTPSignature.StructuredField.serialize_item({"content-type", []})
{:ok, ~S|"content-type"|}

iex> X402.HTTPSignature.StructuredField.serialize_item({{:token, "a"}, [{"x", 1}, {"y", true}]})
{:ok, "a;x=1;y"}

iex> X402.HTTPSignature.StructuredField.serialize_item({"caf\u00e9", []})
{:error, :invalid_structured_field}

serialize_key(key)

(since 0.9.0)
@spec serialize_key(binary()) :: {:ok, binary()} | {:error, error()}

Serializes a dictionary or parameter key (RFC 8941 §4.1.1.3).

Examples

iex> X402.HTTPSignature.StructuredField.serialize_key("sig-b26")
{:ok, "sig-b26"}

iex> X402.HTTPSignature.StructuredField.serialize_key("Sig1")
{:error, :invalid_structured_field}