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 type | Elixir value |
|---|---|
| String | binary |
| Integer | integer |
| Decimal | float |
| Boolean | boolean |
| 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
@type bare() :: binary() | integer() | float() | boolean() | {:token, binary()} | {:bytes, binary()}
A bare item value.
An ordered dictionary.
@type error() :: :invalid_structured_field | :duplicate_key
An inner list with its parameters.
An item with its parameters.
@type member() :: item() | inner_list()
A dictionary member value.
Ordered parameters.
Functions
@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}
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
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}
@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}
@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, "()"}
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}
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}